Tutorial: criar um aplicativo da área de trabalho do Windows Machine Learning (C++)

As APIs do Windows ML podem ser utilizadas para interagir facilmente com modelos de machine learning dentro de aplicativos (Win32) da área de trabalho C++. Usando as três etapas de carregar, associar e avaliar, seu aplicativo pode aproveitar a força do machine learning.

Carregar -> Vincular -> Avaliar

Criaremos uma versão um pouco simplificada do exemplo de detecção de objetos do SqueezeNet, que está disponível no GitHub. Você pode baixar o exemplo completo se quiser ver qual será a aparência quando estiver concluído.

Vamos usar C++/WinRT para acessar as APIs do WinML. Consulte C++/WinRT para obter mais informações.

Neste tutorial, você aprenderá como:

  • Carregar um modelo de machine learning
  • Carregar uma imagem como VideoFrame
  • Associar as entradas e saídas do modelo
  • Avaliar o modelo e imprimir os resultados significativos

Pré-requisitos

Criar o projeto

Primeiro, criaremos o projeto no Visual Studio:

  1. Selecione Arquivo > Novo > Projeto para abrir a janela Novo Projeto.
  2. No painel esquerdo, selecione Instalado > Visual C++ > Área de trabalho do Windows e, no meio, selecione Aplicativo de Console do Windows (C++/WinRT).
  3. Dê um Nome e uma Localização para seu projeto e clique em OK.
  4. Na janela Novo projeto de Plataforma Universal do Windows, defina o Destino e as Versões mínimas para o build 17763 ou posterior e clique em OK.
  5. Verifique se os menus suspensos na barra de ferramentas superior estão definidos como Depurar e x64 ou x86, dependendo da arquitetura do seu computador.
  6. Pressione CTRL+F5 para executar o programa sem depuração. Um terminal deve ser abrir com um texto do tipo "Hello world". Pressione qualquer tecla para fechá-lo.

Carregar o modelo

Em seguida, carregaremos o modelo ONNX em nosso programa usando LearningModel.LoadFromFilePath:

  1. Em pch.h (na pasta Arquivos de cabeçalho), adicione as instruções include a seguir (elas concedem acesso a todas as APIs necessárias):

    #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. Em main.cpp (na pasta Arquivos de origem), adicione as instruções using a seguir:

    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. Adicione as declarações de variáveis a seguir depois das instruções 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. Adicione as declarações de encaminhamento a seguir depois de suas variáveis globais:

    // Forward declarations
    void LoadModel();
    VideoFrame LoadImageFile(hstring filePath);
    void BindModel();
    void EvaluateModel();
    void PrintResults(IVectorView<float> results);
    void LoadLabels();
    
  5. Em main.cpp, remova o código "Hello world" (tudo na função main depois de init_apartment).

  6. Localize o arquivo SqueezeNet.onnx no clone local do repositório do Windows-Machine-Learning. Ele deve estar localizado em \Windows-Machine-Learning\SharedContent\models.

  7. Copie o demarcador do arquivo e atribua-o à sua variável modelPath em que o definimos na parte superior. Lembre-se de prefixar a cadeia de caracteres com um L para torná-la uma cadeia de caracteres larga que funcione corretamente com hstring e para o escape das barras invertidas (\) com uma barra invertida extra. Por exemplo:

    hstring modelPath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\models\\SqueezeNet.onnx";
    
  8. Primeiro, implementaremos o método LoadModel. Adicione o método a seguir depois do método main. Esse método carrega o modelo e informa o tempo necessário:

    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. Por fim, chame esse método no método main:

    LoadModel();
    
  10. Execute o programa sem depuração. Você verá que o modelo foi carregado com êxito!

Carregar a imagem

