Con el creciente desarrollo de modelos de Machine Learning, que aportan soluciones a múltiples problemas, se hace más necesario que nunca tener un proceso definido para simplificar el despliegue en producción de dichos modelos y lograr que sean realmente útiles. En este post te explicamos un enfoque básico de cómo lograrlo usando AWS, Docker y Terraform.

Pero, antes de entrar en materia, es importante tener claro a qué hace referencia el término MLOps y qué beneficios ofrece.

¿Qué es MLOps?

En esencia, MLOps es un conjunto de buenas prácticas para permitir que los modelos de Machine Learning (ML), que muchas veces se quedan como meros experimentos, se desplieguen en producción de manera ágil, consistente, constante y automática, permitiendo que aporten soluciones a los problemas por los cuales fueron concebidos, fomentando la cooperación y comunicación entre los equipos de Data Scientists y TI.

¿Qué beneficios ofrece MLOps?

Dentro de un proceso de MLOps existen múltiples pasos que cubren el ciclo de vida de un modelo ML, desde la extracción de datos, validación de los mismos, ingeniería de características, pasando por el entrenamiento, validación y despliegue de modelos, entre otros más que puedes ver aquí.

En este post nos vamos a centrar en los pasos relativos al entrenamiento, validación y despliegue de modelos y cómo lograr su automatización en la nube de AWS.

Ahora que ya tenemos claro en qué consiste MLOps y el alcance de este post, ¡¡podemos ponernos manos a la obra!!

Definición del proceso MLOps

El objetivo de este post es explicar cómo automatizar el entrenamiento, generación, despliegue y validación de un modelo ML básico basado en el típico ejemplo de la clasificación de la flor Iris.

En el siguiente diagrama observamos los servicios de AWS y demás tecnologías y, sus interacciones, que van a permitir generar el proceso básico de MLOps sobre el modelo ML citado anteriormente.

Diagrama de la arquitectura del proceso MLOps
Diagrama de la arquitectura del proceso MLOps

Cada uno de los servicios y tecnologías del diagrama anterior abarcaría un post completo. Por ello, vamos a explicar a grosso modo cada uno de ellos y cómo interactúan entre sí.

Servicios de AWS

A continuación, os contamos cada uno de los servicios de AWS que se van a utilizar y la interacción entre los mismos dentro del proceso de MLOps.

Docker

Herramienta para la generación de contenedores en base a los modelos ML, permitiendo su portabilidad a cualquier plataforma On-premise o cloud.

A grandes rasgos, debemos generar nuestro contenedor Docker con nuestro modelo ML y una jerarquización de carpetas previamente configurada, tal como se puede ver a continuación.

Jerarquización

