Tutoriel : Créer une application de bureau Windows Machine Learning (C++)

Les API Windows ML peuvent être utilisées pour interagir facilement avec des modèles Machine Learning dans des applications de bureau C++ (Win32). À l’aide des trois étapes de chargement, liaison évaluation, votre application peut tirer parti de la puissance du Machine Learning.

Charger - Lier ->> Évaluer

Nous allons créer une version simplifiée de l’exemple de détection d’objets SqueezeNet, disponible sur GitHub. Vous pouvez télécharger l’exemple complet si vous souhaitez voir à quoi il ressemblera une fois que vous aurez terminé.

Nous allons utiliser C++/WinRT pour accéder aux API WinML. Pour plus d’informations, consultez C++/WinRT.

Dans ce tutoriel, vous allez découvrir comment :

  • Charger un modèle Machine Learning.
  • Charger une image en tant que VideoFrame.
  • Lier les entrées et les sorties du modèle.
  • Évaluer le modèle et imprimer des résultats significatifs.

Prérequis

Créer le projet

Tout d’abord, nous allons créer le projet dans Visual Studio :

  1. Sélectionnez Fichier > Nouveau > Projet pour ouvrir la fenêtre Nouveau projet.
  2. Dans le volet gauche, sélectionnez Installé > Visual C++ > Windows Desktop puis, au centre, sélectionnez Application console Windows (C++/WinRT).
  3. Donnez à votre projet un Nom et un Emplacement, puis cliquez sur OK.
  4. Dans la fenêtre Nouveau projet de plateforme Windows universelle, définissez la Cible et les Versions minimales sur la build 17763 ou version ultérieure, puis cliquez sur OK.
  5. Vérifiez que les menus déroulants de la barre d’outils supérieure sont définis sur Débogage et x64 ou x86 en fonction de l’architecture de votre ordinateur.
  6. Appuyez sur Ctrl+F5 pour exécuter le programme sans débogage. Un terminal doit s’ouvrir avec le texte « Hello World ». Appuyez sur n’importe quelle touche pour le fermer.

Charger le modèle

Nous allons maintenant charger le modèle ONNX dans notre programme à l’aide de LearningModel.LoadFromFilePath :

  1. Dans pch.h (dans le dossier Fichiers d’en-tête), ajoutez les instructions include suivantes (elles nous donnent accès à toutes les API dont nous aurons besoin) :

    #include <winrt/Windows.AI.MachineLearning.h>
    #include <winrt/Windows.Foundation.Collections.h>
    #include <winrt/Windows.Graphics.Imaging.h>
    #include <winrt/Windows.Media.h>
    #include <winrt/Windows.Storage.h>
    
    #include <string>
    #include <fstream>
    
    #include <Windows.h>
    
  2. Dans main.cpp (dans le dossier Fichiers sources), ajoutez les instructions using suivantes :

    using namespace Windows::AI::MachineLearning;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::Graphics::Imaging;
    using namespace Windows::Media;
    using namespace Windows::Storage;
    
    using namespace std;
    
  3. Ajoutez les déclarations de variables suivantes après les instructions using :

    // Global variables
    hstring modelPath;
    string deviceName = "default";
    hstring imagePath;
    LearningModel model = nullptr;
    LearningModelDeviceKind deviceKind = LearningModelDeviceKind::Default;
    LearningModelSession session = nullptr;
    LearningModelBinding binding = nullptr;
    VideoFrame imageFrame = nullptr;
    string labelsFilePath;
    vector<string> labels;
    
  4. Ajoutez les déclarations anticipées suivantes après vos variables globales :

    // Forward declarations
    void LoadModel();
    VideoFrame LoadImageFile(hstring filePath);
    void BindModel();
    void EvaluateModel();
    void PrintResults(IVectorView<float> results);
    void LoadLabels();
    
  5. Dans main.cpp, supprimez le code « Hello World » (tout ce qui se trouve dans la fonction main après init_apartment).

  6. Recherchez le fichier SqueezeNet.onnx dans votre clone local du dépôt Windows-Machine-Learning. Il doit se trouver dans \Windows-Machine-Learning\SharedContent\models.

  7. Copiez le chemin du fichier et affectez-le à votre variable modelPath où nous l’avons définie en haut. N’oubliez pas de faire précéder la chaîne d’un L pour en faire une chaîne de caractères larges, afin qu’elle fonctionne correctement avec hstring, et de faire échapper les barres obliques inverses (\) avec une barre oblique inverse supplémentaire. Par exemple :

    hstring modelPath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\models\\SqueezeNet.onnx";
    
  8. Tout d’abord, nous allons implémenter la méthode LoadModel. Ajoutez le code suivant après la méthode main. Cette méthode charge le modèle et indique combien de temps il a fallu :

    void LoadModel()
    {
         // load the model
         printf("Loading modelfile '%ws' on the '%s' device\n", modelPath.c_str(), deviceName.c_str());
         DWORD ticks = GetTickCount();
         model = LearningModel::LoadFromFilePath(modelPath);
         ticks = GetTickCount() - ticks;
         printf("model file loaded in %d ticks\n", ticks);
    }
    
  9. Pour finir, appelez cette méthode à partir de la méthode main :

    LoadModel();
    
  10. Exécutez votre programme sans débogage. Votre modèle doit se charger correctement.

