Поделиться через


Руководство: Создание настольного приложения для машинного обучения Windows (C++)

API машинного обучения Windows можно использовать для легкого взаимодействия с моделями машинного обучения в классических приложениях C++ (Win32). Используя три этапа загрузки, привязки и оценки, приложение может воспользоваться преимуществами машинного обучения.

Загрузка —> привязка —> оценка

Мы создадим несколько упрощенную версию примера обнаружения объектов SqueezeNet, которая доступна на сайте GitHub. Вы можете скачать полный пример, если вы хотите увидеть, как это будет по завершении.

Для доступа к API WinML мы будем использовать C++/WinRT. Дополнительные сведения см. в статье C++/WinRT .

В этом руководстве описано, как:

  • Загрузка модели машинного обучения
  • Загрузка изображения в виде кадра видео
  • Привязка входных и выходных данных модели
  • Оценка модели и печать значимых результатов

Предпосылки

Создание проекта

Сначала мы создадим проект в Visual Studio:

  1. Выберите файл > нового проекта, чтобы открыть окно "Новый > проект".
  2. В левой области выберите Установленный Visual C++ для рабочего стола Windows>>, и в середине выберите консольное приложение Windows (C++/WinRT).
  3. Присвойте проекту имя и расположение, а затем нажмите кнопку "ОК".
  4. В окне "Новый проект универсальной платформы Windows " задайте целевые и минимальные версии для сборки 17763 или более поздней версии и нажмите кнопку "ОК".
  5. Убедитесь, что раскрывающееся меню на верхней панели инструментов задано значение Debug и x64 или x86 в зависимости от архитектуры компьютера.
  6. Нажмите клавиши CTRL+F5 , чтобы запустить программу без отладки. Терминал должен открыться с текстом Hello world. Нажмите любую клавишу, чтобы закрыть ее.

Загрузка модели

Затем мы загрузим модель ONNX в нашу программу с помощью LearningModel.LoadFromFilePath:

  1. В pch.h (в папке "Файлы заголовков ") добавьте следующие include инструкции (они предоставляют нам доступ ко всем 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. В main.cpp (в папке исходных файлов ) добавьте следующие 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. Добавьте следующие объявления переменных после 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. Добавьте следующие объявления пересылки после глобальных переменных:

    // Forward declarations
    void LoadModel();
    VideoFrame LoadImageFile(hstring filePath);
    void BindModel();
    void EvaluateModel();
    void PrintResults(IVectorView<float> results);
    void LoadLabels();
    
  5. В main.cpp удалите код Hello world (все в main функции после init_apartment).

  6. Найдите файл SqueezeNet.onnx в локальном клоне репозитория Windows-Machine-Learning . Он должен находиться в \Windows-Machine-Learning\SharedContent\models.

  7. Скопируйте путь к файлу и назначьте его переменной modelPath, которую мы определили вверху. Не забудьте добавить к строке префикс L, чтобы она стала строкой символов широкого формата и правильно работала с hstring, а также экранировать все обратные косые черты (\) добавлением дополнительной обратной косой черты. Рассмотрим пример.

    hstring modelPath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\models\\SqueezeNet.onnx";
    
  8. Во-первых, мы реализуем LoadModel метод. Добавьте следующий метод после main метода. Этот метод загружает модель и выводит, сколько времени потребовалось.

    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. Наконец, вызовите этот метод из main метода:

    LoadModel();
    
  10. Запустите программу без отладки. Вы увидите, что модель успешно загружается!

Загрузка образа

Затем мы загрузим файл образа в нашу программу:

  1. Добавьте следующий метод. Этот метод загружает изображение из заданного пути и создает видеофрейм из него:

    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. Добавьте вызов к этому методу в методе main :

    imageFrame = LoadImageFile(imagePath);
    
  3. Найдите папку мультимедиа в локальном клоне репозитория Windows-Machine-Learning . Он должен находиться в \Windows-Machine-Learning\SharedContent\media.

  4. Выберите одно из изображений в этой папке и назначьте путь к файлу переменной imagePath, которую мы определили в верхней части. Не забудьте добавить префикс L, чтобы сделать его строкой широких символов, и экранировать все обратные косые черты с помощью второй обратной косой черты. Рассмотрим пример.

    hstring imagePath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\media\\kitten_224.png";
    
  5. Запустите программу без отладки. Изображение должно быть загружено успешно!

Привязка входных и выходных данных

Затем мы создадим сеанс на основе модели и привязываем входные и выходные данные сеанса с помощью LearningModelBinding.Bind. Дополнительные сведения о привязке см. в разделе "Привязка модели".

  1. Реализуйте метод BindModel. Это создает сеанс на основе модели и устройства, а также связь, основанную на этом сеансе. Затем мы привязываем входные и выходные данные к переменным, созданным с помощью их имен. Мы знаем заранее, что входная функция называется "data_0", а выходной компонент называется "softmaxout_1". Эти свойства можно просмотреть для любой модели, открыв их в Netron, инструмент визуализации онлайн-модели.

    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. Добавьте вызов BindModel из main метода:

    BindModel();
    
  3. Запустите программу без отладки. Входные и выходные данные модели должны быть привязаны успешно. Мы почти там!

Оценка модели

Теперь мы на последнем шаге на схеме в начале этого руководства, оценка. Мы рассмотрим модель с помощью LearningModelSession.Evaluate:

  1. Реализуйте метод EvaluateModel. Этот метод принимает сеанс и оценивает его с помощью нашей привязки и идентификатора корреляции. Идентификатор корреляции — это то, что мы могли бы использовать позже для сопоставления определенного вызова оценки с выходными результатами. Опять же, мы знаем заранее, что имя выходных данных — "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. Теперь давайте реализуем PrintResults. Этот метод получает три лучших вероятности для того, какой объект может находиться на изображении, и выводит их:

    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. Нам также нужно реализовать LoadLabels. Этот метод открывает файл меток, содержащий все различные объекты, которые модель может распознать, и анализирует его:

    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. Найдите файлLabels.txt в локальном клоне репозитория Windows-Machine-Learning . Он должен находиться в \Windows-Machine-Learning\Samples\SqueezeNetObjectDetection\Desktop\cpp.

  5. Назначьте этот путь к файлу переменной labelsFilePath, которую мы определили в начале. Не забудьте избежать любых обратных косых черт с другой обратной косой чертой. Рассмотрим пример.

    string labelsFilePath = "C:\\Repos\\Windows-Machine-Learning\\Samples\\SqueezeNetObjectDetection\\Desktop\\cpp\\Labels.txt";
    
  6. Добавьте вызов EvaluateModel в main метод:

    EvaluateModel();
    
  7. Запустите программу без отладки. Теперь он должен правильно распознавать, что находится на изображении! Ниже приведен пример выходных данных:

    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
    

Дальнейшие шаги

Ура, у вас получилось настроить обнаружение объектов в настольном приложении на C++! Затем можно попробовать использовать аргументы командной строки для ввода файлов модели и изображений, а не жесткого кода, аналогично тому, что делает пример на GitHub. Вы также можете попробовать запустить оценку на другом устройстве, например GPU, чтобы узнать, как производительность отличается.

Поэкспериментируйте с другими примерами на сайте GitHub и расширяйте их, как вам нравится!

См. также

Замечание

Используйте следующие ресурсы, чтобы получить помощь по Windows ML.

  • Чтобы задать или ответить на технические вопросы о Windows ML, используйте тег windows-machine-learning в Stack Overflow.
  • Чтобы сообщить об ошибке, отправьте сообщение о проблеме на сайте GitHub.