Tutorial de Windows ML

Este breve tutorial explica como se utilizasse o Windows ML para executar o modelo de classificação de imagens ResNet-50 no Windows, detalhando os passos de aquisição e pré-processamento do modelo. A implementação envolve a seleção dinâmica de provedores de execução para desempenho de inferência otimizado.

O modelo ResNet-50 é um modelo PyTorch destinado à classificação de imagens.

Neste tutorial, vais adquirir o modelo ResNet-50 do Hugging Face e convertê-lo para o formato QDQ ONNX usando o Foundry Toolkit.

Depois carregas o modelo, preparas os tensores de entrada e executas inferências usando as APIs do Windows ML, incluindo passos de pós-processamento para aplicar o softmax, e recolhes as principais previsões.

Aquisição do modelo e pré-processamento

Você pode adquirir o ResNet-50 da Hugging Face (a plataforma onde a comunidade especializada em aprendizagem de máquina colabora em modelos, bases de dados e aplicações). Vai converter o ResNet-50 para o formato QDQ ONNX usando o Foundry Toolkit (veja converter modelos para o formato ONNX para mais informações).

O objetivo deste código de exemplo é aproveitar o runtime do Windows ML para fazer o trabalho pesado.

O runtime do Windows ML irá:

  • Carregue o modelo.
  • Selecione dinamicamente o fornecedor de execução (EP) fornecido pelo IHV preferido para o modelo e descarregue o seu EP na Microsoft Store, sob pedido.
  • Execute a inferência no modelo usando o EP.

Para referência à API, veja OrtSessionOptions e a classe ExecutionProviderCatalog .

// Create a new instance of EnvironmentCreationOptions
EnvironmentCreationOptions envOptions = new()
{
    logId = "ResnetDemo",
    logLevel = OrtLoggingLevel.ORT_LOGGING_LEVEL_ERROR
};

// Pass the options by reference to CreateInstanceWithOptions
OrtEnv ortEnv = OrtEnv.CreateInstanceWithOptions(ref envOptions);

// Use Windows ML to download and register Execution Providers
var catalog = Microsoft.Windows.AI.MachineLearning.ExecutionProviderCatalog.GetDefault();
Console.WriteLine("Ensuring and registering execution providers...");
await catalog.EnsureAndRegisterCertifiedAsync();

//Create Onnx session
Console.WriteLine("Creating session ...");
var sessionOptions = new SessionOptions();
// Set EP Selection Policy
sessionOptions.SetEpSelectionPolicy(ExecutionProviderDevicePolicy.MIN_OVERALL_POWER);

Compilação EP

Se o seu modelo ainda não está compilado para o EP (que pode variar dependendo do dispositivo), o modelo primeiro precisa ser compilado em relação a esse EP. Este é um processo único. O código de exemplo abaixo manipula-o compilando o modelo na primeira execução e, em seguida, armazenando-o localmente. Execuções subsequentes do código pegam a versão compilada e executam isso; resultando em inferências rápidas otimizadas.

Para referência de API, consulte Ort::ModelCompilationOptions struct, Ort::Status struct e Ort::CompileModel.

// Prepare paths
string executableFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)!;
string labelsPath = Path.Combine(executableFolder, "ResNet50Labels.txt");
string imagePath = Path.Combine(executableFolder, "dog.jpg");
            
// Set the paths to your converted ONNX model
// Use Foundry Toolkit to download and convert ResNet-50 to ONNX format: https://code.visualstudio.com/docs/intelligentapps/modelconversion
string modelPath = Path.Combine(executableFolder, "resnet50-v2-7.onnx");
string compiledModelPath = Path.Combine(executableFolder, "resnet50-v2-7-compiled.onnx");

