Déployer un modèle dans une application Windows avec l’API Windows ML

Dans la partie précédente de ce tutoriel, vous avez appris à créer et à exporter un modèle au format ONNX. À présent, nous allons vous montrer comment incorporer votre modèle exporté dans une application Windows et l’exécuter localement sur un appareil en appelant des API WinML.

Lorsque vous aurez terminé, vous disposerez d’une application de classification d’images fonctionnelle.

À propos de l’exemple d’application

Dans cette étape du tutoriel, vous allez créer une application qui peut classer les images à l’aide de votre modèle ML. Son interface utilisateur basique vous permet de sélectionner une image à partir de votre appareil local et utilise le modèle ONNX de classification que vous avez créé et entraîné dans la partie précédente pour le classifier. Les balises renvoyées par le modèle sont ensuite affichées en regard de l’image.

Ici, nous allons vous guider tout au long de ce processus.

Remarque

Si vous choisissez d’utiliser l’exemple de code prédéfini, vous pouvez cloner le fichier solution. Clonez le dépôt, accédez à cet exemple et ouvrez le fichier classifierPyTorch.sln avec Visual Studio. Passez à la partie Lancer l’application de cette page pour la voir en cours d’utilisation.

Ci-dessous, nous allons vous guider dans la création de votre application et l’ajout de code Windows ML.

