{"id":49014,"date":"2021-12-14T12:00:44","date_gmt":"2021-12-14T20:00:44","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/xamarin\/?p=49014"},"modified":"2021-12-14T12:01:18","modified_gmt":"2021-12-14T20:01:18","slug":"machine-learning-in-xamarin-forms-with-onnx-runtime","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/xamarin\/machine-learning-in-xamarin-forms-with-onnx-runtime\/","title":{"rendered":"Machine Learning in Xamarin.Forms with ONNX Runtime"},"content":{"rendered":"<p>Machine learning can be used to add smart capabilities to mobile applications and enhance the user experience. There are situations where inferencing on-device is required or preferable over cloud-based solutions. Key drivers include:<\/p>\n<ul>\n<li>Availability: works when the device is offline<\/li>\n<li>Privacy: data can remain on the device<\/li>\n<li>Performance: on-device inferencing is often faster than sending data to the cloud for processing<\/li>\n<li>Cost efficiencies: on-device inferencing can be free and more efficient by reducing data transfers between device and cloud<\/li>\n<\/ul>\n<p>Android and iOS provide built-in capabilities for on-device inferencing with technologies such as <a href=\"https:\/\/www.tensorflow.org\/lite\">TensorFlow Lite<\/a> and <a href=\"https:\/\/developer.apple.com\/documentation\/coreml\">Core ML<\/a>.<\/p>\n<p><a href=\"https:\/\/onnxruntime.ai\/docs\/#onnx-runtime-for-inferencing\">ONNX Runtime<\/a> has recently added <a href=\"https:\/\/aka.ms\/ort_xamarin\">support for Xamarin<\/a> and can be integrated into your mobile application to execute cross-platform on-device inferencing of <a href=\"http:\/\/onnx.ai\/\">ONNX (Open Neural Network Exchange)<\/a> models. It already powers machine learning models in key Microsoft products and services across Office, Azure, Bing, as well as other community projects. You can create your own ONNX models, using services such as <a href=\"https:\/\/azure.microsoft.com\/services\/cognitive-services\/custom-vision-service\/\">Azure Custom Vision<\/a>, or <a href=\"https:\/\/github.com\/onnx\/tutorials#converting-to-onnx-format\">convert existing models to ONNX format<\/a>.<\/p>\n<p>This post walks through an example demonstrating the high-level steps for leveraging <a href=\"https:\/\/onnxruntime.ai\/docs\/#onnx-runtime-for-inferencing\">ONNX Runtime<\/a> in a Xamarin.Forms app for on-device inferencing. An existing open-source image classification model (<a href=\"https:\/\/github.com\/onnx\/models\/blob\/master\/vision\/classification\/mobilenet\">MobileNet<\/a>) from the <a href=\"https:\/\/github.com\/onnx\/models#onnx-model-zoo\">ONNX Model Zoo<\/a> has been used for this example.<\/p>\n<h2>Getting Started<\/h2>\n<p>Our <a href=\"https:\/\/github.com\/xamcat\/mobcat-samples\/tree\/master\/onnx_runtime\">sample Xamarin.Forms app<\/a> classifies the primary object in the <a href=\"https:\/\/github.com\/microsoft\/onnxruntime\/raw\/master\/csharp\/sample\/Microsoft.ML.OnnxRuntime.ResNet50v2Sample\/dog.jpeg\">provided image<\/a>, a golden retriever in this case, and displays the result with the highest score.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2021\/12\/inference_result.png\" alt=\"ONNX Runtime inference result displayed in an alert\" \/><\/p>\n<p>The following changes were made to the <a href=\"https:\/\/docs.microsoft.com\/xamarin\/get-started\/first-app\"><strong>Blank App<\/strong><\/a> template which includes a common <a href=\"https:\/\/docs.microsoft.com\/dotnet\/standard\/net-standard\">.NET Standard<\/a> project along with Android and iOS targets.<\/p>\n<ol>\n<li><a href=\"https:\/\/docs.microsoft.com\/visualstudio\/mac\/nuget-walkthrough?toc=%2Fnuget%2Ftoc.json&amp;view=vsmac-2019#updating-packages\">Updating NuGet packages<\/a> for the entire solution<\/li>\n<li>Adding the <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.ML.OnnxRuntime\/\">OnnxRuntime NuGet package<\/a> to each project<\/li>\n<li>Adding the <a href=\"https:\/\/github.com\/microsoft\/onnxruntime\/raw\/master\/csharp\/sample\/Microsoft.ML.OnnxRuntime.ResNet50v2Sample\/dog.jpeg\">sample photo<\/a>, <a href=\"https:\/\/raw.githubusercontent.com\/pytorch\/hub\/master\/imagenet_classes.txt\">classification labels<\/a>, and <a href=\"https:\/\/github.com\/onnx\/models\/blob\/master\/vision\/classification\/mobilenet\/model\/mobilenetv2-7.onnx\">MobileNet model<\/a> to the common project as <a href=\"https:\/\/docs.microsoft.com\/xamarin\/xamarin-forms\/data-cloud\/data\/files?tabs=macos#loading-files-embedded-as-resources\">embedded resources<\/a><\/li>\n<li>Setting the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/language-reference\/configure-language-version#override-a-default\">C# language version<\/a> for the common project to <strong>Latest Major<\/strong><\/li>\n<li>Updating <a href=\"https:\/\/docs.microsoft.com\/xamarin\/ios\/app-fundamentals\/property-lists\">info.plist<\/a>, from the iOS project, to specify a <a href=\"https:\/\/docs.microsoft.com\/xamarin\/ios\/deploy-test\/compiling-for-different-devices?tabs=macos#sdk-options\">minimum system version<\/a> of <strong>11&#46;0<\/strong><\/li>\n<li>Adding a new class called <strong>MobileNetImageClassifier<\/strong> to handle the inferencing<\/li>\n<li>Updating the templated <strong>MainPage<\/strong> XAML to include a <strong>Run<\/strong> Button<\/li>\n<li>Handling the Button Clicked event in the <strong>MainPage<\/strong> code-behind to run the inferencing <\/li>\n<\/ol>\n<h2>Inferencing With ONNX Runtime<\/h2>\n<p>Two classes from the common project we&#8217;ll be focused on are:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/xamcat\/mobcat-samples\/blob\/master\/onnx_runtime\/InferencingSample\/InferencingSample\/MobileNetImageClassifier.cs\">MobileNetImageClassifier<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/xamcat\/mobcat-samples\/blob\/master\/onnx_runtime\/InferencingSample\/InferencingSample\/MainPage.xaml.cs\">MainPage<\/a><\/li>\n<\/ul>\n<h3>MobileNetImageClassifier<\/h3>\n<p><strong>MobileNetImageClassifier<\/strong> encapsulates the use of the <a href=\"https:\/\/github.com\/onnx\/models\/tree\/master\/vision\/classification\/mobilenet\">model<\/a> via <a href=\"https:\/\/onnxruntime.ai\/docs\/#onnx-runtime-for-inferencing\">ONNX Runtime<\/a> as per the <a href=\"https:\/\/github.com\/onnx\/models\/tree\/master\/vision\/classification\/mobilenet#inference\">model documentation<\/a>. You can see visualizations of the model&#8217;s network architecture, including the expected names, types, and shapes (dimensions) for its inputs and outputs using <a href=\"https:\/\/netron.app\">Netron<\/a>.<\/p>\n<p>This class exposes two public methods <strong>GetSampleImageAsync<\/strong> and <strong>GetClassificationAsync<\/strong>. The former loads a sample image for convenience and the latter performs the inferencing on the supplied image. Here&#8217;s a breakdown of the key steps.<\/p>\n<h4>Initialization<\/h4>\n<p>Initialization involves loading those embedded resource files representing the <a href=\"https:\/\/github.com\/onnx\/models\/tree\/master\/vision\/classification\/mobilenet\">model<\/a>, <a href=\"https:\/\/raw.githubusercontent.com\/pytorch\/hub\/master\/imagenet_classes.txt\">labels<\/a>, and <a href=\"https:\/\/github.com\/microsoft\/onnxruntime\/raw\/master\/csharp\/sample\/Microsoft.ML.OnnxRuntime.ResNet50v2Sample\/dog.jpeg\">sample image<\/a>. The <a href=\"https:\/\/docs.microsoft.com\/archive\/msdn-magazine\/2014\/may\/async-programming-patterns-for-asynchronous-mvvm-applications-services#the-asynchronous-initialization-pattern\">asynchronous initialization pattern<\/a> is used to simplify its use downstream while preventing use of those resources before initialization work has completed. Our constructor starts the asynchronous initialization by calling <strong>InitAsync<\/strong>. The initialization Task is stored so several callers can await the completion of the same operation. In this case, the <strong>GetSampleImageAsync<\/strong> and <strong>GetClassificationAsync<\/strong> methods.<\/p>\n<pre><code class=\"language-csharp\">const int DimBatchSize = 1;\nconst int DimNumberOfChannels = 3;\nconst int ImageSizeX = 224;\nconst int ImageSizeY = 224;\nconst string ModelInputName = \"input\";\nconst string ModelOutputName = \"output\";\n\nbyte[] _model;\nbyte[] _sampleImage;\nList&lt;string&gt; _labels;\nInferenceSession _session;\nTask _initTask;\n\n...\n\npublic MobileNetImageClassifier()\n{\n    _ = InitAsync();\n}\n\nTask InitAsync()\n{\n    if (_initTask == null || _initTask.IsFaulted)\n        _initTask = InitTask();\n\n    return _initTask;\n}\n\nasync Task InitTask()\n{\n    var assembly = GetType().Assembly;\n\n    \/\/ Get labels\n    using var labelsStream = assembly.GetManifestResourceStream($\"{assembly.GetName().Name}.imagenet_classes.txt\");\n    using var reader = new StreamReader(labelsStream);\n\n    string text = await reader.ReadToEndAsync();\n    _labels = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).ToList();\n\n    \/\/ Get model and create session\n    using var modelStream = assembly.GetManifestResourceStream($\"{assembly.GetName().Name}.mobilenetv2-7.onnx\");\n    using var modelMemoryStream = new MemoryStream();\n\n    modelStream.CopyTo(modelMemoryStream);\n    _model = modelMemoryStream.ToArray();\n    _session = new InferenceSession(_model);\n\n    \/\/ Get sample image\n    using var sampleImageStream = assembly.GetManifestResourceStream($\"{assembly.GetName().Name}.dog.jpg\");\n    using var sampleImageMemoryStream = new MemoryStream();\n\n    sampleImageStream.CopyTo(sampleImageMemoryStream);\n    _sampleImage = sampleImageMemoryStream.ToArray();\n}<\/code><\/pre>\n<h4>Preprocessing<\/h4>\n<p>Raw images must be transformed according to the <a href=\"https:\/\/github.com\/onnx\/models\/tree\/master\/vision\/classification\/mobilenet#inference\">requirements of the model<\/a> so it matches how the model was trained. Image data is then stored in a contiguous sequential block of memory, represented by a <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#systemnumericstensor\">Tensor<\/a> object, as input for inferencing.<\/p>\n<p>Our first step in this process is to resize the original image if necessary so height and width is at least 224. In this case, images are first resized so the shortest edge is 224 then center-cropped so the longest edge is also 224. For the purposes of this example, <a href=\"https:\/\/docs.microsoft.com\/xamarin\/xamarin-forms\/user-interface\/graphics\/skiasharp\/\">SkiaSharp<\/a> has been used to handle the requisite image processing.<\/p>\n<pre><code class=\"language-csharp\">using var sourceBitmap = SKBitmap.Decode(image);\nvar pixels = sourceBitmap.Bytes;\n\nif (sourceBitmap.Width != ImageSizeX || sourceBitmap.Height != ImageSizeY)\n{\n    float ratio = (float)Math.Min(ImageSizeX, ImageSizeY) \/ Math.Min(sourceBitmap.Width, sourceBitmap.Height);\n\n    using SKBitmap scaledBitmap = sourceBitmap.Resize(new SKImageInfo(\n        (int)(ratio * sourceBitmap.Width), \n        (int)(ratio * sourceBitmap.Height)), \n        SKFilterQuality.Medium);\n\n    var horizontalCrop = scaledBitmap.Width - ImageSizeX;\n    var verticalCrop = scaledBitmap.Height - ImageSizeY;\n    var leftOffset = horizontalCrop == 0 ? 0 : horizontalCrop \/ 2;\n    var topOffset = verticalCrop == 0 ? 0 : verticalCrop \/ 2;\n\n    var cropRect = SKRectI.Create(\n        new SKPointI(leftOffset, topOffset), \n        new SKSizeI(ImageSizeX, ImageSizeY));\n\n    using SKImage currentImage = SKImage.FromBitmap(scaledBitmap);\n    using SKImage croppedImage = currentImage.Subset(cropRect);\n    using SKBitmap croppedBitmap = SKBitmap.FromImage(croppedImage);\n\n    pixels = croppedBitmap.Bytes;\n}<\/code><\/pre>\n<p>The second step is to normalize the resulting image pixels and store them in a flat array that can be used to create the <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#systemnumericstensor\">Tensor<\/a> object. In this case, the model expects the R, G, and B values to be in the range of [0, 1] normalized using mean = [0.485, 0.456, 0.406] and std = [0.229, 0.224, 0.225]. The loop below iterates over the image pixels one row at a time, applies the requisite normalization to each value, then stores each in the channelData array. Our channelData array stores the normalized R,G, and B values sequentially; first all the R values, then all the G, then all the B (instead of the original sequence i.e. RGB, RGB, etc.<\/p>\n<pre><code class=\"language-csharp\">var bytesPerPixel = sourceBitmap.BytesPerPixel;\nvar rowLength = ImageSizeX * bytesPerPixel;\nvar channelLength = ImageSizeX * ImageSizeY;\nvar channelData = new float[channelLength * 3];\nvar channelDataIndex = 0;\n\nfor (int y = 0; y &lt; ImageSizeY; y++)\n{\n    var rowOffset = y * rowLength;\n\n    for (int x = 0, columnOffset = 0; x &lt; ImageSizeX; x++, columnOffset += bytesPerPixel)\n    {\n        var pixelOffset = rowOffset + columnOffset;\n\n        var pixelR = pixels[pixelOffset];\n        var pixelG = pixels[pixelOffset + 1];\n        var pixelB = pixels[pixelOffset + 2];\n\n        var rChannelIndex = channelDataIndex;\n        var gChannelIndex = channelDataIndex + channelLength;\n        var bChannelIndex = channelDataIndex + (channelLength * 2);\n\n        channelData[rChannelIndex] = (pixelR \/ 255f - 0.485f) \/ 0.229f;\n        channelData[gChannelIndex] = (pixelG \/ 255f - 0.456f) \/ 0.224f;\n        channelData[bChannelIndex] = (pixelB \/ 255f - 0.406f) \/ 0.225f;\n\n        channelDataIndex++;\n    }\n}<\/code><\/pre>\n<p>This channelData array is then used to create the requisite <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#systemnumericstensor\">Tensor<\/a> object as input to the <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#inferencesession\">InferenceSession<\/a> <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#methods-1\">Run method<\/a>. The dimensions (1, 3, 224, 224) represent the shape of the <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#systemnumericstensor\">Tensor<\/a> required by the model. In this case, mini-batches of 3-channel RGB images that are expected to have a height and width of 224.<\/p>\n<pre><code class=\"language-csharp\">var input = new DenseTensor&lt;float&gt;(channelData, new[] \n{ \n    DimBatchSize, \n    DimNumberOfChannels, \n    ImageSizeX, \n    ImageSizeY \n});<\/code><\/pre>\n<h4>Inferencing<\/h4>\n<p>An <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#inferencesession\">InferenceSession<\/a> is the runtime representation of an ONNX model. It&#8217;s used to run the model with a given input returning the computed output values. Both the input and output values are collections of <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#namedonnxvalue\">NamedOnnxValue<\/a> objects representing name-value pairs of string names and <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#systemnumericstensor\">Tensor<\/a> objects.<\/p>\n<pre><code class=\"language-csharp\">using var results = _session.Run(new List&lt;NamedOnnxValue&gt; \n{ \n    NamedOnnxValue.CreateFromTensor(ModelInputName, input) \n});<\/code><\/pre>\n<h4>Postprocessing<\/h4>\n<p>This model outputs a score for each classification. Our code resolves the <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#systemnumericstensor\">Tensor<\/a> by name and gets the highest score value for simplicity in this example. The corresponding label item is then used as the return value for the <strong>GetClassificationAsync<\/strong> method. Additional work would be required to calculate the softmax probability if you wanted to include an indication of confidence alongside the label.<\/p>\n<pre><code class=\"language-csharp\">var output = results.FirstOrDefault(i =&gt; i.Name == ModelOutputName);\nvar scores = output.AsTensor&lt;float&gt;().ToList();\nvar highestScore = scores.Max();\nvar highestScoreIndex = scores.IndexOf(highestScore);\nvar label = _labels.ElementAt(highestScoreIndex);<\/code><\/pre>\n<h3>MainPage<\/h3>\n<p>Our <strong>MainPage<\/strong> XAML features a single <a href=\"https:\/\/docs.microsoft.com\/xamarin\/xamarin-forms\/user-interface\/button\">Button<\/a> for running the inference via the <strong>MobileNetImageClassifier<\/strong>. It resolves the sample image then passes it into the <strong>GetClassificationAsync<\/strong> method before displaying the result via an alert.<\/p>\n<pre><code class=\"language-csharp\">var sampleImage = await _classifier.GetSampleImageAsync();\nvar result = await _classifier.GetClassificationAsync(sampleImage);\nawait DisplayAlert(\"Result\", result, \"OK\");<\/code><\/pre>\n<h2>Optimizations and Tips<\/h2>\n<p>This first-principles example demonstrates basic inferencing with <a href=\"https:\/\/onnxruntime.ai\/\">ONNX Runtime<\/a> and leverages the default options for the most part. There are several optimizations recommended by the <a href=\"https:\/\/onnxruntime.ai\/docs\/\">ONNX Runtime documentation<\/a> that can be particularly beneficial for mobile.<\/p>\n<h3>Reuse InferenceSession objects<\/h3>\n<p>You can accelerate inference speed by reusing the same <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#inferencesession\">InferenceSession<\/a> across multiple inference runs to avoid unnecessary allocation\/disposal overhead.<\/p>\n<h3>Consider whether to set values directly on an existing Tensor or create using an existing array<\/h3>\n<p>Several examples create the <a href=\"\/\/onnxruntime.ai\/docs\/api\/csharp-api#systemnumericstensor\">Tensor<\/a> object first and then set values on it directly. This is simpler and easier to follow since it avoids the need to perform the offset calculations used in this example. However, it&#8217;s faster to prepare a primitive array first then use that to create the <a href=\"\/\/onnxruntime.ai\/docs\/api\/csharp-api#systemnumericstensor\">Tensor<\/a> object. The trade-off here is between simplicity and performance.<\/p>\n<h3>Experiment with different EPs (<a href=\"https:\/\/onnxruntime.ai\/docs\/execution-providers\/\">Execution Providers<\/a>)<\/h3>\n<p>ONNX Runtime executes models using the CPU EP (<a href=\"https:\/\/onnxruntime.ai\/docs\/execution-providers\/\">Execution Provider<\/a>) by default. It&#8217;s possible to use the <a href=\"https:\/\/onnxruntime.ai\/docs\/execution-providers\/NNAPI-ExecutionProvider.html\">NNAPI EP<\/a> (Android) or the <a href=\"https:\/\/onnxruntime.ai\/docs\/execution-providers\/CoreML-ExecutionProvider.html\">Core ML EP<\/a> (iOS) for ORT format models instead by using the appropriate <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#sessionoptions\">SessionOptions<\/a> when creating an <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#inferencesession\">InferenceSession<\/a>. These may or may not offer better performance depending on how much of the model can be run using <a href=\"https:\/\/onnxruntime.ai\/docs\/execution-providers\/NNAPI-ExecutionProvider.html\">NNAPI<\/a> \/ <a href=\"https:\/\/onnxruntime.ai\/docs\/execution-providers\/CoreML-ExecutionProvider.html\">Core ML<\/a> and the device capabilities. It&#8217;s worth testing with and without the platform-specific EPs then choosing what works best for your model. There are also several options per EP. See <a href=\"https:\/\/onnxruntime.ai\/docs\/execution-providers\/NNAPI-ExecutionProvider.html#available-options\">NNAPI Options<\/a> and <a href=\"https:\/\/onnxruntime.ai\/docs\/execution-providers\/CoreML-ExecutionProvider.html#available-options\">Core ML Options<\/a> for more detail on how this can impact performance and accuracy.<\/p>\n<p>The platform-specific EPs can be configured via the <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#sessionoptions\">SessionOptions<\/a>. For example:<\/p>\n<h4>Use NNAPI on Android<\/h4>\n<pre><code class=\"language-csharp\">options.AppendExecutionProvider_Nnapi();<\/code><\/pre>\n<h4>Use Core ML on iOS<\/h4>\n<pre><code class=\"language-csharp\">options.AppendExecutionProvider_CoreML(CoreMLFlags.COREML_FLAG_ONLY_ENABLE_DEVICE_WITH_ANE)<\/code><\/pre>\n<p><strong>SessionOptionsContainer<\/strong> can be used to simplify use of platform-specific <a href=\"https:\/\/onnxruntime.ai\/docs\/api\/csharp-api#sessionoptions\">SessionOptions<\/a> from common platform-agnostic code. For example:<\/p>\n<h4>Register named platform-specific SessionOptions configuration<\/h4>\n<pre><code class=\"language-csharp\">\/\/ Android\nSessionOptionsContainer.Register(\"sample_options\", (sessionOptions) \n    =&gt; sessionOptions.AppendExecutionProvider_Nnapi());\n\n\/\/ iOS\nSessionOptionsContainer.Register(\"sample_options\", (sessionOptions) \n    =&gt; sessionOptions.AppendExecutionProvider_CoreML(\n        CoreMLFlags.COREML_FLAG_ONLY_ENABLE_DEVICE_WITH_ANE));\n<\/code><\/pre>\n<h4>Use in common platform-agnostic code<\/h4>\n<pre><code class=\"language-csharp\">\/\/ Apply named configuration to new SessionOptions\nvar options = SessionOptionsContainer.Create(\"sample_options\");\n\n...\n\n\/\/ Alternatively, apply named configuration to existing SessionOptions\noptions.ApplyConfiguration(\"sample_options\");<\/code><\/pre>\n<h3>Quantize models to reduce size and execution time<\/h3>\n<p>If you have access to the data that was used to train the model you can explore quantizing the model. At a high-level, <a href=\"https:\/\/onnxruntime.ai\/docs\/performance\/quantization.html#quantization-overview\">quantization<\/a> in ONNX Runtime involves mapping higher precision floating point values to lower precision 8-bit values. This topic is covered in detail in the <a href=\"https:\/\/onnxruntime.ai\/docs\/performance\/quantization.html#quantization-overview\">ONNX performance tuning documentation<\/a>, but the idea is to trade off some precision in order to reduce the size of the model and increase performance. It can be especially relevant for mobile apps where package size, battery life, and hardware constraints are key considerations. You can test this by switching out the <a href=\"https:\/\/github.com\/xamcat\/mobcat-samples\/blob\/master\/onnx_runtime\/InferencingSample\/InferencingSample\/mobilenetv2-7.onnx\">mobilenetv2-7.onnx<\/a> model, used in this example, for the <a href=\"https:\/\/github.com\/xamcat\/mobcat-samples\/blob\/master\/onnx_runtime\/InferencingSample\/InferencingSample\/mobilenetv2-7-quantized.onnx\">mobilenetv2-7-quantized.onnx<\/a> model included in the same repo. Quantization can only be performed on those models that use <a href=\"https:\/\/onnxruntime.ai\/docs\/reference\/compatibility.html#onnx-opset-support\">opset<\/a> 10 and above. However, models using older opsets can be updated using the <a href=\"https:\/\/github.com\/onnx\/onnx\/blob\/master\/docs\/VersionConverter.md\">VersionConverter<\/a> tool.<\/p>\n<h3>There are several pre-trained ready to deploy models available<\/h3>\n<p>There are several existing ONNX models available such as those highlighted in the <a href=\"https:\/\/github.com\/onnx\/models#onnx-model-zoo\">ONNX Model Zoo<\/a> collection. Not all of these are optimized for mobile but you&#8217;re not limited to using models already in ONNX format. You can convert existing pre-trained ready to deploy models from other popular sources and formats, such as <a href=\"https:\/\/pytorch.org\/hub\">PyTorch Hub<\/a>, <a href=\"https:\/\/tfhub.dev\/s?subtype=module,placeholder\">TensorFlow Hub<\/a>, and <a href=\"https:\/\/scikit-learn.org\/stable\/\">SciKit-Learn<\/a>. The following resources provide guidance on how to convert each of these respective models to ONNX format:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/onnx\/tensorflow-onnx\">TensorFlow<\/a><\/li>\n<li><a href=\"https:\/\/pytorch.org\/docs\/stable\/onnx.html\">PyTorch<\/a><\/li>\n<li><a href=\"http:\/\/onnx.ai\/sklearn-onnx\/\">SciKit-Learn<\/a><\/li>\n<\/ul>\n<p>Your pre-processing must of course match how the model was trained and so you&#8217;ll need to find the model-specific instructions for the model you convert.<\/p>\n<h3>Disable Hot Reload if you hit a <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.missingmethodexception\">MissingMethodException<\/a> related to <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.readonlyspan-1\">ReadOnlySpan<T><\/a><\/h3>\n<p>In <a href=\"https:\/\/visualstudio.microsoft.com\">Visual Studio 2022<\/a>, <a href=\"https:\/\/docs.microsoft.com\/xamarin\/xamarin-forms\/xaml\/hot-reload\">Hot Reload<\/a> loads some additional dependencies including <a href=\"https:\/\/www.nuget.org\/packages\/System.Memory\">System.Memory<\/a> and <a href=\"https:\/\/www.nuget.org\/packages\/System.Buffers\">System.Buffers<\/a> which may cause conflicts with packages such as <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.ML.OnnxRuntime.Managed\">ONNX Runtime<\/a>. You can <a href=\"https:\/\/docs.microsoft.com\/xamarin\/xamarin-forms\/xaml\/hot-reload#enable-xaml-hot-reload-for-xamarinforms\">Disable Hot Reload<\/a> as a workaround until the <a href=\"https:\/\/developercommunity.visualstudio.com\/t\/bug-in-visual-studio-2022-xamarin-signalr-method-n\/1528510#T-N1585809\">issue<\/a> has been addressed.<\/p>\n<h2>Summary<\/h2>\n<p>The intent of this post was to provide a helpful on-ramp for those looking to leverage <a href=\"https:\/\/onnxruntime.ai\">ONNX Runtime<\/a> in their Xamarin.Forms apps. Be sure to checkout the official <a href=\"https:\/\/onnxruntime.ai\/docs\/get-started\/with-csharp.html\">getting started<\/a> and <a href=\"https:\/\/onnxruntime.ai\/docs\/tutorials\/api-basics.html\">tutorial<\/a> content as well as the <a href=\"https:\/\/github.com\/microsoft\/onnxruntime-inference-examples\/tree\/main\/mobile\/examples\/Xamarin\">Xamarin samples<\/a>.<\/p>\n<h2>Useful links<\/h2>\n<ul>\n<li><a href=\"https:\/\/arxiv.org\/abs\/1801.04381v4\">MobileNetV2<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/lutzroeder\/Netron\">Netron (model viewer)<\/a><\/li>\n<li><a href=\"https:\/\/onnx.ai\">ONNX (Open Neural Network Exchange)<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/onnx\/models#onnx-model-zoo\">ONNX Model Zoo<\/a><\/li>\n<li><a href=\"https:\/\/onnxruntime.ai\/docs\/\">ONNX Runtime Documentation<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/microsoft\/onnxruntime-inference-examples\/tree\/main\/mobile\/examples\/Xamarin\">ONNX Runtime Xamarin Samples<\/a><\/li>\n<li><a href=\"https:\/\/onnxruntime.ai\/docs\/performance\/quantization.html\">ONNX Quantization<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to integrate machine learning into your Xamarin.Forms application with ONNX Runtime<\/p>\n","protected":false},"author":1967,"featured_media":49028,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[313,303,1,291,367],"tags":[3617,9214,9215,27,16],"class_list":["post-49014","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-android","category-ios","category-xamarin","category-xamarin-platform","category-xamarin-forms","tag-machine-learning","tag-onnx","tag-onnx-runtime","tag-xamarin","tag-xamarin-forms"],"acf":[],"blog_post_summary":"<p>Learn how to integrate machine learning into your Xamarin.Forms application with ONNX Runtime<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts\/49014","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/users\/1967"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/comments?post=49014"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts\/49014\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/media\/49028"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/media?parent=49014"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/categories?post=49014"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/tags?post=49014"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}