MLOps — Módulo 5: pipelines de entrenamiento — automatizar el ciclo
Quinto módulo del Curso de MLOps: dejar de correr celdas a mano y armar un pipeline reproducible datos → features → train → eval → register. Qué es un orquestador y un DAG, cuándo usar Airflow, Prefect, Kubeflow o Vertex AI Pipelines, y un flujo real de Prefect comentado.
☰ Índice del contenido
En el Módulo 4 guardamos el modelo ganador en un registry, con su versión, sus métricas y su linaje. Pero mira cómo llegó ahí: probablemente corriendo celdas de un notebook en orden, a mano, con la fe de que las corriste todas y en la secuencia correcta. Eso funciona una vez. El problema es cuando hay que rehacerlo — con datos nuevos, con un colega, dentro de tres meses. “Corre el notebook de arriba a abajo, pero sáltate la celda 12, y ojo que la 7 hay que correrla dos veces” no es un proceso: es una leyenda oral. Este módulo convierte ese ritual en un pipeline que una máquina puede correr sola, igual, todas las veces.
La tesis del módulo
Un modelo que solo tú sabes reproducir no está en producción, está en tu cabeza. El entrenamiento a mano es un experimento; el entrenamiento como pipeline es un proceso. Cuando datos → features → train → eval → register es código que corre solo, deja de importar quién aprieta el botón — y eso es lo que permite programarlo, reintentarlo y confiar en él.
Del notebook al pipeline
Un pipeline de entrenamiento es la secuencia de pasos que va del dato crudo al modelo registrado, escrita como código que se ejecuta de principio a fin sin intervención humana. Los cinco pasos canónicos:
- datos — traer y validar la materia prima (una tabla, un export, una consulta).
- features — transformar esos datos en las columnas que el modelo consume (lo que viste en ml-08).
- train — entrenar el modelo con esas features.
- eval — medir el modelo contra un set de prueba y decidir si sirve.
- register — si pasó el corte, guardarlo en el registry con su versión y métricas.
La palabra clave es reproducible: mismos datos y mismo código producen el mismo modelo, lo corra quien lo corra, en tu notebook o en un servidor a las 3 de la mañana.
Orquestador y DAG: los dos términos base
Orquestador (orchestrator) — el software que corre los pasos del pipeline en el orden correcto, espera a que cada uno termine, reintenta los que fallan, y te avisa si algo se rompe. Tú describes qué pasos hay y qué depende de qué; el orquestador se encarga del cuándo y del cómo.
DAG — directed acyclic graph, grafo dirigido acíclico. Es la forma en que el orquestador representa tu pipeline: cada paso es un nodo, cada dependencia es una flecha, y “acíclico” significa que las flechas nunca vuelven hacia atrás (un paso no puede depender de sí mismo, ni formar un círculo). El DAG es el mapa que le dice al orquestador qué puede correr en paralelo y qué tiene que esperar.
Este es el DAG del pipeline de entrenamiento de este módulo:
START
│
▼
┌────────┐
│ datos │ traer y validar la tabla cruda
└────────┘
│
▼
┌────────┐
│features│ construir las columnas del modelo
└────────┘
│
▼
┌────────┐
│ train │ entrenar con las features
└────────┘
│
▼
┌────────┐
│ eval │ medir contra el set de prueba
└────────┘
│
▼
┌──────────────────────┐
│ ¿pasa el corte? │
│ sí ──► register │
│ no ──► descarta │
└──────────────────────┘
│
▼
END
Un pipeline real con Prefect
Vamos a escribirlo con Prefect, un orquestador de Python que se siente como código normal: decoras tus funciones y él construye el DAG por ti. Cada paso es una task (@task) y el pipeline completo es un flow (@flow). La gracia es que el mismo archivo que corres en tu máquina es el que después programas en un servidor.
# pipeline.py — pipeline de entrenamiento con Prefect
from prefect import flow, task
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
import mlflow
@task(retries=2, retry_delay_seconds=30)
def cargar_datos() -> pd.DataFrame:
"""Trae la tabla cruda. Reintenta si la fuente falla (red, permisos)."""
df = pd.read_parquet("s3://datos/clientes.parquet")
if df.empty:
raise ValueError("La tabla llegó vacía; abortar antes de entrenar.")
return df
@task
def construir_features(df: pd.DataFrame) -> pd.DataFrame:
"""Mismas transformaciones que en train y en prod (ver Módulo 6)."""
df = df.copy()
df["antiguedad_meses"] = df["antiguedad_dias"] / 30
df["usa_app"] = (df["logins_app_30d"] > 0).astype(int)
return df.drop(columns=["antiguedad_dias"])
@task
def entrenar(df: pd.DataFrame):
X = df.drop(columns=["target"])
y = df["target"]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=42)
modelo = RandomForestClassifier(n_estimators=300, random_state=42)
modelo.fit(X_tr, y_tr)
return modelo, X_te, y_te
@task
def evaluar(modelo, X_te, y_te) -> float:
"""Devuelve la métrica; el flow decide si registra o descarta."""
proba = modelo.predict_proba(X_te)[:, 1]
return roc_auc_score(y_te, proba)
@task
def registrar(modelo, auc: float):
"""Solo se llama si el modelo pasó el corte. Registra en MLflow (Módulo 4)."""
with mlflow.start_run():
mlflow.log_metric("roc_auc", auc)
mlflow.sklearn.log_model(
modelo, name="modelo", registered_model_name="churn_clientes"
)
@flow(name="entrenamiento-churn")
def entrenamiento(umbral_auc: float = 0.80):
df = cargar_datos()
df = construir_features(df)
modelo, X_te, y_te = entrenar(df)
auc = evaluar(modelo, X_te, y_te)
# El gate de calidad: no todo lo que entrena merece registrarse.
if auc >= umbral_auc:
registrar(modelo, auc)
print(f"Registrado. AUC={auc:.3f}")
else:
print(f"Descartado. AUC={auc:.3f} < umbral {umbral_auc}")
if __name__ == "__main__":
entrenamiento()
Fíjate en lo que ganaste solo con decorar funciones: cargar_datos reintenta sola dos veces si la fuente parpadea; el if auc >= umbral_auc es un gate que impide registrar un modelo malo; y todo el flujo es una función que puedes correr con python pipeline.py hoy y programar mañana sin cambiar una línea de la lógica.
Cuidado: un pipeline reproducible necesita que todo esté fijo, no solo el código. El
random_state=42congela la aleatoriedad del split y del modelo; la versión de los datos la congela DVC (Módulo 2); y las versiones de las librerías, tu lockfile. Si dejas cualquiera de esos tres al azar, “reproducible” se te desarma en silencio.
Programarlo y reintentarlo
Correr python pipeline.py a mano sigue siendo a mano. El paso final es entregarle el flow al orquestador para que lo corra según un horario. En Prefect eso es un deployment con un cron:
# El work pool (quién ejecuta los runs) tiene que existir primero.
prefect work-pool create mi-pool --type process
# Programar el flow para que corra cada lunes a las 6:00.
prefect deploy pipeline.py:entrenamiento \
--name churn-semanal \
--cron "0 6 * * 1" \
--pool mi-pool
Desde ahí el pipeline es autónomo: corre solo el lunes, reintenta las tasks que fallan, y te muestra en el panel de Prefect qué pasos pasaron en verde y cuáles en rojo — el mismo espíritu del CI que viste en deploy-08, pero aplicado al entrenamiento en vez de al código.
Qué orquestador elegir
No todos sirven para lo mismo. La tabla mental:
| Orquestador | Sabor | Cuándo es tu opción |
|---|---|---|
| Prefect | Python puro, decoradores | Empezar rápido, equipos de datos, pipelines de ML sin infra pesada |
| Airflow | El veterano, DAGs explícitos | Ya está en la empresa, muchos pipelines de datos, ecosistema enorme |
| Kubeflow Pipelines | Nativo de Kubernetes | Ya vives en K8s y quieres cada paso en su propio contenedor |
| Vertex AI Pipelines | Gestionado en Google Cloud | Estás en GCP y no quieres administrar el orquestador tú mismo |
Regla de decisión: si arrancas hoy y estás en Python, Prefect te da casi todo con una fracción del esfuerzo. Airflow gana cuando ya es el estándar de tu empresa; Kubeflow y Vertex ganan cuando la plataforma (Kubernetes o GCP) ya te viene impuesta.
Cuidado con el orquestador de moda. No elijas Kubeflow porque suena serio. Un orquestador es infraestructura que hay que mantener, actualizar y depurar cuando falla a las 3 AM. Empieza con el más liviano que resuelva tu caso y sube de peso solo cuando el dolor lo justifique.
Errores comunes
- Confundir un notebook ordenado con un pipeline. Que las celdas estén en orden no las hace reproducibles: sigue dependiendo de que un humano las corra bien. El pipeline es código que corre sin ti.
- Meter las credenciales en el flow. El pipeline corre solo, en un servidor: las llaves van por variables de entorno o el gestor de secretos del orquestador, nunca en
pipeline.py. Es el principio de secretos del curso de Deploy. - Un pipeline sin gate. Si
registercorre siempre, tu registry se llena de modelos malos. Elif auc >= umbralno es opcional: es lo que separa “entrené algo” de “tengo un candidato válido”. - No fijar la aleatoriedad. Sin
random_state, cada corrida entrena un modelo distinto con los mismos datos y “reproducible” es mentira. Congela el azar. - Reintentar lo que no debe reintentarse.
retrieses genial para fallos de red, pésimo para un error de lógica: reintentar tres veces un bug solo tarda tres veces más en fallar.
La frase para llevarse
“El notebook responde ‘¿funciona?’. El pipeline responde ‘¿funciona igual mañana, sin mí, con datos nuevos?’. Producción solo entiende la segunda pregunta.”
Qué viene
Módulo 6: feature stores. En el pipeline de hoy, construir_features vive dentro del entrenamiento — pero en producción el modelo necesita esas mismas features calculadas igualito, o falla en silencio. Ese desajuste tiene nombre (train-serve skew) y una infraestructura pensada para matarlo: el feature store.
Material relacionado
Preguntas sobre este curso
Regístrate gratis para hacer preguntas y guardar tu progreso.
✉ Entérate de lo nuevo
Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.