Развертывание модели в приложении Windows с помощью API Windows ML

Из предыдущей части этого руководства вы узнали, как создавать и экспортировать модели в формате ONNX. Теперь мы продемонстрируем, как внедрить экспортированную ранее модель в приложение Windows и запустить ее на локальном устройстве с помощью вызовов к API WinML.

Когда мы закончим этот процесс, у вас будет готовое приложение для классификации изображений.

Сведения о примере приложения

На этом этапе вы создадите приложение, которое будет классифицировать изображения с использованием модели машинного обучения. Несложный пользовательский интерфейс приложения позволяет выбрать изображение на локальном устройстве и классифицировать его с помощью модели классификации ONNX, которая была создана и обучена в предыдущей части. Теги, возвращаемые этой моделью, отображаются рядом с изображением.

В этой статье мы подробно рассматриваем этот процесс.

Примечание.

Если вы предпочитаете получить готовый пример кода, клонируйте файл решения. Для этого клонируйте репозиторий, перейдите в нем к нужному примеру и откройте файл classifierPyTorch.sln в Visual Studio. Процесс работы с ним описан далее, в разделе Запуск приложения.

Ниже показано, как создать приложение и добавить в него код Windows ML.

Создание приложения UWP (C#) для Windows ML

Чтобы получить работающее приложение Windows ML, сделайте следующее:

  • Загрузите модель машинного обучения.
  • Загрузите изображение в требуемом формате.
  • Создайте привязки для входных и выходных данных модели.
  • Оцените модели и отобразите полезные результаты.

Вам также потребуется создать несложный пользовательский интерфейс, поскольку в командной строке сложно создать хорошее приложение для работы с изображениями.

Открытие нового проекта в Visual Studio

  1. Итак, приступим. Откройте Visual Studio и щелкните Создать проект.

Create new Visual Studio project

  1. В строке поиска введите UWP и выберите Blank APP (Universal Windows). Откроется проект C# для одностраничного приложения универсальной платформы Windows (UWP) с предопределенными элементами управления или макетом. Выберите next, чтобы открыть окно конфигурации для проекта.

Create new UWP app

  1. В окне конфигурации сделайте следующее:
  • Присвойте проекту имя. В нашем примере это classifierPyTorch.
  • Выберите расположение проекта.
  • Если вы используете VS 2019, установите флажок Create directory for solution.
  • Если вы используете VS2017, снимите флажок Place solution and project in the same directory.

New UWP app setup

Щелкните create, чтобы создать проект. Может появиться окно для ввода минимальной целевой версией. Убедитесь, что в качестве минимальной версии указана версия Windows 10 версии 1809 (10.0; сборка 17763) или более поздняя.

  1. После создания проекта перейдите в папку этого проекта, откройте папку assets ([….\classifierPyTorch \Assets]) и скопируйте файл ImageClassifier.onnx в это расположение.

Обзор решения проекта

Давайте изучим решение проекта.

Visual Studio автоматически создает несколько файлов cs-code в Обозревателе решений. Файл MainPage.xaml содержит код XAML для графического пользовательского интерфейса, а файл MainPage.xaml.cs — код приложения, или "код программной части". Если вы уже создавали приложения UWP, эти файлы должны быть вам хорошо знакомы.

UWP app solution

Создание графического пользовательского интерфейса приложения

Сначала мы создадим простой графический пользовательский интерфейс для приложения.

  1. Дважды щелкните файл кода MainPage.xaml. В пустом приложении шаблон XAML для графического пользовательского интерфейса приложения пуст, поэтому нам нужно добавить некоторые возможности пользовательского интерфейса.

  2. Добавьте в MainPage.xaml приведенный ниже код, заменив значения тегов <Grid> и </Grid>.

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 

        <StackPanel Margin="1,0,-1,0"> 
            <TextBlock x:Name="Menu"  
                       FontWeight="Bold"  
                       TextWrapping="Wrap" 
                       Margin="10,0,0,0" 
                       Text="Image Classification"/> 
            <TextBlock Name="space" /> 
            <Button Name="recognizeButton" 
                    Content="Pick Image" 
                    Click="OpenFileButton_Click"  
                    Width="110" 
                    Height="40" 
                    IsEnabled="True"  
                    HorizontalAlignment="Left"/> 
            <TextBlock Name="space3" /> 
            <Button Name="Output" 
                    Content="Result is:" 
                    Width="110" 
                    Height="40" 
                    IsEnabled="True"  
                    HorizontalAlignment="Left"  
                    VerticalAlignment="Top"> 
            </Button> 
            <!--Display the Result--> 
            <TextBlock Name="displayOutput"  
                       FontWeight="Bold"  
                       TextWrapping="Wrap" 
                       Margin="25,0,0,0" 
                       Text="" Width="1471" /> 
            <TextBlock Name="space2" /> 
            <!--Image preview --> 
            <Image Name="UIPreviewImage" Stretch="Uniform" MaxWidth="300" MaxHeight="300"/> 
        </StackPanel> 
    </Grid> 

Добавление модели в проект с помощью генератора кода Windows ML (mlgen)

Генератор кода Windows Machine Learning (mlgen) — это расширение Visual Studio, которое поможет вам приступить к использованию API-интерфейсов WinML в приложениях UWP. Он создает код шаблона при добавлении обученного файла ONNX в проект UWP.

Генератор кода Windows Machine Learning mlgen создает интерфейс (для C#, C++/WinRT и C++/CX) с классами-оболочками, которые вызывают API Windows ML. Это позволяет легко загружать, привязывать и оценивать модель в проекте. Мы будем использовать его в этом руководстве для решения многих задач такого типа.

Генератор кода доступен для Visual Studio 2017 и более поздних версий. Рекомендуется использовать Visual Studio 2019. Имейте в виду, что в Windows 10 версии 1903 и более поздних версиях mlgen больше не входит в Windows 10 SDK, поэтому вы должны скачать и установить расширение. Если вы выполняли все инструкции из этого руководства с самого начала, то у вас уже есть это средство. Если нет, то вам следует скачать версию для Visual Studio 2019 или для Visual Studio 2017.

Примечание.

Дополнительные сведения см. в документации по mlgen.

  1. Установите mlgen, если вы еще этого не сделали.

  2. Щелкните правой кнопкой мыши папку Assets в Обозревателе решений в Visual Studio и выберите Add > Existing Item.

  3. Перейдите в папку ресурсов внутри classifierPyTorch [….\classifierPyTorch \Assets], найдите модель ONNX, которую вы ранее скопировали туда, и выберите add.

  4. После того, как вы добавили модель ONNX в папку assets в Обозревателе решений в Visual Studio, проект теперь должен иметь два новых файла:

  • ImageClassifier.onnx — это модель в формате ONNX;
  • ImageClassifier.cs — автоматически созданный файл кода WinML.

ONNX files in your UWP app solution

  1. Чтобы убедиться, что модель успешно компилируется вместе с приложением, выберите файл ImageClassifier.onnx и щелкните Properties. В Build Action выберите Content.

Код в файле ONNX

Теперь давайте рассмотрим только что созданный код в файле ImageClassifier.cs.

Созданный код включает в себя три класса:

  • Класс ImageClassifierModel содержит два метода: для создания экземпляра модели и оценки модели. Этот класс поможет создать представление модели машинного обучения и сеанс на системном устройстве по умолчанию, привязать к модели определенные входные и выходные данные, а также асинхронно анализировать модель.
  • Класс ImageClassifierInput инициализирует типы входных данных, которые ожидает получить модель. Входные данные модели зависят от требований модели к входным данным.
  • Класс ImageClassifierOutput инициализирует типы, которые модель выводит. Выходные данные модели зависят от того, как они определяются моделью.

В этом руководстве мы не будем рассматривать преобразование в тензор. Поэтому в класс ImageClassifierInput мы внесем небольшое изменение, чтобы использовать другой тип входных данных и немного упростить жизнь.

  1. Внесите следующие изменения в файл ImageClassifier.cs:

Для переменной input замените значение TensorFloat на ImageFeatureValue.

public sealed class ImageClassifierInput 
    { 
        public ImageFeatureValue input; // shape(-1,3,32,32) 
    } 

Загрузка модели

  1. Дважды щелкните файл MainPage.xaml.cs, чтобы открыть код программной части вашего приложения.

  2. Замените все операторы using следующим выражением, чтобы получить доступ ко всем необходимым API.

// Specify all the using statements which give us the access to all the APIs that we'll need 
using System; 
using System.Threading.Tasks; 
using Windows.AI.MachineLearning; 
using Windows.Graphics.Imaging; 
using Windows.Media; 
using Windows.Storage; 
using Windows.Storage.Pickers; 
using Windows.Storage.Streams; 
using Windows.UI.Xaml; 
using Windows.UI.Xaml.Controls; 
using Windows.UI.Xaml.Media.Imaging; 
  1. Добавьте следующие объявления переменных в класс MainPage прямо над функцией public MainPage().
        // All the required fields declaration 
        private ImageClassifierModel modelGen; 
        private ImageClassifierInput image = new ImageClassifierInput(); 
        private ImageClassifierOutput results; 
        private StorageFile selectedStorageFile; 
        private string label = ""; 
        private float probability = 0; 
        private Helper helper = new Helper(); 

        public enum Labels 
        {             
            plane,
            car,
            bird,
            cat,
            deer,
            dog,
            frog,
            horse,
            ship,
            truck
        } 

Теперь вы реализуете метод LoadModel. Этот метод будет обращаться к модели ONNX и хранить ее в памяти. Затем вы примените метод CreateFromStreamAsync для создания экземпляра модели в качестве объекта LearningModel. Класс LearningModel представляет обученную модель машинного обучения. Созданный экземпляр LearningModel является начальным объектом, который вы используете для взаимодействия с Windows ML.

Чтобы загрузить модель, в классе LearningModel можно использовать несколько статических методов. В нашем примере вы будете использовать метод CreateFromStreamAsync.

Метод CreateFromStreamAsync был автоматически создан с помощью mlgen, поэтому вам не нужно реализовывать его. Чтобы проверить этот метод, дважды щелкните файл classifier.cs, созданный с помощью mlgen.

Примечание.

Дополнительные сведения о классе LearningModel см. в документации по классу LearningModel. Дополнительные сведения о способах загрузки модели см. в документации по загрузке модели.

  1. Добавьте вызов метода loadModel в конструктор класса main.
        // The main page to initialize and execute the model.
        public MainPage()
        {
            this.InitializeComponent();
            loadModel();
        }
  1. Добавьте реализацию метода loadModel в класс MainPage.
        private async Task loadModel()
        {
            // Get an access the ONNX model and save it in memory.
            StorageFile modelFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri($"ms-appx:///Assets/ImageClassifier.onnx"));
            // Instantiate the model. 
            modelGen = await ImageClassifierModel.CreateFromStreamAsync(modelFile);
        }

Загрузка изображения

  1. Необходимо определить событие щелчка, которое инициирует последовательность из вызовов четырех методов для выполнения модели — преобразование, привязку и оценку, извлечение выходных данных и отображение результатов. Добавьте следующий метод в файл кода MainPage.xaml.cs внутри класса MainPage.
        // Waiting for a click event to select a file 
        private async void OpenFileButton_Click(object sender, RoutedEventArgs e)
        {
            if (!await getImage())
            {
                return;
            }
            // After the click event happened and an input selected, begin the model execution. 
            // Bind the model input
            await imageBind();
            // Model evaluation
            await evaluate();
            // Extract the results
            extractResult();
            // Display the results  
            await displayResult();
        }
  1. Теперь вы реализуете метод getImage(). Этот метод выбирает входной файл изображения и сохраняет его в памяти. Добавьте следующий метод в файл кода MainPage.xaml.cs внутри класса MainPage.
        // A method to select an input image file
        private async Task<bool> getImage()
        {
            try
            {
                // Trigger file picker to select an image file
                FileOpenPicker fileOpenPicker = new FileOpenPicker();
                fileOpenPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
                fileOpenPicker.FileTypeFilter.Add(".jpg");
                fileOpenPicker.FileTypeFilter.Add(".png");
                fileOpenPicker.ViewMode = PickerViewMode.Thumbnail;
                selectedStorageFile = await fileOpenPicker.PickSingleFileAsync();
                if (selectedStorageFile == null)
                {
                    return false;
                }
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

Теперь вы реализуете метод изображения Bind(), чтобы получить представление файла в формате растрового изображения BGRA8. Но сначала нужно создать вспомогательный класс для изменения размера изображения.

  1. Чтобы создать вспомогательный файл, щелкните имя решения (ClassifierPyTorch) правой кнопкой мыши, а затем выберите Add a new item. В открывшемся окне выберите Class и присвойте ему имя. В нашем примере это Helper.

Add a Helper file

  1. В вашем проекте появится новый файл класса. Откройте этот класс и добавьте в него следующий код:
using System; 
using System.Threading.Tasks; 
using Windows.Graphics.Imaging; 
using Windows.Media; 

namespace classifierPyTorch 
{ 
    public class Helper 
    { 
        private const int SIZE = 32;  
        VideoFrame cropped_vf = null; 
 
        public async Task<VideoFrame> CropAndDisplayInputImageAsync(VideoFrame inputVideoFrame) 
        { 
            bool useDX = inputVideoFrame.SoftwareBitmap == null; 

            BitmapBounds cropBounds = new BitmapBounds(); 
            uint h = SIZE; 
            uint w = SIZE; 
            var frameHeight = useDX ? inputVideoFrame.Direct3DSurface.Description.Height : inputVideoFrame.SoftwareBitmap.PixelHeight; 
            var frameWidth = useDX ? inputVideoFrame.Direct3DSurface.Description.Width : inputVideoFrame.SoftwareBitmap.PixelWidth; 
 
            var requiredAR = ((float)SIZE / SIZE); 
            w = Math.Min((uint)(requiredAR * frameHeight), (uint)frameWidth); 
            h = Math.Min((uint)(frameWidth / requiredAR), (uint)frameHeight); 
            cropBounds.X = (uint)((frameWidth - w) / 2); 
            cropBounds.Y = 0; 
            cropBounds.Width = w; 
            cropBounds.Height = h; 
 
            cropped_vf = new VideoFrame(BitmapPixelFormat.Bgra8, SIZE, SIZE, BitmapAlphaMode.Ignore); 
 
            await inputVideoFrame.CopyToAsync(cropped_vf, cropBounds, null); 
            return cropped_vf; 
        } 
    } 
} 

Теперь нам нужно преобразовать изображение в подходящий формат.

Класс ImageClassifierInput инициализирует типы входных данных, которые ожидает получить модель. В нашем примере код настроен так, чтобы ожидать ImageFeatureValue.

Класс ImageFeatureValue описывает свойства изображения, которое передается в модель. Для создания ImageFeatureValue используется метод CreateFromVideoFrame. Более подробно причины и правила использования этих классов и методов описаны в документации по классу ImageFeatureValue.

Примечание.

В этом руководстве мы вместо тензора используем класс ImageFeatureValue. Если Window ML не поддерживает цветовой формат вашей модели, такой вариант вам не подходит. Пример работы с преобразованием изображений и преобразованием в тензор см. здесь.

  1. Добавьте реализацию метода convert() в файл кода MainPage.xaml.cs внутри класса MainPage. Метод convert получает представление входного файла в формате BGRA8.
// A method to convert and bide the input image. 
private async Task imageBind () 
{
    UIPreviewImage.Source = null; 
    try
    { 
        SoftwareBitmap softwareBitmap;
        using (IRandomAccessStream stream = await selectedStorageFile.OpenAsync(FileAccessMode.Read)) 
        {
            // Create the decoder from the stream
            BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
            // Get the SoftwareBitmap representation of the file in BGRA8 format
            softwareBitmap = await decoder.GetSoftwareBitmapAsync();
            softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
        }
        // Display the image 
        SoftwareBitmapSource imageSource = new SoftwareBitmapSource();
        await imageSource.SetBitmapAsync(softwareBitmap);
        UIPreviewImage.Source = imageSource;

        // Encapsulate the image within a VideoFrame to be bound and evaluated
        VideoFrame inputImage = VideoFrame.CreateWithSoftwareBitmap(softwareBitmap);
        // Resize the image size to 32x32  
        inputImage=await helper.CropAndDisplayInputImageAsync(inputImage); 
        // Bind the model input with image 
        ImageFeatureValue imageTensor = ImageFeatureValue.CreateFromVideoFrame(inputImage); 
        image.modelInput = imageTensor; 

        // Encapsulate the image within a VideoFrame to be bound and evaluated
        VideoFrame inputImage = VideoFrame.CreateWithSoftwareBitmap(softwareBitmap); 
        // bind the input image 
        ImageFeatureValue imageTensor = ImageFeatureValue.CreateFromVideoFrame(inputImage); 
        image.modelInput = imageTensor; 
    }
    catch (Exception e) 
    {
    }
} 

Привязка и оценка модели

Теперь вам нужно создать сеанс на основе модели, привязать входные и выходные данные этого сеанса, а затем оценить модель.

Создайте сеанс для привязки модели:

Чтобы создать сеанс, используйте класс LearningModelSession. Этот класс используется для оценки моделей машинного обучения и привязки модели к устройству, которое затем запускается и оценивает модель. Вы можете выбрать устройство при создании сеанса, чтобы иметь возможность выполнять модель на определенном устройстве компьютера. В качестве устройства по умолчанию используется ЦП.

Примечание.

Дополнительные сведения о том, как выбрать устройство, см. в документации по созданию сеанса.

Привязка входных и выходных данных:

Для привязки входных и выходных данных нужно использовать класс LearningModelBinding. Модель машинного обучения имеет выходные и выходные признаки для передачи информации в модель и из нее. Имейте в виду, что требуемые признаки должны поддерживаться API Window ML. Класс LearningModelBinding применяется к LearningModelSession для привязки значений к именованным входным и выходным признакам.

Реализация привязки автоматически создается с помощью mlgen, поэтому вам не нужно об этом беспокоиться. Привязка реализуется путем вызова предопределенных методов класса LearningModelBinding. В нашем случае используется метод Bind для привязки значения к именованному типу признака.

Оценка модели:

Создав сеанс для привязки модели и привязанные значения для входных и выходных данных модели, вы можете приступить к оценке входных данных модели и получить ее прогнозы. Чтобы запустить выполнение модели, следует вызвать любой из предопределенных методов оценки в LearningModelSession. В нашем примере мы будем использовать метод EvaluateAsync.

Как и CreateFromStreamAsync, метод EvaluateAsync был автоматически создан генератором кода WinML, поэтому вам не нужно реализовывать этот метод. Этот метод можно просмотреть в файле ImageClassifier.cs.

Метод EvaluateAsync будет асинхронно оценивать модель машинного обучения, используя значения признаков, уже привязанные в привязках. Он создает сеанс с помощью метода LearningModelSession, привязывает входные и выходные данные с помощью метода LearningModelBinding, выполняет оценку модели и получает выходные признаки модели с помощью класса LearningModelEvaluationResult.

Примечание.

Чтобы узнать о других методах оценки для запуска модели, проверьте, какие методы можно реализовать в LearningModelSession, просмотрев документацию по классу LearningModelSession.

  1. Добавьте следующий метод в файл кода MainPage.xaml.cs внутри класса MainPage, чтобы создать сеанс, привязать и оценить модель.
        // A method to evaluate the model
        private async Task evaluate()
        {
            results = await modelGen.EvaluateAsync(image);
        }

Извлечение и отображение результатов

Теперь вам нужно извлечь выходные данные модели и отобразить правильный результат, что можно выполнить с помощью реализации методов extractResult и displayResult. Чтобы вернуть правильную метку, нужно найти наибольшую вероятность.

  1. Добавьте метод extractResult в файл кода MainPage.xaml.cs внутри класса MainPage.
        // A method to extract output from the model 
        private void extractResult()
        {
            // Retrieve the results of evaluation
            var mResult = results.modelOutput as TensorFloat;
            // convert the result to vector format
            var resultVector = mResult.GetAsVectorView();
            
            probability = 0;
            int index = 0;
            // find the maximum probability
            for(int i=0; i<resultVector.Count; i++)
            {
                var elementProbability=resultVector[i];
                if (elementProbability > probability)
                {
                    index = i;
                }
            }
            label = ((Labels)index).ToString();
        }
  1. Добавьте метод displayResult в файл кода MainPage.xaml.cs внутри класса MainPage.
        private async Task displayResult() 
        {
            displayOutput.Text = label; 
        }

Вот и все! Вы успешно создали приложение Windows Machine Learning с простым графическим интерфейсом для тестирования модели классификации. Следующий шаг — запуск приложения на локальном устройстве Windows.

Запуск приложения

После того, как вы создали интерфейс приложения, добавили модель и сгенерировали код WinML, вы можете протестировать приложение.

Включите режим разработчика и протестируйте приложение в Visual Studio. В раскрывающихся меню на верхней панели инструментов должен быть указан режим Debug. Для запуска проекта на локальном компьютере укажите для параметра "Платформа решения" значение x64 для 64-разрядного устройства или x86 для 32-разрядного.

Наша модель была обучена классификации изображений по следующим категориям: самолет, автомобиль, птица, кошка, олень, собака, лягушка, лошадь, корабль, грузовик. Чтобы протестировать приложение, вы примените изображение автомобиля из кубиков LEGO, созданного специально для нашего проекта. Давайте посмотрим, как наше приложение классифицирует содержимое этого изображения.

Image for application testing

  1. Сохраните это изображение на локальном устройстве, чтобы протестировать приложение. При необходимости измените формат изображения на .jpg. Вы также можете использовать любое другое изображение подходящей тематики, сохраненное на локальном устройстве в формате JPG или PNG.

  2. Чтобы запустить проект, нажмите кнопку Start Debugging на панели инструментов или нажмите клавишу F5.

  3. Когда приложение откроется, нажмите Pick Image (Выбрать изображение) и выберите изображение на локальном устройстве.

Application interface

Результат сразу отобразится на экране. Как видите, приложение Windows ML успешно распознало автомобиль на этом изображении.

Successful classification in your app

Итоги

Вы только что создали свое первое приложение для Windows Machine Learning — от этапа создания модели до этапа успешного выполнения.

Дополнительные ресурсы

Дополнительные сведения см. в следующих статьях: