Tradicionalmente los modelos de Machine Learning están en el dominio de los científicos de datos. Son éstos quienes se encargan de definir los modelos, entrenarlos y usarlos para realizar las predicciones.
Habitualmente los dispositivos son meros capturadores de información. Estos datos se envían a la nube dónde son procesados por dichos modelos.
Pero esta visión ha cambiado recientemente gracias a la posibilidad de crear modelos, entrenarlo y ejecutarlos en máquinas locales. Esto nos permite además poder publicarlos como módulos en el Edge lo que habilita el poder tomar decisiones en tiempo real en base a las predicciones.
Para poder realizar esta implementación, haremos uso de la librería ML.NET de Microsoft.
La iniciación en la creación de modelos de Inteligencia Artificial es una tarea que resulta muy compleja para desarrolladores que no son expertos en ciencia de datos.
Requiere el conocimiento y aplicación de modelos matemáticos que resultan complicados de implementar y utilizar en los desarrollos.
Pero las cosas han cambiado en parte tras la publicación por parte de Microsoft de la librería ML.NET.
No nos engañemos, si queremos entrar en materia no nos va a quedar más remedio que profundizar en el conocimiento de los modelos matemáticos a los que hacía referencia, pero, sin embargo, con esta librería se nos brinda la posibilidad de ver dichos modelos como una Caja Negra.
Pero, con ML.NET, Microsoft nos brinda la posibilidad de utilizar Visual Studio/Visual Studio Code y C# para crear, entrenar y publicar nuestro propio modelo de Machine Learning utilizando uno de los muchos algoritmos que vienen integrados por defecto en la propia librería.
De esta forma, prescindimos de la necesidad de aprender a desarrollar con alguno de los lenguajes de programación habituales para ello como por ejemplo Python, R, etc…
Adicionalmente, otra ventaja muy importante que conseguimos mediante el uso de esta librería es la posibilidad de entrenar nuestro modelo directamente en nuestra máquina local, prescindiendo de cualquier infraestructura Cloud.
Pero…, ¿es realmente tan sencillo de implementar como parece a primera vista?
Microsoft realmente está intentando facilitarle la vida al desarrollador lo máximo posible.
Un ejemplo de esto es que actualmente en Visual Studio podemos utilizar un pequeño asistente que nos crea toda la infraestructura necesaria para algunos de los casos de uso más comunes.
Esta extensión, denominada ML.NET Model Builder aun se encuentra en Preview, pero actualmente proporciona un asistente para algunos de los escenarios más comunes.
Para realizar este laboratorio, es necesario instalar las siguientes herramientas en nuestra máquina:
También será necesario disponer de una suscripción de Azure para la provisión de todos los recursos utilizados en el laboratorio.
Una vez que hemos cubierto los prerrequisitos en el entorno de desarrollo, podemos proceder a crear nuestro modelo.
Para ello, crearemos dos proyectos en Visual Studio. Uno de ellos contendrá el código necesario para la creación de nuestro modelo.
El otro, contendrá lo necesario para ejecutar predicciones contra dicho modelo.
Pese a que tenemos asistentes en Visual Studio para algunos escenarios, crearemos uno a medida con un algoritmo de detección de anomalías que no está cubierto en los escenarios predefinidos.
La estructura de nuestra solución será la siguiente:
El primer paso que debemos seguir consiste en añadir el paquete nuget Microsoft.ML en ambos proyectos.
De esta forma podremos crear, entrenar y publicar el modelo.
Una vez agregado el paquete Nuget, podemos proceder a crear nuestra librería para definir el modelo.
Para ello, debemos analizar nuestro origen de datos para determinar la estructura.
Para facilitar este proceso, utilizaremos como origen de datos mediciones que incluyen:
Y para entrenar nuestro modelo, dispondremos de un CSV con distintas mediciones y una columna que indicará si las mediciones son anómalas.
Nuestro CSV tendrá cinco columnas con los datos necesarios para realizar la predicción, incluyendo la columna que indica si la medición es anómala o no.
Con estos datos, procedemos a crear los artefactos necesarios para definir nuestro modelo.
En primer lugar, debemos definir las clases que representan la entrada de datos y la respuesta con la medición.
MachineData.cs
using System; using Microsoft.ML.Data; namespace PredictiveManteinance.Model { public class MachinePrediction { public float Score { get; set; } [ColumnName("PredictedLabel")] public bool Anomally { get; set; } public float Probability { get; set; } } }
MachinePrediction.cs
using System; using Microsoft.ML.Data; namespace PredictiveManteinance.Model { public class MachinePrediction { public float Score { get; set; } [ColumnName("Anomally")] public bool Anomally { get; set; } public float Probability { get; set; } } }
Una vez hecho esto, debemos proporcionar los componentes necesarios para construir nuestro modelo.
Para ello, crearemos una clase denominada ModelBuilder en el proyecto PredictiveManteinance.Builder.
A continuación enumeraremos los pasos a seguir con los code snippets necesarios.
using Microsoft.ML; using Microsoft.ML.Data;
private static MLContext mlContext = new MLContext(seed: 1);
static readonly string TEMPERATURE_DATA_PATH = Path.Combine(Environment.CurrentDirectory, "Data", "temperature_data.csv"); static readonly string MODEL_PATH = Path.Combine(Environment.CurrentDirectory, "Model", "model.zip");
IDataView trainingDataView = mlContext.Data.LoadFromTextFile<ModelInput>( path: TEMPERATURE_DATA_PATH, hasHeader: true, separatorChar: ';', allowQuoting: true, allowSparse: false);
var dataProcessPipeline = mlContext.Transforms.Concatenate("Features", new[] { "machine_temperature", "machine_pressure", "ambient_temperature", "ambient_humidity" });
var trainer = mlContext.BinaryClassification.Trainers.LightGbm(labelColumnName: "anomally", featureColumnName: "Features"); var trainingPipeline = dataProcessPipeline.Append(trainer); ITransformer model = trainingPipeline.Fit(trainingDataView);
var crossValidationResults = mlContext.BinaryClassification.CrossValidate(trainingDataView, trainingPipeline, numberOfFolds: 5, labelColumnName: "anomally");
mlContext.Model.Save(mlModel, modelInputSchema, GetAbsolutePath(MODEL_PATH));
Una vez que hemos definido y creado el modelo, sólo tenemos que importarlo en nuestra aplicación y utilizarlo para realizar las predicciones.
Para ello, vamos a agregar una clase que nos permita el consumo en el proyecto PredictiveManteinance.Model.
Esta clase la denominaremos ModelConsumer.cs y permitirá carga el modelo y ejecutar una predicción sobre el mismo.
MLContext mlContext = new MLContext(); string modelPath = "model.zip"; ITransformer mlModel = mlContext.Model.Load(modelPath, out var modelInputSchema); var predEngine = mlContext.Model.CreatePredictionEngine<MachineData, MachinePrediction>(mlModel);
Lo que hacemos es cargar el modelo creado anteriormente en el fichero .zip y a partir del mismo crear el motor de predicción.
MachineData sampleData = new MachineData () { Machine_temperature = Convert.ToSingle(101.937256), Machine_pressure = Convert.ToSingle(10.2207), Ambient_temperature = Convert.ToSingle(21.497833), Ambient_humidity = Convert.ToSingle(26) }; MachinePrediction result = PredictionEngine.Value.Predict(input);
Como respuesta obtendremos un objeto MachinePrediction con una propiedad denominada Anomally que indica si la medición es anómala o no.
Adicionalmente devuelve una propiedad Probability que devuelve un porcentaje de certeza sobre la medición.
El siguiente paso del laboratorio consiste en publicar el modelo como un módulo en el Edge.
Gracias al uso de ML.NET, no tendremos ninguna complicación para utilizar el modelo en un contenedor.
Para ello, crearemos un proyecto en Visual Studio Code con la plantilla de módulo de Azure IoT Edge. Este asistente nos creará el esqueleto del proyecto para añadir nosotros nuestro código.
Una vez creado el proyecto del módulo, copiaremos las clases del modelo del proyecto PredictiveManteinance.Model, de esta forma evitaremos dependencias externas al crear la imagen.
Es importante que añadamos el fichero con el modelo generado model.zip como recurso del proyecto para que siempre se copie cuando se genere la imagen de Docker del módulo.
Si hacemos todos los pasos correctos, tendremos una estructura de proyecto similar a esta..
Nuestro IoT Edge utilizará como origen de datos el módulo que genera simulaciones de datos de temperatura.
Para recibir correctamente estos datos en nuestro módulo de predicción, definimos la clase MachineSensorData.cs, la cuál usaremos para representar el objeto JSON que recibimos de dicho módulo.
public partial class Temperatures { [JsonProperty("machine")] public Machine Machine { get; set; } [JsonProperty("ambient")] public Ambient Ambient { get; set; } [JsonProperty("timeCreated")] public DateTimeOffset TimeCreated { get; set; } [JsonProperty("anomally")] public bool? Anomally { get; set; } } public partial class Ambient { [JsonProperty("temperature")] public float Temperature { get; set; } [JsonProperty("humidity")] public float Humidity { get; set; } } public partial class Machine { [JsonProperty("temperature")] public float Temperature { get; set; } [JsonProperty("pressure")] public float Pressure { get; set; } }
La lógica del módulo la implementaremos en el fichero Program.cs de nuestro proyecto de IoT Edge.
En el fondo no es más que una aplicación de consola que se queda en ejecución de forma indefinida cuando se inicia el contenedor del módulo en Docker o Moby.
El asistente de IoT Edge nos genera un Program.cs con una estructura predefinida que nos permite recibir los datos en el método PipeMessage.
Nosotros lo modificaremos para que consuma nuestro modelo de predicción y enriquezca el mensaje de salida añadiendo una propiedad para indicar si la medición es anómala o no.
Para conseguir esto, modificaremos la clase de la siguiente forma:
byte[] messageBytes = message.GetBytes(); string messageString = Encoding.UTF8.GetString(messageBytes); Temperatures sensorData = JsonConvert.DeserializeObject<Temperatures>(messageString, serializerSettings);
MachineData input = new MachineData() { Machine_pressure = sensorData.Machine.Pressure, Machine_temperature = sensorData.Machine.Temperature, Ambient_humidity = sensorData.Ambient.Humidity, Ambient_temperature = sensorData.Ambient.Temperature }; var modelOutput = ModelConsumer.Predict(input);
sensorData.Anomally = modelOutput.Anomally; messageString = JsonConvert.SerializeObject(sensorData, serializerSettings); messageBytes = Encoding.UTF8.GetBytes(messageString); using var pipeMessage = new Message(messageBytes); await moduleClient.SendEventAsync("output1", pipeMessage);
Una vez hemos desarrollado el módulo para el Edge, debemos realizar toda la provisión de la infraestructura.
en primer lugar aprovisionaremos una máquina virtual basada en una plantilla de dispositivo IoT Edge para desplegar nuestro módulo.
Adicionalmente aprovisionaremos un Container Registry para subir los módulos y un IoT Hub para el envío de mensajería y gobierno del dispositivo.
Los pasos que seguiremos para la provisión de la infraestructura son los siguientes.
az login az account set --subscription <subscriptionId>
az group create --location westeurope --resource-group predictive-manteinance-weu-rg --subscription <subscriptionId>
az acr create -n labacrpredman -g predictive-manteinance-weu-rg --sku Standard
az iot hub create --resource-group predictive-manteinance-weu-rg --name predictive-manteinance-weu-ih --sku F1 --partition-count 2 az iot hub device-identity create --hub-name predictive-manteinance-weu-ih --device-id EdgeDevice_01 --edge-enabled
az group deployment create --name EdgeDevice_01 --resource-group predictive-manteinance-weu-rg --template-uri "https://aka.ms/iotedge-vm-deploy" --parameters dnsLabelPrefix='edge-device-01-vm' --parameters adminUsername='adminlab' --parameters deviceConnectionString=$(az iot hub device-identity show-connection-string --device-id EdgeDevice_01 --hub-name predictive-manteinance-weu-ih -o tsv) --parameters authenticationType='password' --parameters adminPasswordOrKey="development1A"
Con este comando lo que estamos haciendo es crear una máquina virtual en Azure con el DNS edge-device-01-vm.
Esta máquina se provisiona con la plantilla de máquina de IoT Edge (https://aka.ms/iotedge-vm-deploy) y hacemos que se configure el motor del IoT Edge de la máquina.
Si hemos hecho todo correctamente y no han surgido errores, deberíamos tener algo similar a esto en el grupo de recursos de Azure.
Una vez provisionada la infraestructura, podemos conectarnos por ssh a la máquina y verificar que el motor del IoT Edge está funcionando correctamente.
ssh adminlab@edge-device-01-vm.westeurope.cloudapp.azure.com
Para verificar el estado ejecutaremos el comando iotedge status.
sudo -i systemctl status iotedge
Si todo está bien configurado obtendremos una respuesta similar a la siguiente.
Una vez tenemos provisionada toda la infraestructura requerida, el siguiente paso consiste en la publicación de nuestro módulo.
Esta tarea conlleva la realización de los siguientes pasos.
Para ello, es necesario en primer lugar habilitar el usuario administrador en nuestro Azure Container Registry labacrpredman.
Esto lo realizamos con el siguiente comando.
az acr update --admin-enabled true --name labacrpredman
Y con el siguiente comando obtendremos las credenciales para subir las imágenes.
az acr credential show --name labacrpredman
Una vez obtenemos esta información, debemos actualizar el fichero deployment.json de nuestra solución añadiendo estas credenciales.
A continuación, ejecutaremos el comando Build and Push IoT Edge Module en Visual Studio Code para publicar la imagen de docker en el ACR.
Será necesario adaptar el fichero module.json para establecer el nombre correcto de la imagen en el ACR.
{ "$schema-version": "0.0.1", "description": "", "image": { "repository": "labacrpredman.azurecr.io/predictivemanteinance", "tag": { "version": "0.0.1", "platforms": { "amd64": "./Dockerfile.amd64", "amd64.debug": "./Dockerfile.amd64.debug", "arm32v7": "./Dockerfile.arm32v7", "arm32v7.debug": "./Dockerfile.arm32v7.debug", "arm64v8": "./Dockerfile.arm64v8", "arm64v8.debug": "./Dockerfile.arm64v8.debug", "windows-amd64": "./Dockerfile.windows-amd64" } }, "buildOptions": [], "contextPath": "./" }, "language": "csharp" }
Si todo funciona correctamente, deberíamos tener en la respuesta de Visual Studio Code algo similar a esto.
The push refers to repository [labacrpredman.azurecr.io/predictivemanteinance] 32e54520ea3b: Pushed 0c457dc75b8b: Pushed 024bb70514d8: Pushed ba809ad0e044: Pushed 31bd36e157b6: Pushed 6185ed1ad590: Pushed f5600c6330da: Pushed 0.0.1-amd64: digest: sha256:d270298c033b2de7376155a6e2f07e15011772d1ebbff1fac3dcc4882ca8a623 size: 1790
A continuación generaremos un manifiesto de despliegue para el dispositivo.
Para ello ejecutaremos el comando Azure IoT Edge: Generate IoT Edge Deployment Manifest, al que le pasaremos como parámetro el fichero deployment.template.json.
Este comando lo que nos generaré es el template definitivo que se podrá aplicar sobre el dispositivo en la carpeta config del proyecto.
Si queremos aplicar este deployment en nuestro dispositivo, será necesario que nos conectemos con el Iot Hub desde Visual Studio Code.
Esto lo llevaremos a cabo de la siguiente forma.
Una vez conectado al IoT Hub, podemos seleccionar la opción Azure IoT Edge: Create Deployment for Single Device.
Esta opción desencadena un pequeño asistente que solicita seleccionar el dispositivo en el IoT Hub y el deployment que se quiere aplicar.
Al seleccionarlos, aplica la configuración al dispositivo en el IoT Hub, con lo que si accedemos a través del portal de Azure, podremos ver lo siguiente.
Y si accedemos por SSH al dispositivo y ejecutamos el comando docker logs PredictiveMaintenante, podremos comprobar como nuestro módulo estará generando mensajes con la propiedad Anomally que indica si la predicción es anómala o no en base al modelo.
Con ML.NET crear, entrenar y publicar un modelo de Machine Learning está a la alcance de los desarrolladores, incluso para proyectos IoT.
Existen multitud de algoritmos que se pueden utilizar para la creación de los modelos y la mayor complejidad radica en saber cuál utilizar en cada caso.
Todo el código utilizado es Open Source y se puede descargar del siguiente repositorio GitHub.
Este obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial 4.0 Internación