// Compile the model if not already compiled
bool isCompiled = File.Exists(compiledModelPath);
if (!isCompiled)
{
    Console.WriteLine("No compiled model found. Compiling model ...");
    using (var compileOptions = new OrtModelCompilationOptions(sessionOptions))
    {
        compileOptions.SetInputModelPath(modelPath);
        compileOptions.SetOutputModelPath(compiledModelPath);
        compileOptions.CompileModel();
        isCompiled = File.Exists(compiledModelPath);
        if (isCompiled)
        {
            Console.WriteLine("Model compiled successfully!");
        }
        else
        {
            Console.WriteLine("Failed to compile the model. Will use original model.");
        }
    }
}
else
{
    Console.WriteLine("Found precompiled model.");
}
var modelPathToUse = isCompiled ? compiledModelPath : modelPath;

Executando a inferência

A imagem de entrada é convertida em formato de dados tensor e, em seguida, a inferência é executada nela. Embora isto seja típico de todo o código que utiliza o Runtime ONNX, a diferença neste caso é que é o Runtime ONNX diretamente através do Windows ML. O único requisito é adicionar #include <winml/onnxruntime_cxx_api.h> ao código.

Consulte também Converter um modelo com o Foundry Toolkit para o VS Code

Para referência de API, consulte Ort::Session struct, Ort::MemoryInfo struct, Ort::Value struct, Ort::AllocatorWithDefaultOptions struct, Ort::RunOptions struct.

using var session = new InferenceSession(modelPathToUse, sessionOptions);

Console.WriteLine("Preparing input ...");
// Load and preprocess image
var input = await PreprocessImageAsync(await LoadImageFileAsync(imagePath));
// Prepare input tensor
var inputName = session.InputMetadata.First().Key;
var inputTensor = new DenseTensor<float>(
    input.ToArray(),          // Use the DenseTensor<float> directly
    new[] { 1, 3, 224, 224 }, // Shape of the tensor
    false                     // isReversedStride should be explicitly set to false
);

// Bind inputs and run inference
var inputs = new List<NamedOnnxValue>
{
    NamedOnnxValue.CreateFromTensor(inputName, inputTensor)
};

Console.WriteLine("Running inference ...");
var results = session.Run(inputs);
for (int i = 0; i < 40; i++)
{
    results = session.Run(inputs);
}

// Extract output tensor
var outputName = session.OutputMetadata.First().Key;
var resultTensor = results.First(r => r.Name == outputName).AsEnumerable<float>().ToArray();

// Load labels and print results
var labels = LoadLabels(labelsPath);
PrintResults(labels, resultTensor);

Pós-processamento

A função softmax é aplicada à saída bruta retornada, e os dados de rótulo são usados para mapear e imprimir os nomes com as cinco maiores probabilidades.

private static void PrintResults(IList<string> labels, IReadOnlyList<float> results)
{
    // Apply softmax to the results
    float maxLogit = results.Max();
    var expScores = results.Select(r => MathF.Exp(r - maxLogit)).ToList(); // stability with maxLogit
    float sumExp = expScores.Sum();
    var softmaxResults = expScores.Select(e => e / sumExp).ToList();

    // Get top 5 results
    IEnumerable<(int Index, float Confidence)> topResults = softmaxResults
        .Select((value, index) => (Index: index, Confidence: value))
        .OrderByDescending(x => x.Confidence)
        .Take(5);

    // Display results
    Console.WriteLine("Top Predictions:");
    Console.WriteLine("-------------------------------------------");
    Console.WriteLine("{0,-32} {1,10}", "Label", "Confidence");
    Console.WriteLine("-------------------------------------------");

    foreach (var result in topResults)
    {
        Console.WriteLine("{0,-32} {1,10:P2}", labels[result.Index], result.Confidence);
    }

    Console.WriteLine("-------------------------------------------");
}

Resultado

Aqui está um exemplo do tipo de saída a ser esperada.

285, Egyptian cat with confidence of 0.904274
281, tabby with confidence of 0.0620204
282, tiger cat with confidence of 0.0223081
287, lynx with confidence of 0.00119624
761, remote control with confidence of 0.000487919

Exemplos de código completo

Os exemplos completos de código estão disponíveis no repositório WindowsAppSDK-Samples GitHub. Consulte WindowsML.