Charger l’image

Nous allons maintenant charger le fichier image dans notre programme :

  1. Ajoutez la méthode suivante. Cette méthode charge l’image à partir du chemin donné et crée un VideoFrame à partir de celle-ci :

    VideoFrame LoadImageFile(hstring filePath)
    {
        printf("Loading the image...\n");
        DWORD ticks = GetTickCount();
        VideoFrame inputImage = nullptr;
    
        try
        {
            // open the file
            StorageFile file = StorageFile::GetFileFromPathAsync(filePath).get();
            // get a stream on it
            auto stream = file.OpenAsync(FileAccessMode::Read).get();
            // Create the decoder from the stream
            BitmapDecoder decoder = BitmapDecoder::CreateAsync(stream).get();
            // get the bitmap
            SoftwareBitmap softwareBitmap = decoder.GetSoftwareBitmapAsync().get();
            // load a videoframe from it
            inputImage = VideoFrame::CreateWithSoftwareBitmap(softwareBitmap);
        }
        catch (...)
        {
            printf("failed to load the image file, make sure you are using fully qualified paths\r\n");
            exit(EXIT_FAILURE);
        }
    
        ticks = GetTickCount() - ticks;
        printf("image file loaded in %d ticks\n", ticks);
        // all done
        return inputImage;
    }
    
  2. Ajoutez un appel à cette méthode dans la méthode main :

    imageFrame = LoadImageFile(imagePath);
    
  3. Recherchez le dossier media dans votre clone local du dépôt Windows-Machine-Learning. Il doit se trouver à l’emplacement \Windows-Machine-Learning\SharedContent\media.

  4. Choisissez l’une des images dans ce dossier et attribuez son chemin de fichier à la variable imagePath où nous l’avons définie en haut. N’oubliez pas de la faire précéder d’un L pour en faire une chaîne de caractères larges, et d’échapper les barres obliques inverses par une autre barre oblique inverse. Par exemple :

    hstring imagePath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\media\\kitten_224.png";
    
  5. Exécutez le programme sans débogage. L’image doit se charger correctement.

Lier l’entrée et la sortie

Maintenant, nous allons créer une session basée sur le modèle et lier l’entrée et la sortie de la session à l’aide de LearningModelBinding.Bind. Pour plus d’informations sur la liaison, consultez Lier un modèle.

  1. Implémentez la méthode BindModel. Cela crée une session basée sur le modèle et l’appareil, ainsi qu’une liaison basée sur cette session. Nous lions ensuite les entrées et les sorties aux variables que nous avons créées à l’aide de leurs noms. Nous savons à l’avance que la caractéristique d’entrée se nomme « data_0 » et que la caractéristique de sortie se nomme « softmaxout_1 ». Vous pouvez voir ces propriétés pour n’importe quel modèle en les ouvrant dans Netron, un outil de visualisation de modèle en ligne.

    void BindModel()
    {
        printf("Binding the model...\n");
        DWORD ticks = GetTickCount();
    
        // now create a session and binding
        session = LearningModelSession{ model, LearningModelDevice(deviceKind) };
        binding = LearningModelBinding{ session };
        // bind the intput image
        binding.Bind(L"data_0", ImageFeatureValue::CreateFromVideoFrame(imageFrame));
        // bind the output
        vector<int64_t> shape({ 1, 1000, 1, 1 });
        binding.Bind(L"softmaxout_1", TensorFloat::Create(shape));
    
        ticks = GetTickCount() - ticks;
        printf("Model bound in %d ticks\n", ticks);
    }
    
  2. Ajoutez un appel à BindModel à partir de la méthode main :

    BindModel();
    
  3. Exécutez le programme sans débogage. Les entrées et les sorties du modèle doivent être liées correctement. Nous avons presque terminé.

Évaluer le modèle

