Compartir a través de


Tutorial: Creación de una aplicación de escritorio de Windows Machine Learning (C++)

Las API de Windows ML se pueden aprovechar para interactuar fácilmente con modelos de aprendizaje automático en aplicaciones de escritorio de C++ (Win32). Con los tres pasos de carga, enlace y evaluación, la aplicación puede beneficiarse de la eficacia del aprendizaje automático.

Cargar - Enlazar ->> Evaluar

Vamos a crear una versión algo simplificada del ejemplo de detección de objetos de SqueezeNet, que está disponible en GitHub. Puede descargar el ejemplo completo si desea ver cómo será cuando termine.

Usaremos C++/WinRT para acceder a las API de WinML. Consulta C++/WinRT para obtener más información.

En este tutorial, aprenderá a:

  • Carga de un modelo de Machine Learning
  • Cargar una imagen como videoframe
  • Vincula las entradas y salidas del modelo
  • Evaluar el modelo e imprimir resultados significativos

Prerrequisitos

Creación del proyecto

En primer lugar, crearemos el proyecto en Visual Studio:

  1. Seleccione Archivo > Nuevo > Proyecto para abrir la ventana Nuevo proyecto.
  2. En el panel izquierdo, seleccione Instalado > Visual C++ > Escritorio de Windows, y en el panel central, seleccione Aplicación de consola de Windows (C++/WinRT).
  3. Asigne un nombre y una ubicación al proyecto y haga clic en Aceptar.
  4. En la ventana Nuevo proyecto de plataforma universal de Windows, establezca las versiones de destino y mínimas ambas en la compilación 17763 o posterior, y haga clic en Aceptar.
  5. Asegúrese de que los menús desplegables de la barra de herramientas superior están establecidos en Depurar y x64 o x86 en función de la arquitectura del equipo.
  6. Presione Ctrl+F5 para ejecutar el programa sin depurar. Un terminal debe abrirse con texto "Hola mundo". Presione cualquier tecla para cerrarla.

Carga del modelo

A continuación, cargaremos el modelo ONNX en nuestro programa mediante LearningModel.LoadFromFilePath:

  1. En pch.h (en la carpeta Archivos de encabezado ), agregue las siguientes include instrucciones (que nos proporcionan acceso a todas las API que necesitaremos):

    #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. En main.cpp (en la carpeta Archivos de origen), agregue las siguientes using instrucciones:

    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. Agregue las siguientes declaraciones de variable después de las using sentencias:

    // 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. Agregue las siguientes declaraciones de reenvío después de las variables globales:

    // Forward declarations
    void LoadModel();
    VideoFrame LoadImageFile(hstring filePath);
    void BindModel();
    void EvaluateModel();
    void PrintResults(IVectorView<float> results);
    void LoadLabels();
    
  5. En main.cpp, quita el código "Hola mundo" (todo en la función main después de init_apartment).

  6. Busque el archivo SqueezeNet.onnx en el clon local del repositorio Windows-Machine-Learning . Debe encontrarse en \Windows-Machine-Learning\SharedContent\models.

  7. Copie la ruta de acceso del archivo y asígnela a la modelPath variable donde la definimos en la parte superior. No olvides prefijar la cadena con L para convertirla en una cadena de caracteres anchos, de forma que funcione correctamente con hstring, y para que se escapen las barras diagonales inversas (\) con una barra diagonal inversa adicional. Por ejemplo:

    hstring modelPath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\models\\SqueezeNet.onnx";
    
  8. En primer lugar, implementaremos el LoadModel método . Agregue el siguiente método después del main método . Este método carga el modelo y genera cuánto tiempo tardó:

    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 último, llame a este método desde el método main.

    LoadModel();
    
  10. Ejecuta el programa sin depuración. Deberías ver que el modelo se carga correctamente.

Carga de la imagen

A continuación, cargaremos el archivo de imagen en nuestro programa:

  1. Agregue el método siguiente. Este método cargará la imagen desde la ruta de acceso especificada y creará un VideoFrame a partir de ella:

    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. Agregue una llamada a este método en el main método :

    imageFrame = LoadImageFile(imagePath);
    
  3. Busque la carpeta multimedia en el clon local del repositorio Windows-Machine-Learning . Debe encontrarse en \Windows-Machine-Learning\SharedContent\media.

  4. Elija una de las imágenes de esa carpeta y asigne su ruta de acceso de archivo a la imagePath variable donde la definimos en la parte superior. No olvides prefijarla con L para convertirla en una cadena de caracteres anchos, y para que se escapen las barras diagonales inversas con otra barra diagonal inversa. Por ejemplo:

    hstring imagePath = L"C:\\Repos\\Windows-Machine-Learning\\SharedContent\\media\\kitten_224.png";
    
  5. Ejecuta el programa sin depuración. Debería ver que la imagen se cargó correctamente.

Vincula la entrada y la salida

A continuación, crearemos una sesión basada en el modelo y enlazaremos la entrada y salida de la sesión mediante LearningModelBinding.Bind. Para obtener más información sobre la vinculación, consulte Vincular un modelo.

  1. Implemente el método BindModel. Esto crea una sesión basada en el modelo y el dispositivo, y un enlace basado en esa sesión. A continuación, enlazamos las entradas y salidas a las variables que hemos creado con sus nombres. Sabemos con antelación que la característica de entrada se denomina "data_0" y la característica de salida se denomina "softmaxout_1". Puede ver estas propiedades para cualquier modelo si las abre en Netron, una herramienta de visualización de modelos en línea.

    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. Agregue una llamada a BindModel desde el main método :

    BindModel();
    
  3. Ejecuta el programa sin depuración. Las entradas y salidas del modelo deben enlazarse correctamente. ¡Casi estamos ahí!

Evaluación del modelo

Ahora estamos en el último paso del diagrama al principio de este tutorial, Evaluar. Evaluaremos el modelo mediante LearningModelSession.Evaluate:

  1. Implemente el método EvaluateModel. Este método toma nuestra sesión y lo evalúa mediante nuestro enlace y un identificador de correlación. El identificador de correlación es algo que podríamos usar más adelante para hacer coincidir una llamada de evaluación determinada a los resultados de salida. De nuevo, sabemos con antelación que el nombre de la salida es "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. Ahora vamos a implementar PrintResults. Este método obtiene las tres probabilidades principales para qué objeto podría estar en la imagen y los 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. También es necesario implementar LoadLabels. Este método abre el archivo de etiquetas que contiene todos los distintos objetos que el modelo puede reconocer y lo analiza:

    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. Busque el archivo Labels.txt en el clon local del repositorio Windows-Machine-Learning . Debe estar en \Windows-Machine-Learning\Samples\SqueezeNetObjectDetection\Desktop\cpp.

  5. Asigne esta ruta de acceso de archivo a la variable labelsFilePath, donde la definimos en la parte superior. Asegúrate de que se escapen las barras diagonales inversas con otra barra diagonal inversa. Por ejemplo:

    string labelsFilePath = "C:\\Repos\\Windows-Machine-Learning\\Samples\\SqueezeNetObjectDetection\\Desktop\\cpp\\Labels.txt";
    
  6. Agregue una llamada a EvaluateModel en el main método :

    EvaluateModel();
    
  7. Ejecuta el programa sin depuración. Ahora debería reconocer correctamente lo que hay en la imagen. Este es un ejemplo de lo que podría generar:

    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
    

Pasos siguientes

¡Hurra, has conseguido que la detección de objetos funcione en una aplicación de escritorio en C++! A continuación, puede intentar usar argumentos de línea de comandos para introducir los archivos de imagen y modelo en lugar de codificarlos de forma difícil, de forma similar a lo que hace el ejemplo en GitHub. También puede intentar ejecutar la evaluación en un dispositivo diferente, como la GPU, para ver cómo difiere el rendimiento.

Juega con los otros ejemplos en GitHub y ampliarlos como quieras.

Consulte también

Nota:

Use los siguientes recursos para obtener ayuda con Windows ML:

  • Para formular o responder preguntas técnicas sobre Windows ML, use la etiqueta windows-machine-learning en Stack Overflow.
  • Para notificar un error, envíe un problema en nuestra GitHub.