Introducción a Containers y su Impacto en ML

Lectura
15 min~6 min lectura

Concepto clave

Un container es una unidad estandarizada de software que empaqueta código y todas sus dependencias, permitiendo que una aplicación se ejecute de manera rápida y confiable de un entorno informático a otro. Imagina que eres un científico de datos que necesita enviar un modelo de machine learning a producción. Sin containers, podrías enfrentarte al clásico problema: "En mi máquina funciona". Esto ocurre porque tu entorno local tiene versiones específicas de Python, librerías y configuraciones que pueden no existir en el servidor de producción.

Los containers resuelven esto aislando la aplicación en un entorno autocontenido. Una analogía útil es pensar en un container de transporte marítimo: no importa si viaja en un barco, tren o camión, el contenido (tu aplicación) permanece intacto y funcional porque el container proporciona un entorno estándar. En ML, esto significa que puedes desarrollar un modelo con scikit-learn 1.3.0 y pandas 2.0.3 en tu portátil, y garantizar que se ejecutará exactamente igual en un servidor en la nube o en el clúster de Kubernetes de tu empresa.

Cómo funciona en la práctica

Veamos un ejemplo paso a paso de cómo un científico de datos podría usar containers para un flujo de ML típico. Supongamos que has entrenado un modelo de clasificación para predecir fraudes en transacciones financieras. Tu script de Python depende de varias librerías y archivos de datos.

  1. Definición del entorno: Creas un archivo Dockerfile que especifica la imagen base (por ejemplo, Python 3.9), instala las dependencias (pandas, scikit-learn, etc.) y copia tu código.
  2. Construcción de la imagen: Usas Docker para construir una imagen a partir del Dockerfile. Esta imagen es un paquete inmutable que contiene todo lo necesario para ejecutar tu modelo.
  3. Ejecución del container: Ejecutas un container basado en esa imagen. El container aísla tu aplicación, usando recursos del sistema anfitrión pero manteniendo su propio sistema de archivos y entorno.
  4. Despliegue: Puedes subir la imagen a un registro (como Docker Hub) y luego ejecutarla en cualquier máquina con Docker, asegurando consistencia desde desarrollo hasta producción.

Este proceso elimina los problemas de "dependencias faltantes" o "versiones incompatibles" que son comunes en proyectos de ML.

Codigo en accion

Aquí tienes un ejemplo funcional de un Dockerfile para un proyecto simple de ML. Primero, el "antes": un script de Python que podría fallar en otro entorno debido a dependencias.

# model.py
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
import pickle

# Cargar datos y entrenar modelo
data = pd.read_csv('data.csv')
X = data.drop('target', axis=1)
y = data['target']
model = RandomForestClassifier(n_estimators=100)
model.fit(X, y)

# Guardar modelo
with open('model.pkl', 'wb') as f:
    pickle.dump(model, f)

Ahora, el "después" con Docker: un Dockerfile que asegura que el entorno sea consistente.

# Dockerfile
# Usar una imagen base oficial de Python
FROM python:3.9-slim

# Establecer el directorio de trabajo en el container
WORKDIR /app

# Copiar el archivo de requisitos e instalar dependencias
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copiar el código de la aplicación y los datos
COPY model.py .
COPY data.csv .

# Comando para ejecutar la aplicación
CMD ["python", "model.py"]

El archivo requirements.txt contendría las dependencias como "pandas==2.0.3" y "scikit-learn==1.3.0". Para construir y ejecutar: docker build -t ml-model . y luego docker run ml-model.

Errores comunes

  • No fijar versiones de dependencias: Usar "pandas" en lugar de "pandas==2.0.3" en requirements.txt puede llevar a actualizaciones que rompan tu modelo. Siempre especifica versiones exactas para reproducibilidad.
  • Containers demasiado grandes: Incluir datos de entrenamiento o archivos innecesarios en la imagen aumenta el tamaño y el tiempo de despliegue. Usa volúmenes de Docker para datos y mantén las imágenes ligeras con imágenes base como python:3.9-slim.
  • Ignorar el contexto de construcción: Copiar todo el directorio con COPY . . puede incluir archivos como .git o virtualenvs, hinchando la imagen. Usa .dockerignore para excluirlos.
  • Ejecutar como root por defecto: Por seguridad, crea un usuario no privilegiado en el Dockerfile con RUN useradd -m appuser && chown -R appuser /app y USER appuser.
  • No manejar señales de terminación: En producción, los containers deben cerrarse limpiamente. Asegúrate de que tu código de ML maneje excepciones y use CMD en lugar de ENTRYPOINT para comandos simples.

Checklist de dominio

  • Entiendo qué es un container y cómo difiere de una máquina virtual (menos overhead, comparte el kernel del host).
  • Puedo escribir un Dockerfile básico para un script de Python que use librerías de ML como pandas o scikit-learn.
  • Sé cómo construir una imagen de Docker (docker build) y ejecutar un container (docker run) localmente.
  • Reconozco la importancia de fijar versiones en requirements.txt para garantizar reproducibilidad en ML.
  • Puedo listar containers en ejecución (docker ps) y gestionar su ciclo de vida (iniciar, detener, eliminar).
  • Sé cómo usar .dockerignore para excluir archivos innecesarios de la imagen.
  • Entiendo que los containers son efímeros y que los datos persistentes deben manejarse con volúmenes o almacenamiento externo.

Containerizar un script de preprocesamiento de datos

En este ejercicio práctico, containerizarás un script de Python que realiza preprocesamiento de datos, un paso común en flujos de ML. Sigue estos pasos:

  1. Crea un archivo llamado preprocess.py con el siguiente código que lee un CSV, limpia datos y guarda un resultado:
    import pandas as pd
    import sys
    
    # Leer argumento para el archivo de entrada
    input_file = sys.argv[1] if len(sys.argv) > 1 else 'data.csv'
    df = pd.read_csv(input_file)
    
    # Limpieza simple: eliminar filas con valores nulos y normalizar una columna numérica
    df_clean = df.dropna()
    if 'amount' in df_clean.columns:
        df_clean['amount_normalized'] = (df_clean['amount'] - df_clean['amount'].mean()) / df_clean['amount'].std()
    
    # Guardar resultado
    output_file = 'cleaned_data.csv'
    df_clean.to_csv(output_file, index=False)
    print(f"Datos limpiados guardados en {output_file}")
  2. Crea un archivo requirements.txt que especifique pandas==2.0.3.
  3. Escribe un Dockerfile que:
    • Use la imagen base python:3.9-slim.
    • Establezca un directorio de trabajo como /app.
    • Copie requirements.txt e instale dependencias.
    • Copie preprocess.py.
    • Defina un comando para ejecutar el script, pasando un archivo de entrada como argumento (p. ej., CMD ["python", "preprocess.py", "input.csv"]).
  4. Construye la imagen con docker build -t data-preprocessor ..
  5. Ejecuta un container que procese un archivo de ejemplo: crea un archivo sample_data.csv con algunas filas (puedes simular datos o usar un pequeño conjunto real) y ejecuta docker run -v $(pwd):/app/data data-preprocessor python /app/preprocess.py /app/data/sample_data.csv para montar el directorio actual.
  6. Verifica que se generó cleaned_data.csv en tu máquina local.
Pistas
  • Usa el flag -v en docker run para montar un volumen y acceder a archivos locales desde el container.
  • Asegúrate de que las rutas en el Dockerfile y al ejecutar sean consistentes; usa rutas absolutas como /app para evitar confusiones.
  • Si encuentras errores de permisos, revisa que los archivos estén accesibles y considera ajustar ownership en el Dockerfile.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.