Nous en sommes maintenant à la dernière étape du diagramme fourni au début de ce tutoriel, Évaluer. Nous allons évaluer le modèle à l’aide de LearningModelSession.Evaluate :

  1. Implémentez la méthode EvaluateModel. Cette méthode prend notre session et l’évalue à l’aide de notre liaison et d’un ID de corrélation. L’ID de corrélation est un nom que nous pourrions éventuellement utiliser ultérieurement pour établir une correspondance entre un appel d’évaluation particulier et les résultats de sortie. Là encore, nous savons à l’avance que le nom de la sortie est « softmaxout_1 ».

    void EvaluateModel()
    {
        // now run the model
        printf("Running the model...\n");
        DWORD ticks = GetTickCount();
    
        auto results = session.Evaluate(binding, L"RunId");
    
        ticks = GetTickCount() - ticks;
        printf("model run took %d ticks\n", ticks);
    
        // get the output
        auto resultTensor = results.Outputs().Lookup(L"softmaxout_1").as<TensorFloat>();
        auto resultVector = resultTensor.GetAsVectorView();
        PrintResults(resultVector);
    }
    
  2. Implémentons maintenant PrintResults. Cette méthode obtient les trois premières probabilités concernant l’objet susceptible de figurer dans l’image, puis les imprime :

    void PrintResults(IVectorView<float> results)
    {
        // load the labels
        LoadLabels();
        // Find the top 3 probabilities
        vector<float> topProbabilities(3);
        vector<int> topProbabilityLabelIndexes(3);
        // SqueezeNet returns a list of 1000 options, with probabilities for each, loop through all
        for (uint32_t i = 0; i < results.Size(); i++)
        {
            // is it one of the top 3?
            for (int j = 0; j < 3; j++)
            {
                if (results.GetAt(i) > topProbabilities[j])
                {
                    topProbabilityLabelIndexes[j] = i;
                    topProbabilities[j] = results.GetAt(i);
                    break;
                }
            }
        }
        // Display the result
        for (int i = 0; i < 3; i++)
        {
            printf("%s with confidence of %f\n", labels[topProbabilityLabelIndexes[i]].c_str(), topProbabilities[i]);
        }
    }
    
  3. Nous devons également implémenter LoadLabels. Cette méthode ouvre le fichier d’étiquettes qui contient tous les différents objets que le modèle peut reconnaître, puis l’analyse :

    void LoadLabels()
    {
        // Parse labels from labels file.  We know the file's entries are already sorted in order.
        ifstream labelFile{ labelsFilePath, ifstream::in };
        if (labelFile.fail())
        {
            printf("failed to load the %s file.  Make sure it exists in the same folder as the app\r\n", labelsFilePath.c_str());
            exit(EXIT_FAILURE);
        }
    
        std::string s;
        while (std::getline(labelFile, s, ','))
        {
            int labelValue = atoi(s.c_str());
            if (labelValue >= labels.size())
            {
                labels.resize(labelValue + 1);
            }
            std::getline(labelFile, s);
            labels[labelValue] = s;
        }
    }
    
  4. Recherchez le fichier Labels.txt dans votre clone local du dépôt Windows-Machine-Learning. Il doit se trouver dans \Windows-Machine-Learning\Samples\SqueezeNetObjectDetection\Desktop\cpp.

  5. Affectez ce chemin de fichier à la variable labelsFilePath où nous l’avons définie en haut. N’oubliez pas d’échapper les barres obliques inverses avec une autre barre oblique inverse. Par exemple :

    string labelsFilePath = "C:\\Repos\\Windows-Machine-Learning\\Samples\\SqueezeNetObjectDetection\\Desktop\\cpp\\Labels.txt";
    
  6. Ajoutez un appel à EvaluateModel dans la méthode main :

    EvaluateModel();
    
  7. Exécutez le programme sans débogage. Il doit maintenant reconnaître correctement ce qui figure dans l’image ! Voici un exemple de ce qu’il peut générer :

    Loading modelfile 'C:\Repos\Windows-Machine-Learning\SharedContent\models\SqueezeNet.onnx' on the 'default' device
    model file loaded in 250 ticks
    Loading the image...
    image file loaded in 78 ticks
    Binding the model...Model bound in 15 ticks
    Running the model...
    model run took 16 ticks
    tabby, tabby cat with confidence of 0.931461
    Egyptian cat with confidence of 0.065307
    Persian cat with confidence of 0.000193
    

Étapes suivantes

Et voilà, vous avez réussi à implémenter la détection d’objets dans une application de bureau C++. Maintenant, vous pouvez essayer d’utiliser des arguments de ligne de commande pour entrer les fichiers de modèle et d’image plutôt que de les coder en dur, comme le fait l’exemple sur GitHub. Vous pouvez également essayer d’exécuter l’évaluation sur un autre appareil, comme le GPU, pour voir en quoi les performances diffèrent.

Jouez avec les autres exemples sur GitHub et étendez-les comme vous le souhaitez.

Voir aussi

Remarque

Utilisez les ressources suivantes pour obtenir de l’aide sur Windows ML :

  • Pour poser des questions techniques ou apporter des réponses à des questions techniques sur Windows ML, veuillez utiliser le mot clé windows-machine-learning sur Stack Overflow.
  • Pour signaler un bogue, veuillez signaler un problème dans notre plateforme GitHub.