Créer une application de plateforme Windows universelle Windows ML (C#)

Pour créer une application Windows ML fonctionnelle, vous devez effectuer les opérations suivantes :

  • Charger un modèle Machine Learning.
  • Charger une image au format requis.
  • Lier les entrées et les sorties du modèle.
  • Évaluer le modèle et afficher des résultats significatifs.

Vous devez également créer une interface utilisateur basique, car il est difficile de créer une application satisfaisante basée sur l’image dans la ligne de commande.

Ouvrez un nouveau projet dans Visual Studio.

  1. C’est parti. Ouvrez Visual Studio et choisissez Créer un nouveau projet.

Create new Visual Studio project

  1. Dans la barre de recherche, tapez UWP, puis sélectionnez Blank APP (Universal Windows). Cela ouvre un projet C# pour une application Universal Windows Platform (UWP) avec une disposition ou des contrôles prédéfinis. Sélectionnez next pour ouvrir une fenêtre de configuration pour le projet.

Create new UWP app

  1. Dans la fenêtre de configuration, suivez les étapes ci-dessous :
  • Nommez votre projet. Ici, nous l’appelons classifierPyTorch.
  • Choisissez l’emplacement du projet.
  • Si vous utilisez VS 2019, vérifiez que la case Create directory for solution est cochée.
  • Si vous utilisez VS2017, vérifiez que la case Place solution and project in the same directory n’est pas cochée.

New UWP app setup

Appuyez sur create pour créer votre projet. La fenêtre version cible minimale peut s’afficher. Assurez-vous que la version minimale est définie sur Windows 10 version 1809 (10.0, build 17763) ou ultérieure.

  1. Une fois le projet créé, accédez au dossier du projet, ouvrez le dossier des ressources[….\classifierPyTorch \Assets], puis copiez votre fichier ImageClassifier.onnx à cet emplacement.

Explorer la solution de projet

Explorons votre solution de projet.

Visual Studio a créé automatiquement plusieurs fichiers cs-code à l’intérieur de l’Explorateur de solutions. MainPage.xaml contient le code XAML pour votre interface utilisateur graphique et MainPage.xaml.cs contient le code votre application, également appelé code-behind. Si vous avez déjà créé une application UWP, ces fichiers doivent vous être familiers.

UWP app solution

Créer l’interface graphique utilisateur de l’application

Tout d’abord, nous allons créer une interface utilisateur graphique simple pour votre application.

  1. Double-cliquez sur le fichier de code MainPage.xaml. Dans votre application vide, le modèle XAML de l’interface utilisateur graphique de votre application est vide. Nous devons donc ajouter des fonctionnalités d’interface utilisateur.

  2. Ajoutez le code ci-dessous à MainPage.xaml, en remplaçant les balises <Grid> et </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> 

Ajouter le modèle au projet à l’aide de Windows ML Code Generator (mlgen)

Windows Machine Learning Code Generator, ou mlgen, est une extension Visual Studio pour vous aider à utiliser les API WinML sur les applications UWP. Il génère le code du modèle lorsque vous ajoutez un fichier ONNX entraîné dans le projet UWP.

Le générateur de code mlgen de Windows Machine Learning crée une interface (pour C#, C++/WinRT et C++/CX) avec des classes wrapper qui appellent l’API Windows ML à votre place. Cela vous permet de charger, de lier et d’évaluer aisément un modèle dans votre projet. Nous allons l’utiliser dans ce tutoriel pour gérer plusieurs de ces fonctions à notre place.

Le générateur de code est disponible pour Visual Studio 2017 et versions ultérieures. Nous vous recommandons d’utiliser Visual Studio. Notez que, dans Windows 10 version 1903 et ultérieures, mlgen n’est plus inclus dans le SDK Windows 10. Vous devez donc télécharger et installer l’extension. Si vous avez suivi ce tutoriel depuis l’introduction, vous l’avez déjà géré, mais si ce n’est pas le cas, vous devez le télécharger pour Visual Studio 2019 ou pour Visual Studio 2017.

Remarque

Pour en savoir plus sur mlgen, consultez la documentation de mlgen.

  1. Si ce n’est déjà fait, installez mlgen.

  2. Cliquez avec le bouton droit sur le dossier Assets dans l’Explorateur de solutions de Visual Studio, puis sélectionnez Add > Existing Item.

  3. Accédez au dossier des ressources dans classifierPyTorch [….\classifierPyTorch \Assets], recherchez le modèle ONNX que vous avez copié précédemment, puis sélectionnez add.

  4. Une fois que vous avez ajouté un modèle ONNX au dossier des ressources dans l’Explorateur de solutions dans Visual Studio, le projet doit comporter deux nouveaux fichiers :

  • ImageClassifier.onnx : il s’agit de votre modèle au format ONNX.
  • ImageClassifier.cs : fichier de code WinML généré automatiquement.

ONNX files in your UWP app solution

  1. Pour vous assurer que le modèle est généré quand vous compilez l’application, sélectionnez le fichier ImageClassifier.onnx et choisissez Properties. Pour Build Action, sélectionnez Content.

Code du fichier ONNX

À présent, nous allons explorer un code qui vient d’être généré dans le fichier ImageClassifier.cs.

Le code généré comprend trois classes :

  • ImageClassifierModel : cette classe comprend deux méthodes d’instanciation de modèle et d’évaluation de modèle. Elle peut nous aider à créer la représentation du modèle Machine Learning, à créer une session sur l’appareil par défaut du système, à associer les entrées et sorties spécifiques au modèle et à évaluer le modèle de manière asynchrone.
  • ImageClassifierInput : cette classe initialise les types d’entrée attendus par le modèle. L’entrée du modèle dépend des spécifications de celui-ci pour les données d’entrée.
  • ImageClassifierOutput : cette classe initialise les types de sortie qui sont générés par le modèle. La sortie du modèle dépend de la façon dont elle est définie par le modèle.

Dans ce tutoriel, nous ne souhaitons pas traiter de la tenseurisation. Nous apportons une légère modification à la classe ImageClassifierInput, pour modifier le type de données d’entrée et nous faciliter la vie.

  1. Apportez les modifications suivantes dans le fichier ImageClassifier.cs :

Modifiez la variable input en remplaçant TensorFloat par ImageFeatureValue.

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

Charger le modèle

  1. Double-cliquez sur le fichier MainPage.xaml.cs pour ouvrir le code-behind de l’application.

  2. Remplacez les instructions « using » par les instructions suivantes pour accéder à toutes les API dont vous avez besoin :

// 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. Ajoutez les déclarations de variables suivantes à l’intérieur de votre classe MainPage, au-dessus de la fonction 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
        } 

À présent, nous allons implémenter la méthode LoadModel. La méthode accède au modèle ONNX et le stocke dans la mémoire. Ensuite, vous allez utiliser la méthode CreateFromStreamAsync pour instancier le modèle en tant qu’objet LearningModel. La classe LearningModel représente un modèle Machine Learning entraîné. Une fois instancié, le LearningModel est l’objet initial que vous utilisez pour interagir avec Windows ML.

Pour charger le modèle, vous pouvez utiliser plusieurs méthodes statiques dans la classe LearningModel. Dans ce cas, vous allez utiliser la méthode CreateFromStreamAsync.

Vous n’avez pas besoin d’implémenter la méthode CreateFromStreamAsync, car elle a été créée automatiquement avec mlgen. Vous pouvez passer en revue cette méthode en double-cliquant sur le fichier classifier.cs généré par mlgen.

Remarque

Pour en savoir plus sur la classe LearningModel, consultez la documentation de la classe LearningModel. Pour en savoir plus sur les autres méthodes de chargement du modèle, consultez la documentation Charger un modèle

  1. Ajoutez un appel à une méthode loadModel au constructeur de la classe principale.
        // The main page to initialize and execute the model.
        public MainPage()
        {
            this.InitializeComponent();
            loadModel();
        }
  1. Ajoutez l’implémentation de la méthode loadModel, à l’intérieur de cette 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);
        }

