Condividi tramite


Esercitazione: Creare un'applicazione Desktop di Windows Machine Learning (C++)

Le API di Windows Machine Learning possono essere sfruttate per interagire facilmente con modelli di Machine Learning nell'ambito delle applicazioni desktop (Win32) C++. Usando i tre passaggi di caricamento, associazione e valutazione, l'applicazione può sfruttare le potenzialità di Machine Learning.

Load -> Bind -> Evaluate

Verrà creata una versione alquanto semplificata dell'esempio SqueezeNet Object Detection, disponibile su GitHub. Puoi scaricare l'esempio completo se vuoi vedere quale sarà il risultato al termine dell'operazione.

Per accedere alle API WinML, verrà usato C++/WinRT. Per altre informazioni, vedi C++/WinRT.

Questa esercitazione illustra come:

  • Caricare un modello di Machine Learning
  • Caricare un'immagine come VideoFrame
  • Associare input e output del modello
  • Valutare il modello e stampare risultati significativi

Prerequisiti

Creare il progetto

In primo luogo, verrà creato il progetto in Visual Studio:

  1. Selezionare File Nuovo progetto per aprire la finestra Nuovo progetto.> >
  2. Nel riquadro sinistro selezionare Installato > Visual C++ > Windows Desktop e al centro selezionare Applicazione console windows (C++/WinRT).
  3. Specifica un nome e un percorso per il progetto in Nome e Percorso e quindi fai clic su OK.
  4. Nella finestra Nuovo progetto della piattaforma UWP (Universal Windows Platform) imposta Destinazione e Versione minima sulla build 17763 o versioni successive e quindi fai clic su OK.
  5. Verifica che i menu a discesa nella barra degli strumenti superiore siano impostati su Debug e x64 o x86 a seconda dell'architettura del computer.
  6. Premi CTRL + F5 per eseguire il programma senza debug. Dovrebbe aprirsi un terminale con un testo "Hello World". Premi un tasto qualsiasi per chiuderlo.

Caricare il modello

A questo punto, verrà caricato il modello ONNX nel programma tramite LearningModel.LoadFromFilePath:

  1. In pch.h (nella cartella File di intestazione) aggiungi le seguenti istruzioni include (che consentono di accedere a tutte le API necessarie):

    #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. In main.cpp (nella cartella File del codice sorgente) aggiungi le seguenti istruzioni 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. Aggiungi le dichiarazioni di variabili seguenti dopo l'istruzione 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. Aggiungi le dichiarazioni con prototipo seguenti dopo le variabili globali:

    // Forward declarations
    void LoadModel();
    VideoFrame LoadImageFile(hstring filePath);
    void BindModel();
    void EvaluateModel();
    void PrintResults(IVectorView<float> results);
    void LoadLabels();
    
  5. In main.cpp rimuovi il codice "Hello World" (tutto il contenuto della funzione main dopo init_apartment).

  6. Trova il file SqueezeNet.onnx nel clone locale del repository Windows-Machine-Learning. Dovrebbe trovarsi in \Windows-Machine-Learning\SharedContent\models.

  7. Copia il percorso del file e assegnalo alla variabile modelPath dove è stata effettuata la definizione all'inizio. Ricordati di anteporre una L alla stringa per impostarla come stringa di caratteri wide in modo che funzioni correttamente con hstring e di applicare una sequenza di escape alle barre rovesciate (\) usando una barra rovesciata aggiuntiva. Ad esempio:

    hstring modelPath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\models\\SqueezeNet.onnx";
    
  8. Prima di tutto verrà implementato il metodo LoadModel. Dopo il metodo main, aggiungi il metodo seguente. Questo metodo carica il modello e restituisce il tempo impiegato:

    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. Chiama infine questo metodo dal metodo main:

    LoadModel();
    
  10. Esegui il programma senza debug. Noterai che il modello si carica correttamente.

Caricare l'immagine

