Implantar um modelo no aplicativo Windows com a API do Windows ML

Na parte anterior deste tutorial, você aprendeu a criar e exportar um modelo no formato ONNX. Agora, mostraremos como inserir o modelo exportado em um aplicativo do Windows e executá-lo localmente em um dispositivo chamando APIs do WinML.

Quando terminarmos, você terá um aplicativo de classificação de imagem em funcionamento.

Sobre o aplicativo de amostra

Nesta etapa do tutorial, você criará um aplicativo capaz de classificar imagens usando seu modelo de ML. A interface do usuário básica dele permite que você selecione uma imagem do dispositivo local e usa o modelo ONNX de classificação que você criou e treinou na parte anterior para classificá-la. As marcações retornadas pelo modelo são exibidas ao lado da imagem.

Aqui, percorreremos esse processo com você.

Nota

Se você optar por usar o exemplo de código predefinido, poderá clonar o arquivo da solução. Clone o repositório, navegue até este exemplo e abra o arquivo classifierPyTorch.sln com o Visual Studio. Vá diretamente para a parte Iniciar o aplicativo desta página para vê-lo em uso.

A seguir, vamos descrever como criar o aplicativo e adicionar código do Windows ML a ele.

Criar um aplicativo UWP do Windows ML (C#)

Para criar um aplicativo do Windows ML funcional, você precisará fazer o seguinte:

  • Carregar um modelo de machine learning.
  • Carregar uma imagem no formato necessário.
  • Associar as entradas e as saídas do modelo.
  • Avaliar o modelo e exibir os resultados significativos.

Você também precisará criar uma interface do usuário básica, pois é difícil criar um aplicativo baseado em imagem satisfatório na linha de comando.

Abrir um novo projeto no Visual Studio

  1. Vamos começar. Abra o Visual Studio e escolha criar um projeto.

Create new Visual Studio project

  1. Na barra de pesquisa, digite UWP e selecione Blank APP (Universal Windows). Isso abre um projeto em C# de um aplicativo UWP (Plataforma Universal do Windows) de página única com controles e layout predefinidos. Selecione next para abrir uma janela de configuração para o projeto.

Create new UWP app

  1. Na janela de configuração, faça o seguinte:
  • Nomeie o projeto. Aqui, o chamamos de classifierPyTorch.
  • Escolha o local do projeto.
  • Se você está usando o VS2019, verifique se Create directory for solution está marcado.
  • Se estiver usando o VS2017, verifique se a opção Place solution and project in the same directory está desmarcada.

New UWP app setup

Pressione create para criar o projeto. A janela de versão de destino mínima pode aparecer. Verifique se a versão mínima está definida como Windows 10, versão 1809 (10.0; build 17763) ou posterior.

  1. Depois que o projeto for criado, navegue até a pasta do projeto, abra a pasta de ativos [….\classifierPyTorch \Assets] e copie o arquivo ImageClassifier.onnx para esse local.

Explorar a solução do projeto

Vamos explorar sua solução de projeto.

O Visual Studio criou automaticamente vários arquivos cs-code dentro do Gerenciador de Soluções. MainPage.xaml contém o código XAML da GUI e MainPage.xaml.cs contém o código do aplicativo, também conhecido como code-behind. Se você já criou um aplicativo UWP, esses arquivos devem ser muito familiares.

UWP app solution

Criar a GUI do aplicativo

Primeiro, vamos criar uma GUI simples para o aplicativo.

  1. Clique duas vezes no arquivo de código MainPage.xaml. No aplicativo em branco, o modelo XAML da GUI do aplicativo está vazio, de modo que precisaremos adicionar alguns recursos da interface do usuário.

  2. Adicione o código abaixo a MainPage.xaml, substituindo marcas <Grid> e </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> 

Adicionar o modelo ao projeto usando o Gerador de Código do Windows ML (mlgen)

O Gerador de Código do Windows Machine Learning, ou mlgen, é uma extensão do Visual Studio que ajuda você a começar a usar APIs do WinML em aplicativos UWP. Ele gera código de modelo quando você adiciona um arquivo ONNX treinado ao projeto UWP.

O gerador de código do Windows Machine Learning, mlgen, cria uma interface (para C#, C++/WinRT e C++/CX) com classes wrapper que chamam a API do Windows ML para você. Assim, você pode carregar, associar e avaliar facilmente um modelo em seu projeto. Vamos usá-lo neste tutorial para lidar com muitas dessas funções para nós.

O gerador de código está disponível para o Visual Studio 2017 e posterior. Recomendamos usar o Visual Studio. Observe que, no Windows 10 versão 1903 e posterior, o mlgen não está mais incluído no SDK do Windows 10, de modo que você precisa baixar e instalar a extensão. Se está acompanhando o tutorial desde a introdução, você já cuidou disso. Caso contrário, baixe-o para o VS 2019 ou o VS 2017.

Nota

Para saber mais sobre o mlgen, confira a documentação do mlgen

  1. Se ainda não fez isso, instale o mlgen.

  2. Clique com o botão direito do mouse na pasta Assets no Gerenciador de Soluções no Visual Studio e selecione Add > Existing Item.

  3. Navegue até a pasta de ativos dentro de classifierPyTorch [….\classifierPyTorch \Assets], localize o modelo ONNX que você copiou nela e selecione add.

  4. Após você adicionar um modelo ONNX à pasta de ativos no gerenciador de soluções no VS, o projeto deverá ter dois novos arquivos:

  • ImageClassifier.onnx – o modelo no formato ONNX.
  • ImageClassifier.cs – arquivo de código WinML gerado automaticamente.

ONNX files in your UWP app solution

  1. Para que o modelo seja criado quando você compilar o aplicativo, selecione o arquivo ImageClassifier.onnx e escolha Properties. Para Build Action, selecione Content.

Código do arquivo ONNX

Agora, vamos explorar um código que acaba de ser gerado no arquivo ImageClassifier.cs.

O código gerado inclui três classes:

  • ImageClassifierModel: essa classe inclui dois métodos para instanciação de modelo e avaliação de modelo. Ele nos ajudará a criar a representação do modelo de machine learning, criar uma sessão no dispositivo padrão do sistema, associar as entradas e saídas específicas ao modelo e avaliar o modelo de maneira assíncrona.
  • ImageClassifierInput: essa classe inicializa os tipos de entrada que o modelo espera. A entrada de modelo depende dos requisitos de modelo para dados de entrada.
  • ImageClassifierOutput: essa classe inicializa os tipos que o modelo terá como saída. A saída do modelo depende de como ela é definida pelo modelo.

Neste tutorial, não queremos lidar com a tensorização. Vamos fazer uma pequena alteração na classe ImageClassifierInput, alterar o tipo de dados de entrada e facilitar nossa vida.

  1. Faça as seguintes alterações no arquivo ImageClassifier.cs:

Altere a variável input de um TensorFloat para um ImageFeatureValue.

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

Carregar o modelo

  1. Clique duas vezes no arquivo MainPage.xaml.cs para abrir o code-behind do aplicativo.

  2. Substitua as instruções "using" pelo seguinte para ter acesso a todas as APIs que serão necessárias:

// 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. Adicione as declarações de variável a seguir dentro da classe MainPage, acima da função 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
        } 

Agora, você vai implementar o método LoadModel. O método acessará o modelo ONNX e o armazenará na memória. Em seguida, você usará o método CreateFromStreamAsync para criar uma instância do modelo como um objeto LearningModel. A classe LearningModel representa um modelo de machine learning treinado. Uma vez instanciado, o LearningModel é o objeto inicial usado para interagir com o Windows ML.

Para carregar o modelo, você pode usar vários métodos estáticos na classe LearningModel. Nesse caso, você usará o método CreateFromStreamAsync.

O método CreateFromStreamAsync foi criado automaticamente com mlgen, portanto, você não precisa implementá-lo. Você pode examiná-lo clicando duas vezes no arquivo classifier.cs gerado por mlgen.

Nota

Para saber mais sobre a classe LearningModel, examine a documentação da classe LearningModel. Para saber mais sobre outras maneiras de carregar o modelo, examine a documentação Carregar um modelo

  1. Adicione uma chamada a um método loadModel ao construtor da classe principal.
        // The main page to initialize and execute the model.
        public MainPage()
        {
            this.InitializeComponent();
            loadModel();
        }
  1. Adicione a implementação do método loadModel dentro dessa classe 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);
        }

