ML.NET y .Net Core funcionan muy bien en Mac OS y Visual Studio for Mac, pero a veces requiere la corrección de ciertas excepciones. En este post explico cómo ejecutar un proyecto de clasificación de imágenes con ML.NET 1.4 y .Net Core 3.0 en Visual Studio for Mac.
El proyecto no es necesario desarrollarlo desde cero, para este ejercicio podemos descargar el ejemplo de clasificación de imágenes publicado en el GitHub de Machine Learning Samples. Y desde mi cuenta de GitHub se puede descargar el proyecto actualizado para Visual Studio for Mac.
El proyecto se descarga completo incluyendo el modelo Inception de TensorFlow. TensorFlow es una plataforma con recursos para proyectos de Machine Learning, en este caso usamos un modelo que nos ayuda a obtener las características de una imagen para luego poder clasificarla.
Como en casi toda solución de Machine Learning encontraremos dos proyectos, uno para realizar el entrenamiento del modelo (ImageClassification.Train) y otro para realizar la predicción con nuevas imágenes (ImageClassification.Predict), ambos son proyectos de consola.
La explicación completa del código está en la misma página de GitHub del proyecto.

La parte más compleja de comprender, y que vale la pena explicarlo, es la construcción del Pipeline. A diferencia de cuando usamos el AutoML para la selección del algoritmo de clasificación, en este proyecto se especifica de forma manual el algoritmo de clasificación a utilizar.
var dataProcessPipeline = mlContext.Transforms.Conversion.MapValueToKey(outputColumnName: LabelAsKey, inputColumnName: "Label")
.Append(mlContext.Transforms.LoadImages(outputColumnName: "image_object", imageFolder: imagesFolder, inputColumnName: nameof(DataModels.ImageData.ImagePath)))
.Append(mlContext.Transforms.ResizeImages(outputColumnName: "image_object_resized",
imageWidth: ImageSettingsForTFModel.imageWidth, imageHeight: ImageSettingsForTFModel.imageHeight,
inputColumnName: "image_object"))
.Append(mlContext.Transforms.ExtractPixels(outputColumnName:"input", inputColumnName:"image_object_resized",
interleavePixelColors:ImageSettingsForTFModel.channelsLast,
offsetImage:ImageSettingsForTFModel.mean,
scaleImage:ImageSettingsForTFModel.scale)) //for Inception v3 needs scaleImage: set to 1/255f. Not needed for InceptionV1.
.Append(mlContext.Model.LoadTensorFlowModel(inputTensorFlowModelFilePath).
ScoreTensorFlowModel(outputColumnNames: new[] { "InceptionV3/Predictions/Reshape" },
inputColumnNames: new[] { "input" },
addBatchDimensionInput: false)); // (For Inception v1 --> addBatchDimensionInput: true) (For Inception v3 --> addBatchDimensionInput: false)
// 3. Set the training algorithm and convert back the key to the categorical values
var trainer = mlContext.MulticlassClassification.Trainers.LbfgsMaximumEntropy(labelColumnName: LabelAsKey, featureColumnName: "InceptionV3/Predictions/Reshape"); //"softmax2_pre_activation" for Inception v1
var trainingPipeline = dataProcessPipeline.Append(trainer)
.Append(mlContext.Transforms.Conversion.MapKeyToValue(PredictedLabelValue, "PredictedLabel"));
Al revisar el código lo primero que vemos es que los datos necesitan una transformación previa con el método MapValueToKey que sirve para transformar los valores en storing de las categorías a valores numéricos, esto mejora el entrenamiento del algoritmo que vamos a usar para la clasificación.
Los pasos para cargar el modelo de TensorFlow son necesarios para que este «caracterice los datos», es decir, agregue las características que puede detectar en las imágenes en los datos de entrenamiento.
La tarea a utilizar es la de clasificación multiclase (MulticlassClassification) y el algoritmo seleccionado es LbfgsMaximumEntropy, se puede probar el entrenamiento con otros algoritmos pero siempre que sean de la tarea MulticlassClassification.
Una vez que tengamos nuestro proyecto listo y compilado tendremos problemas al ejecutar el proyecto ImageClassification.Train ya que Mac OS requiere de ciertos paquetes para poder usar el modelo de TensorFlow. El error que arroja es el siguiente:
The type initializer for ‘System.Drawing.GDIPlus’ threw an exception. — -> System.DllNotFoundException: Unable to load DLL ‘gdiplus’: The specified module or one of its dependencies could not be found.
Para que el Mac OS reconozca la libreria faltante primero se debe instalar el gestor de paquetes Homebrew con el siguiente commando:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Una vez instalado el gestor de paquetes instalamos la libreria faltante con el siguiente commando.
brew install mono-libgdiplus
Luego de actualizar el proyecto a .Net Core 3.0 y ML.NET 1.4 podemos ejecutar el proyecto ImageClassification.Predict para consumir el modelo creado y realizar las predicciones pero al momento de cargar una imagen arroja la siguiente excepción:
"DllNotFoundException: Unable to load DLL 'tensorflow': The specified module could not be found"
Esto se debe a un bug en las referencias que guarda ML.NET luego de actualizar la versión a la 1.4.0. Para solventar este error de referencias debemos agregar el siguiente paquete Nuget al proyecto ImageClassification.Predict
dotnet add package SciSharp.TensorFlow.Redist
Con estos ajustes se puede ejecutar el proyecto y probar las predicciones con el proyecto de consola.

Artículo relacionado: Segmentación de clientes usando ML.NET