Udostępnij przez


Samouczek: Utwórz aplikację pulpitu Windows Machine Learning (C++)

Interfejsy API uczenia maszynowego systemu Windows mogą być używane do łatwej interakcji z modelami uczenia maszynowego w aplikacjach klasycznych języka C++ (Win32). Korzystając z trzech kroków ładowania, wiązania i oceniania, aplikacja może korzystać z możliwości uczenia maszynowego.

Załaduj -> Wiąż -> Oceń

Utworzymy nieco uproszczoną wersję próbki wykrywania obiektów SqueezeNet, która jest dostępna w witrynie GitHub. Kompletny przykład możesz pobrać, jeśli chcesz zobaczyć, jak będzie wyglądać po zakończeniu.

Użyjemy języka C++/WinRT, aby uzyskać dostęp do interfejsów API WinML. Aby uzyskać więcej informacji, zobacz C++/WinRT .

W tym poradniku dowiesz się, jak:

  • Ładowanie modelu uczenia maszynowego
  • Załaduj obraz jako VideoFrame
  • Wiązanie danych wejściowych i wyjściowych modelu
  • Ocenianie modelu i drukowanie znaczących wyników

Wymagania wstępne

Tworzenie projektu

Najpierw utworzymy projekt w programie Visual Studio:

  1. Wybierz pozycję Plik > Nowy > Projekt, aby otworzyć okno Nowy Projekt.
  2. W okienku po lewej stronie wybierz pozycję Zainstalowany > program Visual C++ > Windows Desktop, a w środku wybierz pozycję Aplikacja konsolowa systemu Windows (C++/WinRT).
  3. Nadaj projektowi nazwę i lokalizację, a następnie kliknij przycisk OK.
  4. W oknie Nowy projekt platformy uniwersalnej systemu Windows ustaw wartość docelową i minimalną wersje na kompilację 17763 lub nowszą, a następnie kliknij przycisk OK.
  5. Upewnij się, że menu rozwijane na górnym pasku narzędzi są ustawione na Debugowanie i x64 lub x86 w zależności od architektury komputera.
  6. Naciśnij Ctrl+F5 , aby uruchomić program bez debugowania. Terminal powinien zostać otwarty z tekstem "Hello world". Naciśnij dowolny klawisz, aby go zamknąć.

Ładowanie modelu

Następnie załadujemy model ONNX do naszego programu przy użyciu metody LearningModel.LoadFromFilePath:

  1. W pliku pch.h (w folderze Pliki nagłówka ) dodaj następujące include instrukcje (umożliwiają one dostęp do wszystkich potrzebnych interfejsów API):

    #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. W main.cpp (w folderze Pliki źródłowe ) dodaj następujące using instrukcje:

    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. Dodaj następujące deklaracje zmiennych po instrukcjach 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. Dodaj następujące deklaracje wyprzedzające po twoich zmiennych globalnych.

    // Forward declarations
    void LoadModel();
    VideoFrame LoadImageFile(hstring filePath);
    void BindModel();
    void EvaluateModel();
    void PrintResults(IVectorView<float> results);
    void LoadLabels();
    
  5. W main.cpp usuń kod "Hello world" (wszystko po main w funkcji init_apartment).

  6. Znajdź plik SqueezeNet.onnx w lokalnym klonie repozytorium Windows-Machine-Learning . Powinien on znajdować się w folderze \Windows-Machine-Learning\SharedContent\models.

  7. Skopiuj ścieżkę pliku i przypisz ją do modelPath zmiennej, w której ją zdefiniowaliśmy u góry. Pamiętaj, aby poprzedzić ciąg ciągiem L , aby był on ciągiem znaków szeroki, tak aby działał prawidłowo z elementem hstring, i aby uniknąć ukośników odwrotnych (\) z dodatkowym ukośnikiem odwrotnym. Przykład:

    hstring modelPath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\models\\SqueezeNet.onnx";
    
  8. Najpierw zaimplementujemy metodę LoadModel . Dodaj następującą metodę po metodzie main . Ta metoda ładuje model i podaje, jak długo to trwało:

    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. Na koniec wywołaj tę metodę z main metody :

    LoadModel();
    
  10. Uruchom program bez debugowania. Powinien zostać wyświetlony komunikat o pomyślnym załadowaniu modelu.

Ładowanie obrazu

