How to add multiple custom columns to IDataView in ML.NET - ml.net

I'd like to add two custom columns after loading my IDataView from file. In each row, the new column values should be natural logarithm of the values.
My environment is:
Windows 10 Pro, Version 10.0.19043 Build 19043
ML.NET version 1.6.0
Microsoft Visual Studio Professional 2022 Preview (64-bit), Version 17.0.0 Preview 3.1
Here is what I have done so far. This is a standalone, reproducible program that demonstrates the problem.
using Microsoft.ML;
using Microsoft.ML.Transforms;
namespace TestLog {
public static class Program {
private class InputData {
public double Velocity { get; set; }
public double Thrust { get; set; }
}
private class CustomMappingOutput {
public double LogVelocity { get; set; }
public double LogThrust { get; set; }
}
private class TransformedData : InputData {
public double LogVelocity { get; set; }
public double LogThrust { get; set; }
}
[CustomMappingFactoryAttribute("LogVelocity")]
private class LogVelocityCustomAction : CustomMappingFactory<InputData, CustomMappingOutput> {
public static void CustomAction(InputData input, CustomMappingOutput output)
=> output.LogVelocity = (float) Math.Log(input.Velocity);
public override Action<InputData, CustomMappingOutput> GetMapping() => CustomAction;
}
[CustomMappingFactoryAttribute("LogThrust")]
private class LogThrustCustomAction : CustomMappingFactory<InputData, CustomMappingOutput> {
public static void CustomAction(InputData input, CustomMappingOutput output)
=> output.LogThrust = (float)Math.Log(input.Thrust);
public override Action<InputData, CustomMappingOutput> GetMapping() => CustomAction;
}
public static void Run() {
var mlContext = new MLContext();
var samples = new List<InputData> {
new InputData { Velocity= 0.006467, Thrust = 1.614237 },
new InputData { Velocity= 0.53451, Thrust = 1.068356 },
new InputData { Velocity= 0.278578, Thrust = 0.216861 },
new InputData { Velocity= 0.014179, Thrust = 0.119712 },
new InputData { Velocity= 0.392814, Thrust = 3.915486 }
};
var data = mlContext.Data.LoadFromEnumerable(samples);
var pipeline = mlContext.Transforms.CustomMapping(new LogVelocityCustomAction().GetMapping(),
contractName: "LogVelocity")
.Append(mlContext.Transforms.CustomMapping(new LogThrustCustomAction().GetMapping(),
contractName: "LogThrust"));
var transformer = pipeline.Fit(data);
// Now save the transform pipeline so that it can be reloaded by another process.
mlContext.Model.Save(transformer, data.Schema, "customTransform.zip");
// We load the saved transform and use it, as if it was in another program.
var loadedTransform = mlContext.Model.Load("customTransform.zip", out _);
// Now we can transform the data.
var transformedIDataView = loadedTransform.Transform(data);
var newDataEnumerable = mlContext.Data.CreateEnumerable<TransformedData>(transformedIDataView,
reuseRowObject: true);
var newIDataView = mlContext.Data.LoadFromEnumerable(newDataEnumerable);
// Save newIDataView as a CSV file so we can verify the transformations.
var path = #"../../../transformed.csv";
var mlContextSave = new MLContext();
using var stream = File.Create(path);
mlContextSave.Data.SaveAsText(newIDataView,
stream,
separatorChar: ',',
headerRow: true,
schema: false);
}
static void Main() {
Program.Run();
Console.WriteLine("Hit return to exit");
Console.ReadLine();
}
}
}
At end of the program, the transformed IDataView is saved as a “.csv” file.
It looks like:
As you can see, the “Velocity” column was not transformed.
Any help or suggestions will be greatly appreciated.
Charles

You're casting the log to float, then assigning it to a variable of type double. Have you tried casting to double?
I'm not sure why it's working for thrust but not velocity. Perhaps the very small number in the first line of velocity is causing an exception.

Related

How to use onnx model in mlnet c#, passing inputs and getting outputs

I trained a model using pytorch
I exported it to onnx format and tested in python that it works (it does)
I want to know how I can use this in ml.net in c#
The usage in python looks like this
the model in netorn looks like
I found an example that uses
using the packages Microsoft.ML, Microsoft.ML.OnnxRuntime and Microsoft.ML.OnnxTransformer
and is able to use an onnx model but that works with images and since I am very new to this I am unable to figure out how to load the model and make a prediction and get the value of the action which is an array of float size 6.
I found this to be useful for this
you'll also have to define your model schema.
here is the class I ended up with for this model
using Microsoft.ML;
using Microsoft.ML.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
namespace godot_net_server
{
class OnnxModelScorer
{
private readonly string modelLocation;
private readonly MLContext mlContext;
public OnnxModelScorer(string modelLocation, MLContext mlContext)
{
this.modelLocation = modelLocation;
this.mlContext = mlContext;
}
public class ModelInput
{
[VectorType(10)]
[ColumnName("input.1")]
public float[] Features { get; set; }
}
private ITransformer LoadModel(string modelLocation)
{
Console.WriteLine("Read model");
Console.WriteLine($"Model location: {modelLocation}");
// Create IDataView from empty list to obtain input data schema
var data = mlContext.Data.LoadFromEnumerable(new List<ModelInput>());
// Define scoring pipeline
var pipeline = mlContext.Transforms.ApplyOnnxModel(modelFile: modelLocation, outputColumnNames: new[] { "31","34" }, inputColumnNames: new[] { "input.1" });
// Fit scoring pipeline
var model = pipeline.Fit(data);
return model;
}
public class Prediction
{
[VectorType(6)]
[ColumnName("31")]
public float[] action{ get; set; }
[VectorType(1)]
[ColumnName("34")]
public float[] state { get; set; }
}
private IEnumerable<float> PredictDataUsingModel(IDataView testData, ITransformer model)
{
Console.WriteLine("");
Console.WriteLine("=====Identify the objects in the images=====");
Console.WriteLine("");
IDataView scoredData = model.Transform(testData);
IEnumerable<float[]> probabilities = scoredData.GetColumn<float[]>("31");
var a = probabilities.ToList();
a.Count.ToString();
return a[0];
}
public IEnumerable<float> Score(IDataView data)
{
var model = LoadModel(modelLocation);
return PredictDataUsingModel(data, model);
}
}
}
this is how I used it to get a prediction
MLContext mlContext = new MLContext();
// Load trained model
var modelScorer = new OnnxModelScorer("my_ppo_1_model.onnx", mlContext);
List<ModelInput> input = new List<ModelInput>();
input.Add(new ModelInput()
{
Features = new[]
{
3.036393f,11.0f,2.958097f,0.0f,0.0f,0.0f,0.015607f,0.684984f,0.0f,0.0f
}
});
var action = modelScorer.Score(mlContext.Data.LoadFromEnumerable(input));
The result is as I was expecting it to be

Integrate Blazor with Chart.js: how to pass an object