Verrà quindi caricato nel programma il file di immagine:

  1. Aggiungere il metodo seguente. Questo metodo caricherà l'immagine dal percorso specificato e creerà un oggetto VideoFrame sulla base di tale immagine:

    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. Aggiungi una chiamata a questo metodo nel metodo main:

    imageFrame = LoadImageFile(imagePath);
    
  3. Trova la cartella media nel clone locale del repository Windows-Machine-Learning. Dovrebbe trovarsi in \Windows-Machine-Learning\SharedContent\media.

  4. Scegli una delle immagini nella cartella e assegna il percorso del file corrispondente alla variabile imagePath dove è stata effettuata la definizione all'inizio. Ricordati di anteporre una L per impostarla come stringa di caratteri wide e di applicare una sequenza di escape alle barre rovesciate usando un'altra barra rovesciata. Ad esempio:

    hstring imagePath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\media\\kitten_224.png";
    
  5. Esegui il programma senza debug. Noterai che l'immagine si è caricata correttamente.

Associare l'input e l'output

Verrà ora creata una sessione basata sul modello e verranno associati l'input e l'output della sessione usando LearningModelBinding.Bind. Per altre informazioni sull'associazione, vedi Associare un modello.

  1. Implementa il metodo BindModel. Vengono create una sessione basata sul modello e sul dispositivo e un'associazione basata su tale sessione. Vengono quindi associati gli input e gli output alle variabili create usando i rispettivi nomi. È già noto che la caratteristica di input è denominata "data_0" e che quella di output è denominata "softmaxout_1". Puoi visualizzare queste proprietà per qualsiasi modello aprendole in Netron, uno strumento di visualizzazione dei modelli online.

    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. Aggiungi una chiamata a BindModel dal metodo main:

    BindModel();
    
  3. Esegui il programma senza debug. Gli input e gli output del modello dovrebbero essere associati correttamente. La procedura è quasi completata.

Valutare il modello

Questo è l'ultimo passaggio indicato nel diagramma all'inizio di questa esercitazione, ovvero la valutazione. Il modello verrà valutato usando LearningModelSession.Evaluate:

  1. Implementa il metodo EvaluateModel. Questo metodo accetta la sessione e la valuta usando l'associazione e un ID di correlazione. L'ID di correlazione è un elemento che potrebbe essere usato in un secondo momento per trovare la corrispondenza con una particolare chiamata di valutazione ai risultati di output. Anche in questo caso, è noto in anticipo che il nome dell'output è "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. A questo punto è possibile implementare PrintResults. Questo metodo ottiene le prime tre probabilità per l'oggetto che potrebbe trovarsi nell'immagine e le stampa:

    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. È anche necessario implementare LoadLabels. Questo metodo apre il file delle etichette che contiene tutti i diversi oggetti che il modello è in grado di riconoscere e lo analizza:

    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. Individua il file Labels.txt nel clone locale del repository Windows-Machine-Learning. Dovrebbe trovarsi in \Windows-Machine-Learning\Samples\SqueezeNetObjectDetection\Desktop\cpp.

  5. Assegna questo percorso di file alla variabile labelsFilePath dove è stata effettuata la definizione all'inizio. Assicurati di applicare una sequenza di escape alle barre rovesciate usando un'altra barra rovesciata. Ad esempio:

    string labelsFilePath = "C:\\Repos\\Windows-Machine-Learning\\Samples\\SqueezeNetObjectDetection\\Desktop\\cpp\\Labels.txt";
    
  6. Aggiungi una chiamata a EvaluateModel nel metodo main:

    EvaluateModel();
    
  7. Esegui il programma senza debug. Dovrebbe ora essere possibile riconoscere correttamente il contenuto dell'immagine. Di seguito è riportato un esempio del possibile output:

    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
    

Passaggi successivi

Disponi ora di un rilevamento di oggetti che funziona in un'applicazione desktop C++. Puoi quindi provare a usare gli argomenti della riga di comando per l'input dei file di modello e di immagine anziché impostarli come hardcoded, analogamente a quanto accade nell'esempio in GitHub. Puoi anche provare a eseguire la valutazione in un dispositivo diverso, ad esempio la GPU, per vedere in che modo cambiano le prestazioni.

Prova anche gli altri esempi su GitHub ed estendili in base alle tue esigenze.

Vedi anche

Nota

Per informazioni su Windows Machine Learning, usa le risorse seguenti:

  • Per porre domande tecniche o rispondere a domande tecniche su Windows Machine Learning, usa il tag windows-machine-learning in Stack Overflow.
  • Per segnalare un bug, registra il problema in GitHub.