Carregar a imagem

  1. Precisamos definir um evento de clique para iniciar a sequência de quatro chamadas de método para execução do modelo: conversão, associação e avaliação, extração de saída e exibição de resultados. Adicione o método a seguir ao seu arquivo de código MainPage.xaml.cs dentro da classe 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. Agora, você vai implementar o método getImage(). Esse método vai selecionar um arquivo de imagem de entrada e salvá-lo na memória. Adicione o método a seguir ao seu arquivo de código MainPage.xaml.cs dentro da classe 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;
        }

Em seguida, você implementará um método Bind() de imagem para obter a representação do arquivo no formato BGRA8 de bitmap. Porém, primeiro vai criar uma classe auxiliar para redimensionar a imagem.

  1. Para criar um arquivo auxiliar, clique com o botão direito do mouse no nome da solução (ClassifierPyTorch) e escolha Add a new item. Na janela aberta, selecione Class e dê um nome a ela. Aqui, nós a chamamos de Helper.

Add a Helper file

  1. Um novo arquivo de classe aparecerá dentro do seu projeto. Abra essa classe e adicione o seguinte código:
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; 
        } 
    } 
} 

Agora, vamos converter a imagem no formato apropriado.

A classe ImageClassifierInput inicializa os tipos de entrada que o modelo espera. Em nosso caso, configuramos o código para esperar um ImageFeatureValue.

