El playbook de la lección anterior terminaba en una casilla con nombre propio: "candidato a reentrenamiento → decisión sistemática". Esta lección construye esa decisión y todo lo que viene después. Veremos las tres políticas de disparo posibles y cuál elige CineClick, materializaremos por fin el esbozo de flows/reentrenamiento.py que dejamos en 05-03 — resolviendo de paso el leakage temporal que anunciamos en 03-03 —, y dejaremos claro el principio que gobierna todo el diseño: el sistema reentrena y propone, pero nunca promociona solo. Al final, el ciclo de vida que dibujamos en 01-02 quedará cerrado de verdad, y CineClick habrá cruzado al nivel 2 de madurez.
Contenido
- Las tres políticas de disparo
- La política de CineClick
- El dataset nuevo: point-in-time correctness (el leakage de 03-03, resuelto)
- El pipeline de reentrenamiento completo
- Evaluación: test congelado y test fresco
- Registrar como challenger, nunca como champion
- Reentrenar no siempre arregla; y no es gratis
- El bucle cerrado: CineClick alcanza el nivel 2
Las tres políticas de disparo
¿Cuándo debe reentrenarse un modelo? Hay tres familias de respuesta:
| Política | Cómo funciona | Pros | Contras |
|---|---|---|---|
| Programada periódica | Cada N semanas/meses, se reentrena pase lo que pase | Simple, predecible, presupuestable; cota superior a la edad del modelo | Reentrena sin necesidad (coste, riesgo, revisiones inútiles) o demasiado tarde si el mundo cambia entre ciclos |
| Por deriva / degradación | Se dispara cuando el detector de 06-02 o las métricas reales de 06-01 cruzan umbrales | Reacciona a la necesidad real; no gasta cuando todo va bien | Requiere la monitorización ya construida; umbrales mal calibrados = tormenta de reentrenamientos o silencio eterno |
| Por volumen de datos nuevos | Se dispara al acumular N etiquetas/filas nuevas | Garantiza que cada entrenamiento aporta información fresca suficiente | Ignora la urgencia: puede acumular despacio justo cuando el mundo cambia rápido; N es otro umbral que calibrar |
No son excluyentes, y de hecho la combinación programada + por degradación es el patrón más común en la industria: la programada como red de seguridad ("el modelo nunca tendrá más de X de antigüedad"), la de degradación como reflejo rápido ("si algo va mal, no esperamos al ciclo").
La política de CineClick
CineClick adopta exactamente esa combinación, con números que ya conoces — están alineados con los umbrales de 06-01, 06-02 y con el criterio de negocio que la CI convierte en bloqueo desde 05-01:
- Disparo programado: reentrenamiento mensual (primer martes de mes, tras el scoring y la detección de deriva del lunes).
- Disparo extraordinario, si ocurre cualquiera de:
- PSI > 0.25 en alguna feature relevante (el umbral de "cambio significativo" de 06-02), una vez que el playbook ha descartado error de datos upstream;
- recall real < 0.60 dos semanas seguidas en el job de verdad terreno de 06-01 — el mismo 0.60 que es umbral bloqueante en CI, porque el criterio de negocio es uno solo, se mida donde se mida. Se piden dos semanas para no reaccionar a ruido de una semana floja.
El disparo extraordinario no es automático al 100%: la alerta llega a #alertas-ml, un humano ejecuta el playbook de 06-02 (¿bug upstream? ¿cohorte legítima?) y, si el desenlace es "candidato a reentrenamiento", lanza el flow con un clic desde la UI de Prefect. La parte cara y mecánica (construir dataset, entrenar, evaluar, registrar) sí es 100% automática; el juicio de "¿tiene sentido reentrenar?" sigue siendo humano. Es la misma filosofía del módulo 5: automatizar la ejecución, no la decisión.
El dataset nuevo: point-in-time correctness (el leakage de 03-03, resuelto)
Aquí pagamos una deuda que arrastramos desde el módulo 3. En 03-03 lo anunciamos: el clientes_churn.csv estático con el que hemos trabajado no tiene timestamps, y eso escondía un problema que solo aflora ahora, al pasar a datos continuos. Si construimos el dataset de reentrenamiento con "el estado actual" de cada cliente, cometemos leakage temporal: las features de un cliente que se dio de baja en marzo incluirían tickets de soporte de abril — información posterior al evento que queremos predecir. El modelo aprendería a "predecir el pasado con datos del futuro", brillaría en validación y fracasaría en producción, donde el futuro no está disponible.
La regla se llama point-in-time correctness: para cada ejemplo de entrenamiento, las features se calculan exclusivamente con datos anteriores a un instante de referencia, y la etiqueta se observa después de ese instante. Definimos para cada cliente:
fecha_corte: el instante en que "hacemos la foto" (para las bajas, un margen antes de la baja; para los activos, una fecha de muestreo).- Features: agregados sobre eventos con
timestamp < fecha_corte. - Etiqueta: ¿hubo baja en la ventana
(fecha_corte, fecha_corte + 30 días]? (la misma ventana de 30 días del job de evaluación de 06-01 — el horizonte de la predicción es uno solo en todo el sistema).
# src/cineclick_churn/data.py — construcción point-in-time del dataset
import pandas as pd
HORIZONTE = pd.Timedelta(days=30)
def construir_dataset_pit(clientes: pd.DataFrame, eventos_tickets: pd.DataFrame,
eventos_visionado: pd.DataFrame, bajas: pd.DataFrame,
fecha_muestreo: pd.Timestamp) -> pd.DataFrame:
"""Dataset de entrenamiento con corte temporal por cliente.
clientes: maestro (id_cliente, fecha_alta, plan, metodo_pago, ...)
eventos_*: tablas de eventos CON timestamp (la novedad frente al CSV estático)
bajas: (id_cliente, fecha_baja)
"""
df = clientes.merge(bajas, on="id_cliente", how="left")
# 1. fecha_corte por cliente: 30 días antes de la baja para los que se fueron
# (queremos predecir la baja ANTES de que ocurra, no el día que ocurre),
# y la fecha de muestreo para los activos.
df["fecha_corte"] = df["fecha_baja"].sub(HORIZONTE).fillna(fecha_muestreo)
# 2. Etiqueta: baja dentro del horizonte posterior al corte.
df["abandono"] = (
df["fecha_baja"].notna() & (df["fecha_baja"] <= df["fecha_corte"] + HORIZONTE)
).astype(int)
# 3. Features SOLO con eventos anteriores al corte. ESTA línea es la que
# resuelve el leakage: el filtro temporal antes de agregar.
tickets_previos = (
eventos_tickets.merge(df[["id_cliente", "fecha_corte"]], on="id_cliente")
.query("timestamp < fecha_corte") # <-- el corte
.groupby("id_cliente").size().rename("tickets_soporte")
)
horas_previas = (
eventos_visionado.merge(df[["id_cliente", "fecha_corte"]], on="id_cliente")
.query("timestamp < fecha_corte") # <-- el corte
.groupby("id_cliente")["horas"].mean().rename("horas_semana")
)
df = df.join(tickets_previos, on="id_cliente").join(horas_previas, on="id_cliente")
df[["tickets_soporte", "horas_semana"]] = df[["tickets_soporte", "horas_semana"]].fillna(0)
# 4. antiguedad_meses también respecto al corte, no respecto a hoy.
df["antiguedad_meses"] = (
(df["fecha_corte"] - df["fecha_alta"]).dt.days / 30.44
).clip(lower=0).round().astype(int)
return df # ratio_tickets y el resto siguen viviendo en features.pyLo esencial está en los dos query("timestamp < fecha_corte") y en calcular antiguedad_meses respecto al corte: sin timestamps en los eventos, esta operación es imposible — por eso el CSV estático nos protegió del problema durante cinco módulos y por eso estalla precisamente aquí. En SQL sería el mismo patrón: JOIN ... ON e.timestamp < c.fecha_corte antes del GROUP BY. Nota que ratio_tickets no necesita cambios: features.py sigue siendo la fuente única de features derivadas (03-03), y como se calcula a partir de columnas ya cortadas en el tiempo, hereda la corrección.
Queda un segundo contaminante, ya conocido de 05-04: la propia campaña de retención. A los clientes marcados en riesgo se les ofrece un descuento; si funciona, no se dan de baja — y su etiqueta dice "no abandono" gracias a nuestra intervención, no gracias a que el modelo se equivocara. Entrenar con esos clientes enseña al modelo que su propio perfil de riesgo "no abandona". La solución ya la tenemos desplegada desde el módulo 1 y formalizada en 05-04: el grupo de control permanente, que nunca recibe el descuento. Las etiquetas del grupo de control son las únicas no intervenidas, y son las que alimentan tanto la evaluación real (06-01) como, con el peso adecuado, el reentrenamiento. Es el mejor ejemplo del curso de por qué las decisiones de MLOps se toman antes de necesitarlas: un grupo de control no se puede crear retroactivamente.
El pipeline de reentrenamiento completo
Ahora sí, materializamos el esbozo de flows/reentrenamiento.py que dejamos apuntado en 05-03 (dvc pull → dvc repro → registrar como challenger). La versión completa:
# flows/reentrenamiento.py
import subprocess
import mlflow
from prefect import flow, task
from prefect.blocks.notifications import SlackWebhook
UMBRAL_RECALL, UMBRAL_PRECISION = 0.60, 0.50 # el criterio de negocio, una vez más
@task(retries=2, retry_delay_seconds=600)
def construir_dataset_nuevo(fecha_muestreo: str) -> str:
"""Ejecuta construir_dataset_pit y deja el resultado bajo control de DVC."""
# ... llama a construir_dataset_pit(...) y escribe data/raw/clientes_churn.csv ...
subprocess.run(["dvc", "add", "data/raw/clientes_churn.csv"], check=True)
subprocess.run(["dvc", "push"], check=True) # al remote `almacen`
md5 = leer_md5_dvc("data/raw/clientes_churn.csv.dvc")
return md5 # linaje: qué datos exactos
@task
def entrenar_con_dvc() -> None:
subprocess.run(["dvc", "pull"], check=True)
# dvc repro re-ejecuta SOLO las etapas afectadas del pipeline del módulo 2:
# preparar_datos -> construir_features -> entrenar -> evaluar
subprocess.run(["dvc", "repro"], check=True)
@task
def evaluar_candidato(run_id: str) -> dict:
metricas = {
"congelado": evaluar_sobre("data/procesado/test.csv", run_id), # test histórico
"fresco": evaluar_sobre("data/procesado/test_fresco.csv", run_id), # datos recientes
}
return metricas
@task
def registrar_challenger(run_id: str, datos_md5: str, ventana: str) -> int:
cliente = mlflow.MlflowClient()
version = mlflow.register_model(f"runs:/{run_id}/modelo", "churn-cineclick")
cliente.set_registered_model_alias("churn-cineclick", "challenger", version.version)
# Tags de linaje: con esto, cualquier versión responde "¿de dónde sales?"
for k, v in {"datos_dvc_md5": datos_md5, "ventana_temporal": ventana,
"origen": "flow reentrenamiento-churn",
"flow_run": obtener_flow_run_id()}.items():
cliente.set_model_version_tag("churn-cineclick", version.version, k, v)
return version.version
@flow(name="reentrenamiento-churn")
def reentrenamiento(fecha_muestreo: str, motivo: str = "programado-mensual"):
datos_md5 = construir_dataset_nuevo(fecha_muestreo)
entrenar_con_dvc()
run_id = ultimo_run("churn-cineclick") # el run que dvc repro registró
metricas = evaluar_candidato(run_id)
m = metricas["congelado"]
if m["recall"] >= UMBRAL_RECALL and m["precision"] >= UMBRAL_PRECISION:
v = registrar_challenger(run_id, datos_md5, ventana=f"hasta {fecha_muestreo}")
SlackWebhook.load("alertas-ml").notify(
f":package: Nuevo challenger churn-cineclick v{v} ({motivo}). "
f"Congelado: recall {m['recall']:.2f} / precision {m['precision']:.2f}. "
f"Fresco: recall {metricas['fresco']['recall']:.2f}. "
f"Pendiente de revisión humana: docs/promocion-modelo.md"
)
else:
SlackWebhook.load("alertas-ml").notify(
f":x: Candidato descartado ({motivo}): recall {m['recall']:.2f} / "
f"precision {m['precision']:.2f} no supera el umbral. Revisar datos y run {run_id}."
)Nada aquí es nuevo como pieza — y esa es la gracia. dvc repro es el pipeline del módulo 2; el tracking y el registry son del módulo 3; los umbrales son los de la CI de 05-01; Prefect, sus reintentos y sus alertas son de 05-03. El flow solo encadena lo ya construido. Cuando un pipeline de reentrenamiento requiere piezas nuevas, suele ser síntoma de que los módulos anteriores dejaron agujeros.
Evaluación: test congelado y test fresco
El flow evalúa contra dos test sets, y la razón merece su propio apartado:
- Test congelado (
data/procesado/test.csv, versionado desde el módulo 2): el mismo examen que aprobaron v1 y v2. Es la única forma de comparar versiones entre sí en igualdad de condiciones — si cada versión se examina de un test distinto, "v3 tiene mejor recall que v2" no significa nada. Contra este test se aplican los umbrales bloqueantes. - Test fresco (
test_fresco.csv, extraído de las etiquetas más recientes del grupo de control, disjunto del entrenamiento): mide lo que de verdad importa hacia delante — ¿funciona el candidato sobre el mundo actual, incluida la cohorte de la promo? Un candidato puede brillar en el congelado (que envejece con el mundo) y pinchar en el fresco.
| Test | Responde a | Riesgo si es el único |
|---|---|---|
| Congelado | ¿Es mejor que las versiones anteriores, a igualdad de examen? | Aprobar modelos ajustados a un mundo que ya no existe |
| Fresco | ¿Funciona sobre la población actual? | Perder la comparabilidad histórica entre versiones |
Con el tiempo, el test congelado también se renueva (cuando se acumula deriva suficiente, se congela uno nuevo y se re-evalúan en él las versiones vivas), pero eso es una decisión documentada — un ADR, como veremos en 06-04 —, nunca un cambio silencioso.
Registrar como challenger, nunca como champion
La regla ya estaba escrita en el esbozo de 05-03 y ahora se entiende del todo: el flow registra el candidato como @challenger y jamás toca @champion. ¿Por qué tan tajante, si el candidato superó los umbrales?
Porque los umbrales offline son condición necesaria, no suficiente. Entre "aprobó el examen" y "merece el tráfico" está todo lo que el módulo 5 construyó: la revisión humana documentada del PR de promoción (docs/promocion-modelo.md + scripts/promocionar_champion.py, 05-02), las dos semanas de sombra obligatorias para todo challenger y el canary 5% → 25% → 100% con métricas de guardia (05-04). Un sistema que auto-promociona se salta exactamente las salvaguardas que existen porque los tests offline no capturan el comportamiento online. El reentrenamiento automatizado no crea un camino nuevo a producción: alimenta el camino que ya existe.
Los tags de linaje (datos_dvc_md5, ventana_temporal, flow_run) son la otra mitad de la regla: cuando dentro de seis meses alguien pregunte "¿con qué datos exactos se entrenó v3 y por qué existía?", la respuesta está en el registry, no en la memoria de Laura. En 06-04 esto se convertirá en pieza central de la auditabilidad.
Reentrenar no siempre arregla; y no es gratis
Dos advertencias que separan un sistema maduro de uno que se dispara en el pie:
Reentrenar no siempre arregla. Si la deriva detectada era un bug de datos upstream (el metodo_pago como "CARD", las horas en minutos), reentrenar no elimina el problema: lo aprende. El modelo nuevo se ajusta al dato corrupto, los tests pasan (el test fresco también está corrupto), y el bug queda consolidado con un lazo. Por eso el playbook de 06-02 va siempre antes que el disparo: el humano que revisa la alerta descarta el bug antes de lanzar el flow. Es la razón de fondo por la que el disparo extraordinario no es 100% automático.
Reentrenar no es gratis. Tres costes que crecen con la frecuencia:
- Cómputo: dataset completo + búsqueda de hiperparámetros + evaluación doble, cada vez.
- Riesgo: cada modelo nuevo es una oportunidad de regresión. Más versiones al año = más sombras, más canaries, más ocasiones de rollback.
- Fatiga de revisión: si cada semana hay un challenger esperando revisión humana, la revisión se vuelve trámite — y una salvaguarda que se aprueba sin mirar ya no es una salvaguarda.
El equilibrio de CineClick — mensual + extraordinario con filtro humano — sale de estos costes: frecuencia suficiente para que el modelo no envejezca (la deriva observada es de semanas/meses, no de horas), y suficientemente baja para que cada challenger reciba una revisión de verdad. Un sistema de recomendación de portada necesitaría otra respuesta; la política de disparo es una decisión de negocio con forma técnica.
El bucle cerrado: CineClick alcanza el nivel 2
Con esta pieza, el ciclo de vida que dibujamos en 01-02 — y que entonces era una promesa — queda cerrado de verdad:
flowchart LR
MON["Monitorización<br/>(06-01: servicio + modelo,<br/>tabla de predicciones)"] --> DER["Detección de deriva<br/>(06-02: Evidently semanal, PSI)"]
DER -->|"alerta #alertas-ml"| PB{"Playbook<br/>(humano)"}
PB -->|"bug upstream"| FIX["Arreglar datos<br/>(no reentrenar)"]
PB -->|"candidato"| TRIG["Disparo<br/>(extraordinario o mensual)"]
TRIG --> RE["reentrenamiento-churn<br/>(06-03: dataset point-in-time,<br/>dvc repro, doble evaluación)"]
RE -->|"supera umbrales"| CH["Registro como @challenger<br/>+ tags de linaje"]
CH --> REV{"Revisión humana<br/>(PR de promoción, 05-02)"}
REV --> SOM["Sombra 2 semanas<br/>(05-04)"]
SOM --> CAN["Canary 5→25→100%<br/>métricas de guardia"]
CAN --> CHAMP["@champion<br/>sirviendo al 100%"]
CHAMP --> MON
FIX --> MONFíjate en los dos rombos: son los únicos puntos donde el bucle se detiene a esperar a una persona, y están exactamente donde hay juicio que ejercer — interpretar la deriva y aprobar la promoción. Todo lo demás fluye solo.
Y ahora, la recapitulación prometida contra la escalera de madurez de 01-03. El nivel 2 exigía: entrenamiento automatizado y disparado por señales (✔ este flow, con sus dos políticas), monitorización de modelo en producción (✔ 06-01), detección de deriva que alimenta decisiones (✔ 06-02), y despliegue continuo del modelo con salvaguardas (✔ módulo 5). Al final del módulo 5 dijimos que a CineClick le faltaba "que el sistema mire solo": las tres últimas lecciones le han dado los ojos y los reflejos. CineClick está en el nivel 2. Lo que queda del curso no añade automatización: añade gobierno (06-04) y perspectiva (06-05).
Errores Comunes y Consejos
- Auto-promocionar "porque las métricas son mejores". Las métricas offline no ven latencia, ni comportamiento con tráfico real, ni desacuerdos por segmento. La sombra y el canary existen porque el examen y el trabajo son cosas distintas. Challenger siempre; champion, solo un humano.
- Construir el dataset con el estado actual de los clientes. Es el leakage temporal en su forma más común y más silenciosa: métricas de validación excelentes, producción decepcionante. Si tu dataset de churn no tiene una columna
fecha_corte(o equivalente), sospecha. - Evaluar solo contra el test congelado. Aprobarás modelos excelentes para 2025. El test fresco es lo que mide 2026.
- Reentrenar como respuesta automática a cualquier alerta de deriva. Si era un bug de datos, acabas de dárselo de comer al modelo. Playbook primero.
- Registrar sin linaje. Un challenger sin
datos_dvc_md5ni ventana temporal es un modelo huérfano: cuando algo vaya mal, no podrás reproducirlo ni explicar de dónde salió. Los tags cuestan cuatro líneas; escríbelas siempre. - Elegir la frecuencia de reentrenamiento por intuición. Mírala como decisión de negocio: velocidad de la deriva observada, coste de cómputo, capacidad real de revisión del equipo. Y revísala cuando cambien esas variables.
Ejercicios
-
Elige la política. Para cada sistema, propón una política de disparo (o combinación) y justifícala con la tabla de pros/contras: (a) un modelo de detección de fraude en pagos, donde los patrones adversarios cambian en días; (b) un modelo de tasación de inmuebles con etiquetas que tardan meses (precio real de venta); (c) el churn de CineClick si el negocio se expandiera a un país nuevo el próximo trimestre.
-
Caza el leakage. Marc propone simplificar
construir_dataset_pitasí: "para los clientes con baja, usamos como features sus agregados a fecha de la baja; total, un día arriba o abajo no importa". Explica qué dos problemas introduce respecto a la versión de la lección y cómo se manifestaría cada uno (uno en validación, otro en producción). -
El challenger sospechosamente bueno. El flow mensual registra v4 con recall 0.81 y precision 0.63 sobre el test congelado — muy por encima del 0.68/0.55 de v2. Antes de alegrarte, ¿qué tres comprobaciones harías usando el linaje y los artefactos del sistema, y qué esperarías encontrar en cada una si hay gato encerrado?
Soluciones
-
(a) Por deriva/degradación con umbrales agresivos y ventanas cortas, más una programada semanal como red: la deriva adversaria es rápida y esperar al ciclo mensual regala días al fraude; el coste de reentrenar es menor que el coste del fraude no detectado. (b) Programada con periodo largo (trimestral) + por volumen de etiquetas nuevas: con verdad terreno que tarda meses, un disparo por degradación de métricas reales reaccionaría tardísimo de todos modos, y disparar por deriva de entrada sin poder confirmar impacto genera reentrenamientos a ciegas; el volumen de etiquetas garantiza que cada ciclo aprende algo nuevo. (c) Mantener mensual + extraordinario, pero planificar un reentrenamiento proactivo al acumular suficiente historia del país nuevo — y hasta entonces, vigilar esa cohorte como segmento separado en el dashboard: es data drift legítimo anunciado con antelación, el caso ideal para adelantarse en lugar de reaccionar.
-
Problema 1 — leakage hacia la etiqueta: con
fecha_corte = fecha_baja, las features capturan el comportamiento durante el proceso de baja (el pico de tickets de quien llama para cancelar, el desplome de horas de quien ya decidió irse). El modelo aprende a reconocer "cliente dándose de baja", no "cliente que se dará de baja": recall espectacular en validación. Problema 2 — inutilidad en producción: el servicio puntúa clientes antes de que decidan irse; las señales de despedida que el modelo aprendió a buscar no existen aún en el momento de la predicción, así que el rendimiento real se hunde. Además desaparece el margen de actuación: predecir la baja el día de la baja no deja tiempo a la campaña de retención, que era el propósito de negocio. El corte 30 días antes (el horizonte) resuelve las dos cosas. -
Primera: con el tag
datos_dvc_md5, recuperar el dataset exacto y comprobar solapamiento entre entrenamiento ytest.csv(¿algún id_cliente en ambos? ¿se regeneró el test por accidente en eldvc repro?); si hay filas compartidas, ahí está el gato. Segunda: revisar la construcción point-in-time de esa ventana — un salto grande de recall es la firma clásica del leakage temporal (ejercicio 2); comparar la distribución defecha_cortey auditar una muestra de clientes con baja. Tercera: en el run de MLflow, comparar hiperparámetros y features contra v2 (¿entró una columna nueva sospechosamente predictiva, tipo "descuento de retención aplicado", que es consecuencia de la predicción y no causa?). Si las tres salen limpias, entonces sí: enhorabuena, y a la sombra dos semanas como todo el mundo — donde un modelo con leakage, por cierto, se delataría con desacuerdos masivos.
Conclusión
El bucle está cerrado. CineClick reentrenará cada mes — o antes, si el PSI supera 0.25 o el recall real pasa dos semanas por debajo de 0.60 — con un flow que construye el dataset con point-in-time correctness (la deuda de 03-03, saldada: features solo con datos anteriores al corte, etiquetas limpias gracias al grupo de control), re-ejecuta el pipeline reproducible de siempre, evalúa contra el test congelado y contra uno fresco, y registra el resultado como @challenger con su linaje completo. Nunca como champion: del challenger al tráfico, el camino sigue siendo el del módulo 5 — revisión humana, dos semanas de sombra, canary con métricas de guardia. Con ello, el ciclo de vida de 01-02 deja de ser un dibujo y CineClick cruza, por fin, al nivel 2 de madurez de la escalera de 01-03. Pero un sistema que decide sobre personas y se mejora a sí mismo plantea preguntas que ningún dashboard responde: ¿por qué se le ofreció retención a este cliente y no a aquel? ¿Trata igual de bien a todos los segmentos? ¿Qué documentamos, qué retenemos, qué exige la regulación? Eso — gobernanza, documentación y cumplimiento — es la siguiente lección.
Curso de MLOps
Módulo 1: Fundamentos de MLOps
- Qué es MLOps y por qué los modelos mueren en el notebook
- El ciclo de vida de un modelo de ML en producción
- Niveles de madurez MLOps y roles del equipo
- El proyecto del curso: del notebook a producción
Módulo 2: Del notebook al código reproducible
- Estructura de un proyecto de ML: del notebook al paquete
- Entornos reproducibles y gestión de dependencias
- Versionado de datos con DVC
- Pipelines de entrenamiento reproducibles
Módulo 3: Experimentos y registro de modelos
- Tracking de experimentos con MLflow
- Model registry: versionar y promocionar modelos
- Feature stores: cuándo y para qué
Módulo 4: Servir modelos en producción
- Patrones de despliegue: batch, online y streaming
- Un servicio de predicción con FastAPI
- Empaquetado con Docker
- Escalado y despliegue: Kubernetes y serverless
- Optimización de la inferencia: latencia y coste
Módulo 5: Automatización: CI/CD y orquestación
- CI para ML: tests de código, datos y modelos
- CD: automatizar el despliegue del modelo
- Orquestación de pipelines de ML
- Estrategias de release: shadow, canary y A/B
