From e0496f8be0218327dbdb641aaceef2dc9e9dba42 Mon Sep 17 00:00:00 2001 From: Jignesh Parmar Date: Sun, 25 Nov 2018 11:02:00 +0000 Subject: [PATCH 1/8] update transform to use onnxruntime library instead of sonoma --- build/Dependencies.props | 1 + .../Dynamic/OnnxTransform.cs | 123 ++++++ .../Microsoft.ML.OnnxTransform.csproj | 4 +- .../OnnxTransform.cs | 59 ++- src/Microsoft.ML.OnnxTransform/OnnxUtils.cs | 349 +++++++----------- .../OnnxTransformTests.cs | 10 +- 6 files changed, 317 insertions(+), 229 deletions(-) create mode 100644 docs/samples/Microsoft.ML.Samples/Dynamic/OnnxTransform.cs diff --git a/build/Dependencies.props b/build/Dependencies.props index c221bf4f80..38080cbc85 100644 --- a/build/Dependencies.props +++ b/build/Dependencies.props @@ -17,6 +17,7 @@ 3.5.1 2.2.1.1 1.1.0 + 0.1.0-dev002 0.0.0.7 2.1.3 4.5.0 diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/OnnxTransform.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/OnnxTransform.cs new file mode 100644 index 0000000000..2336d88545 --- /dev/null +++ b/docs/samples/Microsoft.ML.Samples/Dynamic/OnnxTransform.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.ML.Transforms; +using Microsoft.ML.Runtime.Data; +using Microsoft.ML.Runtime.ImageAnalytics; +using Microsoft.ML; +using System; +using System.IO; + +namespace Microsoft.ML.Samples.Dynamic +{ + class Program + { + static void Main(string[] args) + { + // Download the squeeznet image model from ONNX model zoo, version 1.2 + // https://github.com/onnx/models/tree/master/squeezenet + var model_location = @"squeezenet\model.onnx"; + + var env = new MLContext(); + + // Use the utility functions to inspect models inputs, outputs, shape and type + // Load the model using the OnnxModel class + var onnxModel = new OnnxModel(model_location); + + // This model has only 1 input, so inspect 0th index for input node metadata + var inputSchema = onnxModel.ModelInfo.InputsInfo[0]; + var inputName = inputSchema.Name; + var inputShape = inputSchema.Shape; + var inputType = inputSchema.Type; + + // Deduce image dimensions from inputShape + var numChannels = inputShape[1]; + var imageHeight = inputShape[2]; + var imageWidth = inputShape[3]; + + // Similarly, get output node metadata + var outputSchema = onnxModel.ModelInfo.OutputsInfo[0]; + var outputName = outputSchema.Name; + var outputShape = outputSchema.Shape; + var outputType = outputSchema.Type; + + var dataFile = @"test\data\images\images.tsv"; + var imageFolder = Path.GetDirectoryName(dataFile); + + // Use Textloader to load the text file which references the images to load + // Preview ... + // banana.jpg banana + // hotdog.jpg hotdog + // tomato.jpg tomato + var data = TextLoader.Create(env, new TextLoader.Arguments() + { + Column = new[] + { + new TextLoader.Column("ImagePath", DataKind.TX, 0), + new TextLoader.Column("Name", DataKind.TX, 1), + } + }, new MultiFileSource(dataFile)); + + // Load the images referenced in the text file + var images = ImageLoaderTransform.Create(env, new ImageLoaderTransform.Arguments() + { + Column = new ImageLoaderTransform.Column[1] + { + new ImageLoaderTransform.Column() { Source= "ImagePath", Name="ImageReal" } + }, + ImageFolder = imageFolder + }, data); + + // Resize the images to match model dimensions + var cropped = ImageResizerTransform.Create(env, new ImageResizerTransform.Arguments() + { + Column = new ImageResizerTransform.Column[1]{ + new ImageResizerTransform.Column(){ Source = "ImageReal", Name= "ImageCropped", ImageHeight =imageHeight, ImageWidth = imageWidth, Resizing = ImageResizerTransform.ResizingKind.IsoCrop}} + }, images); + + // Extract out the RBG pixel values. + // InterleaveArgb = true makes the values RGBRGBRGB. Otherwise it's RRR...GGG...BBB. + var pixels = ImagePixelExtractorTransform.Create(env, new ImagePixelExtractorTransform.Arguments() + { + Column = new ImagePixelExtractorTransform.Column[1]{ + new ImagePixelExtractorTransform.Column() { Source= "ImageCropped", Name = inputName, InterleaveArgb=true} + } + }, cropped); + + // Create OnnxTransform, passing in the input and output names the model expects. + IDataView trans = OnnxTransform.Create(env, pixels, model_location, new[] { inputName }, new[] { outputName }); + + trans.Schema.TryGetColumnIndex(outputName, out int output); + using (var cursor = trans.GetRowCursor(col => col == output)) + { + var numRows = 0; + var buffer = default(VBuffer); + var getter = cursor.GetGetter>(output); + // For each image, retrieve the model scores + while (cursor.MoveNext()) + { + int i = 0; + getter(ref buffer); + // print scores for first 3 classes + foreach(var score in buffer.GetValues()) + { + Console.WriteLine(String.Format("Example # {0} :Score for class {1} = {2} ",numRows, i, score)); + if (++i > 2) break; + } + numRows += 1; + } + } + // Results look like below... + // Example # 0 :Score for class 0 = 1.133263E-06 + // Example # 0 :Score for class 1 = 1.80478E-07 + // Example # 0 :Score for class 2 = 1.595297E-07 + // Example # 1 :Score for class 0 = 1.805106E-05 + // Example # 1 :Score for class 1 = 1.257452E-05 + // Example # 1 :Score for class 2 = 2.412128E-06 + // Example # 2 :Score for class 0 = 1.346096E-06 + // Example # 2 :Score for class 1 = 1.918751E-07 + // Example # 2 :Score for class 2 = 7.203341E-08 + } + } +} diff --git a/src/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.csproj b/src/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.csproj index 2298f8d0d7..2daf76b5d6 100644 --- a/src/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.csproj +++ b/src/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.csproj @@ -9,7 +9,9 @@ - + + + diff --git a/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs b/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs index b6eb29286e..29492dccd1 100644 --- a/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs +++ b/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs @@ -12,13 +12,13 @@ using Microsoft.ML.Runtime.EntryPoints; using Microsoft.ML.Runtime.Internal.Utilities; using Microsoft.ML.Runtime.Model; -using Microsoft.ML.Scoring; +using Microsoft.ML.OnnxRuntime; using Microsoft.ML.Transforms; using Microsoft.ML.StaticPipe; using Microsoft.ML.StaticPipe.Runtime; using Microsoft.ML.Core.Data; using Microsoft.ML.Data; -using OnnxShape = System.Collections.Generic.List; +using OnnxShape = System.Collections.Generic.List; [assembly: LoadableClass(OnnxTransform.Summary, typeof(IDataTransform), typeof(OnnxTransform), typeof(OnnxTransform.Arguments), typeof(SignatureDataTransform), OnnxTransform.UserName, OnnxTransform.ShortName, "OnnxTransform", "OnnxScorer")] @@ -36,6 +36,22 @@ namespace Microsoft.ML.Transforms { + /// + ///

A transform for scoring ONNX models in the ML.NET framework.

+ /// + /// + /// + ///
+ /// + ///

Supports inferencing of models in 1.2 and 1.3 format, using the + /// Microsoft.ML.Scoring library + ///

+ ///

The inputs and outputs of the onnx models must of of Tensors. Sequence and Maps are not yet supported.

+ ///

Visit https://github.com/onnx/models to see a list of readily available models to get started with.

+ ///

Refer to http://onnx.ai' for more information about ONNX.

+ ///
public sealed class OnnxTransform : RowToRowTransformerBase { public sealed class Arguments : TransformInputBase @@ -223,7 +239,7 @@ private sealed class Mapper : MapperBase private readonly int[] _inputColIndices; private readonly bool[] _isInputVector; private readonly OnnxShape[] _inputTensorShapes; - private readonly DataType[] _inputOnnxTypes; + private readonly System.Type[] _inputOnnxTypes; public Mapper(OnnxTransform parent, Schema inputSchema) : base(Contracts.CheckRef(parent, nameof(parent)).Host.Register(nameof(Mapper)), inputSchema) @@ -233,7 +249,7 @@ public Mapper(OnnxTransform parent, Schema inputSchema) : _inputColIndices = new int[_parent.Inputs.Length]; _isInputVector = new bool[_parent.Inputs.Length]; _inputTensorShapes = new OnnxShape[_parent.Inputs.Length]; - _inputOnnxTypes = new DataType[_parent.Inputs.Length]; + _inputOnnxTypes = new System.Type[_parent.Inputs.Length]; var model = _parent.Model; for (int i = 0; i < _parent.Inputs.Length; i++) @@ -291,16 +307,16 @@ public override Func GetDependencies(Func activeOutput) private interface ITensorValueGetter { - Tensor GetTensor(); + Object GetTensor(); } private class OutputCache { public long Position; - public Dictionary Outputs; + public Dictionary Outputs; public OutputCache() { Position = -1; - Outputs = new Dictionary(); + Outputs = new Dictionary(); } } @@ -308,17 +324,18 @@ private void UpdateCacheIfNeeded(long position, ITensorValueGetter[] srcTensorGe { if (outputCache.Position != position) { - var inputTensors = new List(); + var inputTensors = new List(); for (int i = 0; i < _inputColIndices.Length; i++) - inputTensors.Add(srcTensorGetters[i].GetTensor()); + inputTensors.Add(new NamedOnnxValue(_parent.Inputs[i], srcTensorGetters[i].GetTensor())); var outputTensors = _parent.Model.Run(inputTensors); Contracts.Assert(outputTensors.Count > 0); - for (int j = 0; j < outputTensors.Count; j++) - outputCache.Outputs[activeOutputColNames[j]] = outputTensors[j]; - + foreach (var outputTensor in outputTensors) + { + outputCache.Outputs[outputTensor.Name] = outputTensor; + } outputCache.Position = position; } } @@ -344,8 +361,8 @@ private Delegate MakeGetter(IRow input, int iinfo, ITensorValueGetter[] srcTe { UpdateCacheIfNeeded(input.Position, srcTensorGetters, activeOutputColNames, outputCache); var tensor = outputCache.Outputs[_parent.Outputs[iinfo]]; - var editor = VBufferEditor.Create(ref dst, tensor.GetSize()); - OnnxUtils.CopyTo(tensor, editor.Values); + var editor = VBufferEditor.Create(ref dst, tensor.AsTensor().Count()); + OnnxUtils.CopyTo(tensor.AsTensor(), editor.Values); dst = editor.Commit(); }; return valuegetter; @@ -354,7 +371,7 @@ private Delegate MakeGetter(IRow input, int iinfo, ITensorValueGetter[] srcTe private static ITensorValueGetter[] GetTensorValueGetters(IRow input, int[] inputColIndices, bool[] isInputVector, - DataType[] onnxInputTypes, + System.Type[] onnxInputTypes, OnnxShape[] onnxInputShapes) { var srcTensorGetters = new ITensorValueGetter[inputColIndices.Length]; @@ -366,7 +383,7 @@ private static ITensorValueGetter[] GetTensorValueGetters(IRow input, return srcTensorGetters; } - private static ITensorValueGetter CreateTensorValueGetter(IRow input, DataType onnxType, bool isVector, int colIndex, OnnxShape onnxShape) + private static ITensorValueGetter CreateTensorValueGetter(IRow input, System.Type onnxType, bool isVector, int colIndex, OnnxShape onnxShape) { var type = OnnxUtils.OnnxToMlNetType(onnxType).RawType; Contracts.AssertValue(type); @@ -388,7 +405,7 @@ public TensorValueGetter(IRow input, int colIndex) { _srcgetter = input.GetGetter(colIndex); } - public Tensor GetTensor() + public Object GetTensor() { var scalar = default(T); _srcgetter(ref scalar); @@ -409,7 +426,7 @@ public TensorValueGetterVec(IRow input, int colIndex, OnnxShape tensorShape) _vBuffer = default; _vBufferDense = default; } - public Tensor GetTensor() + public Object GetTensor() { _srcgetter(ref _vBuffer); _vBuffer.CopyToDense(ref _vBufferDense); @@ -418,6 +435,10 @@ public Tensor GetTensor() } } } + + /// + /// A class implementing the estimator interface of the OnnxTransform. + /// public sealed class OnnxScoringEstimator : TrivialEstimator { public OnnxScoringEstimator(IHostEnvironment env, string modelFile, string[] inputs, string[] outputs) @@ -459,7 +480,7 @@ public override SchemaShape GetOutputSchema(SchemaShape inputSchema) { resultDic[Transformer.Outputs[i]] = new SchemaShape.Column(Transformer.Outputs[i], Transformer.OutputTypes[i].IsKnownSizeVector ? SchemaShape.Column.VectorKind.Vector - : SchemaShape.Column.VectorKind.VariableVector, NumberType.R4, false); + : SchemaShape.Column.VectorKind.VariableVector, Transformer.OutputTypes[i].ItemType, false); } return new SchemaShape(resultDic.Values); } diff --git a/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs b/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs index 611359d9cc..1261317d14 100644 --- a/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs +++ b/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs @@ -5,26 +5,29 @@ using Microsoft.ML.Runtime; using Microsoft.ML.Runtime.Data; using Microsoft.ML.Runtime.Internal.Utilities; -using Microsoft.ML.Scoring; +//using Microsoft.ML.Scoring; +using Microsoft.ML.OnnxRuntime; +using System.Numerics.Tensors; using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.ML.StaticPipe; -using OnnxShape = System.Collections.Generic.List; +using OnnxShape = System.Collections.Generic.List; using Microsoft.ML.Data; namespace Microsoft.ML.Transforms { /// - /// OnnxModel is a facad for ModelManager. ModelManager is provided by Sonoma API, - /// and it has a lot of functionality (multiple models, multiple versions) that are not - /// needed by Onnx transform, which only needs a single model. This facad simplifies the - /// usage of onnx model. + /// OnnxModel is a utility class to load ONNX models, and retrieve metadata + /// for inputs and outputs. The metadata includes the names, shapes and types + /// It provides API to open a session, score tensors (NamedOnnxValues) and return + /// the results. /// - internal sealed class OnnxModel + public sealed class OnnxModel { + /// /// OnnxModelInfo contains the data that we should get from /// Sonoma API once that functionality is added. @@ -47,11 +50,20 @@ public OnnxModelInfo(OnnxNodeInfo[] inputsInfo, OnnxNodeInfo[] outputsInfo) /// public class OnnxNodeInfo { + /// + /// The Name of the input node + /// public readonly string Name; + /// + /// The shape of the input node + /// public readonly OnnxShape Shape; - public readonly DataType Type; + /// + /// The type of the input node + /// + public readonly System.Type Type; - public OnnxNodeInfo(string name, OnnxShape shape, DataType type) + public OnnxNodeInfo(string name, OnnxShape shape, System.Type type) { Name = name; Shape = shape; @@ -60,29 +72,25 @@ public OnnxNodeInfo(string name, OnnxShape shape, DataType type) } public readonly OnnxModelInfo ModelInfo; - - private static readonly int _ignoredVersion = int.MaxValue; - private readonly ModelManager _modelManager; + private readonly InferenceSession _session; private readonly string _modelFile; - private readonly string _modelName; public readonly List InputNames; public readonly List OutputNames; public OnnxModel(string modelFile) { _modelFile = modelFile; - - // Load the onnx model - var modelFileInfo = new FileInfo(modelFile); - _modelName = Path.GetFileNameWithoutExtension(modelFileInfo.Name); - _modelManager = new ModelManager(modelFileInfo.Directory.FullName, true); - _modelManager.InitOnnxModel(_modelName, _ignoredVersion); - + _session = new InferenceSession(modelFile); ModelInfo = new OnnxModelInfo(GetInputsInfo(), GetOutputsInfo()); InputNames = ModelInfo.InputsInfo.Select(i => i.Name).ToList(); OutputNames = ModelInfo.OutputsInfo.Select(i => i.Name).ToList(); } + /// + /// Create an OnnxModel from a byte[] + /// + /// + /// OnnxModel public static OnnxModel CreateFromBytes(byte[] modelBytes) { var tempModelDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); @@ -98,239 +106,164 @@ public static OnnxModel CreateFromBytes(byte[] modelBytes) // or keep the dir/file and write proper cleanup when application closes } - public List Run(List inputTensors) + /// + /// Uses an already open session to score a list of Tensors/NamedOnnxValues. + /// + /// The NamedOnnxValues/Tensors to score + /// A list of NamedOnnxValues/Tensors + public List Run(List inputTensors) { - var outputTensors = _modelManager.RunModel( - _modelName, _ignoredVersion, InputNames, inputTensors, OutputNames); - - return outputTensors; + var outputTensors = _session.Run(inputTensors); + var results = new List(); + foreach (var tensor in outputTensors) + { + results.Add(tensor); + } + return results; } + /// + /// Convert the model to a byte array. + /// + /// byte[] public byte[] ToByteArray() { return File.ReadAllBytes(_modelFile); } - private OnnxNodeInfo[] GetInputsInfo() - { - return DictToNodesInfo( - _modelManager.GetInputTypeDict(_modelName, _ignoredVersion), - _modelManager.GetInputShapesDict(_modelName, _ignoredVersion)); - } - - private OnnxNodeInfo[] GetOutputsInfo() + /// + /// Returns input metadata of the ONNX model. + /// + /// OnnxNodeInfo[] + public OnnxNodeInfo[] GetInputsInfo() { - return DictToNodesInfo( - _modelManager.GetOutputTypeDict(_modelName, _ignoredVersion), - _modelManager.GetOutputShapesDict(_modelName, _ignoredVersion)); + var nodeInfos = new List(); + var inputMeta = _session.InputMetadata; + foreach (var kv in inputMeta) + { + nodeInfos.Add( new OnnxNodeInfo(kv.Key, kv.Value.Dimensions.ToList(), kv.Value.Type)); + } + return nodeInfos.ToArray(); } - private static OnnxNodeInfo[] DictToNodesInfo( - Dictionary typeDict, - Dictionary shapeDictArray) + /// + /// Returns output metadata of the ONNX model. + /// + /// + public OnnxNodeInfo[] GetOutputsInfo() { - var shapeDict = new Dictionary>(); - foreach (var key in shapeDictArray.Keys) - shapeDict.Add(key, shapeDictArray[key].ToList()); - - var sameKey = typeDict.Count == shapeDict.Count && - typeDict.Keys.SequenceEqual(shapeDict.Keys); - Contracts.Assert(sameKey, "Type and shape dictionaries should have the same keys"); - return typeDict.Select(kv => new OnnxNodeInfo( - name: kv.Key, type: kv.Value, shape: shapeDict[kv.Key])).OrderBy(x => x.Name).ToArray(); + var nodeInfos = new List(); + var outputMeta = _session.OutputMetadata; + foreach (var kv in outputMeta) + { + nodeInfos.Add(new OnnxNodeInfo(kv.Key, kv.Value.Dimensions.ToList(), kv.Value.Type)); + } + return nodeInfos.ToArray(); } } internal sealed class OnnxUtils { + private static Dictionary _onnxTypeMap; + private static Dictionary _typeToKindMap; + /// - /// Sonoma API only provides Tensor() constructors with overloaded - /// versions based on data type. + /// Creates a Tensor from a scalar value. /// - - private static Dictionary _typeMap; - - public static Tensor CreateScalarTensor(T data) + /// The type of the Tensor. + /// The data values of the Tensor + /// An object which can be cast to an Tensor. The shape of the Tensor is not filled. + public static Object CreateScalarTensor(T data) { - if (typeof(T) == typeof(System.Boolean)) - { - return new Tensor((System.Boolean)(object)data); - } - else if (typeof(T) == typeof(System.Byte)) - { - return new Tensor((System.Byte)(object)data); - } - else if (typeof(T) == typeof(System.Char)) - { - return new Tensor((System.Char)(object)data); - } - else if (typeof(T) == typeof(System.Double)) - { - return new Tensor((System.Double)(object)data); - } - else if (typeof(T) == typeof(System.Single)) - { - return new Tensor((System.Single)(object)data); - } - else if (typeof(T) == typeof(System.Int32)) - { - return new Tensor((System.Int32)(object)data); - } - else if (typeof(T) == typeof(System.Int64)) - { - return new Tensor((System.Int64)(object)data); - } - else if (typeof(T) == typeof(System.SByte)) - { - return new Tensor((System.SByte)(object)data); - } - else if (typeof(T) == typeof(System.Int16)) - { - return new Tensor((System.Int16)(object)data); - } - else if (typeof(T) == typeof(System.UInt32)) - { - return new Tensor((System.UInt32)(object)data); - } - else if (typeof(T) == typeof(System.UInt64)) - { - return new Tensor((System.UInt64)(object)data); - } - else if (typeof(T) == typeof(System.UInt16)) - { - return new Tensor((System.UInt16)(object)data); - } - throw new NotSupportedException($"Unsupported type {typeof(T)}"); + var typeMap = SystemTypeToOnnxType(); + if (!typeMap.ContainsKey(typeof(T))) + throw new NotImplementedException($"Not implemented type {typeof(T)}"); + return new DenseTensor(new T[] { data }, new int[] { }); } /// - /// Sonoma API only provides Tensor() constructors with overloaded versions - /// based on data type. ML.NET cannot use the overloaded version and requires - /// generic version. CreateTensor<T> is generic wrapper on top of - /// overloaded Tensor(T[] data, OnnxShape shape) constructors. + /// Create a Tensor from vbuffer span. Checks if the tensor type + /// is supported by OnnxRuntime prior to execution. /// - public static Tensor CreateTensor(ReadOnlySpan data, OnnxShape shape) + /// The type of Tensor to create. + /// A span containing the data. + /// The shape of the tensor being created + /// + public static Object CreateTensor(ReadOnlySpan data, OnnxShape shape) { - if (typeof(T) == typeof(System.Boolean)) - { - return new Tensor((System.Boolean[])(object)data.ToArray(), shape.ToArray()); - } - else if (typeof(T) == typeof(System.Double)) - { - return new Tensor((System.Double[])(object)data.ToArray(), shape.ToArray()); - } - else if (typeof(T) == typeof(System.Single)) - { - return new Tensor((System.Single[])(object)data.ToArray(), shape.ToArray()); - } - else if (typeof(T) == typeof(System.Int32)) - { - return new Tensor((System.Int32[])(object)data.ToArray(), shape.ToArray()); - } - else if (typeof(T) == typeof(System.Int64)) - { - return new Tensor((System.Int64[])(object)data.ToArray(), shape.ToArray()); - } - throw new NotImplementedException($"Not implemented type {typeof(T)}"); + var typeMap = SystemTypeToOnnxType(); + if (!typeMap.ContainsKey(typeof(T))) + throw new NotImplementedException($"Not implemented type {typeof(T)}"); + return new DenseTensor(data.ToArray(), shape.Select(x => (int)x).ToArray()); } /// - /// Sonoma API only provides CopyTo() functions with overloaded versions - /// based on data type. ML.NET cannot use the overloaded version and requires - /// generic version. CopyTo<T> is generic wrapper on top of - /// overloaded Tensor.CopyTo(List<T> dst) methods. - /// Also Tensor.CopyTo(List<T> dst) requires a list input, whereas ML.NET - /// provides array buffers to copy values to. This mismatch causes an extra copy. + /// Copies a Tensor to a Span element by element. /// - public static unsafe void CopyTo(Tensor tensor, Span dst) + /// The type of both Span and Tensor + /// The source tensor + /// The destination span + public static unsafe void CopyTo(Tensor tensor, Span dst) { - var typeMap = SystemTypeToOnnxType(); - if (typeMap.ContainsKey(typeof(T))) + for (int i = 0; i < tensor.Length; i++) { - if (tensor.GetDataType() != typeMap[typeof(T)]) - { - throw new InvalidOperationException( string.Format("Cannot copy source tensor of type {0} to managed type {1}.", tensor.GetDataType(), typeof(T))); - } - Span tensorSpan = new Span(tensor.UnsafeGetData().ToPointer(), tensor.GetSize()); - tensorSpan.CopyTo(dst); - // TODO: the CopyTo() function is susceptible to GC reclaiming tensor - // during the method call. Use KeepAlive for now, and remove - // after permanent fix in CopyTo(). + dst[i] = tensor.GetValue(i); } - else - throw new NotImplementedException($"Not implemented type {typeof(T)}"); GC.KeepAlive(tensor); } - public static PrimitiveType OnnxToMlNetType(DataType type) + /// + /// Converts a Onnx type, that follows the System.Type convention + /// to the type system ML.NET recognizes (e.g. I4, I8, R4 etc.) + /// + /// + /// + public static PrimitiveType OnnxToMlNetType(System.Type type) { - DataKind kind; - switch (type) - { - case DataType.Type_Float: - kind = DataKind.R4; - break; - - case DataType.Type_Double: - kind = DataKind.R8; - break; - - case DataType.Type_Int8: - kind = DataKind.I1; - break; - - case DataType.Type_Int16: - kind = DataKind.I2; - break; - - case DataType.Type_Int32: - kind = DataKind.I4; - break; - - case DataType.Type_Int64: - kind = DataKind.I8; - break; - - case DataType.Type_Uint8: - kind = DataKind.U1; - break; - - case DataType.Type_Uint16: - kind = DataKind.U2; - break; - - case DataType.Type_String: - kind = DataKind.TX; - break; - - case DataType.Type_Bool: - kind = DataKind.BL; - break; + var map = OnnxToMlNetTypeMap(); + if (!map.ContainsKey(type)) + throw Contracts.ExceptNotSupp("Onnx type not supported", type); + return PrimitiveType.FromKind(map[type]); + } - case DataType.Type_Invalid: - default: - throw Contracts.ExceptNotSupp("Onnx type not supported", type); + internal static Dictionary OnnxToMlNetTypeMap() + { + if (_typeToKindMap == null) + { + _typeToKindMap = new Dictionary + { + { typeof(Single) , DataKind.R4}, + { typeof(Double) , DataKind.R8}, + { typeof(Int16) , DataKind.I2}, + { typeof(Int32) , DataKind.I4}, + { typeof(Int64) , DataKind.I8}, + { typeof(UInt16) , DataKind.U2}, + { typeof(UInt32) , DataKind.U4}, + { typeof(UInt64) , DataKind.U8}, + { typeof(String) , DataKind.TX}, + { typeof(Boolean) , DataKind.BL}, + }; } - - return PrimitiveType.FromKind(kind); + return _typeToKindMap; } - internal static Dictionary SystemTypeToOnnxType() + internal static Dictionary SystemTypeToOnnxType() { - if (_typeMap == null) + if (_onnxTypeMap == null) { - _typeMap = new Dictionary + _onnxTypeMap = new Dictionary { - { typeof(Boolean) , DataType.Type_Bool }, - { typeof(Double) , DataType.Type_Double }, - { typeof(Single) , DataType.Type_Float }, - { typeof(Int16) , DataType.Type_Int16 }, - { typeof(Int32) , DataType.Type_Int32 }, - { typeof(Int64) , DataType.Type_Int64 }, - { typeof(UInt16) , DataType.Type_Uint16 } + { typeof(Double) , typeof(Double) }, + { typeof(Single) , typeof(Single) }, + { typeof(Int16) , typeof(Int16) }, + { typeof(Int32) , typeof(Int32) }, + { typeof(Int64) , typeof(Int64) }, + { typeof(UInt16) , typeof(UInt16) }, + { typeof(UInt32) , typeof(UInt32) }, + { typeof(UInt64) , typeof(UInt64) } }; } - return _typeMap; + return _onnxTypeMap; } } } diff --git a/test/Microsoft.ML.OnnxTransformTest/OnnxTransformTests.cs b/test/Microsoft.ML.OnnxTransformTest/OnnxTransformTests.cs index af6a26d440..b8a105adc9 100644 --- a/test/Microsoft.ML.OnnxTransformTest/OnnxTransformTests.cs +++ b/test/Microsoft.ML.OnnxTransformTest/OnnxTransformTests.cs @@ -298,8 +298,16 @@ public void OnnxModelMultiInput() { getScoresa(ref buffera); getScoresb(ref bufferb); - Console.WriteLine(buffera.GetValues().ToArray()); + var suma = 0f; + var sumb = 0f; + foreach (var x in buffera.DenseValues()) + suma += x; + foreach (var x in bufferb.DenseValues()) + sumb += x; Assert.Equal(5, buffera.Length); + Assert.Equal(5, bufferb.Length); + Assert.Equal(0, suma); + Assert.Equal(30, sumb); } } } From 8bc3bb18d29ce61ae2f98c6c00a22709ed314ea3 Mon Sep 17 00:00:00 2001 From: Jignesh Parmar Date: Sun, 25 Nov 2018 23:39:17 +0000 Subject: [PATCH 2/8] Updated to conform to newer API --- build/Dependencies.props | 2 +- .../Dynamic/OnnxTransform.cs | 16 ++-- .../Microsoft.ML.Samples.csproj | 2 + .../OnnxTransform.cs | 76 +++++++++++-------- src/Microsoft.ML.OnnxTransform/OnnxUtils.cs | 71 +++++++---------- 5 files changed, 80 insertions(+), 87 deletions(-) diff --git a/build/Dependencies.props b/build/Dependencies.props index 38080cbc85..b42998ff44 100644 --- a/build/Dependencies.props +++ b/build/Dependencies.props @@ -17,7 +17,7 @@ 3.5.1 2.2.1.1 1.1.0 - 0.1.0-dev002 + 0.1.4-dev-48f58fc4 0.0.0.7 2.1.3 4.5.0 diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/OnnxTransform.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/OnnxTransform.cs index 2336d88545..a0fd62ca45 100644 --- a/docs/samples/Microsoft.ML.Samples/Dynamic/OnnxTransform.cs +++ b/docs/samples/Microsoft.ML.Samples/Dynamic/OnnxTransform.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -11,9 +11,9 @@ namespace Microsoft.ML.Samples.Dynamic { - class Program + class OnnxTransformExample { - static void Main(string[] args) + public static void OnnxTransformSample(string[] args) { // Download the squeeznet image model from ONNX model zoo, version 1.2 // https://github.com/onnx/models/tree/master/squeezenet @@ -29,18 +29,16 @@ static void Main(string[] args) var inputSchema = onnxModel.ModelInfo.InputsInfo[0]; var inputName = inputSchema.Name; var inputShape = inputSchema.Shape; - var inputType = inputSchema.Type; // Deduce image dimensions from inputShape - var numChannels = inputShape[1]; - var imageHeight = inputShape[2]; - var imageWidth = inputShape[3]; + var numChannels = (int) inputShape[1]; + var imageHeight = (int) inputShape[2]; + var imageWidth = (int) inputShape[3]; // Similarly, get output node metadata var outputSchema = onnxModel.ModelInfo.OutputsInfo[0]; var outputName = outputSchema.Name; var outputShape = outputSchema.Shape; - var outputType = outputSchema.Type; var dataFile = @"test\data\images\images.tsv"; var imageFolder = Path.GetDirectoryName(dataFile); @@ -120,4 +118,4 @@ static void Main(string[] args) // Example # 2 :Score for class 2 = 7.203341E-08 } } -} +} \ No newline at end of file diff --git a/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj b/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj index 6b3d9f957b..8029135d61 100644 --- a/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj +++ b/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj @@ -13,6 +13,8 @@ + + diff --git a/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs b/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs index 29492dccd1..acda9341ae 100644 --- a/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs +++ b/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs @@ -305,9 +305,9 @@ public override Func GetDependencies(Func activeOutput) public override void Save(ModelSaveContext ctx) => _parent.Save(ctx); - private interface ITensorValueGetter + private interface INamedOnnxValueGetter { - Object GetTensor(); + NamedOnnxValue GetNamedOnnxValue(); } private class OutputCache { @@ -320,21 +320,23 @@ public OutputCache() } } - private void UpdateCacheIfNeeded(long position, ITensorValueGetter[] srcTensorGetters, string[] activeOutputColNames, OutputCache outputCache) + private void UpdateCacheIfNeeded(long position, INamedOnnxValueGetter[] srcNamedOnnxValueGetters, string[] activeOutputColNames, OutputCache outputCache) { if (outputCache.Position != position) { - var inputTensors = new List(); + var inputNameOnnxValues = new List(); for (int i = 0; i < _inputColIndices.Length; i++) - inputTensors.Add(new NamedOnnxValue(_parent.Inputs[i], srcTensorGetters[i].GetTensor())); + { + inputNameOnnxValues.Add(srcNamedOnnxValueGetters[i].GetNamedOnnxValue()); + } - var outputTensors = _parent.Model.Run(inputTensors); - Contracts.Assert(outputTensors.Count > 0); + var outputNamedOnnxValues = _parent.Model.Run(inputNameOnnxValues); + Contracts.Assert(outputNamedOnnxValues.Count > 0); - foreach (var outputTensor in outputTensors) + foreach (var outputNameOnnxValue in outputNamedOnnxValues) { - outputCache.Outputs[outputTensor.Name] = outputTensor; + outputCache.Outputs[outputNameOnnxValue.Name] = outputNameOnnxValue; } outputCache.Position = position; } @@ -350,87 +352,95 @@ protected override Delegate MakeGetter(IRow input, int iinfo, Func ac var activeOutputColNames = _parent.Outputs.Where((x, i) => activeOutput(i)).ToArray(); var type = OnnxUtils.OnnxToMlNetType(_parent.Model.ModelInfo.OutputsInfo[iinfo].Type).RawType; Host.Assert(type == _parent.OutputTypes[iinfo].ItemType.RawType); - var srcTensorGetters = GetTensorValueGetters(input, _inputColIndices, _isInputVector, _inputOnnxTypes, _inputTensorShapes); - return Utils.MarshalInvoke(MakeGetter, type, input, iinfo, srcTensorGetters, activeOutputColNames, outputCache); + var srcNamedValueGetters = GetNamedOnnxValueGetters(input, _parent.Inputs, _inputColIndices, _isInputVector, _inputOnnxTypes, _inputTensorShapes); + return Utils.MarshalInvoke(MakeGetter, type, input, iinfo, srcNamedValueGetters, activeOutputColNames, outputCache); } - private Delegate MakeGetter(IRow input, int iinfo, ITensorValueGetter[] srcTensorGetters, string[] activeOutputColNames, OutputCache outputCache) + private Delegate MakeGetter(IRow input, int iinfo, INamedOnnxValueGetter[] srcNamedValueGetters, string[] activeOutputColNames, OutputCache outputCache) { Host.AssertValue(input); ValueGetter> valuegetter = (ref VBuffer dst) => { - UpdateCacheIfNeeded(input.Position, srcTensorGetters, activeOutputColNames, outputCache); - var tensor = outputCache.Outputs[_parent.Outputs[iinfo]]; - var editor = VBufferEditor.Create(ref dst, tensor.AsTensor().Count()); - OnnxUtils.CopyTo(tensor.AsTensor(), editor.Values); + UpdateCacheIfNeeded(input.Position, srcNamedValueGetters, activeOutputColNames, outputCache); + var namedOnnxValue = outputCache.Outputs[_parent.Outputs[iinfo]]; + var editor = VBufferEditor.Create(ref dst, namedOnnxValue.AsTensor().Count()); + var denseTensor = namedOnnxValue.AsTensor() as System.Numerics.Tensors.DenseTensor; + if (denseTensor == null) + throw Host.Except($"Output column {namedOnnxValue.Name} doesn't contain a DenseTensor of expected type {typeof(T)}"); + denseTensor.Buffer.Span.CopyTo(editor.Values); dst = editor.Commit(); }; return valuegetter; } - private static ITensorValueGetter[] GetTensorValueGetters(IRow input, + private static INamedOnnxValueGetter[] GetNamedOnnxValueGetters(IRow input, + string[] inputColNames, int[] inputColIndices, bool[] isInputVector, System.Type[] onnxInputTypes, OnnxShape[] onnxInputShapes) { - var srcTensorGetters = new ITensorValueGetter[inputColIndices.Length]; + var srcNamedOnnxValueGetters = new INamedOnnxValueGetter[inputColIndices.Length]; for (int i = 0; i < inputColIndices.Length; i++) { int colIndex = inputColIndices[i]; - srcTensorGetters[i] = CreateTensorValueGetter(input, onnxInputTypes[i], isInputVector[i], colIndex, onnxInputShapes[i]); + srcNamedOnnxValueGetters[i] = CreateNamedOnnxValueGetter(input, onnxInputTypes[i], isInputVector[i], inputColNames[i], colIndex, onnxInputShapes[i]); } - return srcTensorGetters; + return srcNamedOnnxValueGetters; } - private static ITensorValueGetter CreateTensorValueGetter(IRow input, System.Type onnxType, bool isVector, int colIndex, OnnxShape onnxShape) + private static INamedOnnxValueGetter CreateNamedOnnxValueGetter(IRow input, System.Type onnxType, bool isVector, string colName, int colIndex, OnnxShape onnxShape) { var type = OnnxUtils.OnnxToMlNetType(onnxType).RawType; Contracts.AssertValue(type); - return Utils.MarshalInvoke(CreateTensorValueGetter, type, input, isVector, colIndex, onnxShape); + return Utils.MarshalInvoke(CreateNameOnnxValueGetter, type, input, isVector, colName, colIndex, onnxShape); } - private static ITensorValueGetter CreateTensorValueGetter(IRow input, bool isVector, int colIndex, OnnxShape onnxShape) + private static INamedOnnxValueGetter CreateNameOnnxValueGetter(IRow input, bool isVector, string colName, int colIndex, OnnxShape onnxShape) { if (isVector) - return new TensorValueGetterVec(input, colIndex, onnxShape); - return new TensorValueGetter(input, colIndex); + return new NamedOnnxValueGetterVec(input, colName, colIndex, onnxShape); + return new NameOnnxValueGetter(input, colName, colIndex); } - private class TensorValueGetter : ITensorValueGetter + private class NameOnnxValueGetter : INamedOnnxValueGetter { private readonly ValueGetter _srcgetter; + private readonly string _colName; - public TensorValueGetter(IRow input, int colIndex) + public NameOnnxValueGetter(IRow input, string colName, int colIndex) { + _colName = colName; _srcgetter = input.GetGetter(colIndex); } - public Object GetTensor() + public NamedOnnxValue GetNamedOnnxValue() { var scalar = default(T); _srcgetter(ref scalar); - return OnnxUtils.CreateScalarTensor(scalar); + return OnnxUtils.CreateScalarNamedOnnxValue(_colName, scalar); } } - private class TensorValueGetterVec : ITensorValueGetter + private class NamedOnnxValueGetterVec : INamedOnnxValueGetter { private readonly ValueGetter> _srcgetter; private readonly OnnxShape _tensorShape; + private readonly string _colName; private VBuffer _vBuffer; private VBuffer _vBufferDense; - public TensorValueGetterVec(IRow input, int colIndex, OnnxShape tensorShape) + public NamedOnnxValueGetterVec(IRow input, string colName, int colIndex, OnnxShape tensorShape) { _srcgetter = input.GetGetter>(colIndex); _tensorShape = tensorShape; + _colName = colName; _vBuffer = default; _vBufferDense = default; } - public Object GetTensor() + public NamedOnnxValue GetNamedOnnxValue() { _srcgetter(ref _vBuffer); _vBuffer.CopyToDense(ref _vBufferDense); - return OnnxUtils.CreateTensor(_vBufferDense.GetValues(), _tensorShape); + return OnnxUtils.CreateNamedOnnxValue(_colName, _vBufferDense.GetValues(), _tensorShape); } } } diff --git a/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs b/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs index 1261317d14..e60c835049 100644 --- a/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs +++ b/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs @@ -4,23 +4,19 @@ using Microsoft.ML.Runtime; using Microsoft.ML.Runtime.Data; -using Microsoft.ML.Runtime.Internal.Utilities; -//using Microsoft.ML.Scoring; using Microsoft.ML.OnnxRuntime; using System.Numerics.Tensors; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using Microsoft.ML.StaticPipe; using OnnxShape = System.Collections.Generic.List; -using Microsoft.ML.Data; namespace Microsoft.ML.Transforms { /// - /// OnnxModel is a utility class to load ONNX models, and retrieve metadata + /// OnnxModel is a utility class to load ONNX models and retrieve metadata /// for inputs and outputs. The metadata includes the names, shapes and types /// It provides API to open a session, score tensors (NamedOnnxValues) and return /// the results. @@ -51,15 +47,15 @@ public OnnxModelInfo(OnnxNodeInfo[] inputsInfo, OnnxNodeInfo[] outputsInfo) public class OnnxNodeInfo { /// - /// The Name of the input node + /// The Name of the node /// public readonly string Name; /// - /// The shape of the input node + /// The shape of the node /// public readonly OnnxShape Shape; /// - /// The type of the input node + /// The type of the node /// public readonly System.Type Type; @@ -107,17 +103,17 @@ public static OnnxModel CreateFromBytes(byte[] modelBytes) } /// - /// Uses an already open session to score a list of Tensors/NamedOnnxValues. + /// Uses an open session to score a list of NamedOnnxValues. /// - /// The NamedOnnxValues/Tensors to score - /// A list of NamedOnnxValues/Tensors - public List Run(List inputTensors) + /// The NamedOnnxValues to score + /// Resulting output NamedOnnxValues list + public List Run(List inputNamedOnnxValues) { - var outputTensors = _session.Run(inputTensors); + var outputNameOnnxValues = _session.Run(inputNamedOnnxValues); var results = new List(); - foreach (var tensor in outputTensors) + foreach (var value in outputNameOnnxValues) { - results.Add(tensor); + results.Add(value); } return results; } @@ -141,7 +137,7 @@ public OnnxNodeInfo[] GetInputsInfo() var inputMeta = _session.InputMetadata; foreach (var kv in inputMeta) { - nodeInfos.Add( new OnnxNodeInfo(kv.Key, kv.Value.Dimensions.ToList(), kv.Value.Type)); + nodeInfos.Add( new OnnxNodeInfo(kv.Key, kv.Value.Dimensions.ToList(), kv.Value.ElementType)); } return nodeInfos.ToArray(); } @@ -156,7 +152,7 @@ public OnnxNodeInfo[] GetOutputsInfo() var outputMeta = _session.OutputMetadata; foreach (var kv in outputMeta) { - nodeInfos.Add(new OnnxNodeInfo(kv.Key, kv.Value.Dimensions.ToList(), kv.Value.Type)); + nodeInfos.Add(new OnnxNodeInfo(kv.Key, kv.Value.Dimensions.ToList(), kv.Value.ElementType)); } return nodeInfos.ToArray(); } @@ -168,48 +164,35 @@ internal sealed class OnnxUtils private static Dictionary _typeToKindMap; /// - /// Creates a Tensor from a scalar value. + /// Creates a NamedOnnxValue from a scalar value. /// - /// The type of the Tensor. + /// The type of the Tensor contained in the NamedOnnxValue + /// The name of the NamedOnnxValue /// The data values of the Tensor - /// An object which can be cast to an Tensor. The shape of the Tensor is not filled. - public static Object CreateScalarTensor(T data) + /// NamedOnnxValue + public static NamedOnnxValue CreateScalarNamedOnnxValue(string name, T data) { var typeMap = SystemTypeToOnnxType(); if (!typeMap.ContainsKey(typeof(T))) throw new NotImplementedException($"Not implemented type {typeof(T)}"); - return new DenseTensor(new T[] { data }, new int[] { }); + return NamedOnnxValue.CreateFromTensor(name, new DenseTensor(new T[] { data }, new int[] { })); } /// - /// Create a Tensor from vbuffer span. Checks if the tensor type + /// Create a NamedOnnxValue from vbuffer span. Checks if the tensor type /// is supported by OnnxRuntime prior to execution. /// - /// The type of Tensor to create. - /// A span containing the data. - /// The shape of the tensor being created - /// - public static Object CreateTensor(ReadOnlySpan data, OnnxShape shape) + /// The type of the Tensor contained in the NamedOnnxValue + /// The name of the NamedOnnxValue + /// A span containing the data + /// The shape of the Tensor being created + /// NamedOnnxValue + public static NamedOnnxValue CreateNamedOnnxValue(string name, ReadOnlySpan data, OnnxShape shape) { var typeMap = SystemTypeToOnnxType(); if (!typeMap.ContainsKey(typeof(T))) throw new NotImplementedException($"Not implemented type {typeof(T)}"); - return new DenseTensor(data.ToArray(), shape.Select(x => (int)x).ToArray()); - } - - /// - /// Copies a Tensor to a Span element by element. - /// - /// The type of both Span and Tensor - /// The source tensor - /// The destination span - public static unsafe void CopyTo(Tensor tensor, Span dst) - { - for (int i = 0; i < tensor.Length; i++) - { - dst[i] = tensor.GetValue(i); - } - GC.KeepAlive(tensor); + return NamedOnnxValue.CreateFromTensor(name, new DenseTensor(data.ToArray(), shape.Select(x => (int)x).ToArray())); } /// From e04f1163a249f53f704a38d1914324a4c3e39e1f Mon Sep 17 00:00:00 2001 From: Jignesh Parmar Date: Tue, 27 Nov 2018 00:13:01 +0000 Subject: [PATCH 3/8] Resolved PR comments --- build/Dependencies.props | 3 +- .../Microsoft.ML.OnnxTransform.nupkgproj | 2 +- .../Microsoft.ML.OnnxTransform.csproj | 5 ++- .../OnnxTransform.cs | 4 +-- src/Microsoft.ML.OnnxTransform/OnnxUtils.cs | 34 +++++++++---------- .../OnnxTransformTests.cs | 11 ++---- 6 files changed, 26 insertions(+), 33 deletions(-) diff --git a/build/Dependencies.props b/build/Dependencies.props index b42998ff44..808b82f83b 100644 --- a/build/Dependencies.props +++ b/build/Dependencies.props @@ -16,8 +16,7 @@ 3.5.1 2.2.1.1 - 1.1.0 - 0.1.4-dev-48f58fc4 + 1.0.0 0.0.0.7 2.1.3 4.5.0 diff --git a/pkg/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.nupkgproj b/pkg/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.nupkgproj index 4d64d756fe..aae9c81d3e 100644 --- a/pkg/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.nupkgproj +++ b/pkg/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.nupkgproj @@ -7,7 +7,7 @@ - + diff --git a/src/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.csproj b/src/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.csproj index 2daf76b5d6..cd8eb8b8c2 100644 --- a/src/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.csproj +++ b/src/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.csproj @@ -4,14 +4,13 @@ netstandard2.0 Microsoft.ML.OnnxTransform true + None - - - + diff --git a/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs b/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs index acda9341ae..4a8b82ae31 100644 --- a/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs +++ b/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs @@ -46,7 +46,7 @@ namespace Microsoft.ML.Transforms /// /// ///

Supports inferencing of models in 1.2 and 1.3 format, using the - /// Microsoft.ML.Scoring library + /// Microsoft.ML.OnnxRuntime library ///

///

The inputs and outputs of the onnx models must of of Tensors. Sequence and Maps are not yet supported.

///

Visit https://github.com/onnx/models to see a list of readily available models to get started with.

@@ -363,10 +363,10 @@ private Delegate MakeGetter(IRow input, int iinfo, INamedOnnxValueGetter[] sr { UpdateCacheIfNeeded(input.Position, srcNamedValueGetters, activeOutputColNames, outputCache); var namedOnnxValue = outputCache.Outputs[_parent.Outputs[iinfo]]; - var editor = VBufferEditor.Create(ref dst, namedOnnxValue.AsTensor().Count()); var denseTensor = namedOnnxValue.AsTensor() as System.Numerics.Tensors.DenseTensor; if (denseTensor == null) throw Host.Except($"Output column {namedOnnxValue.Name} doesn't contain a DenseTensor of expected type {typeof(T)}"); + var editor = VBufferEditor.Create(ref dst, (int) denseTensor.Length); denseTensor.Buffer.Span.CopyTo(editor.Values); dst = editor.Commit(); }; diff --git a/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs b/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs index e60c835049..9104eb88ec 100644 --- a/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs +++ b/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs @@ -26,7 +26,7 @@ public sealed class OnnxModel /// /// OnnxModelInfo contains the data that we should get from - /// Sonoma API once that functionality is added. + /// OnnxRuntime API once that functionality is added. /// public sealed class OnnxModelInfo { @@ -160,7 +160,7 @@ public OnnxNodeInfo[] GetOutputsInfo() internal sealed class OnnxUtils { - private static Dictionary _onnxTypeMap; + private static HashSet _onnxTypeMap; private static Dictionary _typeToKindMap; /// @@ -172,10 +172,10 @@ internal sealed class OnnxUtils /// NamedOnnxValue public static NamedOnnxValue CreateScalarNamedOnnxValue(string name, T data) { - var typeMap = SystemTypeToOnnxType(); - if (!typeMap.ContainsKey(typeof(T))) + var typeMap = SupportedOnnxRuntimeTypes(); + if (!typeMap.Contains(typeof(T))) throw new NotImplementedException($"Not implemented type {typeof(T)}"); - return NamedOnnxValue.CreateFromTensor(name, new DenseTensor(new T[] { data }, new int[] { })); + return NamedOnnxValue.CreateFromTensor(name, new DenseTensor(new T[] { data }, new int[] { 0 })); } /// @@ -189,8 +189,8 @@ public static NamedOnnxValue CreateScalarNamedOnnxValue(string name, T data) /// NamedOnnxValue public static NamedOnnxValue CreateNamedOnnxValue(string name, ReadOnlySpan data, OnnxShape shape) { - var typeMap = SystemTypeToOnnxType(); - if (!typeMap.ContainsKey(typeof(T))) + var typeMap = SupportedOnnxRuntimeTypes(); + if (!typeMap.Contains(typeof(T))) throw new NotImplementedException($"Not implemented type {typeof(T)}"); return NamedOnnxValue.CreateFromTensor(name, new DenseTensor(data.ToArray(), shape.Select(x => (int)x).ToArray())); } @@ -230,20 +230,20 @@ public static PrimitiveType OnnxToMlNetType(System.Type type) return _typeToKindMap; } - internal static Dictionary SystemTypeToOnnxType() + internal static HashSet SupportedOnnxRuntimeTypes() { if (_onnxTypeMap == null) { - _onnxTypeMap = new Dictionary + _onnxTypeMap = new HashSet { - { typeof(Double) , typeof(Double) }, - { typeof(Single) , typeof(Single) }, - { typeof(Int16) , typeof(Int16) }, - { typeof(Int32) , typeof(Int32) }, - { typeof(Int64) , typeof(Int64) }, - { typeof(UInt16) , typeof(UInt16) }, - { typeof(UInt32) , typeof(UInt32) }, - { typeof(UInt64) , typeof(UInt64) } + typeof(Double), + typeof(Single), + typeof(Int16), + typeof(Int32), + typeof(Int64), + typeof(UInt16), + typeof(UInt32), + typeof(UInt64) }; } return _onnxTypeMap; diff --git a/test/Microsoft.ML.OnnxTransformTest/OnnxTransformTests.cs b/test/Microsoft.ML.OnnxTransformTest/OnnxTransformTests.cs index b8a105adc9..cf60bf28d3 100644 --- a/test/Microsoft.ML.OnnxTransformTest/OnnxTransformTests.cs +++ b/test/Microsoft.ML.OnnxTransformTest/OnnxTransformTests.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using Xunit; using Xunit.Abstractions; @@ -298,16 +299,10 @@ public void OnnxModelMultiInput() { getScoresa(ref buffera); getScoresb(ref bufferb); - var suma = 0f; - var sumb = 0f; - foreach (var x in buffera.DenseValues()) - suma += x; - foreach (var x in bufferb.DenseValues()) - sumb += x; Assert.Equal(5, buffera.Length); Assert.Equal(5, bufferb.Length); - Assert.Equal(0, suma); - Assert.Equal(30, sumb); + Assert.Equal(0, buffera.GetValues().ToArray().Sum()); + Assert.Equal(30, bufferb.GetValues().ToArray().Sum()); } } } From 2bf66d0fc8a1870a100a4c624c5bd65a1e674df5 Mon Sep 17 00:00:00 2001 From: Jignesh Parmar Date: Tue, 27 Nov 2018 00:41:18 +0000 Subject: [PATCH 4/8] minor fix --- .../Microsoft.ML.OnnxTransform.nupkgproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.nupkgproj b/pkg/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.nupkgproj index aae9c81d3e..b817e809d1 100644 --- a/pkg/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.nupkgproj +++ b/pkg/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.nupkgproj @@ -7,7 +7,7 @@ - + From a8d91693f3d97cd716a159e39f992e64b10b7bf2 Mon Sep 17 00:00:00 2001 From: Jignesh Parmar Date: Tue, 27 Nov 2018 03:46:08 +0000 Subject: [PATCH 5/8] updated per PR comments --- build/Dependencies.props | 2 +- .../Dynamic/OnnxTransform.cs | 173 ++++++++---------- .../Microsoft.ML.Samples.csproj | 2 +- src/Microsoft.ML.OnnxTransform/OnnxUtils.cs | 2 +- 4 files changed, 84 insertions(+), 95 deletions(-) diff --git a/build/Dependencies.props b/build/Dependencies.props index 808b82f83b..7c6743c106 100644 --- a/build/Dependencies.props +++ b/build/Dependencies.props @@ -16,7 +16,7 @@ 3.5.1 2.2.1.1 - 1.0.0 + 0.1.4 0.0.0.7 2.1.3 4.5.0 diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/OnnxTransform.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/OnnxTransform.cs index a0fd62ca45..6db1884c00 100644 --- a/docs/samples/Microsoft.ML.Samples/Dynamic/OnnxTransform.cs +++ b/docs/samples/Microsoft.ML.Samples/Dynamic/OnnxTransform.cs @@ -2,120 +2,109 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.ML.Transforms; +using Microsoft.ML.OnnxRuntime; +using Microsoft.ML.Runtime.Api; using Microsoft.ML.Runtime.Data; -using Microsoft.ML.Runtime.ImageAnalytics; -using Microsoft.ML; +using Microsoft.ML.Transforms; using System; -using System.IO; +using System.Linq; namespace Microsoft.ML.Samples.Dynamic { class OnnxTransformExample { - public static void OnnxTransformSample(string[] args) + /// + /// Example use of OnnxEstimator in an ML.NET pipeline + /// + 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 model_location = @"squeezenet\model.onnx"; - - var env = new MLContext(); + var modelPath = @"squeezenet\model.onnx"; - // Use the utility functions to inspect models inputs, outputs, shape and type - // Load the model using the OnnxModel class - var onnxModel = new OnnxModel(model_location); + // 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 - // This model has only 1 input, so inspect 0th index for input node metadata - var inputSchema = onnxModel.ModelInfo.InputsInfo[0]; - var inputName = inputSchema.Name; - var inputShape = inputSchema.Shape; + // Create ML pipeline to score the data using OnnxScoringEstimator + var mlContext = new MLContext(); + var data = GetTensorData(); + var idv = mlContext.CreateStreamingDataView(data); + var pipeline = new OnnxScoringEstimator(mlContext, modelPath, new[] { inputInfo.Key }, new[] { outputInfo.Key }); - // Deduce image dimensions from inputShape - var numChannels = (int) inputShape[1]; - var imageHeight = (int) inputShape[2]; - var imageWidth = (int) inputShape[3]; + // Run the pipeline and get the transformed values + var transformedValues = pipeline.Fit(idv).Transform(idv); - // Similarly, get output node metadata - var outputSchema = onnxModel.ModelInfo.OutputsInfo[0]; - var outputName = outputSchema.Name; - var outputShape = outputSchema.Shape; + // Retrieve model scores into Prediction class + var predictions = transformedValues.AsEnumerable(mlContext, reuseRowObject: false); - var dataFile = @"test\data\images\images.tsv"; - var imageFolder = Path.GetDirectoryName(dataFile); - - // Use Textloader to load the text file which references the images to load - // Preview ... - // banana.jpg banana - // hotdog.jpg hotdog - // tomato.jpg tomato - var data = TextLoader.Create(env, new TextLoader.Arguments() + // Iterate rows + foreach (var prediction in predictions) { - Column = new[] + int numClasses = 0; + foreach (var classScore in prediction.softmaxout_1.Take(3)) { - new TextLoader.Column("ImagePath", DataKind.TX, 0), - new TextLoader.Column("Name", DataKind.TX, 1), - } - }, new MultiFileSource(dataFile)); + Console.WriteLine($"Class #{numClasses++} score = {classScore}"); + } + Console.WriteLine(new string('-', 10)); + } - // Load the images referenced in the text file - var images = ImageLoaderTransform.Create(env, new ImageLoaderTransform.Arguments() - { - Column = new ImageLoaderTransform.Column[1] - { - new ImageLoaderTransform.Column() { Source= "ImagePath", Name="ImageReal" } - }, - ImageFolder = imageFolder - }, data); + // 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 + // ---------- + } - // Resize the images to match model dimensions - var cropped = ImageResizerTransform.Create(env, new ImageResizerTransform.Arguments() - { - Column = new ImageResizerTransform.Column[1]{ - new ImageResizerTransform.Column(){ Source = "ImageReal", Name= "ImageCropped", ImageHeight =imageHeight, ImageWidth = imageWidth, Resizing = ImageResizerTransform.ResizingKind.IsoCrop}} - }, images); + /// + /// inputSize is the overall dimensions of the model input tensor. + /// + private const int inputSize = 224 * 224 * 3; - // Extract out the RBG pixel values. - // InterleaveArgb = true makes the values RGBRGBRGB. Otherwise it's RRR...GGG...BBB. - var pixels = ImagePixelExtractorTransform.Create(env, new ImagePixelExtractorTransform.Arguments() - { - Column = new ImagePixelExtractorTransform.Column[1]{ - new ImagePixelExtractorTransform.Column() { Source= "ImageCropped", Name = inputName, InterleaveArgb=true} - } - }, cropped); + /// + /// A class to hold sample tensor data. Member name should match + /// the inputs that the model expects (in this case, data_0) + /// + public class TensorData + { + [VectorType(inputSize)] + public float[] data_0 { get; set; } + } - // Create OnnxTransform, passing in the input and output names the model expects. - IDataView trans = OnnxTransform.Create(env, pixels, model_location, new[] { inputName }, new[] { outputName }); + /// + /// Method to generate sample test data. Returns 2 sample rows. + /// + /// + public static TensorData[] GetTensorData() + { + // This can be any numerical data. Assume image pixel values. + var image1 = Enumerable.Range(0, inputSize).Select(x => (float)x / inputSize).ToArray(); + var image2 = Enumerable.Range(0, inputSize).Select(x => (float)(x + 10000) / inputSize).ToArray(); + return new TensorData[] { new TensorData() { data_0 = image1 }, new TensorData() { data_0 = image2 } }; + } - trans.Schema.TryGetColumnIndex(outputName, out int output); - using (var cursor = trans.GetRowCursor(col => col == output)) - { - var numRows = 0; - var buffer = default(VBuffer); - var getter = cursor.GetGetter>(output); - // For each image, retrieve the model scores - while (cursor.MoveNext()) - { - int i = 0; - getter(ref buffer); - // print scores for first 3 classes - foreach(var score in buffer.GetValues()) - { - Console.WriteLine(String.Format("Example # {0} :Score for class {1} = {2} ",numRows, i, score)); - if (++i > 2) break; - } - numRows += 1; - } - } - // Results look like below... - // Example # 0 :Score for class 0 = 1.133263E-06 - // Example # 0 :Score for class 1 = 1.80478E-07 - // Example # 0 :Score for class 2 = 1.595297E-07 - // Example # 1 :Score for class 0 = 1.805106E-05 - // Example # 1 :Score for class 1 = 1.257452E-05 - // Example # 1 :Score for class 2 = 2.412128E-06 - // Example # 2 :Score for class 0 = 1.346096E-06 - // Example # 2 :Score for class 1 = 1.918751E-07 - // Example # 2 :Score for class 2 = 7.203341E-08 + /// + /// Class to contain the output values from the transformation. + /// This model generates a vector of 1000 floats. + /// + class Prediction + { + [VectorType(1000)] + public float[] softmaxout_1 { get; set; } } } } \ No newline at end of file diff --git a/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj b/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj index 8029135d61..54dceb684a 100644 --- a/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj +++ b/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs b/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs index 9104eb88ec..78852481ad 100644 --- a/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs +++ b/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs @@ -21,7 +21,7 @@ namespace Microsoft.ML.Transforms /// It provides API to open a session, score tensors (NamedOnnxValues) and return /// the results. /// - public sealed class OnnxModel + internal sealed class OnnxModel { /// From cefc3799ba10d74395ddd47018633a545c454924 Mon Sep 17 00:00:00 2001 From: Jignesh Parmar Date: Tue, 27 Nov 2018 19:43:04 +0000 Subject: [PATCH 6/8] Resolved more PR comments --- .../Microsoft.ML.Samples.csproj | 1 - .../Microsoft.ML.OnnxTransform.csproj | 1 - src/Microsoft.ML.OnnxTransform/OnnxUtils.cs | 117 ++++++------------ 3 files changed, 40 insertions(+), 79 deletions(-) diff --git a/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj b/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj index 54dceb684a..b938b20b15 100644 --- a/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj +++ b/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj @@ -13,7 +13,6 @@ - diff --git a/src/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.csproj b/src/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.csproj index cd8eb8b8c2..ce2ac23746 100644 --- a/src/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.csproj +++ b/src/Microsoft.ML.OnnxTransform/Microsoft.ML.OnnxTransform.csproj @@ -4,7 +4,6 @@ netstandard2.0 Microsoft.ML.OnnxTransform true - None diff --git a/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs b/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs index 78852481ad..001f2ce9d8 100644 --- a/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs +++ b/src/Microsoft.ML.OnnxTransform/OnnxUtils.cs @@ -33,10 +33,10 @@ public sealed class OnnxModelInfo public readonly OnnxNodeInfo[] InputsInfo; public readonly OnnxNodeInfo[] OutputsInfo; - public OnnxModelInfo(OnnxNodeInfo[] inputsInfo, OnnxNodeInfo[] outputsInfo) + public OnnxModelInfo(IEnumerable inputsInfo, IEnumerable outputsInfo) { - InputsInfo = inputsInfo; - OutputsInfo = outputsInfo; + InputsInfo = inputsInfo.ToArray(); + OutputsInfo = outputsInfo.ToArray(); } } @@ -107,15 +107,9 @@ public static OnnxModel CreateFromBytes(byte[] modelBytes) /// /// The NamedOnnxValues to score /// Resulting output NamedOnnxValues list - public List Run(List inputNamedOnnxValues) + public IReadOnlyCollection Run(List inputNamedOnnxValues) { - var outputNameOnnxValues = _session.Run(inputNamedOnnxValues); - var results = new List(); - foreach (var value in outputNameOnnxValues) - { - results.Add(value); - } - return results; + return _session.Run(inputNamedOnnxValues); } /// @@ -131,37 +125,49 @@ public byte[] ToByteArray() /// Returns input metadata of the ONNX model. /// /// OnnxNodeInfo[] - public OnnxNodeInfo[] GetInputsInfo() + private IEnumerable GetInputsInfo() { - var nodeInfos = new List(); - var inputMeta = _session.InputMetadata; - foreach (var kv in inputMeta) - { - nodeInfos.Add( new OnnxNodeInfo(kv.Key, kv.Value.Dimensions.ToList(), kv.Value.ElementType)); - } - return nodeInfos.ToArray(); + return _session.InputMetadata.Select(kv => new OnnxNodeInfo(kv.Key, kv.Value.Dimensions.ToList(), kv.Value.ElementType)); } /// /// Returns output metadata of the ONNX model. /// /// - public OnnxNodeInfo[] GetOutputsInfo() + private IEnumerable GetOutputsInfo() { - var nodeInfos = new List(); - var outputMeta = _session.OutputMetadata; - foreach (var kv in outputMeta) - { - nodeInfos.Add(new OnnxNodeInfo(kv.Key, kv.Value.Dimensions.ToList(), kv.Value.ElementType)); - } - return nodeInfos.ToArray(); + return _session.OutputMetadata.Select(kv => new OnnxNodeInfo(kv.Key, kv.Value.Dimensions.ToList(), kv.Value.ElementType)); } } internal sealed class OnnxUtils { - private static HashSet _onnxTypeMap; - private static Dictionary _typeToKindMap; + private static HashSet _onnxTypeMap = + new HashSet + { + typeof(Double), + typeof(Single), + typeof(Int16), + typeof(Int32), + typeof(Int64), + typeof(UInt16), + typeof(UInt32), + typeof(UInt64) + }; + private static Dictionary _typeToKindMap= + new Dictionary + { + { typeof(Single) , DataKind.R4}, + { typeof(Double) , DataKind.R8}, + { typeof(Int16) , DataKind.I2}, + { typeof(Int32) , DataKind.I4}, + { typeof(Int64) , DataKind.I8}, + { typeof(UInt16) , DataKind.U2}, + { typeof(UInt32) , DataKind.U4}, + { typeof(UInt64) , DataKind.U8}, + { typeof(String) , DataKind.TX}, + { typeof(Boolean) , DataKind.BL}, + }; /// /// Creates a NamedOnnxValue from a scalar value. @@ -172,10 +178,9 @@ internal sealed class OnnxUtils /// NamedOnnxValue public static NamedOnnxValue CreateScalarNamedOnnxValue(string name, T data) { - var typeMap = SupportedOnnxRuntimeTypes(); - if (!typeMap.Contains(typeof(T))) + if (!_onnxTypeMap.Contains(typeof(T))) throw new NotImplementedException($"Not implemented type {typeof(T)}"); - return NamedOnnxValue.CreateFromTensor(name, new DenseTensor(new T[] { data }, new int[] { 0 })); + return NamedOnnxValue.CreateFromTensor(name, new DenseTensor(new T[] { data }, new int[] { 1 })); } /// @@ -189,8 +194,7 @@ public static NamedOnnxValue CreateScalarNamedOnnxValue(string name, T data) /// NamedOnnxValue public static NamedOnnxValue CreateNamedOnnxValue(string name, ReadOnlySpan data, OnnxShape shape) { - var typeMap = SupportedOnnxRuntimeTypes(); - if (!typeMap.Contains(typeof(T))) + if (!_onnxTypeMap.Contains(typeof(T))) throw new NotImplementedException($"Not implemented type {typeof(T)}"); return NamedOnnxValue.CreateFromTensor(name, new DenseTensor(data.ToArray(), shape.Select(x => (int)x).ToArray())); } @@ -203,50 +207,9 @@ public static NamedOnnxValue CreateNamedOnnxValue(string name, ReadOnlySpan public static PrimitiveType OnnxToMlNetType(System.Type type) { - var map = OnnxToMlNetTypeMap(); - if (!map.ContainsKey(type)) + if (!_typeToKindMap.ContainsKey(type)) throw Contracts.ExceptNotSupp("Onnx type not supported", type); - return PrimitiveType.FromKind(map[type]); - } - - internal static Dictionary OnnxToMlNetTypeMap() - { - if (_typeToKindMap == null) - { - _typeToKindMap = new Dictionary - { - { typeof(Single) , DataKind.R4}, - { typeof(Double) , DataKind.R8}, - { typeof(Int16) , DataKind.I2}, - { typeof(Int32) , DataKind.I4}, - { typeof(Int64) , DataKind.I8}, - { typeof(UInt16) , DataKind.U2}, - { typeof(UInt32) , DataKind.U4}, - { typeof(UInt64) , DataKind.U8}, - { typeof(String) , DataKind.TX}, - { typeof(Boolean) , DataKind.BL}, - }; - } - return _typeToKindMap; - } - - internal static HashSet SupportedOnnxRuntimeTypes() - { - if (_onnxTypeMap == null) - { - _onnxTypeMap = new HashSet - { - typeof(Double), - typeof(Single), - typeof(Int16), - typeof(Int32), - typeof(Int64), - typeof(UInt16), - typeof(UInt32), - typeof(UInt64) - }; - } - return _onnxTypeMap; + return PrimitiveType.FromKind(_typeToKindMap[type]); } } } From 6f78e0d9bd7fbbcd62d937d7fdf138f1d6b53290 Mon Sep 17 00:00:00 2001 From: Jignesh Parmar Date: Thu, 29 Nov 2018 21:26:35 +0000 Subject: [PATCH 7/8] added functionality to pull input/output names from model, without user enumerating them explicitly --- .../OnnxTransform.cs | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs b/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs index 4a8b82ae31..e414261336 100644 --- a/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs +++ b/src/Microsoft.ML.OnnxTransform/OnnxTransform.cs @@ -91,6 +91,12 @@ private static VersionInfo GetVersionInfo() loaderAssemblyName: typeof(OnnxTransform).Assembly.FullName); } + public static IDataTransform Create(IHostEnvironment env, IDataView input, string modelFile) + { + var args = new Arguments { ModelFile = modelFile, InputColumns = new string[] { }, OutputColumns = new string[] { } }; + return Create(env, args, input); + } + public static IDataTransform Create(IHostEnvironment env, IDataView input, string modelFile, string[] inputColumns, string[] outputColumns) { var args = new Arguments { ModelFile = modelFile, InputColumns = inputColumns, OutputColumns = outputColumns }; @@ -163,15 +169,15 @@ private OnnxTransform(IHostEnvironment env, Arguments args, byte[] modelBytes = Model = OnnxModel.CreateFromBytes(modelBytes); var modelInfo = Model.ModelInfo; - Inputs = args.InputColumns; - Outputs = args.OutputColumns; - OutputTypes = new ColumnType[args.OutputColumns.Length]; + Inputs = (args.InputColumns.Count() == 0 ) ? Model.InputNames.ToArray() : args.InputColumns; + Outputs = (args.OutputColumns.Count() == 0 ) ? Model.OutputNames.ToArray() : args.OutputColumns; + OutputTypes = new ColumnType[Outputs.Length]; var numModelOutputs = Model.ModelInfo.OutputsInfo.Length; - for (int i=0; i < args.OutputColumns.Length; i++) + for (int i=0; i < Outputs.Length; i++) { - var idx = Model.OutputNames.IndexOf(args.OutputColumns[i]); + var idx = Model.OutputNames.IndexOf(Outputs[i]); if (idx < 0) - throw Host.Except($"Column {args.OutputColumns[i]} doesn't match output node names of model"); + throw Host.Except($"Column {Outputs[i]} doesn't match output node names of model"); var outputNodeInfo = Model.ModelInfo.OutputsInfo[idx]; var shape = outputNodeInfo.Shape; @@ -181,6 +187,11 @@ private OnnxTransform(IHostEnvironment env, Arguments args, byte[] modelBytes = _args = args; } + public OnnxTransform(IHostEnvironment env, string modelFile) + : this(env, new Arguments() { ModelFile = modelFile, InputColumns = new string[] { }, OutputColumns = new string[] { } }) + { + } + public OnnxTransform(IHostEnvironment env, string modelFile, string inputColumn, string outputColumn) : this(env, new Arguments() { ModelFile = modelFile, InputColumns = new[] { inputColumn }, OutputColumns = new[] { outputColumn } }) { @@ -451,6 +462,11 @@ public NamedOnnxValue GetNamedOnnxValue() /// public sealed class OnnxScoringEstimator : TrivialEstimator { + public OnnxScoringEstimator(IHostEnvironment env, string modelFile) + : this(env, new OnnxTransform(env, modelFile, new string[] { }, new string[] { })) + { + } + public OnnxScoringEstimator(IHostEnvironment env, string modelFile, string[] inputs, string[] outputs) : this(env, new OnnxTransform(env, modelFile, inputs, outputs)) { From 95008791b5b7788154830e6aa2df11872634853f Mon Sep 17 00:00:00 2001 From: Jignesh Parmar Date: Sat, 1 Dec 2018 03:07:07 +0000 Subject: [PATCH 8/8] update version to mlscoring 0.1.5 --- build/Dependencies.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Dependencies.props b/build/Dependencies.props index 7c6743c106..375414b61c 100644 --- a/build/Dependencies.props +++ b/build/Dependencies.props @@ -16,7 +16,7 @@ 3.5.1 2.2.1.1 - 0.1.4 + 0.1.5 0.0.0.7 2.1.3 4.5.0