/// <summary> /// Constructor: model location relative to .exe, mlcontext, model settings of input name and output name /// </summary> /// <param name="inputModelLocation"></param> /// <param name="inputMlContext"></param> /// <param name="inputModelSettings"></param> public OnnxRNNScorer(string inputModelLocation, MLContext inputMlContext, ModelSettings inputModelSettings) { modelLocation = inputModelLocation; mlContext = inputMlContext; modelSettings = inputModelSettings; pipeline = mlContext.Transforms.ApplyOnnxModel(modelFile: modelLocation, outputColumnNames: new[] { modelSettings.modelOutput }, inputColumnNames: new[] { modelSettings.modelInput }); }
[ConditionalFact(typeof(Environment), nameof(Environment.Is64BitProcess))] // x86 output differs from Baseline public void TestUnknownDimensions() { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return; } // model contains -1 in input and output shape dimensions // model: input dims = [-1, 3], output argmax dims = [-1] var modelFile = @"unknowndimensions/test_unknowndimensions_float.onnx"; var mlContext = new MLContext(); var data = new TestDataUnknownDimensions[] { new TestDataUnknownDimensions() { input = new float[] { 1.1f, 1.3f, 1.2f } }, new TestDataUnknownDimensions() { input = new float[] { -1.1f, -1.3f, -1.2f } }, new TestDataUnknownDimensions() { input = new float[] { -1.1f, -1.3f, 1.2f } }, }; var idv = mlContext.CreateStreamingDataView(data); var pipeline = new OnnxScoringEstimator(mlContext, modelFile); var transformedValues = pipeline.Fit(idv).Transform(idv); var predictions = transformedValues.AsEnumerable <PredictionUnknownDimensions>(mlContext, reuseRowObject: false).ToArray(); Assert.Equal(1, predictions[0].argmax[0]); Assert.Equal(0, predictions[1].argmax[0]); Assert.Equal(2, predictions[2].argmax[0]); }
[ConditionalFact(typeof(Environment), nameof(Environment.Is64BitProcess))] // x86 fails with "An attempt was made to load a program with an incorrect format." void TestSimpleCase() { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return; } var modelFile = "squeezenet/00000001/model.onnx"; var samplevector = GetSampleArrayData(); var dataView = ComponentCreation.CreateDataView(Env, new TestData[] { new TestData() { data_0 = samplevector }, new TestData() { data_0 = samplevector } }); var xyData = new List <TestDataXY> { new TestDataXY() { A = new float[inputSize] } }; var stringData = new List <TestDataDifferntType> { new TestDataDifferntType() { data_0 = new string[inputSize] } }; var sizeData = new List <TestDataSize> { new TestDataSize() { data_0 = new float[2] } }; var pipe = new OnnxScoringEstimator(Env, modelFile, new[] { "data_0" }, new[] { "softmaxout_1" }); var invalidDataWrongNames = ComponentCreation.CreateDataView(Env, xyData); var invalidDataWrongTypes = ComponentCreation.CreateDataView(Env, stringData); var invalidDataWrongVectorSize = ComponentCreation.CreateDataView(Env, sizeData); TestEstimatorCore(pipe, dataView, invalidInput: invalidDataWrongNames); TestEstimatorCore(pipe, dataView, invalidInput: invalidDataWrongTypes); pipe.GetOutputSchema(SchemaShape.Create(invalidDataWrongVectorSize.Schema)); try { pipe.Fit(invalidDataWrongVectorSize); Assert.False(true); } catch (ArgumentOutOfRangeException) { } catch (InvalidOperationException) { } }
/// <summary> /// Example use of OnnxEstimator in an ML.NET pipeline /// </summary> public static void OnnxTransformSample() { // Download the squeeznet image model from ONNX model zoo, version 1.2 // https://github.com/onnx/models/tree/master/squeezenet var modelPath = @"squeezenet\model.onnx"; // Inspect the model's inputs and outputs var session = new InferenceSession(modelPath); var inputInfo = session.InputMetadata.First(); var outputInfo = session.OutputMetadata.First(); Console.WriteLine($"Input Name is {String.Join(",", inputInfo.Key)}"); Console.WriteLine($"Input Dimensions are {String.Join(",", inputInfo.Value.Dimensions)}"); Console.WriteLine($"Output Name is {String.Join(",", outputInfo.Key)}"); Console.WriteLine($"Output Dimensions are {String.Join(",", outputInfo.Value.Dimensions)}"); // Results.. // Input Name is data_0 // Input Dimensions are 1,3,224,224 // Output Name is softmaxout_1 // Output Dimensions are 1,1000,1,1 // Create ML pipeline to score the data using OnnxScoringEstimator var mlContext = new MLContext(); var data = GetTensorData(); var idv = mlContext.Data.ReadFromEnumerable(data); var pipeline = new OnnxScoringEstimator(mlContext, new[] { outputInfo.Key }, new[] { inputInfo.Key }, modelPath); // Run the pipeline and get the transformed values var transformedValues = pipeline.Fit(idv).Transform(idv); // Retrieve model scores into Prediction class var predictions = mlContext.CreateEnumerable <Prediction>(transformedValues, reuseRowObject: false); // Iterate rows foreach (var prediction in predictions) { int numClasses = 0; foreach (var classScore in prediction.softmaxout_1.Take(3)) { Console.WriteLine($"Class #{numClasses++} score = {classScore}"); } Console.WriteLine(new string('-', 10)); } // Results look like below... // Class #0 score = 4.544065E-05 // Class #1 score = 0.003845858 // Class #2 score = 0.0001249467 // ---------- // Class #0 score = 4.491953E-05 // Class #1 score = 0.003848222 // Class #2 score = 0.0001245592 // ---------- }
[ConditionalFact(typeof(BaseTestBaseline), nameof(BaseTestBaseline.LessThanNetCore30AndNotFullFramework))] // Tracked by https://github.com/dotnet/machinelearning/issues/2087 public void KmeansOnnxConversionTest() { // Create a new context for ML.NET operations. It can be used for exception tracking and logging, // as a catalog of available operations and as the source of randomness. var mlContext = new MLContext(seed: 1, conc: 1); string dataPath = GetDataPath("breast-cancer.txt"); // Now read the file (remember though, readers are lazy, so the actual reading will happen when the data is accessed). var data = mlContext.Data.ReadFromTextFile <BreastCancerFeatureVector>(dataPath, hasHeader: true, separatorChar: '\t'); var pipeline = mlContext.Transforms.Normalize("Features"). Append(mlContext.Clustering.Trainers.KMeans(new Trainers.KMeans.KMeansPlusPlusTrainer.Options { FeatureColumn = DefaultColumnNames.Features, MaxIterations = 1, ClustersCount = 4, NumThreads = 1, InitAlgorithm = Trainers.KMeans.KMeansPlusPlusTrainer.InitAlgorithm.Random })); var model = pipeline.Fit(data); var transformedData = model.Transform(data); var onnxModel = mlContext.Model.ConvertToOnnx(model, data); // Compare results produced by ML.NET and ONNX's runtime. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.Is64BitProcess) { var onnxFileName = "model.onnx"; var onnxModelPath = GetOutputPath(onnxFileName); SaveOnnxModel(onnxModel, onnxModelPath, null); // Evaluate the saved ONNX model using the data used to train the ML.NET pipeline. string[] inputNames = onnxModel.Graph.Input.Select(valueInfoProto => valueInfoProto.Name).ToArray(); string[] outputNames = onnxModel.Graph.Output.Select(valueInfoProto => valueInfoProto.Name).ToArray(); var onnxEstimator = new OnnxScoringEstimator(mlContext, onnxModelPath, inputNames, outputNames); var onnxTransformer = onnxEstimator.Fit(data); var onnxResult = onnxTransformer.Transform(data); CompareSelectedR4VectorColumns("Score", "Score0", transformedData, onnxResult, 3); } // Check ONNX model's text format. We save the produced ONNX model as a text file and compare it against // the associated file in ML.NET repo. Such a comparison can be retired if ONNXRuntime ported to ML.NET // can support Linux and Mac. var subDir = Path.Combine("..", "..", "BaselineOutput", "Common", "Onnx", "Cluster", "BreastCancer"); var onnxTextName = "Kmeans.txt"; var onnxTextPath = GetOutputPath(subDir, onnxTextName); SaveOnnxModel(onnxModel, null, onnxTextPath); CheckEquality(subDir, onnxTextName, digitsOfPrecision: 2); Done(); }
void TestSimpleCase() { var modelFile = "squeezenet/00000001/model.onnx"; var samplevector = GetSampleArrayData(); var dataView = ML.Data.ReadFromEnumerable( new TestData[] { new TestData() { data_0 = samplevector }, new TestData() { data_0 = samplevector } }); var xyData = new List <TestDataXY> { new TestDataXY() { A = new float[inputSize] } }; var stringData = new List <TestDataDifferntType> { new TestDataDifferntType() { data_0 = new string[inputSize] } }; var sizeData = new List <TestDataSize> { new TestDataSize() { data_0 = new float[2] } }; var pipe = new OnnxScoringEstimator(Env, new[] { "softmaxout_1" }, new[] { "data_0" }, modelFile); var invalidDataWrongNames = ML.Data.ReadFromEnumerable(xyData); var invalidDataWrongTypes = ML.Data.ReadFromEnumerable(stringData); var invalidDataWrongVectorSize = ML.Data.ReadFromEnumerable(sizeData); TestEstimatorCore(pipe, dataView, invalidInput: invalidDataWrongNames); TestEstimatorCore(pipe, dataView, invalidInput: invalidDataWrongTypes); pipe.GetOutputSchema(SchemaShape.Create(invalidDataWrongVectorSize.Schema)); try { pipe.Fit(invalidDataWrongVectorSize); Assert.False(true); } catch (ArgumentOutOfRangeException) { } catch (InvalidOperationException) { } }
[ConditionalFact(typeof(BaseTestBaseline), nameof(BaseTestBaseline.NotFullFramework))] // Tracked by https://github.com/dotnet/machinelearning/issues/2106 public void SimpleEndToEndOnnxConversionTest() { // Step 1: Create and train a ML.NET pipeline. var trainDataPath = GetDataPath(TestDatasets.generatedRegressionDataset.trainFilename); var mlContext = new MLContext(seed: 1, conc: 1); var data = mlContext.Data.ReadFromTextFile <AdultData>(trainDataPath, hasHeader: true, separatorChar: ';' ); var cachedTrainData = mlContext.Data.Cache(data); var dynamicPipeline = mlContext.Transforms.Normalize("FeatureVector") .AppendCacheCheckpoint(mlContext) .Append(mlContext.Regression.Trainers.StochasticDualCoordinateAscent(labelColumn: "Target", featureColumn: "FeatureVector")); var model = dynamicPipeline.Fit(data); var transformedData = model.Transform(data); // Step 2: Convert ML.NET model to ONNX format and save it as a file. var onnxModel = mlContext.Model.ConvertToOnnx(model, data); var onnxFileName = "model.onnx"; var onnxModelPath = GetOutputPath(onnxFileName); SaveOnnxModel(onnxModel, onnxModelPath, null); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.Is64BitProcess) { // Step 3: Evaluate the saved ONNX model using the data used to train the ML.NET pipeline. string[] inputNames = onnxModel.Graph.Input.Select(valueInfoProto => valueInfoProto.Name).ToArray(); string[] outputNames = onnxModel.Graph.Output.Select(valueInfoProto => valueInfoProto.Name).ToArray(); var onnxEstimator = new OnnxScoringEstimator(mlContext, onnxModelPath, inputNames, outputNames); var onnxTransformer = onnxEstimator.Fit(data); var onnxResult = onnxTransformer.Transform(data); // Step 4: Compare ONNX and ML.NET results. CompareSelectedR4ScalarColumns("Score", "Score0", transformedData, onnxResult, 1); } // Step 5: Check ONNX model's text format. This test will be not necessary if Step 3 and Step 4 can run on Linux and // Mac to support cross-platform tests. var subDir = Path.Combine("..", "..", "BaselineOutput", "Common", "Onnx", "Regression", "Adult"); var onnxTextName = "SimplePipeline.txt"; var onnxTextPath = GetOutputPath(subDir, onnxTextName); SaveOnnxModel(onnxModel, null, onnxTextPath); CheckEquality(subDir, onnxTextName, digitsOfPrecision: 3); Done(); }
[ConditionalFact(typeof(Environment), nameof(Environment.Is64BitProcess))] // x86 fails with "An attempt was made to load a program with an incorrect format." void TestOldSavingAndLoading() { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return; } var modelFile = "squeezenet/00000001/model.onnx"; var samplevector = GetSampleArrayData(); var dataView = ComponentCreation.CreateDataView(Env, new TestData[] { new TestData() { data_0 = samplevector } }); var inputNames = new[] { "data_0" }; var outputNames = new[] { "softmaxout_1" }; var est = new OnnxScoringEstimator(Env, modelFile, inputNames, outputNames); var transformer = est.Fit(dataView); var result = transformer.Transform(dataView); var resultRoles = new RoleMappedData(result); using (var ms = new MemoryStream()) { TrainUtils.SaveModel(Env, Env.Start("saving"), ms, null, resultRoles); ms.Position = 0; var loadedView = ModelFileUtils.LoadTransforms(Env, dataView, ms); loadedView.Schema.TryGetColumnIndex(outputNames[0], out int softMaxOut1); using (var cursor = loadedView.GetRowCursor(col => col == softMaxOut1)) { VBuffer <float> softMaxValue = default; var softMaxGetter = cursor.GetGetter <VBuffer <float> >(softMaxOut1); float sum = 0f; int i = 0; while (cursor.MoveNext()) { softMaxGetter(ref softMaxValue); var values = softMaxValue.DenseValues(); foreach (var val in values) { sum += val; if (i == 0) { Assert.InRange(val, 0.00004, 0.00005); } if (i == 1) { Assert.InRange(val, 0.003844, 0.003845); } if (i == 999) { Assert.InRange(val, 0.0029566, 0.0029567); } i++; } } Assert.InRange(sum, 1.0, 1.00001); } } }
public void TestOnnxTransformWithCustomShapes() { // The loaded model has input shape [-1, 3] and output shape [-1]. var modelFile = Path.Combine(Directory.GetCurrentDirectory(), "unknowndimensions", "test_unknowndimensions_float.onnx"); var dataPoints = new InputWithCustomShape[] { // It's a flattened 3-by-3 tensor. // [1.1, 1.3, 1.2] // |1.9, 1.3, 1.2| // [1.1, 1.3, 1.8] new InputWithCustomShape() { input = new float[] { 1.1f, 1.3f, 1.2f, 1.9f, 1.3f, 1.2f, 1.1f, 1.3f, 1.8f } }, // It's a flattened 3-by-3 tensor. // [0, 0, 1] // |1, 0, 0| // [1, 0, 0] new InputWithCustomShape() { input = new float[] { 0f, 0f, 1f, 1f, 0f, 0f, 1f, 0f, 0f } } }; var shapeDictionary = new Dictionary <string, int[]>() { { nameof(InputWithCustomShape.input), new int[] { 3, 3 } } }; var dataView = ML.Data.LoadFromEnumerable(dataPoints); var pipeline = new OnnxScoringEstimator[3]; var onnxTransformer = new OnnxTransformer[3]; var transformedDataViews = new IDataView[3]; // Test three public ONNX APIs with the custom shape. // Test 1. pipeline[0] = ML.Transforms.ApplyOnnxModel( new[] { nameof(PredictionWithCustomShape.argmax) }, new[] { nameof(InputWithCustomShape.input) }, modelFile, shapeDictionary); onnxTransformer[0] = pipeline[0].Fit(dataView); transformedDataViews[0] = onnxTransformer[0].Transform(dataView); // Test 2. pipeline[1] = ML.Transforms.ApplyOnnxModel( nameof(PredictionWithCustomShape.argmax), nameof(InputWithCustomShape.input), modelFile, shapeDictionary); onnxTransformer[1] = pipeline[1].Fit(dataView); transformedDataViews[1] = onnxTransformer[1].Transform(dataView); // Test 3. pipeline[2] = ML.Transforms.ApplyOnnxModel(modelFile, shapeDictionary); onnxTransformer[2] = pipeline[2].Fit(dataView); transformedDataViews[2] = onnxTransformer[2].Transform(dataView); // Conduct the same check for all the 3 called public APIs. foreach (var transformedDataView in transformedDataViews) { var transformedDataPoints = ML.Data.CreateEnumerable <PredictionWithCustomShape>(transformedDataView, false).ToList(); // One data point generates one transformed data point. Assert.Equal(dataPoints.Count(), transformedDataPoints.Count); // Check result numbers. They are results of applying ONNX argmax along the second axis; for example // [1.1, 1.3, 1.2] ---> [1] because 1.3 (indexed by 1) is the largest element. // |1.9, 1.3, 1.2| ---> |0| 1.9 0 // [1.1, 1.3, 1.8] ---> [2] 1.8 2 var expectedResults = new long[][] { new long[] { 1, 0, 2 }, new long[] { 2, 0, 0 } }; for (int i = 0; i < transformedDataPoints.Count; ++i) { Assert.Equal(transformedDataPoints[i].argmax, expectedResults[i]); } } for (int i = 0; i < 3; i++) { (onnxTransformer[i] as IDisposable)?.Dispose(); } }