El modelo de churn de MercaFresh vive hasta ahora en un notebook: pipeline de preprocesamiento del módulo 3, algoritmo optimizado en el módulo 7, umbral de decisión elegido por costes en el módulo 6. Pero un notebook no puede recibir la llamada del sistema de campañas a las 3 de la madrugada preguntando "¿qué probabilidad de abandono tiene el cliente 48210?". Llevar un modelo a producción significa convertirlo en un servicio fiable que otros sistemas puedan usar. En esta lección recorremos ese camino completo: serializar el pipeline con joblib, elegir entre servir en batch o en tiempo real, construir una API con FastAPI, empaquetarla con Docker y desplegarla de forma gradual y segura.
Contenido
- Del notebook a producción: qué cambia
- Serializar el pipeline con joblib
- Versionado del artefacto y sus riesgos
- Patrones de servicio: batch vs. online
- Una API REST con FastAPI
- Empaquetado con Docker
- Despliegue gradual: shadow mode y test A/B
- Checklist de puesta en producción
Del notebook a producción: qué cambia
Un notebook y un servicio en producción son objetos de naturaleza distinta, aunque contengan el mismo modelo:
graph LR
subgraph nb["Notebook (exploracion)"]
A[Datos historicos CSV] --> B[Pipeline + modelo]
B --> C[Metricas y graficos]
end
subgraph prod["Produccion (servicio)"]
D[Peticiones de otros sistemas] --> E[Artefacto serializado]
E --> F[Predicciones]
F --> G[Sistema de campanas]
E --> H[Logs y monitoreo]
end
nb -- "serializar + empaquetar + desplegar" --> prod
| Aspecto | Notebook | Producción |
|---|---|---|
| Quién lo ejecuta | Tú, a mano | Otros sistemas, automáticamente |
| Datos | Fichero histórico conocido | Peticiones nuevas, a veces malformadas |
| Fallos | Reejecutas la celda | Deben detectarse, registrarse y no tumbar el servicio |
| Entorno | El de tu máquina | Reproducible e idéntico en cada despliegue (lección 08-01) |
| Código | Lineal, exploratorio | Validación de entradas, manejo de errores, logs |
La consecuencia práctica: en producción no se despliega "el notebook", se despliega un artefacto (el pipeline serializado) envuelto en código de servicio (API o proceso batch) dentro de un entorno reproducible (contenedor). Vamos pieza a pieza.
Serializar el pipeline con joblib
Serializar es convertir el objeto Python entrenado —con todos sus parámetros aprendidos— en un fichero que puede guardarse, copiarse a un servidor y volver a cargarse. En el ecosistema scikit-learn el estándar es joblib, eficiente con los arrays grandes de NumPy que viven dentro de los modelos.
El punto crítico, que justifica todo el trabajo del módulo 3: se serializa el pipeline completo, no solo el modelo. El Pipeline con su ColumnTransformer guarda también lo aprendido por los preprocesadores (medias del escalador, categorías del codificador). Así, producción aplica exactamente las mismas transformaciones que el entrenamiento, y eliminamos de raíz la familia de errores "entrené con datos escalados pero predigo con datos sin escalar".
import joblib
# --- Al final del entrenamiento (notebook o script de training) ---
# pipeline_churn es el Pipeline completo del curso:
# ColumnTransformer (imputacion + escalado + one-hot) -> modelo optimizado
pipeline_churn.fit(X_train, y_train)
joblib.dump(pipeline_churn, "modelo_churn_v3.joblib")
# --- En el servidor de produccion ---
pipeline = joblib.load("modelo_churn_v3.joblib")
# El pipeline cargado recibe datos CRUDOS, como los del dataset original;
# el preprocesamiento va dentro:
import pandas as pd
cliente = pd.DataFrame([{
"recencia_dias": 45, "frecuencia_90d": 2, "gasto_medio": 38.50,
"antiguedad_meses": 14, "incidencias_entrega": 3, "ciudad": "Valencia",
"canal_captacion": "app",
}])
prob_churn = pipeline.predict_proba(cliente)[0, 1]
print(f"Probabilidad de abandono: {prob_churn:.2%}")Fíjate en que en producción usamos predict_proba y no predict: como viste en 06-04, la decisión final se toma comparando la probabilidad con el umbral elegido por costes de negocio, y ese umbral conviene mantenerlo como configuración explícita del servicio, no enterrado en el modelo.
Versionado del artefacto y sus riesgos
Dos advertencias importantes antes de seguir:
1. Versiona el artefacto como versionas el código. Un nombre como modelo.joblib que se sobrescribe es una bomba de relojería: cuando algo falle no sabrás qué modelo estaba sirviendo. Prácticas mínimas:
- Nombre con versión y fecha:
modelo_churn_v3_2026-08-24.joblib. - Junto al artefacto, un fichero de metadatos: fecha de entrenamiento, rango de datos usado, métricas de validación, versiones de las bibliotecas (
pip freeze), y el umbral de decisión. - Nunca borres la versión anterior al desplegar la nueva: es tu plan de rollback (lo retomaremos en 08-03, junto con los registros de modelos tipo MLflow).
2. Dos riesgos de seguridad y compatibilidad que debes conocer:
- Deserializar es ejecutar código. Un fichero joblib/pickle puede contener código arbitrario que se ejecuta al cargarlo. Regla de oro:
joblib.loadsolo sobre ficheros cuyo origen controlas (tus propios entrenamientos, tu almacenamiento interno). Jamás cargues un modelo descargado de una fuente no confiable. - Sensibilidad a versiones. Un pipeline guardado con scikit-learn 1.5 puede fallar —o peor, comportarse sutilmente distinto sin error— al cargarse con la 1.2 o la 1.7. Por eso el
requirements.txtcon versiones fijadas de la lección anterior viaja siempre con el artefacto: entrenar y servir deben usar las mismas versiones.
Patrones de servicio: batch vs. online
Antes de escribir una API, una decisión de arquitectura: ¿cómo van a consumirse las predicciones? Hay dos patrones fundamentales.
| Aspecto | Batch (por lotes) | Online (tiempo real) |
|---|---|---|
| Cuándo se predice | En momentos programados (p. ej. cada noche) | En el instante en que llega la petición |
| Sobre qué | Todos los clientes (o un lote grande) | Un cliente (o pocos) por petición |
| Latencia exigida | Horas: da igual si tarda 20 minutos | Milisegundos: alguien espera la respuesta |
| Forma técnica | Script/job programado que escribe en la base de datos | API REST siempre levantada |
| Complejidad operativa | Baja (un cron job) | Media-alta (disponibilidad, escalado, monitoreo) |
| Ejemplo MercaFresh | Scoring nocturno de churn de toda la base de clientes | Decidir en el checkout si mostrar una oferta de retención |
El caso natural del churn de MercaFresh es batch: el equipo de retención planifica campañas semanales; no necesita saber la probabilidad de abandono en este milisegundo, sino una tabla fresca cada mañana. Un scoring nocturno es más simple, más barato y más fácil de operar:
# score_nocturno.py — se ejecuta cada noche a las 02:00 (cron / orquestador)
import joblib
import pandas as pd
from datetime import date
pipeline = joblib.load("modelo_churn_v3_2026-08-24.joblib")
UMBRAL = 0.42 # elegido por costes en 06-04
clientes = pd.read_parquet("clientes_features_hoy.parquet") # datos crudos actualizados
clientes["prob_churn"] = pipeline.predict_proba(
clientes.drop(columns=["cliente_id"])
)[:, 1]
clientes["accion_retencion"] = clientes["prob_churn"] >= UMBRAL
clientes[["cliente_id", "prob_churn", "accion_retencion"]].to_parquet(
f"scores_churn_{date.today()}.parquet"
)El patrón online se justifica cuando la predicción depende de información que solo existe en el momento (el contenido del carrito actual) o cuando otro sistema necesita respuesta inmediata. Como muchos sistemas acaban necesitando ambas cosas —y porque es el patrón técnicamente más rico— construimos ahora la versión online.
Una API REST con FastAPI
FastAPI es el framework Python más usado hoy para servir modelos: rápido, con validación automática de datos vía pydantic y documentación interactiva generada sola. La idea: el modelo se carga una vez al arrancar el servicio, y cada petición HTTP a /predict devuelve una predicción.
# app.py — servicio de prediccion de churn
from fastapi import FastAPI
from pydantic import BaseModel, Field
import joblib
import pandas as pd
app = FastAPI(title="MercaFresh Churn API")
# Se ejecuta UNA vez al arrancar, no en cada peticion (cargar es lento)
pipeline = joblib.load("modelo_churn_v3_2026-08-24.joblib")
UMBRAL = 0.42
class DatosCliente(BaseModel):
"""Esquema de entrada: pydantic valida tipos y rangos automaticamente."""
recencia_dias: int = Field(ge=0, description="Dias desde la ultima compra")
frecuencia_90d: int = Field(ge=0)
gasto_medio: float = Field(ge=0)
antiguedad_meses: int = Field(ge=0)
incidencias_entrega: int = Field(ge=0)
ciudad: str
canal_captacion: str
@app.post("/predict")
def predecir_churn(datos: DatosCliente):
X = pd.DataFrame([datos.model_dump()]) # dict -> DataFrame de una fila
prob = float(pipeline.predict_proba(X)[0, 1])
return {
"prob_churn": round(prob, 4),
"accion_retencion": prob >= UMBRAL,
"modelo": "churn_v3",
}Puntos que conviene entender línea a línea:
DatosCliente(BaseModel)define el contrato de entrada. Si llega una petición conrecencia_dias: "muchos"o sin el campociudad, FastAPI la rechaza con un error 422 claro antes de tocar el modelo. En producción, la mitad de los incidentes son datos malformados; la validación en la puerta los convierte en errores explícitos en lugar de predicciones absurdas.Field(ge=0)añade restricciones (aquí: mayor o igual que cero) — validación de negocio, no solo de tipos.- La respuesta incluye
"modelo": "churn_v3": identificar qué versión respondió cada petición es oro para el monitoreo (08-03).
Se levanta y se prueba así:
pip install fastapi uvicorn
uvicorn app:app --host 0.0.0.0 --port 8000
# Documentacion interactiva automatica en http://localhost:8000/docs# probar_api.py — cliente de prueba
import requests
respuesta = requests.post("http://localhost:8000/predict", json={
"recencia_dias": 45, "frecuencia_90d": 2, "gasto_medio": 38.5,
"antiguedad_meses": 14, "incidencias_entrega": 3,
"ciudad": "Valencia", "canal_captacion": "app",
})
print(respuesta.status_code) # 200
print(respuesta.json()) # {'prob_churn': 0.61..., 'accion_retencion': True, ...}Con esto, cualquier sistema de MercaFresh —el backend web, el sistema de campañas— puede pedir predicciones con una llamada HTTP, sin saber nada de Python ni de scikit-learn.
Empaquetado con Docker
Queda un problema: la API funciona en tu máquina, con tu Python y tus versiones. Docker resuelve el "en mi máquina funcionaba" empaquetando la aplicación con su entorno completo (sistema base, Python, bibliotecas exactas, código y artefacto) en una imagen inmutable. Esa imagen se ejecuta como contenedor de forma idéntica en tu portátil, en el servidor de MercaFresh o en la nube.
La receta de la imagen es el Dockerfile:
# Dockerfile del servicio de churn de MercaFresh
# 1. Imagen base: Python 3.12 minimo sobre Debian (variante "slim")
FROM python:3.12-slim
# 2. Directorio de trabajo dentro del contenedor
WORKDIR /app
# 3. Copiar SOLO requirements primero e instalar dependencias.
# Docker cachea capas: si el codigo cambia pero requirements no,
# la reinstalacion (lo mas lento) no se repite.
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 4. Copiar el codigo del servicio y el artefacto del modelo
COPY app.py .
COPY modelo_churn_v3_2026-08-24.joblib .
# 5. Puerto en el que escucha el servicio
EXPOSE 8000
# 6. Comando de arranque del contenedor
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]Explicación de las decisiones:
FROM python:3.12-slim: se parte de una imagen oficial con Python ya instalado;slimrecorta lo innecesario (imagen más pequeña, menos superficie de ataque).WORKDIR /app: todas las rutas siguientes son relativas a/appdentro del contenedor.- El orden
COPY requirements.txt→RUN pip install→COPY app.pyaprovecha la caché por capas: en el ciclo habitual (cambias código, no dependencias) reconstruir la imagen tarda segundos. - El artefacto del modelo viaja dentro de la imagen: imagen y modelo se versionan juntos, y desplegar una versión es desplegar un binario cerrado. (En sistemas grandes el modelo puede descargarse de un registro al arrancar; empezar con él dentro es más simple.)
EXPOSEdocumenta el puerto; 6.CMDdefine qué ejecuta el contenedor al arrancar.
docker build -t mercafresh/churn-api:v3 . # construir la imagen, etiquetada v3
docker run -p 8000:8000 mercafresh/churn-api:v3 # ejecutarla mapeando el puerto
# La API responde igual que antes en http://localhost:8000/predictPor qué contenedores, en una frase por motivo: reproducibilidad (el entorno viaja con la app), aislamiento (no interfiere con nada más del servidor), portabilidad (la misma imagen corre en cualquier sitio) y escalado (¿más tráfico? se arrancan más contenedores idénticos, a mano o con un orquestador como Kubernetes — que queda fuera de este curso).
Despliegue gradual: shadow mode y test A/B
Tienes la imagen v3 lista. Error clásico: reemplazar de golpe la versión anterior. Si v3 tiene un problema que la validación no detectó —y el mundo real siempre encuentra alguno—, lo descubrirás con todos los clientes afectados. El despliegue profesional es gradual:
Fase 1 — Shadow mode (modo sombra). La v3 se despliega junto a la v2 y recibe copia de todas las peticiones, pero sus predicciones no se usan: solo se registran. Durante una o dos semanas comparas: ¿responde con la latencia esperada? ¿falla con algún tipo de entrada? ¿sus probabilidades tienen una distribución razonable frente a las de v2? Todo el riesgo de un despliegue, sin ningún impacto en clientes.
Fase 2 — Test A/B del modelo. Superada la sombra, la v3 empieza a decidir para una fracción aleatoria de clientes (p. ej. el 10 %), y la v2 para el resto. Es exactamente el test A/B de la lección 02-04, con el modelo como tratamiento: se compara la métrica de negocio (tasa de retención tras campaña, coste por cliente retenido) entre grupos, y se exige significancia estadística antes de concluir que v3 es mejor en el mundo real — no solo en el conjunto de test. Nota importante: la métrica offline (AUC, F1) y la de negocio no siempre se mueven juntas; el A/B es el juez final.
Fase 3 — Rollout completo. v3 pasa al 100 % del tráfico. La v2 no se borra: queda lista para rollback inmediato si algo va mal.
graph LR
A[v3 validada offline] --> B["Shadow mode<br/>predice, no decide"]
B --> C["Test A/B<br/>10 por ciento del trafico"]
C --> D["Rollout 100 por ciento"]
B -- problema --> X[Descartar / corregir]
C -- sin mejora significativa --> X
D -- incidente --> R[Rollback a v2]
Checklist de puesta en producción
Antes de dar por desplegado el modelo de churn, repasa:
- [ ] El artefacto es el pipeline completo (preprocesamiento incluido) y acepta datos crudos.
- [ ] Artefacto versionado, con metadatos (datos, métricas, umbral,
pip freeze) y versión anterior conservada. - [ ] Versiones de bibliotecas idénticas entre entrenamiento y servicio (requirements fijado, 08-01).
- [ ] Patrón de servicio elegido conscientemente (batch vs. online) según la necesidad real.
- [ ] Validación de entradas en la puerta (pydantic): tipos, rangos, campos obligatorios.
- [ ] El umbral de decisión es configuración explícita, justificada por costes (06-04).
- [ ] Imagen Docker construida y probada en local; puerto y comando de arranque correctos.
- [ ] Cada respuesta registra qué versión del modelo la generó.
- [ ] Plan de despliegue gradual: shadow → A/B → rollout, con rollback preparado.
- [ ] Logs de peticiones y predicciones activados — son la materia prima del monitoreo (08-03).
Errores Comunes y Consejos
- Serializar solo el modelo y reimplementar el preprocesamiento "a mano" en producción. Tarde o temprano las dos implementaciones divergen y las predicciones se corrompen en silencio. Siempre el pipeline completo.
- Cargar el modelo dentro del endpoint.
joblib.loaden cada petición multiplica la latencia por cien. Se carga una vez, al arrancar el proceso. - Ignorar los avisos de versión de scikit-learn al cargar un artefacto. Ese warning de "unpickling from a different version" no es ruido: es el síntoma exacto del riesgo de incompatibilidad. Alinea versiones.
- Montar una API online cuando un batch nocturno bastaba. Es el error de arquitectura más caro: pagas disponibilidad 24/7, escalado y guardias para una necesidad que era una tabla diaria.
- Desplegar al 100 % sin fase de sombra. El conjunto de test no contiene las sorpresas del tráfico real (campos nuevos, valores extremos, picos de carga). La sombra las encuentra gratis.
- Consejo: añade a la API un endpoint
/healthque devuelva la versión del modelo y un "ok". Los sistemas de despliegue lo usan para saber si el servicio está vivo, y a ti te ahorra el clásico "¿qué versión hay en producción?".
Ejercicios
Ejercicio 1. Para cada escenario de MercaFresh, decide batch u online y justifica en una frase: (a) lista diaria de clientes en riesgo para el equipo de retención por email; (b) decidir, mientras el cliente navega, si mostrarle un banner con descuento de retención; (c) informe mensual de evolución del riesgo medio de churn por ciudad.
Ejercicio 2. La API del ejemplo recibe esta petición: {"recencia_dias": -5, "frecuencia_90d": 2, "gasto_medio": 38.5, "antiguedad_meses": 14, "incidencias_entrega": 3, "ciudad": "Valencia", "canal_captacion": "app"}. ¿Qué ocurre y por qué es preferible ese comportamiento a que el pipeline predijera sin más?
Ejercicio 3. Escribe (sin ejecutarlo) el plan de despliegue gradual de la versión v4 del modelo de churn, indicando: qué se compara en shadow mode, qué métrica decide el test A/B y qué condición dispararía un rollback tras el rollout.
Soluciones
Solución 1. (a) Batch: la lista se necesita una vez al día; un scoring nocturno programado es suficiente y mucho más simple. (b) Online: la decisión depende del momento de la visita y alguien (el frontend) espera la respuesta en milisegundos; requiere la API. (c) Batch: es agregación mensual sobre scores ya calculados; ni siquiera necesita predicciones nuevas, solo los resultados del batch diario.
Solución 2. Pydantic rechaza la petición con un error 422 antes de llegar al modelo, porque recencia_dias tiene la restricción Field(ge=0) y llega -5. Es preferible porque una recencia negativa es imposible: casi seguro hay un bug en el sistema que llama (o en la integración). Si el pipeline predijera de todos modos, devolvería un número con apariencia legítima calculado sobre datos corruptos — un fallo silencioso que nadie detectaría. Un error explícito en la puerta convierte el problema en visible y atribuible.
Solución 3. Plan tipo: (1) Shadow, 2 semanas: v4 recibe copia del tráfico real; se comparan con v3 la tasa de errores, la latencia (p. ej. percentil 95) y la distribución de probabilidades predichas (si v4 predice sistemáticamente más alto/bajo, investigar antes de seguir). (2) A/B, 4-6 semanas al 10 %: métrica de decisión de negocio — por ejemplo, tasa de retención de los clientes contactados (o coste por cliente retenido) en el grupo v4 frente al grupo v3, con test de significancia como en 02-04; si no hay mejora significativa, v4 no avanza. (3) Rollout 100 % conservando la imagen v3; rollback si se dispara la tasa de errores del servicio, la latencia supera el umbral acordado, o la distribución de predicciones/métrica de negocio se desvía bruscamente de lo visto en el A/B.
Conclusión
El modelo de churn de MercaFresh ya no vive en un notebook: es un pipeline serializado y versionado con joblib, servido cada noche en batch para las campañas y disponible en tiempo real tras una API FastAPI que valida cada entrada, todo empaquetado en una imagen Docker que se ejecuta igual en cualquier máquina, y desplegado con red de seguridad — sombra, test A/B como el de 02-04 y rollback preparado. Pero poner el modelo en producción no es el final del trabajo, sino el principio de una etapa nueva: el mundo cambia, los clientes cambian, y un modelo entrenado con el pasado se degrada en silencio. Detectar esa degradación y decidir cuándo y cómo reentrenar es el tema de la próxima lección: mantenimiento y monitoreo de modelos.
Curso de Machine Learning
Módulo 1: Introducción al Machine Learning
- ¿Qué es el Machine Learning?
- Historia y evolución del Machine Learning
- Tipos de Machine Learning
- Aplicaciones del Machine Learning
- El flujo de trabajo de un proyecto de Machine Learning
Módulo 2: Fundamentos de Estadística y Probabilidad
- Conceptos básicos de estadística
- Distribuciones de probabilidad
- Correlación y covarianza
- Inferencia estadística
- Teorema de Bayes
Módulo 3: Preprocesamiento de Datos
- Limpieza de datos
- Manejo de datos faltantes
- Transformación de datos
- Codificación de variables categóricas
- Normalización y estandarización
- Ingeniería de características
Módulo 4: Algoritmos de Machine Learning Supervisado
- Regresión lineal
- Regresión logística
- Árboles de decisión
- Máquinas de soporte vectorial (SVM)
- K-Vecinos más cercanos (K-NN)
- Naive Bayes
- Redes neuronales
Módulo 5: Algoritmos de Machine Learning No Supervisado
- Clustering: K-means
- Clustering jerárquico
- Análisis de componentes principales (PCA)
- Análisis de agrupamiento DBSCAN
- Visualización de datos con t-SNE y UMAP
Módulo 6: Evaluación y Validación de Modelos
- División de datos: entrenamiento, validación y prueba
- Métricas de evaluación
- Validación cruzada
- Curva ROC y AUC
- Overfitting y underfitting
Módulo 7: Técnicas Avanzadas y Optimización
- Regularización: Ridge, Lasso y Elastic Net
- Ensemble Learning
- Gradient Boosting
- Redes neuronales profundas (Deep Learning)
- Optimización de hiperparámetros
Módulo 8: Implementación y Despliegue de Modelos
- Frameworks y bibliotecas populares
- Implementación de modelos en producción
- Mantenimiento y monitoreo de modelos
- Consideraciones éticas y de privacidad
Módulo 9: Proyectos Prácticos
- Proyecto 1: Predicción de precios de viviendas
- Proyecto 2: Clasificación de imágenes
- Proyecto 3: Análisis de sentimientos en redes sociales
- Proyecto 4: Detección de fraudes
- Proyecto 5: Segmentación de clientes
