forked from amkovkov/GranuSightSoftware2
feat: models caching
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
##
|
||||
## Get latest from `dotnet new gitignore`
|
||||
|
||||
.models_cache
|
||||
.zed
|
||||
*.png
|
||||
cache
|
||||
|
||||
@@ -141,69 +141,102 @@ public partial class AnalyzerService
|
||||
var sampleRecords = _context.SampleRecords
|
||||
.Where(r => r.BrandId == brand.Id);
|
||||
|
||||
_logger.LogInformation("Найдено калибровочных записей проб для марки: {}", sampleRecords.Count());
|
||||
if (sampleRecords.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных записей для марки: \"{brand.BrandName} - {brand.MixtureName}\"");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (separator.Features.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных признаков для сепаратора: \"{separator.Type}\"");
|
||||
if (separator.Features.Count() % FEATURES_LENGTH != 0)
|
||||
throw new InvalidDataException($"Неожидаемое количество калибровочных признаков для сепаратора: \"{separator.Type}\"");
|
||||
var separatorDataSet = separator.Features.Chunk(FEATURES_LENGTH)
|
||||
.Select(v => new ClusterizationData
|
||||
{
|
||||
Label = "separator",
|
||||
Features = v.ToArray()
|
||||
})
|
||||
.Shuffle();
|
||||
|
||||
_logger.LogInformation("Загружено векторов для сепаратора: {}", separatorDataSet.Count());
|
||||
if (separatorDataSet.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных данных для сепаратора: \"{separator.Type}\"");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
foreach (var sampleRecord in sampleRecords)
|
||||
var dataSb = new System.Text.StringBuilder();
|
||||
dataSb.AppendLine(brand.Id.ToString());
|
||||
dataSb.AppendLine(brand.EditDateTime.ToString());
|
||||
foreach (var record in sampleRecords.OrderBy(r => r.Id))
|
||||
{
|
||||
if (sampleRecord.Features.Count() == 0)
|
||||
_logger.LogError("Не найдено калибровочных признаков для пробы: \"{}\"", sampleRecord.SampleName);
|
||||
if (sampleRecord.Features.Count() % FEATURES_LENGTH != 0)
|
||||
_logger.LogError("Неожидаемое количество калибровочных признаков для пробы: \"{}\"", sampleRecord.SampleName);
|
||||
dataSb.AppendLine(record.Id.ToString());
|
||||
dataSb.AppendLine(record.EditDateTime.ToString());
|
||||
}
|
||||
dataSb.AppendLine(separator.Id.ToString());
|
||||
dataSb.AppendLine(separator.EditDateTime.ToString());
|
||||
var dataHashCode = Convert.ToHexString(
|
||||
System.Security.Cryptography.MD5.HashData(
|
||||
System.Text.Encoding.UTF8.GetBytes(dataSb.ToString())
|
||||
)
|
||||
);
|
||||
|
||||
var sampleDataSet = sampleRecords
|
||||
.AsEnumerable()
|
||||
.Where(r => r.Features.Count() > 0 && r.Features.Count() % FEATURES_LENGTH == 0)
|
||||
.SelectMany(r => r.Features.Chunk(FEATURES_LENGTH))
|
||||
.Select(f => new ClusterizationData
|
||||
_logger.LogInformation("Хэш калибровочных записей: {}", dataHashCode);
|
||||
|
||||
Directory.CreateDirectory(".models_cache");
|
||||
var cachedModelPath = Path.Combine(".models_cache", dataHashCode) + ".zip";
|
||||
|
||||
if (File.Exists(cachedModelPath))
|
||||
{
|
||||
_logger.LogInformation("Модель найдена к кэше");
|
||||
var model = _ml.Model.Load(cachedModelPath, out _);
|
||||
_clusterizationEngine = _ml.Model.CreatePredictionEngine<ClusterizationData, ClusterizationPrediction>(model);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Найдено калибровочных записей проб для марки: {}", sampleRecords.Count());
|
||||
if (sampleRecords.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных записей для марки: \"{brand.BrandName} - {brand.MixtureName}\"");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (separator.Features.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных признаков для сепаратора: \"{separator.Type}\"");
|
||||
if (separator.Features.Count() % FEATURES_LENGTH != 0)
|
||||
throw new InvalidDataException($"Неожидаемое количество калибровочных признаков для сепаратора: \"{separator.Type}\"");
|
||||
var separatorDataSet = separator.Features.Chunk(FEATURES_LENGTH)
|
||||
.Select(v => new ClusterizationData
|
||||
{
|
||||
Label = "separator",
|
||||
Features = v.ToArray()
|
||||
})
|
||||
.Shuffle();
|
||||
|
||||
_logger.LogInformation("Загружено векторов для сепаратора: {}", separatorDataSet.Count());
|
||||
if (separatorDataSet.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных данных для сепаратора: \"{separator.Type}\"");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
foreach (var sampleRecord in sampleRecords)
|
||||
{
|
||||
Label = "sample",
|
||||
Features = f.ToArray()
|
||||
})
|
||||
.Shuffle();
|
||||
if (sampleRecord.Features.Count() == 0)
|
||||
_logger.LogError("Не найдено калибровочных признаков для пробы: \"{}\"", sampleRecord.SampleName);
|
||||
if (sampleRecord.Features.Count() % FEATURES_LENGTH != 0)
|
||||
_logger.LogError("Неожидаемое количество калибровочных признаков для пробы: \"{}\"", sampleRecord.SampleName);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Загружено векторов для пробы: {}", sampleDataSet.Count());
|
||||
if (separatorDataSet.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных данных для марки: \"{brand.BrandName} - {brand.MixtureName}\"");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var sampleDataSet = sampleRecords
|
||||
.AsEnumerable()
|
||||
.Where(r => r.Features.Count() > 0 && r.Features.Count() % FEATURES_LENGTH == 0)
|
||||
.SelectMany(r => r.Features.Chunk(FEATURES_LENGTH))
|
||||
.Select(f => new ClusterizationData
|
||||
{
|
||||
Label = "sample",
|
||||
Features = f.ToArray()
|
||||
})
|
||||
.Shuffle();
|
||||
|
||||
var trainDataSet = Enumerable.Concat(separatorDataSet, sampleDataSet);
|
||||
_logger.LogInformation("Загружено векторов для обучения: {}", trainDataSet.Count());
|
||||
var trainData = _ml.Data.LoadFromEnumerable(trainDataSet);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_logger.LogInformation("Загружено векторов для пробы: {}", sampleDataSet.Count());
|
||||
if (separatorDataSet.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных данных для марки: \"{brand.BrandName} - {brand.MixtureName}\"");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.LogInformation("Создание конвейера");
|
||||
var pipeline = _ml.Transforms.Conversion.MapValueToKey("Label")
|
||||
.Append(_ml.MulticlassClassification.Trainers.LbfgsMaximumEntropy())
|
||||
.Append(_ml.Transforms.Conversion.MapKeyToValue("PredictedLabel"));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var trainDataSet = Enumerable.Concat(separatorDataSet, sampleDataSet);
|
||||
_logger.LogInformation("Загружено векторов для обучения: {}", trainDataSet.Count());
|
||||
var trainData = _ml.Data.LoadFromEnumerable(trainDataSet);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.LogInformation("Обучение модели");
|
||||
var model = pipeline.Fit(trainData);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_logger.LogInformation("Создание конвейера");
|
||||
var pipeline = _ml.Transforms.Conversion.MapValueToKey("Label")
|
||||
.Append(_ml.MulticlassClassification.Trainers.LbfgsMaximumEntropy())
|
||||
.Append(_ml.Transforms.Conversion.MapKeyToValue("PredictedLabel"));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_clusterizationEngine = _ml.Model.CreatePredictionEngine<ClusterizationData, ClusterizationPrediction>(model);
|
||||
model.Dispose();
|
||||
_logger.LogInformation("Обучение модели");
|
||||
var model = pipeline.Fit(trainData);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_ml.Model.Save(model, trainData.Schema, cachedModelPath);
|
||||
|
||||
_clusterizationEngine = _ml.Model.CreatePredictionEngine<ClusterizationData, ClusterizationPrediction>(model);
|
||||
|
||||
model.Dispose();
|
||||
}
|
||||
}
|
||||
private async Task InitializeMlRegressionEngine(BrandRecord brand, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -217,79 +250,110 @@ public partial class AnalyzerService
|
||||
var sampleRecords = _context.SampleRecords
|
||||
.Where(r => r.BrandId == brand.Id);
|
||||
|
||||
_logger.LogInformation("Найдено калибровочных записей проб для марки: {}", sampleRecords.Count());
|
||||
if (sampleRecords.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных записей для марки: \"{brand.BrandName} - {brand.MixtureName}\"");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
foreach (var sampleRecord in sampleRecords)
|
||||
var dataSb = new System.Text.StringBuilder();
|
||||
dataSb.AppendLine(brand.Id.ToString());
|
||||
dataSb.AppendLine(brand.EditDateTime.ToString());
|
||||
foreach (var record in sampleRecords.OrderBy(r => r.Id))
|
||||
{
|
||||
if (sampleRecord.Features.Count() == 0)
|
||||
_logger.LogError("Не найдено калибровочных признаков для пробы: \"{}\"", sampleRecord.SampleName);
|
||||
if (sampleRecord.Features.Count() % FEATURES_LENGTH != 0)
|
||||
_logger.LogError("Неожидаемое количество калибровочных признаков для пробы: \"{}\"", sampleRecord.SampleName);
|
||||
dataSb.AppendLine(record.Id.ToString());
|
||||
dataSb.AppendLine(record.EditDateTime.ToString());
|
||||
}
|
||||
|
||||
_logger.LogInformation("Подготовка данных");
|
||||
var trainDataSet = sampleRecords
|
||||
.AsEnumerable()
|
||||
.Where(r => r.Features.Count() > 0 && r.Features.Count() % FEATURES_LENGTH == 0)
|
||||
.Where(r => r.MeasuredContent >= 0 || r.MixtureActualRate >= 0)
|
||||
.SelectMany(r =>
|
||||
r.Features
|
||||
.Chunk(FEATURES_LENGTH)
|
||||
.Select(f =>
|
||||
new RegressionData
|
||||
{
|
||||
Value = (float)(r.MeasuredContent >= 0 ? r.MeasuredContent : r.MixtureActualRate),
|
||||
Features = f.ToArray()
|
||||
}
|
||||
)
|
||||
var dataHashCode = Convert.ToHexString(
|
||||
System.Security.Cryptography.MD5.HashData(
|
||||
System.Text.Encoding.UTF8.GetBytes(dataSb.ToString())
|
||||
)
|
||||
.Shuffle()
|
||||
.ToList();
|
||||
);
|
||||
|
||||
_logger.LogInformation("Загружено векторов для обучения: {}", trainDataSet.Count());
|
||||
if (trainDataSet.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных данных для марки: \"{brand.BrandName} - {brand.MixtureName}\"");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_logger.LogInformation("Хэш калибровочных записей: {}", dataHashCode);
|
||||
|
||||
int rowCount = trainDataSet.Count();
|
||||
double[][] featuresArray = trainDataSet.Select(d => d.Features.Select(f => (double)f).ToArray()).ToArray();
|
||||
Matrix<double> matrix = DenseMatrix.OfRowArrays(featuresArray);
|
||||
for (int i = 0; i < IMAGES_COUNT; i++)
|
||||
Directory.CreateDirectory(".models_cache");
|
||||
var cachedModelPath = Path.Combine(".models_cache", dataHashCode) + ".zip";
|
||||
|
||||
if (File.Exists(cachedModelPath))
|
||||
{
|
||||
matrix = matrix.RemoveColumn(i * 3);
|
||||
matrix = matrix.RemoveColumn(i * 3);
|
||||
matrix = matrix.RemoveColumn(i * 3);
|
||||
_logger.LogInformation("Модель найдена к кэше");
|
||||
var model = _ml.Model.Load(cachedModelPath, out _);
|
||||
_regressionEngine = _ml.Model.CreatePredictionEngine<RegressionData, RegressionPrediction>(model);
|
||||
}
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Найдено калибровочных записей проб для марки: {}", sampleRecords.Count());
|
||||
if (sampleRecords.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных записей для марки: \"{brand.BrandName} - {brand.MixtureName}\"");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
Vector<double> meanVector = matrix.ColumnSums() / matrix.RowCount;
|
||||
Matrix<double> covarianceMatrix = MatrixCovariance(matrix, meanVector);
|
||||
Matrix<double> invCovarianceMatrix = covarianceMatrix.Inverse();
|
||||
trainDataSet = trainDataSet
|
||||
.Where((data, index) =>
|
||||
foreach (var sampleRecord in sampleRecords)
|
||||
{
|
||||
var row = matrix.Row(index);
|
||||
double distance = CalculateMahalanobis(row, meanVector, invCovarianceMatrix);
|
||||
if (sampleRecord.Features.Count() == 0)
|
||||
_logger.LogError("Не найдено калибровочных признаков для пробы: \"{}\"", sampleRecord.SampleName);
|
||||
if (sampleRecord.Features.Count() % FEATURES_LENGTH != 0)
|
||||
_logger.LogError("Неожидаемое количество калибровочных признаков для пробы: \"{}\"", sampleRecord.SampleName);
|
||||
}
|
||||
|
||||
return distance <= FILTERING_THRESHOLD;
|
||||
})
|
||||
.ToList();
|
||||
_logger.LogInformation("Векторов для обучения после фильтрации: {}", trainDataSet.Count());
|
||||
var trainData = _ml.Data.LoadFromEnumerable(trainDataSet);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_logger.LogInformation("Подготовка данных");
|
||||
var trainDataSet = sampleRecords
|
||||
.AsEnumerable()
|
||||
.Where(r => r.Features.Count() > 0 && r.Features.Count() % FEATURES_LENGTH == 0)
|
||||
.Where(r => r.MeasuredContent >= 0 || r.MixtureActualRate >= 0)
|
||||
.SelectMany(r =>
|
||||
r.Features
|
||||
.Chunk(FEATURES_LENGTH)
|
||||
.Select(f =>
|
||||
new RegressionData
|
||||
{
|
||||
Value = (float)(r.MeasuredContent >= 0 ? r.MeasuredContent : r.MixtureActualRate),
|
||||
Features = f.ToArray()
|
||||
}
|
||||
)
|
||||
)
|
||||
.Shuffle()
|
||||
.ToList();
|
||||
|
||||
_logger.LogInformation("Создание конвейера");
|
||||
var pipeline = _ml.Regression.Trainers.FastTree();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_logger.LogInformation("Загружено векторов для обучения: {}", trainDataSet.Count());
|
||||
|
||||
_logger.LogInformation("Обучение модели");
|
||||
var model = pipeline.Fit(trainData);
|
||||
_regressionEngine = _ml.Model.CreatePredictionEngine<RegressionData, RegressionPrediction>(model);
|
||||
if (trainDataSet.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных данных для марки: \"{brand.BrandName} - {brand.MixtureName}\"");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
model.Dispose();
|
||||
int rowCount = trainDataSet.Count();
|
||||
double[][] featuresArray = trainDataSet.Select(d => d.Features.Select(f => (double)f).ToArray()).ToArray();
|
||||
Matrix<double> matrix = DenseMatrix.OfRowArrays(featuresArray);
|
||||
for (int i = 0; i < IMAGES_COUNT; i++)
|
||||
{
|
||||
matrix = matrix.RemoveColumn(i * 3);
|
||||
matrix = matrix.RemoveColumn(i * 3);
|
||||
matrix = matrix.RemoveColumn(i * 3);
|
||||
}
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
Vector<double> meanVector = matrix.ColumnSums() / matrix.RowCount;
|
||||
Matrix<double> covarianceMatrix = MatrixCovariance(matrix, meanVector);
|
||||
Matrix<double> invCovarianceMatrix = covarianceMatrix.Inverse();
|
||||
trainDataSet = trainDataSet
|
||||
.Where((data, index) =>
|
||||
{
|
||||
var row = matrix.Row(index);
|
||||
double distance = CalculateMahalanobis(row, meanVector, invCovarianceMatrix);
|
||||
|
||||
return distance <= FILTERING_THRESHOLD;
|
||||
})
|
||||
.ToList();
|
||||
_logger.LogInformation("Векторов для обучения после фильтрации: {}", trainDataSet.Count());
|
||||
var trainData = _ml.Data.LoadFromEnumerable(trainDataSet);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.LogInformation("Создание конвейера");
|
||||
var pipeline = _ml.Regression.Trainers.FastTree();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.LogInformation("Обучение модели");
|
||||
var model = pipeline.Fit(trainData);
|
||||
_ml.Model.Save(model, trainData.Schema, cachedModelPath);
|
||||
|
||||
_regressionEngine = _ml.Model.CreatePredictionEngine<RegressionData, RegressionPrediction>(model);
|
||||
|
||||
model.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(Mat avgImage, Rect roi, Mat mask, IEnumerable<Point[]> contours, Mat result)?> Analyze(IEnumerable<ImageData> imagesData)
|
||||
|
||||
Reference in New Issue
Block a user