El framework ML.NET es de gran utilidad para crear aplicaciones con Machine Learning de punta a punta -end to end-, es decir, desde el entrenamiento de un modelo con datos históricos hasta la inferencia o predicción de un resultado con nuevos datos. Cuando creamos una aplicación utilizando ML.NET Model Builder podemos hacer predicciones de un resultado o una predicción a la vez, pero hay ocaciones en que necesitamos hacer muchas predicciones a la vez en forma de batch. En este post haremos un ejercicio para crear una aplicación que predice la cancelación de una reserva de hotel y modificaremos el código para hacer predicciones en batch a partir de varios registros en un archivo .csv.
Problema
Tenemos un dataset con el histórico de las reservas de varios hoteles y el registro de los cambios que tuvo la reserva. Con estos datos vamos a crear un modelo de Machine Learning para predecir si una reserva puede ser cancelada, modificada o no tener cambios. Este escenario puede ser útil para ofrecer un seguro de cancelación de reserva a un menor o mayor costo según el resultado de la predicción.
Dataset
Contamos con un Dataset tomado de kaggle y modificado para marcar tres categorías en la columna de reservation_status, esta puede tener los valores: Booking-Changed; Canceled; Check-Out

Solución
Crearemos un modelo de Machine Learning usando el framework ML.NET. Ya que son tres categorías las que queremos predecir (Booking-Changed; Canceled; Check-Out) debemos crear un modelo de clasificación multiclase (multiclass classification).
El proyecto completo se puede descargar del repositorio de GitHub
Usaremos ML.NET Model Builder para crear la aplicación -para conocer más sobre ML.NET puedes ver este post aquí-. Seguiremos los siguientes pasos para crear la aplicación.
- Primero creamos en Visual Studio 2019 un proyecto ASP.NET Core MVC en el cual crearemos una simple interfaz para cargar un archivo .cvs con los datos a predecir en batch. Este proyecto lo llamaremos Multiclass_Classification_Batch.
- Ahora agregamos un proyecto de Machine Learning usando la extensión de Model Builder y seleccionaremos la opción Text Classification. Aunque la opción parezca confusa esta es la opción que podemos usar para cualquier tipo de clasificación binara (binary) o multiclase (Multiclass).

- Cuando seleccionemos el archivo con el dataset para el entrenamiento del modelo debemos asegurarnos que el archivo contenga una columna id, de esta forma cuando hagamos la predicción en batch podremos identificar sus resultados por el ‘id’ del registro. Sien embargo no queremos que el ‘id’ influya en el entrenamiento por lo que debemos desmarcar esa columna como parte de las características (features). La columna a predecir (label) será la columna reservation_status, esta contiene el estado de la reserva (Booking-Changed; Canceled; Check-Out)

- Model Builder realizará el entrenamiento del modelo y seleccionará el mejor algoritmo por nosotros, al final se agregará a la solución los proyectos para entrenar y consumir el modelo y nos quedará de esta forma

Ahora realizaremos las modificaciones al código para hacer una predicción en batch con nuevos registros de reservas de hotel. El primer cambio que debemos hacer es en la clase ModelOutput, esta clase contiene la estructura con el resultado de una predicción, pero como vamos a hacer predicciones en batch vamos a agregar la columna id para poder identificar a cada predicción -por eso es importante que el archivo con los datos de entrenamiento también tengan la columna id-.
public class ModelOutput
{
// ColumnName attribute is used to change the column name from
// its default value, which is the name of the field.
[ColumnName("id")]
public float Id { get; set; }
[ColumnName("PredictedLabel")]
public String Prediction { get; set; }
public float[] Score { get; set; }
}
Ahora vamos a modificar el archivo ConsumeModel, este archivo contienen el método para hacer predicciones para una nueva reserva de hotel, aquí agregamos un método PredictBatch para hacer las predicciones en batch con nuevas reservas en un archivo .csv y el resultado también será un archivo .csv con las predicciones.
public static string PredictBatch(string dataPath)
{
MLContext mlContext = new MLContext();
// Load model
ITransformer mlModel = mlContext.Model.Load(GetAbsolutePath(MLNetModelPath), out var modelInputSchema);
IDataView inputData = mlContext.Data.LoadFromTextFile<ModelInput>(dataPath,
separatorChar: ',',
hasHeader: true);
var transformedDataView = mlModel.Transform(inputData);
var predictions = mlContext.Data.CreateEnumerable<ModelOutput>(transformedDataView, false).ToList();
IDataView outputView = mlContext.Data.LoadFromEnumerable<ModelOutput>(predictions);
string dataPathOutput = Path.GetDirectoryName(dataPath) + @"\predictions.csv";
using (FileStream stream = new FileStream(dataPathOutput, FileMode.Create))
{
mlContext.Data.SaveAsText(outputView, stream,
separatorChar: ',',
headerRow: true, schema: false);
}
return dataPathOutput;
}
Lo interesante de este código es el uso del método LoadFromEnumerable que lo usamos para crear un IDataView con la estructura ModelOutput, lo que demuestra que este método se puede usar con cualquier estructura, no solo con la del ModelInput. Además usamos el método SaveAsText para transformar fácilmente un objeto de tipo List a un archivo .csv sin necesidad de escribir código adicional para este propósito.
Ahora solo nos resta crear una interfaz web en el proyecto Multiclass_Classification_Batch para cargar un archivo con los registros de las reservas de hotel que queremos predecir y descargar el archivo con las predicciones de respuesta. Esto lo haremos directamente en la página Index y agregamos el siguiente código en el HomeController.
[HttpPost]
public async Task<FileResult> Index(IFormFile formFile)
{
if (formFile != null && formFile.Length > 0)
{
string pathImgPredict = Path.Combine(pathPrediction, ConsumeModel.MLNetBatchFileName);
pathImgPredict = ConsumeModel.GetAbsolutePath(pathImgPredict);
pathImgPredict = Path.GetFullPath(pathImgPredict);
using (var stream = System.IO.File.Create(pathImgPredict))
{
await formFile.CopyToAsync(stream);
}
var predictionsFile = ConsumeModel.PredictBatch(pathImgPredict);
var streamResult = await System.IO.File.ReadAllBytesAsync(predictionsFile);
return File(streamResult, "application/csv;charset=utf-8", "Predictions.csv");
}
return null;
}
El resultado final de nuestra aplicación será este:

Nótese como en el archivo de respuesta con las predicciones consta la columna Id para poder identificar a qué reserva corresponde cada predicción.

El proyecto completo se puede descargar del repositorio de GitHub
Artículos relacionados
Machine Learning | Trabajar con múltiples fuentes de datos usando ML.NET
Machine Learning | 5 ventajas de la programación orientada a objetos para la ingeniería de datos