Realización de predicciones con un modelo AutoML de ONNX en .NET

En este artículo, aprenderá a usar un modelo de ML automatizado (AutoML) de Neural Network Exchange (ONNX) para hacer predicciones en una aplicación de consola de .NET Core de C# con ML.NET.

ML.NET es un marco de aprendizaje automático multiplataforma de código abierto del ecosistema de .NET que permite entrenar y consumir modelos de aprendizaje automático personalizados utilizando un enfoque Code First de C# o F# junto con herramientas que apenas requieren código, como Model Builder y la CLI de ML.NET. Este marco puede ampliarse y permite utilizar otros marcos de aprendizaje automático populares, como TensorFlow y ONNX.

ONNX es un formato de código abierto para modelos de IA. ONNX admite la interoperabilidad entre marcos. Esto significa que puede entrenar un modelo en uno de los muchos marcos de aprendizaje automático que son más conocidos, como PyTorch, convertirlo a un formato ONNX y consumir el modelo de ONNX en un marco de trabajo diferente, como ML.NET. Para más información, visite el sitio web de ONNX.

Requisitos previos

Creación de una aplicación de consola en C#

En este ejemplo, va a utilizar la CLI de .NET Core para compilar la aplicación, pero puede realizar las mismas tareas con Visual Studio. Más información sobre la CLI de .NET Core.

  1. Abra un terminal y cree una aplicación de consola de .NET Core en C#. En este ejemplo, el nombre de la aplicación es AutoMLONNXConsoleApp. Se creará un directorio que tendrá el mismo nombre con el contenido de la aplicación.

    dotnet new console -o AutoMLONNXConsoleApp
    
  2. En el terminal, vaya al directorio AutoMLONNXConsoleApp.

    cd AutoMLONNXConsoleApp
    

Incorporación de los paquetes de software

  1. Instale los paquetes de NuGet Microsoft.ML, Microsoft.ML.OnnxRuntime y Microsoft.ML.OnnxTransformer mediante la CLI de .NET Core.

    dotnet add package Microsoft.ML
    dotnet add package Microsoft.ML.OnnxRuntime
    dotnet add package Microsoft.ML.OnnxTransformer
    

    Estos paquetes contienen las dependencias necesarias para usar un modelo de ONNX en una aplicación .NET. ML.NET cuenta con una API que usa el entorno de ejecución de ONNX para realizar las predicciones.

  2. Abra el archivo Program.cs y agregue al comienzo las siguientes instrucciones using para hacer referencia a los paquetes correspondientes.

    using System.Linq;
    using Microsoft.ML;
    using Microsoft.ML.Data;
    using Microsoft.ML.Transforms.Onnx;
    

Incorporación de una referencia al modelo de ONNX

Una forma de conseguir que la aplicación de consola acceda al modelo de ONNX es agregarla al directorio de salida de la compilación. Para más información sobre los elementos comunes de MSBuild, consulte la guía de MSBuild.

Agregue una referencia al archivo del modelo de ONNX de la aplicación.

  1. Copie el modelo ONNX en el directorio raíz AutoMLONNXConsoleApp de la aplicación.

  2. Abra el archivo AutoMLONNXConsoleApp. csproj y agregue el siguiente contenido dentro del nodo Project.

    <ItemGroup>
        <None Include="automl-model.onnx">
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
    </ItemGroup>
    

    En este caso, el nombre del archivo de modelo de ONNX es automl-model.onnx.

  3. Abra el archivo Program.cs y agregue la línea siguiente dentro de la clase Program.

    static string ONNX_MODEL_PATH = "automl-model.onnx";
    

Inicialización de MLContext

Dentro del método Main de la clase Program, cree una nueva instancia de MLContext.

MLContext mlContext = new MLContext();

La clase MLContext es el punto de partida de todas las operaciones de ML.NET, mientras que la inicialización de mlContext crea un nuevo entorno de ML.NET que puede compartirse durante el ciclo de vida del modelo. Conceptualmente, es similar a DbContext en Entity Framework.

Definición del esquema de datos del modelo

