El pipeline de CD de la lección 05-02 dejó una limitación anunciada: tras staging y la aprobación, la versión nueva llega a producción de golpe, a todas las réplicas. Para el código del API es un riesgo acotado; para un modelo es otra historia, porque el test set versionado — por riguroso que sea — no es el tráfico real. Un challenger con recall 0.71 offline puede comportarse de forma extraña con los clientes que llegan hoy por el flujo de cancelación: distribuciones que el histórico no contenía, combinaciones raras, volúmenes distintos. Esta lección presenta las tres estrategias para que un modelo nuevo se gane la confianza gradualmente: shadow (predice en la sombra, sin consecuencias), canary (recibe un porcentaje creciente de tráfico real) y A/B (experimento controlado para responder una pregunta de negocio). Las tres se implementan sobre lo que ya tenemos, y cierran el módulo — y con él, un nivel entero de madurez MLOps.
Contenido
- El problema: offline no garantiza online
- Shadow deployment: predecir sin consecuencias
- Qué se compara tras dos semanas de sombra
- Canary release: tráfico real, poco a poco
- Métricas de guardia y rollback automático
- A/B testing: cuando la pregunta es de negocio
- Comparativa y decisión de CineClick
- Cierre del módulo: dónde estamos en la escalera de madurez
El problema: offline no garantiza online
Las métricas offline del challenger se calculan sobre el test set versionado: un split de datos históricos, con random_state=42, congelado con DVC. Eso lo hace comparable (todos los candidatos se miden en el mismo terreno), pero no lo hace representativo del futuro. Motivos concretos por los que un buen modelo offline puede decepcionar online:
- El tráfico real no es una muestra aleatoria del histórico. Al endpoint
/predecirllegan sobre todo clientes en el flujo de cancelación — un subconjunto sesgadísimo de la base de clientes. El test set contiene de todo. - El mundo se movió. Entre el corte del dataset y hoy hubo campañas, subidas de precio, un plan nuevo. El test set es una foto; el tráfico es la película.
- Efectos que solo existen en producción. Latencia del modelo nuevo bajo carga, interacción con la caché TTL de 04-05, valores extremos que la validación permite pero el entrenamiento apenas vio.
- La métrica de ML no es la métrica de negocio. El modelo sirve para retener clientes; recall y precision son aproximaciones. Puede subir el F1 y no mover — o empeorar — la retención (lo veremos en el A/B).
Conclusión operativa: staging + smoke test verifican que el sistema funciona; no verifican que el modelo nuevo decide bien con el tráfico de hoy. Para eso hay que exponerlo a tráfico real — la cuestión es cuánto riesgo aceptas mientras lo compruebas. Las tres estrategias son tres respuestas a esa pregunta:
flowchart LR
A[Challenger aprobado<br/>offline: 05-01/05-02] --> B[Shadow<br/>riesgo cero: nadie ve sus predicciones]
B --> C[Canary<br/>riesgo acotado: 5% -> 25% -> 100%]
C --> D[A/B<br/>riesgo repartido: mide el impacto de NEGOCIO]Shadow deployment: predecir sin consecuencias
En un despliegue en sombra, el challenger recibe todo el tráfico en copia: para cada petición real se calcula también su predicción, se registra, y se descarta — la respuesta al cliente sale siempre del champion. Riesgo funcional: cero. Información obtenida: cómo se comportaría el challenger con exactamente el tráfico de producción.
Hay dos maneras de montarlo: un segundo deployment que recibe tráfico duplicado (a nivel de infraestructura, con un mirror en el ingress/service mesh), o — la opción simple que usará CineClick — que el propio servicio cargue ambos modelos y registre las dos probabilidades. Con el logging JSON por predicción que ya existe desde 04-02, la implementación es pequeña:
# src/cineclick_churn/api/main.py (fragmento: modo sombra)
import os
import time
import mlflow
MODO_SOMBRA = os.getenv("MODO_SOMBRA", "false").lower() == "true"
modelo_champion = mlflow.sklearn.load_model("models:/churn-cineclick@champion")
modelo_sombra = (
mlflow.sklearn.load_model("models:/churn-cineclick@challenger")
if MODO_SOMBRA else None
)
@app.post("/predecir")
def predecir(entrada: EntradaCliente):
X = construir_features(entrada.como_dataframe())
t0 = time.perf_counter()
prob = float(modelo_champion.predict_proba(X)[0, 1])
latencia_ms = (time.perf_counter() - t0) * 1000
registro = {
"hash_entrada": hash_entrada(entrada),
"probabilidad": round(prob, 4),
"version_modelo": VERSION_CHAMPION,
"latencia_ms": round(latencia_ms, 1),
}
if modelo_sombra is not None:
t0 = time.perf_counter()
prob_sombra = float(modelo_sombra.predict_proba(X)[0, 1])
registro["probabilidad_sombra"] = round(prob_sombra, 4)
registro["version_modelo_sombra"] = VERSION_CHALLENGER
registro["latencia_sombra_ms"] = round((time.perf_counter() - t0) * 1000, 1)
logger.info(json.dumps(registro))
# La RESPUESTA sale siempre del champion: la sombra no decide nada.
umbral = float(os.getenv("UMBRAL_ABANDONO", "0.5"))
return {"probabilidad_abandono": prob, "abandonara": prob >= umbral}Detalles que importan:
MODO_SOMBRAes configuración, no código: se activa añadiendo la variable al ConfigMapchurn-api-configy reiniciando — sin release de imagen. Apagarla es igual de barato.- Las features se calculan una vez y se reutilizan para ambos modelos: mismo coste de features, doble coste solo de inferencia.
- La respuesta y el campo
probabilidaddel log no cambian de significado: cualquier dashboard o consumidor existente sigue funcionando; la sombra solo añade campos. - El coste real es latencia y CPU: la segunda inferencia suma unos milisegundos dentro del presupuesto (p95 = 92 ms sobre un presupuesto de 300 ms: hay margen de sobra, pero se mide, no se supone — para eso se registra
latencia_sombra_ms). - En rigor, un
predict_probaextra en el mismo proceso también puede fallar; se envuelve en untry/exceptque registre el error y jamás afecte a la respuesta (queda como ejercicio 1).
Qué se compara tras dos semanas de sombra
La sombra no emite un veredicto sola: acumula datos para un análisis. En CineClick el periodo fijado son dos semanas (cubre dos ciclos semanales completos, incluido el patrón fin de semana), y el análisis mira tres cosas sobre los logs JSON:
- Distribución de probabilidades. ¿El challenger asigna probabilidades con una forma razonable, parecida a la del champion o desplazada de forma explicable? Un challenger que concentra todo en 0.45-0.55 o que dispara la tasa de positivos del ~15% esperado al 40% es sospechoso aunque sus métricas offline fueran buenas.
- Desacuerdos champion/challenger. ¿En qué porcentaje de peticiones cruzan el umbral en sentidos opuestos? Esos casos discordantes son la lupa: se extraen (vía
hash_entrada) y Laura los revisa — ¿el challenger acierta donde el champion fallaba, o al revés? Nota honesta: en churn la etiqueta real tarda semanas en conocerse, así que esta revisión es cualitativa y de plausibilidad; la confirmación con etiquetas llega más tarde (y sistematizarla es materia del módulo 6). - Latencia añadida. ¿
latencia_sombra_msse comporta? ¿El p95 conjunto sigue dentro del presupuesto de 300 ms? Un challenger 4 veces más lento es un problema operativo aunque prediga de maravilla.
Si las tres salidas son sanas, el challenger ha demostrado que funciona con tráfico real. Lo que la sombra no puede demostrar es que sus decisiones produzcan mejores resultados — sus predicciones no se usaron, así que no causaron nada. Para eso hay que darle tráfico de verdad: canary o A/B.
Canary release: tráfico real, poco a poco
El canary (por el canario de las minas) enruta un porcentaje pequeño y creciente de tráfico real al modelo nuevo: en CineClick, 5% → 25% → 100%, avanzando solo si las métricas de guardia se mantienen sanas en cada escalón (típicamente 24-48 h por escalón). Si algo se degrada, se vuelve al 0% en segundos y solo un 5% de las peticiones se vio afectado.
Dos niveles donde implementarlo:
- Nivel Kubernetes: dos deployments (
churn-api-estableychurn-api-canary) detrás del mismo Service; el reparto por número de réplicas da porcentajes gruesos (1 pod canary de 4 ≈ 25%). Para porcentajes finos y control real de pesos se usa un ingress con soporte de pesos o un service mesh (Istio, Linkerd) — existen, funcionan, y no los vamos a tutorializar aquí: para el tamaño de CineClick sería más infraestructura que problema. - Nivel aplicación (la opción de CineClick): el servicio carga ambos modelos y decide por petición cuál usa. Requisito clave: la asignación debe ser estable por cliente — que el mismo cliente reciba siempre el mismo modelo mientras dure el canary, para que su experiencia sea coherente y los grupos sean comparables. Un
random()por petición no cumple eso; un hash delid_cliente, sí:
# src/cineclick_churn/api/enrutado.py
"""Enrutado canary: asignacion estable de cliente a modelo."""
import hashlib
import os
def cubo_cliente(id_cliente: str) -> int:
"""Proyecta el id a un entero 0-99, estable entre procesos y replicas.
md5 no es criptografia aqui: solo un mezclador determinista y uniforme
(hash() de Python NO vale: cambia entre procesos por PYTHONHASHSEED)."""
digest = hashlib.md5(id_cliente.encode("utf-8")).hexdigest()
return int(digest, 16) % 100
def usar_canary(id_cliente: str) -> bool:
porcentaje = int(os.getenv("PORCENTAJE_CANARY", "0"))
return cubo_cliente(id_cliente) < porcentajeY en el endpoint:
modelo = modelo_canary if usar_canary(entrada.id_cliente) else modelo_champion registro["grupo"] = "canary" if usar_canary(entrada.id_cliente) else "estable"
Propiedades de este diseño, que conviene saborear:
- Estabilidad: el cliente
C-0042cae siempre en el mismo cubo, en cualquier réplica, hoy y mañana. - Monotonía al escalar: al pasar
PORCENTAJE_CANARYde 5 a 25, los cubos 0-4 siguen en canary y se añaden el 5-24. Nadie rebota entre modelos al subir el escalón. - Operación sin release: el porcentaje vive en el ConfigMap
churn-api-config; subir el escalón es editar un valor y reiniciar. El rollback (a 0) también. - El campo
grupoen el log JSON es lo que permite comparar métricas por grupo — sin él, el canary es tráfico ciego.
Métricas de guardia y rollback automático
Un canary sin vigilancia automática es una ruleta con extra de pasos. Las métricas de guardia (guardrail metrics) son el conjunto pequeño de señales que, si se degradan en el grupo canary respecto al estable, abortan la progresión sin esperar a un humano:
| Métrica de guardia | Umbral en CineClick | Por qué |
|---|---|---|
| Tasa de errores 5xx (grupo canary) | > 1% durante 10 min | El modelo nuevo rompe con entradas que el estable tolera |
| Latencia p95 (grupo canary) | > 300 ms (el presupuesto de 04-05) | Modelo más pesado de lo asumido |
| Tasa de predicciones positivas | Fuera de 10%-25% | El histórico ronda el 15%; una tasa del 45% señala un modelo desquiciado, aunque técnicamente "funcione" |
Las dos primeras son de servicio; la tercera es la interesante en ML: no necesita etiquetas (que en churn tardan semanas) y aun así detecta los desastres grandes al instante. La mecánica del rollback automático: un chequeo recurrente compara las métricas por grupo y, si un umbral se viola de forma sostenida, pone PORCENTAJE_CANARY=0 y alerta — el humano llega después, con el incidente ya contenido. Es la evolución natural del rollback "semiautomático" que dejamos anotado en 05-02: aquí el disparo sí se automatiza, porque el criterio es objetivo y el coste de un falso positivo (retirar un canary sano) es mínimo. Cómo se recogen y consultan estas métricas de forma continua — dashboards, alerting de verdad — es exactamente el tema de 06-01; aquí nos basta el contrato: sin métricas de guardia definidas antes de empezar, no hay canary.
A/B testing: cuando la pregunta es de negocio
Shadow y canary responden preguntas de comportamiento del sistema: ¿predice razonablemente? ¿aguanta? Pero la pregunta que de verdad importa a CineClick es otra: ¿el modelo nuevo retiene más clientes? Esa es una pregunta causal sobre un resultado de negocio, y solo la responde un experimento: el A/B test.
Diseño para CineClick:
- Unidad: el cliente (asignado por el mismo hash estable del canary — el código ya existe).
- Grupo A: sus clientes en riesgo se identifican con el champion; grupo B: con el challenger. En ambos, los identificados reciben la oferta de retención de marketing.
- Grupo de control: una fracción de clientes en riesgo que no recibe oferta. Reaparece aquí el problema que diagnosticamos en el módulo 1: la intervención contamina los datos — si un cliente en riesgo recibe descuento y se queda, ¿el modelo "falló" al predecir su abandono, o la oferta funcionó? Sin control es imposible separar la calidad del modelo del efecto de la oferta, y de paso el control da los datos limpios que los reentrenamientos futuros necesitarán.
- Métrica primaria: tasa de retención a 30 días de la oferta. Secundarias: coste de las ofertas emitidas, ingresos retenidos.
- Duración mínima: fijada antes de empezar, calculando cuántos clientes hacen falta para detectar el efecto que importa (con ~2.000 clientes en riesgo/mes por grupo, detectar una mejora de 5 puntos de retención exige del orden de un mes; efectos más pequeños, bastante más). Parar el experimento "cuando va ganando B" es la manera clásica de engañarse.
Significancia estadística, versión práctica
La intuición: si B retiene al 23% y A al 20%, ¿es mejora real o ruido de muestreo? La pregunta operativa es "si no hubiera diferencia real, ¿cómo de raro sería ver esta diferencia por azar?" — eso es el p-valor. Con dos proporciones, la prueba estándar es un test chi-cuadrado sobre la tabla de contingencia:
# Comparar tasas de retencion de dos grupos (datos ficticios)
from scipy.stats import chi2_contingency
# grupo A (champion): 2000 clientes con oferta, 400 retenidos (20.0%)
# grupo B (challenger): 2000 clientes con oferta, 460 retenidos (23.0%)
tabla = [
[400, 1600], # A: retenidos, no retenidos
[460, 1540], # B: retenidos, no retenidos
]
chi2, p_valor, _, _ = chi2_contingency(tabla)
print(f"p-valor: {p_valor:.4f}") # p-valor: 0.0233Lectura práctica: p = 0.023 < 0.05 → si los modelos fueran igual de buenos, una diferencia así aparecería por azar solo ~2 de cada 100 veces; es razonable atribuirla al challenger. Tres matices que evitan el 90% de los malos usos: el umbral 0.05 es convención, no física; con muestras enormes casi todo sale "significativo" — pregunta también si los 3 puntos importan al negocio (aquí: 60 clientes/mes retenidos de más, claramente sí); y mirar el p-valor cada día y parar cuando cruza 0.05 lo invalida — duración fijada de antemano.
Cuando negocio contradice a ML
El resultado más instructivo posible: el challenger tiene mejor recall offline y peor retención en el A/B. ¿Cómo? Por ejemplo: el challenger identifica más abandonos reales (recall ↑) pero los que añade son clientes ya decididos a irse, a los que la oferta no mueve — mientras que deja de señalar a indecisos que el champion sí capturaba y que la oferta sí retenía. La métrica de ML mide acierto en la predicción; la de negocio mide efecto de la acción tomada con la predicción. Cuando chocan, manda la de negocio — es la única razón por la que el modelo existe. Este escenario, imposible de ver en el test set y invisible para la sombra, es la justificación definitiva del A/B.
Comparativa y decisión de CineClick
| Shadow | Canary | A/B test | |
|---|---|---|---|
| ¿El nuevo modelo afecta a usuarios? | No | Sí, a un % creciente | Sí, al ~50% |
| Riesgo | Ninguno (funcional) | Acotado y reversible | Repartido por diseño |
| Coste | Doble inferencia + análisis | Operación de dos versiones + vigilancia | El mayor: diseño, duración, disciplina |
| Qué pregunta responde | ¿Se comporta razonablemente con tráfico real? | ¿Funciona bien sirviendo de verdad, sin degradar el servicio? | ¿Mejora el resultado de NEGOCIO? |
| Necesita etiquetas/resultados | No | No (guardias sin etiqueta) | Sí (retención a 30 días) |
| Duración típica | 1-2 semanas | Días por escalón | Semanas-meses |
| Cuándo usarla | Siempre, como primer filtro barato | Para promocionar con red | Cuando la decisión depende del impacto de negocio |
No son alternativas excluyentes sino capas: cada una filtra lo que la anterior no puede ver. La política que CineClick deja escrita:
- Todo challenger que aspire a
@championpasa 2 semanas en sombra (además de los gates offline de 05-01 y la aprobación de 05-02). Barato, riesgo cero, caza los desastres. - La promoción se ejecuta como canary 5% → 25% → 100% con las métricas de guardia de esta lección y rollback automático a 0%.
- A/B test reservado para decisiones de negocio: cambios de umbral (
UMBRAL_ABANDONO), cambios de oferta de retención, o challengers cuyo caso de valor es discutido. Con grupo de control permanente, que además protege los datos de entrenamiento futuros.
Cierre del módulo: dónde estamos en la escalera de madurez
Merece la pena mirar atrás con la vara de medir del módulo 1. CineClick partía del nivel 0: notebook, manos y memoria. Los módulos 2-4 construyeron las piezas (paquete, DVC, MLflow, servicio, imagen, Kubernetes) y este módulo las ha hecho moverse solas:
- CI (05-01): cada cambio ejecuta tests de código, de datos (pandera) y de modelo (humo, comportamiento, umbrales de negocio).
- CD (05-02): dos pipelines desacoplados — imagen por tag con aprobación, modelo por alias con humano en el bucle — cada uno con su rollback.
- Orquestación (05-03): el batch de los lunes corre solo, se reintenta, avisa y deja rastro.
- Release (05-04): los modelos nuevos se ganan la confianza por capas — sombra, canary con guardias, A/B para el negocio.
Eso es un nivel 1 largo, rozando el 2. ¿Qué falta exactamente para el 2? Que algo dispare todo esto sin que una persona lo decida: hoy, el reentrenamiento se lanza a mano, la sombra se activa a mano, y nadie sabe — con datos — cuándo el champion empieza a envejecer. La pieza que falta no es más automatización: es observación. Saber cómo se comporta el servicio y el modelo en producción, detectar cuándo los datos derivan, y usar esas señales para disparar los pipelines que este módulo dejó listos. Ese es el módulo 6.
Errores Comunes y Consejos
- Saltarse la sombra "porque las métricas offline son muy buenas". La sombra existe precisamente porque esa frase no es un argumento: es la hipótesis a comprobar. Dos semanas de sombra cuestan casi nada; un champion desquiciado en el flujo de cancelación cuesta clientes.
- Canary con asignación aleatoria por petición. El mismo cliente recibe el modelo A a las 10:00 y el B a las 10:05: experiencia incoherente y grupos incomparables. Hash estable de
id_cliente, siempre. - Usar
hash()de Python para el enrutado. Desde Python 3.3,hash()de strings cambia entre procesos (PYTHONHASHSEED): cada réplica asignaría cubos distintos y la "asignación estable" sería una ilusión.hashlibes determinista. - Canary sin métricas de guardia definidas de antemano. Si decides los umbrales mirando los datos del canary en marcha, decidirás lo que confirme tus ganas de promocionar. Guardias y umbrales por escrito antes del 5%.
- Parar el A/B en cuanto el p-valor cruza 0.05. El peeking infla los falsos positivos: mirando cada día, la probabilidad de cruzar 0.05 por puro azar en algún momento es mucho mayor que el 5%. Duración fijada de antemano; análisis al final.
- A/B sin grupo de control. Medirás qué modelo señala clientes "mejores para la oferta", pero no si la oferta misma aporta, y contaminarás el histórico para los reentrenamientos (la lección del módulo 1). El control no es opcional: es el precio de poder aprender.
- Consejo: escribe la política de release (los tres puntos de la sección anterior) en el repo, junto al procedimiento de promoción de 05-02. Cuando dentro de seis meses alguien proponga "promocionar directo, que hay prisa", el coste de la excepción será visible y la decisión, consciente.
Ejercicios
Ejercicio 1
Endurece el modo sombra: reescribe el bloque de la inferencia en sombra para que (a) un fallo del challenger jamás afecte a la respuesta, (b) el fallo quede registrado en el JSON (error_sombra), y (c) si el challenger falla en más ocasiones de la cuenta, tengas forma de detectarlo después desde los logs.
Ejercicio 2
Con PORCENTAJE_CANARY=25, indica de forma razonada si estos clientes reciben el canary, usando cubo_cliente: (a) un cliente cuyo cubo es 4; (b) uno cuyo cubo es 24; (c) uno cuyo cubo es 25; (d) ¿qué pasa con cada uno cuando el escalón sube a 100? ¿Y si en mitad del escalón del 25% hay rollback a 0?
Ejercicio 3
En el A/B de la oferta de retención: grupo A (champion) 1.800 clientes con oferta y 342 retenidos; grupo B (challenger) 1.750 y 368 retenidos. Calcula las tasas, ejecuta el test chi-cuadrado con scipy e interpreta el resultado como se lo contarías a la responsable de retención (sin jerga). ¿Qué decisión recomiendas?
Soluciones
Ejercicio 1
if modelo_sombra is not None:
try:
t0 = time.perf_counter()
prob_sombra = float(modelo_sombra.predict_proba(X)[0, 1])
registro["probabilidad_sombra"] = round(prob_sombra, 4)
registro["latencia_sombra_ms"] = round((time.perf_counter() - t0) * 1000, 1)
except Exception as exc: # noqa: BLE001 — la sombra nunca tumba la respuesta
registro["error_sombra"] = type(exc).__name__(a) El try/except envuelve solo la inferencia en sombra: pase lo que pase, la respuesta del champion ya está calculada y se devuelve intacta. (b) Se registra el tipo de excepción, suficiente para diagnóstico sin volcar trazas gigantes en cada línea de log. (c) Al ser un campo del JSON estructurado, la tasa de fallos de la sombra se obtiene contando líneas con error_sombra sobre el total — y un challenger que falla en el 3% de las peticiones reales acaba de darte una información valiosísima antes de tocar a un solo usuario: exactamente para eso estaba la sombra.
Ejercicio 2
La regla es cubo < porcentaje. (a) cubo 4 < 25 → canary (y ya lo era con el 5%: los cubos 0-4 entraron en el primer escalón). (b) cubo 24 < 25 → canary; entró en el escalón del 25%. (c) cubo 25 no es < 25 → estable; entrará cuando el porcentaje supere 25 (en nuestro plan, directamente en el 100%). (d) Con 100, todos los cubos (0-99) cumplen la condición → todos en canary, que en ese punto pasa a ser el nuevo estable y se consolida moviendo @champion (el flujo de 05-02). Con rollback a 0, ningún cubo cumple cubo < 0 → todos vuelven al champion al instante, con una edición de ConfigMap y sin release. La gracia del diseño es que las cuatro respuestas salen de una sola línea de código y son idénticas en todas las réplicas.
Ejercicio 3
from scipy.stats import chi2_contingency
tabla = [
[342, 1800 - 342], # A: 19.0% de retencion
[368, 1750 - 368], # B: 21.0% de retencion
]
chi2, p_valor, _, _ = chi2_contingency(tabla)
print(f"A: {342/1800:.1%} B: {368/1750:.1%} p-valor: {p_valor:.3f}")
# A: 19.0% B: 21.0% p-valor: 0.137Tasas: A 19,0%, B 21,0%. El p-valor ≈ 0,14 significa: si ambos modelos fueran igual de buenos reteniendo, veríamos una diferencia como esta (o mayor) por puro azar más o menos una de cada siete veces. Es demasiado frecuente para descartar el azar. Traducción para la responsable de retención: "B pinta algo mejor, pero con estos números no podemos distinguirlo de la suerte; no es un 'no', es un 'todavía no lo sabemos'". Recomendación: no promocionar aún por esta evidencia — si el experimento tenía duración prevista mayor, continuar hasta completarla (sin ir mirando el p-valor por el camino); si ya terminó, la mejora real, de existir, es probablemente menor de la que el experimento estaba dimensionado para detectar, y toca decidir si una mejora tan pequeña justifica un experimento más largo o quedarse con el champion.
Conclusión
El módulo 5 empezó con una lista de tareas manuales y termina con un sistema que se mueve solo y — más difícil — que cambia con red de seguridad. La CI ejecuta en cada cambio los tests de código, de datos y de modelo, con el criterio de negocio convertido en umbral bloqueante; el CD entrega por dos vías desacopladas — la imagen por tag con aprobación en el environment, el modelo por alias con revisión humana documentada — cada una con su rollback ensayado; Prefect lanza el scoring de los lunes a las 06:00 con reintentos, alertas y rastro; y esta última lección ha puesto la pieza que faltaba entre "aprobado en staging" y "sirviendo al 100%": dos semanas de sombra para todo challenger, canary 5% → 25% → 100% con métricas de guardia y rollback automático a cero, y A/B con grupo de control cuando la pregunta es de negocio — porque la retención, no el recall, es la razón de que este modelo exista. CineClick vive ya en un nivel 1 de madurez muy largo, rozando el 2. Lo que le falta para cruzar tiene nombre: nada de todo esto se dispara solo. La sombra se activa a mano, el reentrenamiento espera a que alguien lo pida, y la única forma de saber si el champion envejece es que un humano se acuerde de mirar. El módulo 6 construye los ojos del sistema — monitorización de servicio y de modelo, detección de deriva de datos y de concepto — y con ellos, los reflejos: reentrenar cuando toca, gobernar lo que se despliega y documentar lo que se decide. Las piezas ya se mueven solas; ahora tienen que aprender a mirar.
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