I want to display in my Blazor WebAssembly application, some graphs with Chart.js. I tried to use Chartjs.Blazor.Fork but I have few errors, for example I have opened another post about here.
So, after a day without results, I decided to start my own component. I follow the instruction I found in a blog. Basically, I have my Razor component called Chart.razor with the following code
#inject IJSRuntime JSRuntime
<canvas id="#Id"></canvas>
#code {
public enum ChartType
{
Pie,
Bar
}
[Parameter]
public string Id { get; set; }
[Parameter]
public ChartType Type { get; set; }
[Parameter]
public string[] Data { get; set; }
[Parameter]
public string[] BackgroundColor { get; set; }
[Parameter]
public string[] Labels { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// Here we create an anonymous type with all the options
// that need to be sent to Chart.js
var config = new
{
Type = Type.ToString().ToLower(),
Options = new
{
Responsive = true,
Scales = new
{
YAxes = new[]
{
new { Ticks = new {
BeginAtZero=true
} }
}
}
},
Data = new
{
Datasets = new[]
{
new { Data = Data, BackgroundColor = BackgroundColor}
},
Labels = Labels
}
};
await JSRuntime.InvokeVoidAsync("setup", Id, config);
}
}
then I have my own mychart.js script to update the chart
window.setup = (id,config) => {
var ctx = document.getElementById(id).getContext('2d');
new Chart(ctx, config);
}
So, I use this code
<Chart Id="bar1" Type="#Chart.ChartType.Bar"
Data="#(new[] { " 10", "9" } )"
BackgroundColor="#(new[] { " yellow","red"} )"
Labels="#(new[] { " Fail","Ok" } )">
</Chart>
Ugly code but it is working. Now, I can display a graph in my page. Cool! What I want to display is something more complex, because I have to show a stacked bar graph with groups and the configuration is quite complicated.
I want to replace the config you can see in the page with for example a class. In this class I want to collect all configuration, like Type, Options, Data, Labels and so on, and pass them in the await JSRuntime.InvokeVoidAsync("setup", Id, config);`
For starting I created my base class like
public abstract class ConfigBase
{
protected ConfigBase(ChartType chartType)
{
Type = chartType;
}
public ChartType Type { get; }
public string CanvasId { get; } = Guid.NewGuid().ToString();
}
My problem is how to transform this class to obtain a valid object for the JavaScript to execute correctly new Chart(ctx, config);.
OK, it was quite easy. I saw a lot of different technics for that but there is a very basic simple way
await JSRuntime.InvokeVoidAsync("setup", Config.CanvasId, Config);

Iron Python throws exception on testing environment

I'm having a code which runs Iron Python scripts on VS 2010. Every time a test completes I get an exception of type ObjectDisposedException, with the description: Cannot write to a closed TextWriter. I can't see the stack trace. I'm accessing the scripts via this wrapper:
public static class PythonWrapper
{
public static dynamic GetClient(string clientName, string clientType)
{
var file = string.Format(#"{0}\Python\webcore.eas", Directory.GetCurrentDirectory());
dynamic result = null;
var ipy = GetRuntime();
var engine = ipy.GetEngine("py");
ScriptScope clientScope = engine.CreateScope();
if(File.Exists(file))
{
clientScope.SetVariable("asm", Assembly.Load(ServiceManager.Get<FileEncryptionSevice>().Decrypt(file)));
string dllWrapper = string.Format("import clr\n" +
"clr.AddReference(asm)\n" +
"from Clients.{0} import {1}\n" +
"del clr", clientName, clientType);
var src = engine.CreateScriptSourceFromString(dllWrapper);
var compiled = src.Compile();
compiled.Execute(clientScope);
result = clientScope.GetVariable(clientType);
}
else
{
var scope = ipy.UseFile(string.Format(#"{0}\Python\Clients\{1}.py", Directory.GetCurrentDirectory(),clientName));
result = scope.GetVariable(clientType);
}
return result;
}
private static ScriptRuntime GetRuntime()
{
var result = Python.CreateRuntime();
var engine = Python.GetEngine(result);
var baseFolder = string.Format(#"{0}\Python\", Directory.GetCurrentDirectory());
engine.SetSearchPaths(new[] {
string.Format("{0}", baseFolder),
string.Format(#"{0}\Lib\", Directory.GetCurrentDirectory())
});
return result;
}
}
I guess I'm attempting to access a disposed object but none of the scripting objects is IDisposable. I've also tried calling ScriptRuntime.ShutDown at the end of each test, but it only has the test stuck.
Please help me.
Kind regards,
Izhar
Turns out it was a simple bug of setting an output stream for the iron python. The following code solved the error
private static void SetOutputStream(ScriptEngine engine)
{
ScriptScope sys = engine.GetSysModule();
sys.SetVariable("stdout", new PythonStreamWrapper(LogLevel.Debug));
sys.SetVariable("stderr", new PythonStreamWrapper(LogLevel.Debug));
}
public class PythonStreamWrapper
{
private readonly ILogger _logger = LoggerService.GetLogger("Obj.Gen");
private LogLevel _logLevel;
public PythonStreamWrapper(LogLevel logLevel)
{
_logLevel = logLevel;
}
public void write(string text)
{
if (text.Trim() == "") return;
_logger.Write(_logLevel, text);
}
public int softspace
{
get;
set;
}
}

Automoq Documentation

I am getting started with Automoq. I was trying to do something like this:
mocker.GetMock<IMyObjectToTweak>();
var line = mocker.Resolve<IMyObjectToTweak>();
line.PropertyOne = .75;
line.PropertyTwo = 100;
MyCalc calc = new MyCalc();
calc.Multiply(line);
Assert.AreEqual(75, line.result);
This runs bu fails. My properties do not get set. Am I missing the idea of Automoq? What is a good resource/tutorial?
To set property with Moq (this is what Automoq uses to create mock objects) you have to use different calls, - Setup, SetupGet or SetupProperty:
var line = mocker.Resolve<IMyObjectToTweak>();
// each does the same thing - "tells" PropertyOne to return .75 upon get
line.Setup(l => l.PropertyOne).Returns(.75);
line.SetupGet(l => l.PropertyOne).Returns(.75);
line.SetupProperty(l => l.PropertyOne, .75);
I suggest expose a Result property in your Sut (System Under test)
[TestClass]
public class SomeTest : ControllerTestBase
{
[TestMethod]
public void MethodNameOrSubject_ScenarioOrCondition_ExpectedBehaviourOrReturnValue()
{
var mock = _autoMoqContainer.GetMock<IMyObjectToTweak>();
var line = _autoMoqContainer.Resolve<IMyObjectToTweak>();
mock.Setup(x => x.PropertyOne).Returns(.75);
mock.Setup(x => x.PropertyTwo).Returns(100);
MyCalc calc = new MyCalc();
calc.Multiply(line);
Assert.AreEqual(75, calc.Result);
}
}
public interface IMyObjectToTweak
{
double PropertyOne { get; set; }
int PropertyTwo { get; set; }
}
public class MyCalc
{
public double Result { get; set; }
public void Multiply(IMyObjectToTweak line)
{
Result = line.PropertyOne*line.PropertyTwo;
}
}
Not related - But read my post more on AutoMocking
http://www.dotnetcurry.com/ShowArticle.aspx?ID=767

Cannot get google visualisation/graph to load up via Ajax call?

Is there a special way of creating a google chart via an Ajax call, which is different from the static method?
The HTML i am producing is correct because it will load from a normal HTML file, but when im calling the Ajax, the data in the graph is not showing.
I am using google.setOnLoadCallback() and google.load('visualization', '1', {packages: ['table']})
You need to get data from ajax call and then put it in to your visualization function.
Here is my code:
google.load('visualization', '1', { packages: ['corechart'] });
google.setOnLoadCallback(OnLoad);
var url = '/Charting/GetData';
function OnLoad() {
$.ajax({
url: url,
dataType: 'json',
success: function (response) {
drawVisualization(response);
}
});
};
function drawVisualization(response) {
var chart = new google.visualization.ColumnChart(
document.getElementById('visualization'));
var data = new google.visualization.DataTable(response);
chart.draw(data);
};
Also i recommend you to use this class to generate correct JSON response:
public class ChartHelper
{
public ColInfo[] cols { get; set; }
public DataPointSet[] rows { get; set; }
}
public class ColInfo
{
public string id { get; set; }
public string label { get; set; }
public string type { get; set; }
}
public class DataPointSet
{
public DataPoint[] c { get; set; }
}
public class DataPoint
{
public object v { get; set; } // value
public string f { get; set; } // format
}
Then you can use it like this:
[ActionName("data")]
public JsonResult Data()
{
Random r = new Random();
var graph = new ChartHelper
{
cols = new ColInfo[] {
new ColInfo { id = "A", label = "Name", type = "string" },
new ColInfo { id = "B", label = "Value", type = "number" },
},
rows = new DataPointSet[] {
new DataPointSet {
c = new DataPoint[]
{
new DataPoint { v = "Name" },
new DataPoint { v = r.NextDouble()},
}},
new DataPointSet {
c = new DataPoint[]
{
new DataPoint { v = "Name2" },
new DataPoint { v = r.NextDouble()},
}},
new DataPointSet {
c = new DataPoint[]
{
new DataPoint { v = "Name3" },
new DataPoint { v = r.NextDouble()},
}}
}
};
return Json(graph, JsonRequestBehavior.AllowGet);
}