Partager via


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

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

Charger -> Lier -> Évaluer

Nous allons créer une version légèrement 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 ce qu’il sera comme lorsque vous avez terminé.

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

Dans ce tutoriel, vous apprendrez comment le faire :

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

Conditions préalables

Créer le projet

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

  1. Sélectionnez Nouveau projet de fichier >> pour ouvrir la fenêtre Nouveau projet.
  2. Dans le volet gauche, sélectionnez Installé > Visual C++ > Windows Desktop, puis, au milieu, sélectionnez Application console Windows (C++/WinRT).
  3. Donnez un nom et un emplacement à votre projet, puis cliquez sur OK.
  4. Dans la fenêtre Nouveau projet de plateforme Windows universelle , définissez les versions cibles et minimales à la fois sur la build 17763 ou 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 du texte « Hello World ». Appuyez sur n’importe quelle touche pour la fermer.

Charger le modèle

Ensuite, nous allons 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 suivantes (celles-ci include 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 suivantes using :

    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 using instructions :

    // 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 de transfert 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 dans la main fonction après init_apartment).

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

  7. Copiez le chemin du fichier et affectez-le à votre modelPath variable 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 d’é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 LoadModel méthode. Ajoutez la méthode suivante après la main méthode. Cette méthode charge le modèle et génère le temps nécessaire :

    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. Enfin, appelez cette méthode à partir de la main méthode :

    LoadModel();
    
  10. Exécutez votre programme sans débogage. Vous devriez voir que votre modèle se charge correctement !

Charger l’image

Ensuite, nous allons 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 celui-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 main méthode :

    imageFrame = LoadImageFile(imagePath);
    
  3. Recherchez le dossier multimédia dans votre clone local du dépôt Windows-Machine-Learning . Elle doit se trouver sur \Windows-Machine-Learning\SharedContent\media.

  4. Choisissez l’une des images de ce dossier et attribuez son chemin d’accès au fichier à la imagePath variable dans laquelle 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 avec 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. Vous devriez voir l’image chargée avec succès !

Lier l’entrée et la sortie

Ensuite, 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 liez ensuite les entrées et sorties aux variables que nous avons créées à l’aide de leurs noms. Nous savons à l’avance que la fonctionnalité d’entrée est nommée « data_0 » et que la fonctionnalité de sortie est nommée « 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 depuis la méthode main.

    BindModel();
    
  3. Exécutez le programme sans débogage. Les entrées et sorties du modèle doivent être liées avec succès. On y est presque!

Évaluer le modèle

Nous sommes maintenant à la dernière étape du diagramme au début de ce didacticiel, É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 quelque chose que nous pourrions utiliser ultérieurement pour faire correspondre un appel d’évaluation particulier aux 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. Maintenant, implémentons PrintResults. Cette méthode obtient les trois principales probabilités pour l’objet qui pourrait se trouver dans l’image et 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 et 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 référentiel Windows-Machine-Learning . Il doit être dans \Windows-Machine-Learning\Samples\SqueezeNetObjectDetection\Desktop\cpp.

  5. Affectez ce chemin d’accès de fichier à la labelsFilePath variable 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 devrait maintenant reconnaître correctement ce qui est 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

Hourra, vous avez réussi à faire fonctionner la détection d'objets dans une application de bureau C++ ! Ensuite, 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 l’exemple sur GitHub. Vous pouvez également essayer d’exécuter l’évaluation sur un autre appareil, comme le GPU, pour voir comment 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 ou répondre à des questions techniques sur Windows ML, utilisez la balise Windows-Machine Learning sur Stack Overflow.
  • Pour signaler un bogue, veuillez signaler un problème sur notre GitHub.