Następnie załadujemy plik obrazu do naszego programu:

  1. Dodaj następującą metodę. Ta metoda załaduje obraz z podanej ścieżki i utworzy z niej ramkę wideo :

    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. Dodaj wywołanie do tej metody w metodzie main :

    imageFrame = LoadImageFile(imagePath);
    
  3. Znajdź folder multimedialny w lokalnym klonie repozytorium Windows-Machine-Learning . Powinien on znajdować się w folderze \Windows-Machine-Learning\SharedContent\media.

  4. Wybierz jeden z obrazów w tym folderze i przypisz jego ścieżkę pliku do imagePath zmiennej, w której ją zdefiniowaliśmy u góry. Pamiętaj, aby poprzedzić go znakiem , L aby był on ciągiem znaków szeroki i aby uniknąć ukośników odwrotnych za pomocą innego ukośnika odwrotnego. Przykład:

    hstring imagePath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\media\\kitten_224.png";
    
  5. Uruchom program bez debugowania. Powinieneś zobaczyć, że obraz został pomyślnie załadowany!

Wiązanie danych wejściowych i wyjściowych

Następnie utworzymy sesję na podstawie modelu i powiążemy dane wejściowe i wyjściowe z sesji przy użyciu elementu LearningModelBinding.Bind. Aby uzyskać więcej informacji na temat powiązania, zobacz Wiązanie modelu.

  1. Zaimplementuj metodę BindModel. Spowoduje to utworzenie sesji na podstawie modelu i urządzenia oraz powiązania na podstawie tej sesji. Następnie powiążemy dane wejściowe i wyjściowe ze zmiennymi utworzonymi przy użyciu ich nazw. Z wyprzedzeniem wiemy, że funkcja wprowadzania nosi nazwę "data_0", a funkcja danych wyjściowych nosi nazwę "softmaxout_1". Te właściwości można wyświetlić dla dowolnego modelu, otwierając je w narzędziu Netron do wizualizacji modelu 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. Dodaj wywołanie do BindModel z metody main.

    BindModel();
    
  3. Uruchom program bez debugowania. Dane wejściowe i wyjściowe modelu powinny zostać pomyślnie powiązane. Jesteśmy prawie tam!

Ocena modelu

Jesteśmy teraz w ostatnim kroku na diagramie na początku tego samouczka Evaluate. Ocenimy model przy użyciu modelu LearningModelSession.Evaluate:

  1. Zaimplementuj metodę EvaluateModel. Ta metoda pobiera sesję i ocenia ją przy użyciu powiązania i identyfikatora korelacji. Identyfikator korelacji to coś, czego moglibyśmy użyć później, aby dopasować określone wywołanie oceny do wyników wyjściowych. Ponownie wiemy wcześniej, że nazwa danych wyjściowych to "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. Teraz zaimplementujmy PrintResults. Ta metoda pobiera trzy pierwsze prawdopodobieństwa dla tego, jaki obiekt może znajdować się na obrazie, i drukuje je:

    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. Musimy również zaimplementować metodę LoadLabels. Ta metoda otwiera plik etykiet, który zawiera wszystkie różne obiekty, które model może rozpoznać, i analizuje go:

    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. Znajdź plik Labels.txt w lokalnym klonie repozytorium Windows-Machine-Learning . Powinien on znajdować się w folderze \Windows-Machine-Learning\Samples\SqueezeNetObjectDetection\Desktop\cpp.

  5. Przypisz tę ścieżkę pliku do zmiennej labelsFilePath , w której ją zdefiniowaliśmy u góry. Pamiętaj, aby uniknąć ukośników odwrotnych z innym ukośnikiem odwrotnym. Przykład:

    string labelsFilePath = "C:\\Repos\\Windows-Machine-Learning\\Samples\\SqueezeNetObjectDetection\\Desktop\\cpp\\Labels.txt";
    
  6. Dodaj wywołanie metody EvaluateModel w metodzie main :

    EvaluateModel();
    
  7. Uruchom program bez debugowania. Powinna ona teraz poprawnie rozpoznać zawartość obrazu! Oto przykład danych wyjściowych:

    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
    

Dalsze kroki

Hurra, udało się uruchomić działające wykrywanie obiektów w aplikacji na komputer C++! Następnie możesz spróbować użyć argumentów wiersza polecenia, aby wprowadzić pliki modelu i obrazu zamiast zakodowywać je na stałe, podobnie jak w przypadku przykładu na GitHubie. Możesz również spróbować uruchomić ocenę na innym urządzeniu, jak procesor GPU, aby zobaczyć, jak różni się wydajność.

Pobaw się z innymi przykładami na GitHubie i rozszerz je tak, jak chcesz!

Zobacz także

Uwaga / Notatka

Skorzystaj z następujących zasobów, aby uzyskać pomoc dotyczącą uczenia maszynowego z systemem Windows:

  • Aby zadać lub odpowiedzieć na pytania techniczne dotyczące uczenia maszynowego z systemem Windows, użyj tagu windows-machine-learning w witrynie Stack Overflow.
  • Aby zgłosić usterkę, popełnij zgłoszenie na GitHubie .