Charger l’image

  1. Nous devons définir un événement Click afin d’initier la séquence de quatre appels de méthode pour l’exécution du modèle (conversion, liaison et évaluation, extraction de sortie et affichage des résultats). Ajoutez la méthode suivante à votre fichier de code MainPage.xaml.cs à l’intérieur de la 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. À présent, nous allons implémenter la méthode getImage(). Cette méthode sélectionne un fichier image d’entrée et l’enregistre en mémoire. Ajoutez la méthode suivante à votre fichier de code MainPage.xaml.cs à l’intérieur de la 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;
        }

Ensuite, vous allez implémenter une méthode Bind() d’image pour récupérer la représentation du fichier au format BGRA8 bitmap. Mais tout d’abord, vous allez créer une classe d’assistance pour redimensionner l’image.

  1. Pour créer un fichier d’assistance, cliquez avec le bouton droit sur le nom de la solution (ClassifierPyTorch), puis choisissez Add a new item. Dans la fenêtre Ouvrir, sélectionnez Class et donnez-lui un nom. Ici, nous l’appelons Helper.

Add a Helper file

  1. Un nouveau fichier de classe va apparaître dans votre projet. Ouvrez cette classe, puis ajoutez le code suivant :
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; 
        } 
    } 
} 

À présent, nous allons convertir l’image au format approprié.

La classe ImageClassifierInput initialise les types d’entrée attendues par le modèle. Dans notre cas, nous avons configuré notre code pour attendre un ImageFeatureValue.

La classe ImageFeatureValue décrit les propriétés de l’image utilisée à transmettre dans un modèle. Pour créer un ImageFeatureValue, vous utilisez la méthode CreateFromVideoFrame. Pour plus de détails sur les raisons de cette situation et sur le fonctionnement de ces classes et méthodes, consultez la documentation de la classe ImageFeatureValue.

Remarque

Dans ce tutoriel, nous utilisons la classe ImageFeatureValue au lieu d’un tenseur. Si Windows ML ne prend pas en charge le format de couleur de votre modèle, ce ne sera pas une option. Pour obtenir un exemple d’utilisation des conversions d’images et de tenseurisation, consultez l’exemple de tenseurisation personnalisé.

  1. Ajoutez l’implémentation de la méthode convert() à votre fichier de code MainPage.xaml.cs à l’intérieur de la classe MainPage. La méthode de conversion fournit une représentation du fichier d’entrée dans un format 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) 
    {
    }
} 

Lier et évaluer le modèle

Ensuite, vous allez créer une session basée sur le modèle, lier l’entrée et la sortie de la session, et évaluer le modèle.

Créez une session pour lier le modèle :

Pour créer une session, utilisez la classe LearningModelSession. Cette classe est utilisée pour évaluer les modèles Machine Learning, et lie le modèle à un dispositif qui exécute et évalue ensuite le modèle. Vous pouvez sélectionner un appareil lorsque vous créez une session pour exécuter votre modèle sur un appareil spécifique de votre ordinateur. L’appareil par défaut est le processeur.

Remarque

Pour en savoir plus sur le choix d’un appareil, consultez la documentation Créer une session.

Liez les entrées et les sorties du modèle :

La classe LearningModelBinding permet de lier une entrée et une sortie. Un modèle Machine Learning a des caractéristiques d’entrée et de sortie, qui transmettent des informations dans et hors du modèle. Sachez que les fonctionnalités requises doivent être prises en charge par les API Windows ML. La classe LearningModelBinding est appliquée sur un LearningModelSession pour lier des valeurs aux fonctionnalités d’entrée et de sortie nommées.

L’implémentation de la liaison est générée automatiquement par mlgen ; vous n’avez donc pas à vous en occuper. La liaison est implémentée en appelant les méthodes prédéfinies de la classe LearningModelBinding. Dans notre cas, elle utilise la méthode Bind pour lier une valeur au type de fonctionnalité nommé.