El modelo espera que los datos de entrada y salida estén en un formato específico. ML.NET permite definir el formato de los datos mediante clases. En ocasiones, es posible que ya sepa cuál es el formato. De lo contrario, puede usar herramientas como Netron para inspeccionar el modelo de ONNX.

El modelo de este ejemplo utiliza la información del conjunto de datos NYC TLC Taxi Trip. A continuación, se muestra un ejemplo de los datos:

vendor_id rate_code passenger_count trip_time_in_secs trip_distance payment_type fare_amount
VTS 1 1 1140 3,75 CRD 15,5
VTS 1 1 480 2.72 CRD 10.0
VTS 1 1 1680 7.8 CSH 26,5

Inspección del modelo de ONNX (opcional)

Utilice una herramienta como Netron para inspeccionar las entradas y salidas del modelo.

  1. Abra Netron.

  2. En la barra de menús superior, seleccione Archivo> Abrir y use el explorador de archivos para seleccionar el modelo.

  3. Se abre el modelo. Por ejemplo, la estructura del modelo automl-model.onnx se parece a la siguiente:

    Modelo AutoML de ONNX en Netron

  4. Seleccione el último nodo de la parte inferior del grafo (variable_out1 en este caso) para ver los metadatos del modelo. Las entradas y salidas de la barra lateral representan las entradas, salidas y tipos de datos esperados del modelo. Utilice esta información para definir el esquema de entrada y salida del modelo.

Definición del esquema de entrada del modelo

Cree una nueva clase llamada OnnxInput con las siguientes propiedades en el archivo Program.cs.

public class OnnxInput
{
    [ColumnName("vendor_id")]
    public string VendorId { get; set; }

    [ColumnName("rate_code"),OnnxMapType(typeof(Int64),typeof(Single))]
    public Int64 RateCode { get; set; }

    [ColumnName("passenger_count"), OnnxMapType(typeof(Int64), typeof(Single))]
    public Int64 PassengerCount { get; set; }

    [ColumnName("trip_time_in_secs"), OnnxMapType(typeof(Int64), typeof(Single))]
    public Int64 TripTimeInSecs { get; set; }

    [ColumnName("trip_distance")]
    public float TripDistance { get; set; }

    [ColumnName("payment_type")]
    public string PaymentType { get; set; }
}

Cada una de las propiedades se asigna a una columna del conjunto de datos. Además, se incluirán atributos en las notas de las propiedades.

El atributo ColumnName permite especificar cómo ML.NET debe hacer referencia a la columna al trabajar con los datos. Por ejemplo, aunque la propiedad TripDistance sigue las convenciones de nomenclatura estándar de .NET, el modelo solo tiene información de una columna o característica llamada trip_distance. Para solucionar esta discrepancia de nomenclatura, el atributo ColumnName asigna la propiedad TripDistance a una columna o característica con el nombre trip_distance.

En el caso de los valores numéricos, ML.NET solo trabaja con tipos de valor Single. Sin embargo, el tipo de datos original de algunas de las columnas es un entero. El atributo OnnxMapType asigna tipos entre ONNX y ML.NET.

Para más información sobre los atributos de datos, consulte la guía de datos de carga de ML.NET.

Definición del esquema de salida del modelo

Una vez procesados los datos, se genera una salida con un formato determinado. Defina el esquema de salida de los datos. Cree una nueva clase llamada OnnxOutput con las siguientes propiedades en el archivo Program.cs.

public class OnnxOutput
{
    [ColumnName("variable_out1")]
    public float[] PredictedFare { get; set; }
}

Al igual que con OnnxInput, utilice el atributo ColumnName para asignar a la salida variable_out1 un nombre más descriptivo: PredictedFare.

Definición de una canalización de predicciones

Normalmente, las canalizaciones de ML.NET son una serie de transformaciones en cadena que se realizan en los datos de entrada para generar una salida. Para más información sobre las transformaciones de datos, consulte la guía de transformación de datos de ML.NET.

  1. Cree un nuevo método llamado GetPredictionPipeline dentro de la clase Program.

    static ITransformer GetPredictionPipeline(MLContext mlContext)
    {
    
    }
    
  2. Defina el nombre de las columnas de entrada y de salida. Agregue el código siguiente dentro del método GetPredictionPipeline.

    var inputColumns = new string []
    {
        "vendor_id", "rate_code", "passenger_count", "trip_time_in_secs", "trip_distance", "payment_type"
    };
    
    var outputColumns = new string [] { "variable_out1" };
    
  3. Defina la canalización. Un objeto IEstimator proporciona un proyecto de las operaciones y de los esquemas de entrada y salida de la canalización.

    var onnxPredictionPipeline =
        mlContext
            .Transforms
            .ApplyOnnxModel(
                outputColumnNames: outputColumns,
                inputColumnNames: inputColumns,
                ONNX_MODEL_PATH);
    

    En este caso, ApplyOnnxModel es la única transformación de la canalización, que toma los nombres de las columnas de entrada y salida, y la ruta de acceso al archivo del modelo de ONNX.

  4. Los objetos IEstimator solo definen el conjunto de operaciones que se van a aplicar a los datos. Lo que realmente trabaja con los datos se conoce como ITransformer. Utilice el método Fit para crear uno a partir de onnxPredictionPipeline.

    var emptyDv = mlContext.Data.LoadFromEnumerable(new OnnxInput[] {});
    
    return onnxPredictionPipeline.Fit(emptyDv);
    

    El método Fit espera un objeto IDataView como entrada para realizar las operaciones. Los objetos IDataView permiten representar los datos en ML.NET con un formato tabular. Dado que en este caso la canalización solo se usa para realizar predicciones, puede proporcionar un objeto IDataView para que ITransformer tenga la información necesaria del esquema de entrada y de salida. A continuación, se devolverá el objeto ITransformer al que se le ha aplicado el método Fit para que siga utilizándose en la aplicación.

    Sugerencia

    En este ejemplo, la canalización se define y se utiliza dentro de la misma aplicación. Sin embargo, cuando se creen predicciones, se recomienda usar diferentes aplicaciones para definir y usar la canalización. En ML.NET, las canalizaciones se pueden serializar y guardar para utilizarse en otras aplicaciones NET dirigidas a usuarios finales. ML.NET admite varios destinos de implementación, como aplicaciones de escritorio, servicios web, aplicaciones de WebAssembly * y muchos otros. Para más información acerca de cómo guardar las canalizaciones, consulte la guía para guardar y cargar modelos entrenados de ML.NET.

    *WebAssembly solo se admite en .NET Core 5 o versiones posteriores

  5. En el método Main, llame al método GetPredictionPipeline con los parámetros necesarios.

    var onnxPredictionPipeline = GetPredictionPipeline(mlContext);
    

Uso del modelo para realizar predicciones

Ahora que tiene una canalización, es el momento de utilizarla para realizar predicciones. ML.NET dispone de una práctica API que permite llevar a cabo predicciones en una única instancia de datos denominada PredictionEngine.

  1. En el método Main, cree un objeto PredictionEngine utilizando el método CreatePredictionEngine.

    var onnxPredictionEngine = mlContext.Model.CreatePredictionEngine<OnnxInput, OnnxOutput>(onnxPredictionPipeline);
    
  2. Cree una entrada de datos de prueba.

    var testInput = new OnnxInput
    {
        VendorId = "CMT",
        RateCode = 1,
        PassengerCount = 1,
        TripTimeInSecs = 1271,
        TripDistance = 3.8f,
        PaymentType = "CRD"
    };
    
  3. Utilice el objeto predictionEngine para hacer predicciones basadas en los nuevos datos de testInput con el método Predict.

    var prediction = onnxPredictionEngine.Predict(testInput);
    
  4. Envíe la salida de la predicción a la consola.

    Console.WriteLine($"Predicted Fare: {prediction.PredictedFare.First()}");
    
  5. Use la CLI de .NET Core para ejecutar la aplicación.

    dotnet run
    

    La salida debe ser parecida a esta:

    Predicted Fare: 15.621523
    

Para más información acerca de cómo realizar predicciones en ML.NET, consulte la guía sobre el uso de un modelo para hacer predicciones.

Pasos siguientes