A classe ImageFeatureValue descreve as propriedades da imagem usada para passar para um modelo. Para criar um ImageFeatureValue, use o método CreateFromVideoFrame. Para obter detalhes mais específicos sobre por que esse é o caso e como essas classes e métodos funcionam, confira a documentação da classe ImageFeatureValue

Nota

Neste tutorial, usamos a classe ImageFeatureValue em vez de um tensor. Se o Windows ML não oferecer suporte ao formato de cor do modelo, essa não será uma opção. Para obter um exemplo de como trabalhar com conversões de imagem e tensorização, confira o Exemplo de tensorização personalizado.

  1. Adicione a implementação do método convert() ao seu arquivo de código MainPage.xaml.cs dentro da classe MainPage. O método convert nos fará uma representação do arquivo de entrada em um formato 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) 
    {
    }
} 

Associar e avaliar o modelo

A seguir, você criará uma sessão com base no modelo, associará a entrada e a saída da sessão e avaliará o modelo.

Criar uma sessão para associar o modelo:

Para criar uma sessão, use a classe LearningModelSession. Essa classe é usada para avaliar modelos de machine learning e associa o modelo a um dispositivo que, por sua vez, executa e avalia o modelo. Você pode selecionar um dispositivo ao criar uma sessão para executar o modelo em um dispositivo específico do computador. O dispositivo padrão é a CPU.

Nota

Para saber mais sobre como escolher um dispositivo, examine a documentação Criar uma sessão.

Associar entradas e saídas do modelo:

Para associar entrada e saída, use a classe LearningModelBinding. Um modelo de machine learning tem recursos de entrada e saída, que transmitem informações para dentro e fora do modelo. Lembre-se de que os recursos necessários devem ter suporte nas APIs do Windows ML. A classe LearningModelBinding é aplicada em um LearningModelSession para associar valores aos recursos de entrada e saída nomeados.

