DEEP-38 Initial work for configurable Measures

This commit is contained in:
Andrey Shabarshov 2023-07-25 09:22:02 +01:00
parent fc98c3b50a
commit 0b26c25620
14 changed files with 312 additions and 14 deletions

View File

@ -8,9 +8,9 @@ namespace DeepTrace.Controllers
[Route("api/[controller]")] [Route("api/[controller]")]
public class DownloadController : Controller public class DownloadController : Controller
{ {
private readonly IModelDefinitionService _modelService; private readonly IModelStorageService _modelService;
public DownloadController(IModelDefinitionService modelService) public DownloadController(IModelStorageService modelService)
{ {
_modelService = modelService; _modelService = modelService;
} }

View File

@ -29,14 +29,14 @@ public class ModelDefinition
{ {
columnNames.AddRange(measureNames.Select(x => $"{item.Query}_{x}")); columnNames.AddRange(measureNames.Select(x => $"{item.Query}_{x}"));
} }
columnNames.Add("Name");
return columnNames; return columnNames;
} }
public string ToCsv() public string ToCsv()
{ {
var current = IntervalDefinitionList.First(); var current = IntervalDefinitionList.First();
var headers = string.Join(",", GetColumnNames().Select(x=>$"\"{x}\"")) + ",Name"; var headers = string.Join(",", GetColumnNames().Select(x=>$"\"{x}\""));
var writer = new StringBuilder(); var writer = new StringBuilder();

View File

@ -0,0 +1,13 @@
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson;
namespace DeepTrace.Data
{
public class TrainedModelDefinition
{
[BsonId]
public ObjectId? Id { get; set; }
public string Name { get; set; }
public byte[] Value { get; set; } //base64
}
}

11
DeepTrace/ML/IMeasure.cs Normal file
View File

@ -0,0 +1,11 @@
using PrometheusAPI;
namespace DeepTrace.ML
{
public interface IMeasure
{
public string Name { get; }
void Reset();
float Calculate(IEnumerable<TimeSeries> data);
}
}

View File

@ -34,7 +34,7 @@ public static class MLHelpers
var columnNames = model.GetColumnNames(); var columnNames = model.GetColumnNames();
var columns = columnNames var columns = columnNames
.Select((x,i) => new TextLoader.Column(x, DataKind.Double, i)) .Select((x,i) => new TextLoader.Column(x, DataKind.String, i))
.ToArray() .ToArray()
; ;

View File

@ -13,7 +13,7 @@ namespace DeepTrace.ML
private DataViewSchema? _schema; private DataViewSchema? _schema;
private ITransformer? _transformer; private ITransformer? _transformer;
private string Name { get; set; } private string Name { get; set; } = "TestModel";
public async Task Train(ModelDefinition modelDef) public async Task Train(ModelDefinition modelDef)
{ {

89
DeepTrace/ML/Measures.cs Normal file
View File

@ -0,0 +1,89 @@
using PrometheusAPI;
namespace DeepTrace.ML
{
public class MeasureMin : IMeasure
{
public string Name => "Min";
public float Calculate(IEnumerable<TimeSeries> data) =>
data
.Where(x => x.Value != 0.0f)
.Min( x => x.Value )
;
public void Reset() { }
}
public class MeasureMax : IMeasure
{
public string Name => "Max";
public float Calculate(IEnumerable<TimeSeries> data) => data.Max(x => x.Value);
public void Reset() { }
}
public class MeasureAvg : IMeasure
{
public string Name => "Avg";
public float Calculate(IEnumerable<TimeSeries> data) => data.Average(x => x.Value);
public void Reset() { }
}
/// <summary>
/// WARNING: Only works with fixed length interval
/// </summary>
public class MeasureSum : IMeasure
{
public string Name => "Sum";
public float Calculate(IEnumerable<TimeSeries> data) => data.Sum(x => x.Value);
public void Reset() { }
}
public class MeasureMedian : IMeasure
{
public string Name => "Median";
public float Calculate(IEnumerable<TimeSeries> data)
=> MedianHelper.Median(data, x => x.Value);
public void Reset() { }
}
public class MeasureDiff<T> : IMeasure where T : IMeasure, new()
{
private T _measure = new();
public string Name => "Diff_"+_measure.Name;
private float _prev = float.NaN;
public float Calculate(IEnumerable<TimeSeries> data)
{
var val = _measure.Calculate(data);
if (float.IsNaN(_prev))
{
_prev = val;
return 0.0f;
}
val = val - _prev;
_prev = val;
return val;
}
public void Reset()
{
_measure.Reset();
_prev = float.NaN;
}
}
public class MeasureDiffMin : MeasureDiff<MeasureMin> { }
public class MeasureDiffMax : MeasureDiff<MeasureMax> { }
public class MeasureDiffAvg : MeasureDiff<MeasureAvg> { }
/// <summary>
/// WARNING: Only works with fixed length interval
/// </summary>
public class MeasureDiffSum : MeasureDiff<MeasureSum> { }
public class MeasureDiffMedian : MeasureDiff<MeasureMedian> { }
}

View File

@ -0,0 +1,80 @@
namespace DeepTrace.ML;
/// <summary>
/// Calculate median.
/// https://stackoverflow.com/questions/4140719/calculate-median-in-c-sharp
/// </summary>
public static class MedianHelper
{
/// <summary>
/// Partitions the given list around a pivot element such that all elements on left of pivot are <= pivot
/// and the ones at thr right are > pivot. This method can be used for sorting, N-order statistics such as
/// as median finding algorithms.
/// Pivot is selected ranodmly if random number generator is supplied else its selected as last element in the list.
/// Reference: Introduction to Algorithms 3rd Edition, Corman et al, pp 171
/// </summary>
private static int Partition<T>(this IList<T> list, int start, int end, Random rnd = null) where T : IComparable<T>
{
if (rnd != null)
list.Swap(end, rnd.Next(start, end + 1));
var pivot = list[end];
var lastLow = start - 1;
for (var i = start; i < end; i++)
{
if (list[i].CompareTo(pivot) <= 0)
list.Swap(i, ++lastLow);
}
list.Swap(end, ++lastLow);
return lastLow;
}
/// <summary>
/// Returns Nth smallest element from the list. Here n starts from 0 so that n=0 returns minimum, n=1 returns 2nd smallest element etc.
/// Note: specified list would be mutated in the process.
/// Reference: Introduction to Algorithms 3rd Edition, Corman et al, pp 216
/// </summary>
public static T NthOrderStatistic<T>(this IList<T> list, int n, Random rnd = null) where T : IComparable<T>
{
return NthOrderStatistic(list, n, 0, list.Count - 1, rnd);
}
private static T NthOrderStatistic<T>(this IList<T> list, int n, int start, int end, Random rnd) where T : IComparable<T>
{
while (true)
{
var pivotIndex = list.Partition(start, end, rnd);
if (pivotIndex == n)
return list[pivotIndex];
if (n < pivotIndex)
end = pivotIndex - 1;
else
start = pivotIndex + 1;
}
}
public static void Swap<T>(this IList<T> list, int i, int j)
{
if (i == j) //This check is not required but Partition function may make many calls so its for perf reason
return;
var temp = list[i];
list[i] = list[j];
list[j] = temp;
}
/// <summary>
/// Note: specified list would be mutated in the process.
/// </summary>
public static T Median<T>(IList<T> list) where T : IComparable<T>
{
return list.NthOrderStatistic((list.Count - 1) / 2);
}
public static TValue Median<T,TValue>(IEnumerable<T> sequence, Func<T, TValue> getValue)
where TValue : IComparable<TValue>
{
var list = sequence.Select(getValue).ToList();
var mid = (list.Count - 1) / 2;
return list.NthOrderStatistic(mid);
}
}

View File

@ -6,11 +6,13 @@
@using DeepTrace.Controls; @using DeepTrace.Controls;
@using Microsoft.ML; @using Microsoft.ML;
@using PrometheusAPI; @using PrometheusAPI;
@using System.Text;
@inject PrometheusClient Prometheus @inject PrometheusClient Prometheus
@inject IDialogService DialogService @inject IDialogService DialogService
@inject IDataSourceStorageService StorageService @inject IDataSourceStorageService StorageService
@inject IModelDefinitionService ModelService @inject IModelStorageService ModelService
@inject ITrainedModelStorageService TrainedModelService
@inject IEstimatorBuilder EstimatorBuilder @inject IEstimatorBuilder EstimatorBuilder
@inject NavigationManager NavManager @inject NavigationManager NavManager
@inject IJSRuntime Js @inject IJSRuntime Js
@ -64,12 +66,24 @@
<MudCardActions> <MudCardActions>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="MudBlazor.Color.Primary" Class="ml-3" OnClick="@HandleAddTableContent" Disabled="@IsAddDisabled">Add</MudButton> <MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="MudBlazor.Color.Primary" Class="ml-3" OnClick="@HandleAddTableContent" Disabled="@IsAddDisabled">Add</MudButton>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="MudBlazor.Color.Primary" Class="ml-3" OnClick="@HandleRefresh" Disabled="@IsAddDisabled">Refresh</MudButton> <MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="MudBlazor.Color.Primary" Class="ml-3" OnClick="@HandleRefresh" Disabled="@IsAddDisabled">Refresh</MudButton>
<MudFileUpload T="IBrowserFile" Accept=".csv" FilesChanged="@HandleImport" MaximumFileCount="1" Class="ml-3">
<ButtonTemplate>
<MudButton HtmlTag="label"
Variant="Variant.Filled"
Color="MudBlazor.Color.Primary"
StartIcon="@Icons.Material.Filled.CloudUpload"
for="@context">
Import
</MudButton>
</ButtonTemplate>
</MudFileUpload>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="MudBlazor.Color.Primary" Class="ml-3" OnClick="@HandleExport" Disabled="@IsAddDisabled">Export</MudButton> <MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="MudBlazor.Color.Primary" Class="ml-3" OnClick="@HandleExport" Disabled="@IsAddDisabled">Export</MudButton>
<MudSpacer/> <MudSpacer/>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="MudBlazor.Color.Primary" Class="ml-3" OnClick="@HandleTrain">Train</MudButton> <MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="MudBlazor.Color.Primary" Class="ml-3" OnClick="@HandleTrain">Train</MudButton>
</MudCardActions> </MudCardActions>
<MudTable <MudTable
Items="@_modelForm!.CurrentModel.IntervalDefinitionList" Items="@_modelForm!.CurrentModel.IntervalDefinitionList"
Hover="true" Hover="true"
@ -233,6 +247,9 @@
var sources = await StorageService.Load(); var sources = await StorageService.Load();
var models = await ModelService.Load(); var models = await ModelService.Load();
var trainedModels = await TrainedModelService.Load();
IList<IBrowserFile> files = new List<IBrowserFile>();
if (sources.Count > 0) if (sources.Count > 0)
_dataSources = sources; _dataSources = sources;
if (models.Count > 0) if (models.Count > 0)
@ -398,6 +415,18 @@
// await InvokeAsync(StateHasChanged); // await InvokeAsync(StateHasChanged);
} }
//Doesn't work
private async Task HandleImport(IBrowserFile file)
{
var result = new StringBuilder();
var reader = new StreamReader(file.OpenReadStream(file.Size));
while (reader.Peek() >= 0)
result.AppendLine(await reader.ReadLineAsync());
result.ToString();
}
private async Task HandleExport() private async Task HandleExport()
{ {
await Js.InvokeVoidAsync("open", $"{NavManager.BaseUri}api/download/mldata/{Uri.EscapeDataString(_modelForm.CurrentModel.Name)}", "_blank"); await Js.InvokeVoidAsync("open", $"{NavManager.BaseUri}api/download/mldata/{Uri.EscapeDataString(_modelForm.CurrentModel.Name)}", "_blank");
@ -430,6 +459,12 @@
var bytes = mlProcessor.Export(); var bytes = mlProcessor.Export();
//save to Mongo //save to Mongo
var trainedModel = new TrainedModelDefinition
{
Name = "TrainedModel",
Value = bytes
};
await TrainedModelService.Store(trainedModel);
} }

View File

@ -16,7 +16,8 @@ builder.Services.AddHttpClient<PrometheusClient>(c => c.BaseAddress = new UriBui
builder.Services builder.Services
.AddSingleton<IMongoClient>( s => new MongoClient(builder.Configuration.GetValue<string>("Connections:MongoDb") )) .AddSingleton<IMongoClient>( s => new MongoClient(builder.Configuration.GetValue<string>("Connections:MongoDb") ))
.AddSingleton<IDataSourceStorageService, DataSourceStorageService>() .AddSingleton<IDataSourceStorageService, DataSourceStorageService>()
.AddSingleton<IModelDefinitionService, ModelDefinitionService>() .AddSingleton<IModelStorageService, ModelStorageService>()
.AddSingleton<ITrainedModelStorageService, TrainedModelStorageService>()
.AddSingleton<IEstimatorBuilder, EstimatorBuilder>() .AddSingleton<IEstimatorBuilder, EstimatorBuilder>()
; ;

View File

@ -6,7 +6,7 @@ using System.Text;
namespace DeepTrace.Services namespace DeepTrace.Services
{ {
public interface IModelDefinitionService public interface IModelStorageService
{ {
Task Delete(ModelDefinition source, bool ignoreNotStored = false); Task Delete(ModelDefinition source, bool ignoreNotStored = false);
Task<List<ModelDefinition>> Load(); Task<List<ModelDefinition>> Load();

View File

@ -0,0 +1,11 @@
using DeepTrace.Data;
namespace DeepTrace.Services
{
public interface ITrainedModelStorageService
{
Task Delete(TrainedModelDefinition source, bool ignoreNotStored = false);
Task<List<TrainedModelDefinition>> Load();
Task Store(TrainedModelDefinition source);
}
}

View File

@ -4,7 +4,7 @@ using MongoDB.Driver;
namespace DeepTrace.Services namespace DeepTrace.Services
{ {
public class ModelDefinitionService : IModelDefinitionService public class ModelStorageService : IModelStorageService
{ {
private const string MongoDBDatabaseName = "DeepTrace"; private const string MongoDBDatabaseName = "DeepTrace";
@ -12,7 +12,7 @@ namespace DeepTrace.Services
private readonly IMongoClient _client; private readonly IMongoClient _client;
public ModelDefinitionService(IMongoClient client) public ModelStorageService(IMongoClient client)
{ {
_client = client; _client = client;
} }
@ -51,7 +51,7 @@ namespace DeepTrace.Services
} }
var db = _client.GetDatabase(MongoDBDatabaseName); var db = _client.GetDatabase(MongoDBDatabaseName);
var collection = db.GetCollection<DataSourceStorage>(MongoDBCollection); var collection = db.GetCollection<ModelDefinition>(MongoDBCollection);
await collection.DeleteOneAsync(filter: new BsonDocument("_id", source.Id)); await collection.DeleteOneAsync(filter: new BsonDocument("_id", source.Id));
} }

View File

@ -0,0 +1,58 @@
using DeepTrace.Data;
using MongoDB.Bson;
using MongoDB.Driver;
namespace DeepTrace.Services
{
public class TrainedModelStorageService: ITrainedModelStorageService
{
private const string MongoDBDatabaseName = "DeepTrace";
private const string MongoDBCollection = "TrainedModels";
private readonly IMongoClient _client;
public TrainedModelStorageService(IMongoClient client)
{
_client = client;
}
public async Task<List<TrainedModelDefinition>> Load()
{
var db = _client.GetDatabase(MongoDBDatabaseName);
var collection = db.GetCollection<TrainedModelDefinition>(MongoDBCollection);
var res = await (await collection.FindAsync("{}")).ToListAsync();
return res;
}
public async Task Store(TrainedModelDefinition source)
{
var db = _client.GetDatabase(MongoDBDatabaseName);
var collection = db.GetCollection<TrainedModelDefinition>(MongoDBCollection);
if (source.Id == null)
source.Id = ObjectId.GenerateNewId();
// use upsert (insert or update) to automatically handle subsequent updates
await collection.ReplaceOneAsync(
filter: new BsonDocument("_id", source.Id),
options: new ReplaceOptions { IsUpsert = true },
replacement: source
);
}
public async Task Delete(TrainedModelDefinition source, bool ignoreNotStored = false)
{
if (source.Id == null)
{
if (!ignoreNotStored)
throw new InvalidDataException("Source was not stored yet. There is nothing to delete");
return;
}
var db = _client.GetDatabase(MongoDBDatabaseName);
var collection = db.GetCollection<TrainedModelDefinition>(MongoDBCollection);
await collection.DeleteOneAsync(filter: new BsonDocument("_id", source.Id));
}
}
}