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.
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
- Visual Studio 2019 (ou Visual Studio 2017, version 15.7.4 ou ultérieure)
- Windows 10, version 1809 ou ultérieure
- SDK Windows, build 17763 ou ultérieure
- Extension Visual Studio pour C++/WinRT
- Dans Visual Studio, sélectionnez Outils > Extensions et mises à jour.
- Sélectionnez En ligne dans le volet gauche et recherchez « WinRT » à l’aide de la zone de recherche située à droite.
- Sélectionnez C++/WinRT, cliquez sur Télécharger, puis fermez Visual Studio.
- Suivez les instructions d’installation, puis rouvrez Visual Studio.
- Dépôt Github Windows-Machine-Learning (vous pouvez le télécharger en tant que fichier ZIP ou le cloner sur votre ordinateur)
Créer le projet
Tout d’abord, nous allons créer le projet dans Visual Studio :
- Sélectionnez Fichier > Nouveau > Projet pour ouvrir la fenêtre Nouveau projet.
- Dans le volet gauche, sélectionnez Installé > Visual C++ > Windows Desktop puis, au centre, sélectionnez Application console Windows (C++/WinRT).
- Donnez à votre projet un Nom et un Emplacement, puis cliquez sur OK.
- 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.
- 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.
- 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 :
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>
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;
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;
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();
Dans main.cpp, supprimez le code « Hello World » (tout ce qui se trouve dans la fonction
main
aprèsinit_apartment
).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.
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’unL
pour en faire une chaîne de caractères larges, afin qu’elle fonctionne correctement avechstring
, 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";
Tout d’abord, nous allons implémenter la méthode
LoadModel
. Ajoutez le code suivant après la méthodemain
. 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); }
Pour finir, appelez cette méthode à partir de la méthode
main
:LoadModel();
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 :
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; }
Ajoutez un appel à cette méthode dans la méthode
main
:imageFrame = LoadImageFile(imagePath);
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.
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’unL
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";
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.
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); }
Ajoutez un appel à
BindModel
à partir de la méthodemain
:BindModel();
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 :
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); }
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]); } }
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; } }
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.
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";
Ajoutez un appel à
EvaluateModel
dans la méthodemain
:EvaluateModel();
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.