A implementação da associação é gerada automaticamente pelo mlgen, portanto, você não precisa cuidar dela. A associação é implementada chamando os métodos predefinidos da classe LearningModelBinding. Em nosso caso, ela usa o método Bind para associar um valor ao tipo de recurso nomeado.

Avaliar o modelo:

Depois de criar uma sessão para associar o modelo e de associar valores às entradas e saídas do modelo, você pode avaliar as entradas do modelo e obter as previsões. Para executar o modelo, chame um dos métodos de avaliação predefinidos em LearningModelSession. Em nosso caso, usaremos o método EvaluateAsync.

Semelhante a CreateFromStreamAsync, o método EvaluateAsync também foi gerado automaticamente pelo Gerador de Código do WinML, portanto, você não precisa implementá-lo. Examine esse método no arquivo ImageClassifier.cs.

O método EvaluateAsync avaliará de modo assíncrono o modelo de machine learning usando os valores de recurso já associados nas associações. Ele criará uma sessão com LearningModelSession, associará a entrada e a saída com LearningModelBinding, executará a avaliação do modelo e obterá os recursos de saída do modelo usando a classe LearningModelEvaluationResult.

Nota

Para saber mais sobre outros métodos de avaliação para executar o modelo, verifique quais métodos podem ser implementados em LearningModelSession examinando a documentação da Classe LearningModelSession.

  1. Adicione o método a seguir ao arquivo de código MainPage.xaml.cs dentro da classe MainPage para criar uma sessão, associar e avaliar o modelo.
        // A method to evaluate the model
        private async Task evaluate()
        {
            results = await modelGen.EvaluateAsync(image);
        }

Extrair e exibir os resultados

Agora, você precisará extrair a saída do modelo e exibir o resultado certo, o que será feito com a implementação dos métodos extractResult e displayResult. Você precisará encontrar a probabilidade mais alta para retornar o rótulo correto.

  1. Adicione o método extractResult ao seu arquivo de código MainPage.xaml.cs dentro da classe 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. Adicione o método displayResult ao seu arquivo de código MainPage.xaml.cs dentro da classe MainPage.
        private async Task displayResult() 
        {
            displayOutput.Text = label; 
        }

Pronto! Você criou com êxito o aplicativo Windows Machine Learning com uma GUI básica para testar o modelo de classificação. A próxima etapa é iniciar o aplicativo e executá-lo localmente em seu dispositivo Windows.

Iniciar o aplicativo

Depois de concluir a interface do aplicativo, adicionar o modelo e gerar o código do Windows ML, você poderá testar o aplicativo.

Habilite o modo de desenvolvedor e teste seu aplicativo com o Visual Studio. Verifique se os menus suspensos na barra de ferramentas superior estão definidos como Debug. Altere a Plataforma de Solução para x64 para executar o projeto no computador local se seu dispositivo for de 64 bits ou x86 se for de 32 bits.

Nosso modelo foi treinado para classificar as seguintes imagens: avião, carro, ave, gato, veado, cão, sapo, cavalo, navio, caminhão. Para testar o aplicativo, você usará a imagem do carro de Lego criada para este projeto. Vamos ver como o aplicativo classifica o conteúdo da imagem.

Image for application testing

  1. Salve essa imagem em seu dispositivo local para testar o aplicativo. Altere o formato da imagem para .jpg, se necessário. Você também pode adicionar outra imagem relevante do dispositivo local em um formato .jpg ou .png.

  2. Para executar o projeto, selecione o botão Start Debugging na barra de ferramentas ou pressione F5.

  3. Quando o aplicativo for iniciado, pressione Escolher imagem e selecione a imagem do dispositivo local.

Application interface

O resultado será exibido na tela imediatamente. Como você pode ver, nosso aplicativo do Windows ML classificou com êxito a imagem como um carro.

Successful classification in your app

Resumo

Você acabou de criar seu primeiro aplicativo do Windows Machine Learning, da criação do modelo até a execução bem-sucedida.

Recursos adicionais

Para saber mais sobre os tópicos mencionados neste tutorial, visite os seguintes recursos: