Freigeben über


Lernprogramm: Erstellen einer Windows Machine Learning-Desktopanwendung (C++)

Windows ML-APIs können verwendet werden, um problemlos mit Machine Learning-Modellen in C++-Desktopanwendungen (Win32) zu interagieren. Mit den drei Schritten zum Laden, Binden und Auswerten kann Ihre Anwendung von der Leistungsfähigkeit des maschinellen Lernens profitieren.

Laden - Binden -> Auswerten>

Wir werden eine etwas vereinfachte Version des SqueezeNet Object Detection-Beispiels erstellen, das auf GitHub verfügbar ist. Sie können das vollständige Beispiel herunterladen, wenn Sie sehen möchten, wie es aussieht, wenn Sie fertig sind.

Wir verwenden C++/WinRT, um auf die WinML-APIs zuzugreifen. Weitere Informationen finden Sie unter C++/WinRT .

In diesem Tutorial lernen Sie, wie Sie:

  • Laden eines Machine Learning-Modells
  • Laden eines Bilds als VideoFrame
  • Eingaben und Ausgaben des Modells binden
  • Auswerten des Modells und Drucken aussagekräftiger Ergebnisse

Voraussetzungen

  • Visual Studio 2019 (oder Visual Studio 2017, Version 15.7.4 oder höher)
  • Windows 10, Version 1809 oder höher
  • Windows SDK, Build 17763 oder höher
  • Visual Studio-Erweiterung für C++/WinRT
    1. Wählen Sie in Visual Studio Werkzeuge > Erweiterungen und Aktualisierungen aus.
    2. Wählen Sie im linken Bereich "Online " aus, und suchen Sie mithilfe des Suchfelds auf der rechten Seite nach "WinRT".
    3. Wählen Sie C++/WinRT aus, klicken Sie auf "Herunterladen", und schließen Sie Visual Studio.
    4. Folgen Sie den Installationsanweisungen, und öffnen Sie Visual Studio erneut.
  • Windows-Machine-Learning GitHub-Repository (Sie können es entweder als ZIP-Datei herunterladen oder auf Ihren Computer klonen)

Erstelle das Projekt

Zunächst erstellen wir das Projekt in Visual Studio:

  1. Wählen Sie „Datei > Neues > Projekt“, um das Fenster „Neues Projekt“ zu öffnen.
  2. Wählen Sie im linken Bereich "Installierter > Visual C++ > -Windows-Desktop" aus, und wählen Sie in der Mitte Die Windows-Konsolenanwendung (C++/WinRT) aus.
  3. Geben Sie Ihrem Projekt einen Namen und einen Speicherort, und klicken Sie dann auf "OK".
  4. Legen Sie im Fenster " Neues Projekt für die universelle Windows-Plattform " das Ziel und die Mindestversion auf Build 17763 oder höher fest, und klicken Sie auf "OK".
  5. Stellen Sie sicher, dass die Dropdownmenüs in der oberen Symbolleiste abhängig von der Architektur Ihres Computers auf "Debuggen" und entweder auf "x64 " oder "x86 " festgelegt sind.
  6. Drücken Sie STRG+F5 , um das Programm ohne Debugging auszuführen. Ein Terminal sollte mit einigen "Hello world"-Text geöffnet werden. Drücken Sie eine beliebige Taste, um sie zu schließen.

Laden des Modells

Als Nächstes laden wir das ONNX-Modell mithilfe von LearningModel.LoadFromFilePath in unser Programm:

  1. Fügen Sie in "pch.h " (im Ordner "Headerdateien ") die folgenden include Anweisungen hinzu (diese geben uns Zugriff auf alle APIs, die wir benötigen):

    #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. Fügen Sie in main.cpp (im Ordner " Quelldateien ") die folgenden using Anweisungen hinzu:

    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. Fügen Sie die folgenden Variablendeklarationen nach den using Anweisungen hinzu:

    // 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. Fügen Sie die folgenden Vorwärtsdeklarationen nach Ihren globalen Variablen hinzu:

    // Forward declarations
    void LoadModel();
    VideoFrame LoadImageFile(hstring filePath);
    void BindModel();
    void EvaluateModel();
    void PrintResults(IVectorView<float> results);
    void LoadLabels();
    
  5. Entfernen Sie in main.cpp den Code "Hello world" (alles in der main Funktion nach init_apartment).

  6. Suchen Sie die Datei "SqueezeNet.onnx " in Ihrem lokalen Klon des Windows-Machine-Learning-Repositorys . Sie sollte sich in \Windows-Machine-Learning\SharedContent\models befinden.

  7. Kopieren Sie den Dateipfad, und weisen Sie ihn Ihrer modelPath Variablen zu, wo sie oben definiert wurde. Denken Sie daran, der Zeichenfolge ein L als Präfix voranzustellen, um sie zu einer Breitzeichenfolge zu machen, damit sie mit hstring ordnungsgemäß funktioniert, und versehen Sie etwaige umgekehrte Schrägstriche (\) mit einem zusätzlichen umgekehrten Schrägstrich als Escapezeichen. Beispiel:

    hstring modelPath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\models\\SqueezeNet.onnx";
    
  8. Zunächst implementieren wir die LoadModel Methode. Fügen Sie die folgende Methode nach der main Methode hinzu. Diese Methode lädt das Modell und gibt aus, wie lange es dauerte:

    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. Rufen Sie schließlich diese Methode aus der main Methode auf:

    LoadModel();
    
  10. Führen Sie Ihr Programm ohne Debugging aus. Sie sollten sehen, dass das Modell erfolgreich geladen wird.

Laden des Bilds

Als Nächstes laden wir die Bilddatei in unser Programm:

  1. Fügen Sie die folgende Methode hinzu. Mit dieser Methode wird das Bild aus dem angegebenen Pfad geladen und ein VideoFrame daraus erstellt:

    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. Fügen Sie einen Aufruf dieser Methode in der main Methode hinzu.

    imageFrame = LoadImageFile(imagePath);
    
  3. Suchen Sie den Medienordner im lokalen Klon des Windows-Machine-Learning-Repositorys . Er sollte sich unter \Windows-Machine-Learning\SharedContent\media befinden.

  4. Wählen Sie ein Bild aus dem Ordner aus und weisen Sie seinen Dateipfad der Variablen imagePath zu, die wir oben definiert haben. Denken Sie daran, ihm ein L als Präfix voranzustellen, um die Zeichenfolge zu einer Breitzeichenfolge zu machen, und versehen Sie etwaige umgekehrte Schrägstriche mit einem zusätzlichen umgekehrten Schrägstrich als Escapezeichen. Beispiel:

    hstring imagePath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\media\\kitten_224.png";
    
  5. Führen Sie das Programm ohne Debugging aus. Das Bild sollte erfolgreich geladen werden!

Binden Sie die Eingabe und Ausgabe

Als Nächstes erstellen wir eine Sitzung basierend auf dem Modell und binden die Eingabe und Ausgabe aus der Sitzung mithilfe von LearningModelBinding.Bind. Weitere Informationen zur Bindung finden Sie unter Binden eines Modells.

  1. Implementieren Sie die BindModel-Methode. Dadurch wird eine Sitzung basierend auf dem Modell und dem Gerät und eine Bindung basierend auf dieser Sitzung erstellt. Anschließend binden wir die Eingaben und Ausgaben an Variablen, die wir mit ihren Namen erstellt haben. Wir wissen im Voraus, dass das Eingabefeature "data_0" heißt und das Ausgabefeature "softmaxout_1" heißt. Sie können diese Eigenschaften für jedes Modell anzeigen, indem Sie sie in Netron, einem Onlinemodellvisualisierungstool, öffnen.

    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. Fügen Sie einen Aufruf von BindModel aus der main-Methode hinzu:

    BindModel();
    
  3. Führen Sie das Programm ohne Debugging aus. Die Eingaben und Ausgaben des Modells sollten erfolgreich gebunden werden. Wir sind fast da!

Auswerten des Modells

Wir befinden uns jetzt im letzten Schritt der Abbildung am Anfang dieses Tutorials: Auswerten. Wir bewerten das Modell mithilfe von LearningModelSession.Evaluate:

  1. Implementieren Sie die EvaluateModel-Methode. Diese Methode verwendet unsere Sitzung und wertet sie anhand unserer Bindung und einer Korrelations-ID aus. Die Korrelations-ID könnten wir möglicherweise später verwenden, um einen bestimmten Auswertungsaufruf mit den Ausgabeergebnissen abzugleichen. Auch hier wissen wir vorab, dass der Name der Ausgabe "softmaxout_1" lautet.

    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. Lasst uns jetzt PrintResults implementieren. Diese Methode ruft die drei wichtigsten Wahrscheinlichkeiten für das Objekt im Bild ab und druckt sie:

    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. Wir müssen auch LoadLabels implementieren. Diese Methode öffnet die Bezeichnungsdatei, die alle verschiedenen Objekte enthält, die vom Modell erkannt werden können, und analysiert sie:

    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. Suchen Sie die Labels.txt Datei im lokalen Klon des Windows-Machine-Learning-Repositorys . Er sollte sich in \Windows-Machine-Learning\Samples\SqueezeNetObjectDetection\Desktop\cpp befinden.

  5. Weisen Sie diesen Dateipfad der Variablen labelsFilePath zu, die wir oben definiert haben. Stellen Sie sicher, dass alle umgekehrten Schrägstriche mit einem weiteren umgekehrten Schrägstrich als Escapezeichen versehen werden. Beispiel:

    string labelsFilePath = "C:\\Repos\\Windows-Machine-Learning\\Samples\\SqueezeNetObjectDetection\\Desktop\\cpp\\Labels.txt";
    
  6. Fügen Sie einen Aufruf von EvaluateModel in der main-Methode hinzu:

    EvaluateModel();
    
  7. Führen Sie das Programm ohne Debugging aus. Es sollte nun richtig erkennen, was im Bild ist! Hier ist ein Beispiel für das, was möglicherweise ausgegeben wird:

    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
    

Nächste Schritte

Hurra, die Objekterkennung funktioniert in Ihrer C++-Desktopanwendung! Als Nächstes können Sie die Befehlszeilenargumente verwenden, um die Modell- und Bilddateien einzugeben, anstatt sie zu hartcodieren, ähnlich wie das Beispiel auf GitHub. Sie können auch versuchen, die Auswertung auf einem anderen Gerät wie der GPU auszuführen, um zu sehen, wie sich die Leistung unterscheidet.

Spielen Sie mit den anderen Beispielen auf GitHub herum, und erweitern Sie sie, wie Sie möchten!

Siehe auch

Hinweis

Verwenden Sie die folgenden Ressourcen für Hilfe zu Windows ML:

  • Um technische Fragen zu Windows ML zu stellen oder zu beantworten, verwenden Sie bitte das Windows-Machine-Learning-Tag auf Stack Overflow.
  • Um einen Fehler zu melden, reichen Sie bitte ein Issue auf unserem GitHub ein.