Évaluez le modèle :

Après avoir créé une session pour lier le modèle et les valeurs délimitées aux entrées et sorties d’un modèle, vous pouvez évaluer les entrées du modèle et obtenir ses prédictions. Pour exécuter l’exécution du modèle, vous devez appeler l’une des méthodes d’évaluation prédéfinies sur LearningModelSession. Dans notre cas, nous allons utiliser la méthode EvaluateAsync.

À l’instar de CreateFromStreamAsync, la méthode EvaluateAsync a également été générée automatiquement par WinML Code Generator. Vous n’avez donc pas besoin d’implémenter cette méthode. Vous pouvez passer en revue cette méthode dans le fichier ImageClassifier.cs.

La méthode EvaluateAsync évalue de manière asynchrone le modèle Machine Learning à l’aide des valeurs de fonctionnalité déjà associées dans les liaisons. Elle crée une session avec LearningModelSession, lie l’entrée et la sortie à LearningModelBinding, exécute l’évaluation du modèle et obtient les fonctionnalités de sortie du modèle à l’aide de la classe LearningModelEvaluationResult.

Remarque

Pour en savoir plus sur les autres méthodes d’évaluation en vue d’exécuter le modèle, vérifiez les méthodes qui peuvent être implémentées sur le LearningModelSession en consultant la documentation de la classe LearningModelSession.

  1. Ajoutez la méthode suivante à votre fichier de code MainPage.xaml.cs à l’intérieur de la classe MainPage pour créer une session, lier et évaluer le modèle.
        // A method to evaluate the model
        private async Task evaluate()
        {
            results = await modelGen.EvaluateAsync(image);
        }

Extraire et afficher les résultats

Vous devez maintenant extraire la sortie du modèle et afficher le résultat correct, ce que vous allez effectuer en implémentant les méthodes extractResult et displayResult. Vous devez trouver la probabilité la plus élevée pour retourner l’étiquette adéquate.

  1. Ajoutez la méthode extractResult à votre fichier de code MainPage.xaml.cs dans la 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. Ajoutez la méthode displayResult à votre fichier de code MainPage.xaml.cs dans la classe MainPage.
        private async Task displayResult() 
        {
            displayOutput.Text = label; 
        }

Et voilà ! Vous avez créé avec succès l’application Windows Machine Learning avec une interface graphique utilisateur de base pour tester notre modèle de classification. L’étape suivante consiste à lancer l’application et à l’exécuter localement sur votre appareil Windows.

Lancer l’application

Une fois que vous avez terminé l’interface de l’application, ajouté le modèle et généré le code Windows ML, vous pouvez tester l’application.

Activez le mode développeur et testez votre application à partir de Visual Studio. Assurez-vous que les menus déroulants de la barre d’outils supérieure sont définis sur Debug. Remplacez la plateforme de solution par la plateforme x64 pour exécuter le projet sur votre ordinateur local s’il s’agit d’un appareil 64 bits, ou par la plateforme x86 s’il s’agit d’un appareil 32 bits.

Notre modèle a été entraîné pour classer les images suivantes : avion, voiture, oiseau, chat, cerf, chien, grenouille, cheval, navire, camion. Pour tester votre application, vous allez utiliser l’image de la voiture Lego créée pour ce projet. Voyons comment notre application classe le contenu de l’image.

Image for application testing

  1. Enregistrez cette image sur votre appareil local pour tester l’application. Remplacez le format d’image par .jpg si nécessaire. Vous pouvez également ajouter toute autre image adéquate à partir de votre appareil local dans le format .jpg ou le format .png.

  2. Pour exécuter le projet, sélectionnez le bouton Start Debugging dans la barre d’outils ou appuyez sur F5.

  3. Lorsque l’application démarre, appuyez sur Choisir l’image et sélectionnez l’image à partir de votre appareil local.

Application interface

Le résultat s’affiche immédiatement à l’écran. Comme vous pouvez le constater, notre application Windows ML a correctement classé l’image en tant qu’image représentant une voiture.

Successful classification in your app

Résumé

Vous venez de créer votre première application Windows Machine Learning, de la création du modèle à l’exécution réussie de l’application.

Ressources complémentaires

Pour en savoir plus sur les rubriques mentionnées dans ce tutoriel, consultez les ressources suivantes :