Em seguida, carregaremos o arquivo de imagem em nosso programa:

  1. Adicione o seguinte método . Esse método carregará a imagem do demarcador fornecido e criará um VideoFrame nela:

    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. Adicione uma chamada para esse método no método main:

    imageFrame = LoadImageFile(imagePath);
    
  3. Localize a pasta media no clone local do repositório do Windows-Machine-Learning. Ela deve estar localizada em \Windows-Machine-Learning\SharedContent\media.

  4. Escolha uma das imagens nessa pasta e atribua o demarcador do arquivo à variável imagePath em que a definimos na parte superior. Lembre-se de prefixá-la com um L para torná-la uma cadeia de caracteres larga e fazer o escape das barras invertidas com outra barra invertida. Por exemplo:

    hstring imagePath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\media\\kitten_224.png";
    
  5. Execute o programa sem depuração. A imagem deve ter sido carregada com êxito!

Associar a entrada e a saída

Em seguida, criaremos uma sessão com base no modelo e associaremos a entrada e a saída da sessão usando LearningModelBinding.Bind. Para obter mais informações sobre associação, consulte Associar um modelo.

  1. Implementar o método de BindModel . Isso cria uma sessão com base no modelo e no dispositivo, bem como uma associação baseada nessa sessão. Em seguida, ligamos as entradas e saídas às variáveis que criamos usando os nomes delas. Sabemos antecipadamente que o recurso de entrada chama-se "data_0" e o recurso de saída chama-se "softmaxout_1". Você pode ver essas propriedades para qualquer modelo abrindo-as na Netron, uma ferramenta de visualização de modelo 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. Adicione uma chamada para BindModel no método main:

    BindModel();
    
  3. Execute o programa sem depuração. As entradas e saídas do modelo devem ter sido associadas com êxito. Estamos quase lá!

Avaliar o modelo

Agora estamos na última etapa do diagrama no início deste tutorial, Avaliar. Avaliaremos o modelo usando LearningModelSession.Evaluate:

  1. Implementar o método de EvaluateModel . Esse método usa nossa sessão e a avalia usando nossa associação e uma ID de correlação. A ID de correlação talvez possa ser usada posteriormente para corresponder uma chamada de avaliação específica aos resultados de saída. Mais uma vez, sabemos antecipadamente que o nome da saída é "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. Agora, vamos implementar PrintResults. Esse método obtém as três principais probabilidades de qual objeto pode estar na imagem e as 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]);
        }
    }
    
  3. Também precisamos implementar LoadLabels. Esse método abre o arquivo de rótulos que contém todos os objetos diferentes que o modelo pode reconhecer e o analisa:

    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. Localize o arquivo Labels.txt no clone local do repositório do Windows-Machine-Learning. Ele deve estar em \Windows-Machine-Learning\Samples\SqueezeNetObjectDetection\Desktop\cpp.

  5. Atribua esse demarcador de arquivo à variável labelsFilePath em que o definimos na parte superior. Faça o escape das barras invertidas com outra barra invertida. Por exemplo:

    string labelsFilePath = "C:\\Repos\\Windows-Machine-Learning\\Samples\\SqueezeNetObjectDetection\\Desktop\\cpp\\Labels.txt";
    
  6. Adicione uma chamada para EvaluateModel no método main:

    EvaluateModel();
    
  7. Execute o programa sem depuração. Agora, ele deve reconhecer corretamente o que está na imagem! Veja um exemplo do que ele pode gerar:

    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
    

Próximas etapas

Parabéns, você fez a detecção de objetos funcionar em um aplicativo da área de trabalho C++! Em seguida, você pode tentar usar argumentos de linha de comando para inserir os arquivos de modelo e imagem em vez de codificá-los, de modo semelhante ao que o exemplo no GitHub faz. Você também pode tentar executar a avaliação em um dispositivo diferente, como a GPU, para ver a diferença no desempenho.

Experimente os outros exemplos no GitHub e estenda-os como desejar!

Confira também

Observação

Use os recursos a seguir para obter ajuda com o Windows ML:

  • Para fazer perguntas ou responder a perguntas técnicas sobre o Windows ML, use a marca windows-machine-learning no Stack Overflow.
  • Para relatar um bug, registre um problema no nosso GitHub.