raiz/
|-- Dockerfile
|-- build_push_ecr_image.sh
|-- model_src/
|   |-- requirements.txt
|   `-- serve
|   `-- train
|-- test_dir/
|   |-- input/
|   |   |-- config/
|   |   |   |-- hyperparameters.json
|   |   |   `-- resourceConfig.json
|   |   `-- data/
|   |        `-- <training>/
|   |               `-- <input data>
|   |-- model/
|   |     `-- <model files>
    `-- output/
           `-- failure

Podemos ver de forma más detalla la configuración necesaria en la documentación de AWS en este enlace: Sagemaker-scikit_bring_your_own.

Dockerfile

FROM python:3.6

# Get model name from args (we can have different prediction models in our stack and choose what model we want to deploy)
ARG MODEL_FOLDER_NAME=model_src
ARG DATA_INPUT=test_dir 
ARG SERVE=model_src/serve

# Set some environment variables. PYTHONUNBUFFERED keeps Python from buffering our standard
# output stream, which means that logs can be delivered to the user quickly. PYTHONDONTWRITEBYTECODE
# keeps Python from writing the .pyc files which are unnecessary in this case. We also update
# PATH so that the train and serve programs are found when the container is invoked. And set FLASK_APP to launch
# the scoring service
ENV PYTHONUNBUFFERED=TRUE
ENV PYTHONDONTWRITEBYTECODE=TRUE
ENV PROGRAM_PATH=/opt/program
ENV PATH=$PROGRAM_PATH:$PATH
ENV FLASK_APP=$PROGRAM_PATH/serve
ENV PATH=$DATA_INPUT:$PATH

# Set up the program in the image
COPY $MODEL_FOLDER_NAME $PROGRAM_PATH
WORKDIR $PROGRAM_PATH

#RUN apt-get -y update && apt-get install -y --no-install-recommends wget ca-certificates && rm -rf /var/lib/apt/lists/*

# Install libraries needed for training and scoring
RUN pip install -r requirements.txt

# Env vars needed for training
ENV PATH_PREFFIX=/opt/ml/
ENV TRAINING_ERROR_PATH=output/failure
ENV TRAINING_MODEL_PATH=model/model.pkl
ENV TRAINING_PARAMS_PATH=input/config/hyperparameters.json

COPY $DATA_INPUT $PATH_PREFFIX 

# Env var needed to be overwritten by the workflow with file path to train
ENV TRAINING_INPUT_DATA_PATH=input/data/training/iris_final.csv

Script SH - build and push

#!/usr/bin/env bash

docker_image=${1:-aws-docker-test}
docker_repositorie=${2:-aws-docker-test}
docker_path=${3:-../container/}
model_folder_name=${4:-model_src}
data_input=${5:-test_dir}

if [ "$docker_image" == "" ]
then
    echo "Usage: $0 <image-name>"
    exit 1
fi

chmod +x model_src/train
chmod +x model_src/serve

# construct the ECR name.
account=$(aws sts get-caller-identity --profile mlops-sagemaker --query Account --output text)
region=$(aws configure get region --profile mlops-sagemaker)
fullname="${account}.dkr.ecr.${region}.amazonaws.com/${docker_repositorie}:latest"

aws ecr describe-repositories --repository-names "${docker_repositorie}" --profile mlops-sagemaker > /dev/null 2>&1

# Check the error code, if it's non-zero then know we threw an error and no repo exists
if [ $? -ne 0 ]
then
    aws ecr create-repository --repository-name "${docker_repositorie}" --profile mlops-sagemaker > /dev/null
fi

sudo $(aws ecr get-login-password --region eu-west-1 | docker login --username AWS --password-stdin {user_name}.dkr.ecr.eu-west-1.amazonaws.com)

# Build the docker docker_image, tag it with the full name, and push it to ECR
docker build -t "${docker_image}" --build-arg MODEL_FOLDER_NAME="${model_folder_name}" --build-arg DATA_INPUT="${data_input}" ../container/

docker tag "${docker_image}" ${fullname}

docker push ${fullname

Terraform

Terraform es la herramienta con la que se define la Infrastructure as Code (IaC), es decir, la infraestructura sobre la cual se realizará el entrenamiento, validación y despliegue del modelo ML.

Dado que Terraform es Open Source, su uso es muy fácil y extendido, permitiendo la definición de la IaC con lenguaje de alto nivel HCL, además no está ligada a ningún proveedor Cloud. Para saber más sobre las cualidades de esta herramienta aquí tienes un post muy interesante.

Todos los servicios de AWS mencionados anteriormente se crean a partir de ficheros .tf, permitiendo que la creación de los recursos asociados a dichos servicios se pueda realizar en múltiples entornos de manera rápida y consistente, y manteniendo la trazabilidad de los cambios de la IaC en todo momento, a través del fichero tfstate, el cual se almacena en bucket de S3.

Vamos a explorar en qué consisten varios de los ficheros donde se definen los recursos a utilizar en AWS.

data "template_file" "sf_definition" {
  template = "${file(path_fichero_definicion_step_function)}"

  vars = {
    sagemaker_job_name       = "sagemaker_training_job_prueba"
    sagemaker_instance_count = 1
    sagemaker_volume_size_GB = 10
    sagemaker_s3_input_path  = "nombre_bucket_dataset_entrenamiento"
    sagemaker_s3_output_path = "nombre_bucket_resultados_entrenamiento"
    instance_training_type   = "ml.m5.4xlarge"
    role_arn                 = "arn_rol_con_permisos"
    ecr_image_repository     = "nombre_repositorio_ecr"
  }
}

resource "aws_sfn_state_machine" "create_state_machine" {
  name     = "sfn_state_machine_name"
  role_arn = "arn_rol_con_permisos"
  definition = data.template_file.sf_definition.rendered

  lifecycle {
    prevent_destroy = true
  }
}
resource "aws_cloudtrail" "trail_s3_event" {
  name           = "trail_s3_event"
  s3_bucket_name = "id_bucket_dataset_entrenamiento"

  include_global_service_events = false

  event_selector {
    read_write_type           = "All"
    include_management_events = true

    data_resource {
      type   = "AWS::S3::Object"
      values = ["arn_bucket_dataset_entrenamiento/training-data"]
    }
  }
}
resource "aws_cloudwatch_event_rule" "s3_event_rule" {
    name        = "event_rule_name"
    description = "Capture s3 event to trigger event target"

    event_pattern = <<PATTERN
  {
    "source": [
      "aws.s3"
    ],
    "detail-type": [
      "AWS API Call via CloudTrail"
    ],
    "detail": {
      "eventSource": [
        "s3.amazonaws.com"
      ],
      "eventName": [
        "PutObject"
      ],
      "requestParameters": {
        "bucketName": [
          "nombre_bucket_dataset_entrenamiento"
        ]
      }
    }
  }
  PATTERN
  }

  resource "aws_cloudwatch_event_target" "step_function_target" {
    role_arn  = "arn_rol_con_permisos"
    rule      = aws_cloudwatch_event_rule.s3_event_rule.name
    target_id = "SendToStepFunction"
    arn       = "arn_step_function"
  }

Una vez tengamos definidos todos los recursos de AWS necesarios para entrenar y desplegar un modelo ML, estos se pueden agrupar por módulos (tal como se puede ver en la imagen de abajo) permitiendo que puedan ser reutilizados para usarlos con múltiples modelos ML y/o crear recursos de manera consistente y repetible en los diferentes entornos donde se quiera desplegar un modelo ML.

Dichos módulos se deben “instanciar” en el módulo root llamado_ main.tf_, que será utilizado para crear todos los recursos en AWS mediante los comandos plan y apply.

Mediante el uso de workspaces es fácil desplegar la IaC en múltiples entornos sin necesidad de repetir código, ya que con una única definición en el módulo root, citado anteriormente, se puede lograr esto, manteniendo la trazabilidad y estado actual de la IaC en cada entorno por separado.

# Provider's definition 
provider "aws" {
  version = "~> 2.61"
  profile = "terraform_deploy"
  region  = "eu-west-1"
}

# Mandatory create previously backend's bucket-tfstate to stores tfstate file with real world resources deployed on AWS.
terraform {
  required_version = ">= 0.12"
  backend "s3" {
    bucket  = "bucket-tfstate"
    key     = "iac.tfstate"
    region  = "eu-west-1"
    encrypt = "true"
    profile = "terraform_deploy"
  }
}

#  Module for create Step Function and underlying SageMaker resources 
module "step_function_model_001" {
  source = "./../modules/step_functions"
}

#  Module for create CloudTrail resource 
module "cloud_trail_model_001" {
  source = "./../modules/cloud_trail"
}

#  Module for create CloudWatch Event Rule
module "cloudwatch_model_001" {
  source = "./../modules/cloudwatch"
}

Orquestación del proceso MLOps

Una vez que tenemos todas las piezas del puzzle, es decir, el código del modelo ML almacenado en el repositorio Git (p. ej. GitLab), el fichero Dockerfile para generar la imagen docker, la definición de la infraestructura de AWS necesaria para entrenar y desplegar el modelo, es momento de hacer que las piezas funcionen en conjunto.

Para lograr esto, vienen los ¡Pipelines de Jenkins al rescate!

Mediante la implementación de un pipeline compuesto por varias fases, como la generación de la imagen docker del modelo ML, hacer push de dicha imagen al repositorio ECR, generar la infraestructura definida en Terraform y validar que el endpoint del modelo se ha generado correctamente, estos pasos se pueden ejecutar automáticamente, respondiendo típicamente a un evento como un merge/pull request en la rama master del repositorio.

Un ejemplo del fichero Jenkinsfile escrito en groovy con la definición del pipeline podría ser el siguiente:

pipeline {
  agent any
  environment {
    TF_WORKSPACE = 'pro' //Sets the Terraform Workspace
    TF_IN_AUTOMATION = 'true'
  }
  stages {
    stage('Clone Repo') {
       steps {
          bat 'set'
          sh 'git clone --depth=1 <usuario@url_git_repo/nombre_repo.git>'
       }
    }
    stage('Build and Push Docker Image ML to ECR') {
       steps {
          sh 'cd mlops/cloud/docker/container && ./build_push_ecr_image.sh'
       }
    }
    stage('TF Plan Core IaC') {
       steps {
          sh 'cd mlops/IaC/environments/core && terraform init && terraform plan -var-file=core.tfvars'
       }
    }
    stage('TF Apply Core IaC') { //Deploy IaC core (role, policy, s3 bucket)
      steps {
          sh 'cd mlops/IaC/environments/core && terraform apply -auto-approve -var-file=core.tfvars'
      }
    }
    stage('TF Plan Remaining IaC') {
       steps {
         sh  'cd mlops/IaC/environments && terraform init && terraform plan -var-file=./${TF_WORKSPACE}/${TF_WORKSPACE}.tfvars'
       }
    }
    stage('TF Apply Remaining IaC') { //Deploy CloudTrail, CloudWatch, StepFunction...
      input{ //User must review and approve the IaC’s deploy
        message 'Apply Terraform?'
      }
      steps {
         sh 'cd mlops/IaC/environments && terraform apply -auto-approve -var-file=./${TF_WORKSPACE}/${TF_WORKSPACE}.tfvars'
      }
    }
  }
}
Fases del pipeline de Jenkins
Fases del pipeline de Jenkins

Ejecución del proceso MLOps

Ya tenemos el despliegue de todos los componentes necesarios para el entrenamiento de nuestro modelo, i.e., el modelo contenido y subido a un contenedor docker en AWS (ECR), para poder empezar la ejecución orquestada desde la Step Function.

Si Terraform nos sirve como orquestador para la creación y despliegue de la arquitectura deseada, Step Functions será nuestro orquestador de los servicios AWS que vamos a utilizar dentro de la propia plataforma.

Step Functions ejecutará de manera sincrona o asincrona los servicios para entrenar, guardar y desplegar el modelo, además de llamar a la función que realiza una predicción sobre el mismo.

Para que sea posible la comunicación entre los distintos servicios a través de la Step Function, debemos crear los roles con las políticas de acceso y ejecución (permisos mínimos) para: Sagemaker, Lambda, S3, etc.

El desencadenante de la ejecución en Step Functions es el evento PutObject de S3, que se realiza sobre el bucket donde se almacenará el fichero csv con los datos de entrada del entrenamiento del modelo ML. Cuando dicho fichero “iris_final.csv” se sube al bucket, se genera un evento PutObject, el cual es identificado y registrado por CloudTrail y, a continuación, CloudWatch ejecutará la regla que invoca a la Step Function en respuesta al evento mencionado sobre el bucket de S3. Aquí puedes consultar más información sobre cómo ejecutar Step Function en respuesta a un evento de S3.

Bucket de S3 con fichero de entrada del entrenamiento del modelo ML
Bucket de S3 con fichero de entrada del entrenamiento del modelo ML

Los pasos que se ejecutan en la Step Function se describen a continuación:

Ejecución del entrenamiento del modelo en SageMaker
Ejecución del entrenamiento del modelo en SageMaker
Modelo entrenado y generado por SageMaker, almacenado en S3
Modelo entrenado y generado por SageMaker, almacenado en S3
Resultado de la inferencia del job de transformación batch
Resultado de la inferencia del job de transformación batch
Configuración del Endpoint del modelo generado por SageMaker
Configuración del Endpoint del modelo generado por SageMaker
Endpoint generado por SageMaker y que es invocado por función Lambda
Endpoint generado por SageMaker y que es invocado por función Lambda

Una vez finalizada la ejecución de todos los pasos definidos en la Step Function, el modelo ML ya está listo para ser utilizado.

Finalización satisfactoria de la ejecución de la Step Function
Finalización satisfactoria de la ejecución de la Step Function

Conclusiones

Adoptar la filosofía de MLOps es muy importante hoy en día. Así como DevOps ha ayudado a facilitar la entrega continua de software tradicional a los usuarios, MLOps hace los mismo con los modelos de Machine Learning pero manteniendo un seguimiento continuo de los mismos, a través de métricas, para identificar alguna degradación en la precisión de los resultados y actuar en consecuencia.

Hemos podido comprobar lo sencillo que puede llegar a ser desplegar un modelo en AWS. Con un poco de conocimiento de la plataforma podemos implementar una solución práctica que se adhiera a nuestros requerimientos. Además, con las plataformas cloud las limitaciones como servidores, capacidad, escalabilidad no son un problema; solamente debes pagar por lo que utilizas.

El proceso MLOps descrito en este post no es agnóstico a los servicios propios que utilizan las diversas plataformas en la nube; sin embargo, es una solución eficaz que sirve como esqueleto para la implementación y réplica de los mismos servicios e infraestructuras en diferentes áreas, modificando solamente una pequeña parte del script o de los parámetros que se deben embeber.

Existen herramientas como Mlflow, que aborda desde otro enfoque el uso de MLOps, permitiendo desplegar modelos en On-premise o en Cloud, sin necesidad de usar servicios como Sagemaker, comparar los resultados de varias ejecuciones del entrenamiento de un modelo y decidir cuál es mejor.

Hay que aprovechar al máximo los diversos servicios administrados que ofrece AWS y que hemos descrito en este post, ya que facilitan la implementación de un proceso de MLOps, incluso más amplio del que hemos intentado explicar. Solo hay que saber en qué consisten los servicios que se quieren usar y entender el modo de integrarlos entre sí y, una vez hecho esto, los resultados que se obtienen son muy buenos a la hora de poner en producción modelos ML para su uso en la solución de problemas.

Cuéntanos qué te parece.

Enviar.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete