El model de churn de MercaFresh viu fins ara en un notebook: pipeline de preprocessament del mòdul 3, algorisme optimitzat al mòdul 7, llindar de decisió triat per costos al mòdul 6. Però un notebook no pot rebre la trucada del sistema de campanyes a les 3 de la matinada preguntant "quina probabilitat d'abandonament té el client 48210?". Portar un model a producció significa convertir-lo en un servei fiable que altres sistemes puguin fer servir. En aquesta lliçó recorrem aquest camí complet: serialitzar el pipeline amb joblib, triar entre servir en batch o en temps real, construir una API amb FastAPI, empaquetar-la amb Docker i desplegar-la de manera gradual i segura.
Contingut
- Del notebook a producció: què canvia
- Serialitzar el pipeline amb joblib
- Versionatge de l'artefacte i els seus riscos
- Patrons de servei: batch vs. online
- Una API REST amb FastAPI
- Empaquetatge amb Docker
- Desplegament gradual: shadow mode i test A/B
- Checklist de posada en producció
Del notebook a producció: què canvia
Un notebook i un servei en producció són objectes de naturalesa diferent, encara que continguin el mateix model:
graph LR
subgraph nb["Notebook (exploracio)"]
A[Dades historiques CSV] --> B[Pipeline + model]
B --> C[Metriques i grafics]
end
subgraph prod["Produccio (servei)"]
D[Peticions d'altres sistemes] --> E[Artefacte serialitzat]
E --> F[Prediccions]
F --> G[Sistema de campanyes]
E --> H[Logs i monitoratge]
end
nb -- "serialitzar + empaquetar + desplegar" --> prod
| Aspecte | Notebook | Producció |
|---|---|---|
| Qui l'executa | Tu, a mà | Altres sistemes, automàticament |
| Dades | Fitxer històric conegut | Peticions noves, de vegades mal formades |
| Fallades | Reexecutes la cel·la | S'han de detectar, registrar i no tombar el servei |
| Entorn | El de la teva màquina | Reproduïble i idèntic a cada desplegament (lliçó 08-01) |
| Codi | Lineal, exploratori | Validació d'entrades, maneig d'errors, logs |
La conseqüència pràctica: en producció no es desplega "el notebook", es desplega un artefacte (el pipeline serialitzat) embolcallat en codi de servei (API o procés batch) dins d'un entorn reproduïble (contenidor). Anem peça a peça.
Serialitzar el pipeline amb joblib
Serialitzar és convertir l'objecte Python entrenat —amb tots els seus paràmetres apresos— en un fitxer que es pot desar, copiar a un servidor i tornar a carregar. A l'ecosistema scikit-learn l'estàndard és joblib, eficient amb els arrays grans de NumPy que viuen dins dels models.
El punt crític, que justifica tota la feina del mòdul 3: es serialitza el pipeline complet, no només el model. El Pipeline amb el seu ColumnTransformer desa també el que han après els preprocessadors (mitjanes de l'escalador, categories del codificador). Així, producció aplica exactament les mateixes transformacions que l'entrenament, i eliminem d'arrel la família d'errors "vaig entrenar amb dades escalades però predic amb dades sense escalar".
import joblib
# --- Al final de l'entrenament (notebook o script de training) ---
# pipeline_churn es el Pipeline complet del curs:
# ColumnTransformer (imputacio + escalat + one-hot) -> model optimitzat
pipeline_churn.fit(X_entrenament, y_entrenament)
joblib.dump(pipeline_churn, "model_churn_v3.joblib")
# --- Al servidor de produccio ---
pipeline = joblib.load("model_churn_v3.joblib")
# El pipeline carregat rep dades CRUES, com les del dataset original;
# el preprocessament va a dins:
import pandas as pd
client = pd.DataFrame([{
"recencia_dies": 45, "frequencia_90d": 2, "despesa_mitjana": 38.50,
"antiguitat_mesos": 14, "incidencies_lliurament": 3, "ciutat": "Valencia",
"canal_captacio": "app",
}])
prob_churn = pipeline.predict_proba(client)[0, 1]
print(f"Probabilitat d'abandonament: {prob_churn:.2%}")Fixa't que en producció fem servir predict_proba i no predict: com vas veure a 06-04, la decisió final es pren comparant la probabilitat amb el llindar triat per costos de negoci, i aquest llindar convé mantenir-lo com a configuració explícita del servei, no enterrat dins del model.
Versionatge de l'artefacte i els seus riscos
Dues advertències importants abans de continuar:
1. Versiona l'artefacte com versiones el codi. Un nom com model.joblib que se sobreescriu és una bomba de rellotgeria: quan alguna cosa falli no sabràs quin model estava servint. Pràctiques mínimes:
- Nom amb versió i data:
model_churn_v3_2026-08-24.joblib. - Al costat de l'artefacte, un fitxer de metadades: data d'entrenament, rang de dades usat, mètriques de validació, versions de les biblioteques (
pip freeze), i el llindar de decisió. - No esborris mai la versió anterior en desplegar la nova: és el teu pla de rollback (ho reprendrem a 08-03, juntament amb els registres de models tipus MLflow).
2. Dos riscos de seguretat i compatibilitat que has de conèixer:
- Deserialitzar és executar codi. Un fitxer joblib/pickle pot contenir codi arbitrari que s'executa en carregar-lo. Regla d'or:
joblib.loadnomés sobre fitxers l'origen dels quals controles (els teus propis entrenaments, el teu emmagatzematge intern). No carreguis mai un model descarregat d'una font no fiable. - Sensibilitat a versions. Un pipeline desat amb scikit-learn 1.5 pot fallar —o pitjor, comportar-se subtilment diferent sense error— en carregar-se amb la 1.2 o la 1.7. Per això el
requirements.txtamb versions fixades de la lliçó anterior viatja sempre amb l'artefacte: entrenar i servir han de fer servir les mateixes versions.
Patrons de servei: batch vs. online
Abans d'escriure una API, una decisió d'arquitectura: com es consumiran les prediccions? Hi ha dos patrons fonamentals.
| Aspecte | Batch (per lots) | Online (temps real) |
|---|---|---|
| Quan es prediu | En moments programats (p. ex. cada nit) | En l'instant en què arriba la petició |
| Sobre què | Tots els clients (o un lot gran) | Un client (o pocs) per petició |
| Latència exigida | Hores: tant és si triga 20 minuts | Mil·lisegons: algú espera la resposta |
| Forma tècnica | Script/job programat que escriu a la base de dades | API REST sempre aixecada |
| Complexitat operativa | Baixa (un cron job) | Mitjana-alta (disponibilitat, escalat, monitoratge) |
| Exemple MercaFresh | Scoring nocturn de churn de tota la base de clients | Decidir al checkout si mostrar una oferta de retenció |
El cas natural del churn de MercaFresh és batch: l'equip de retenció planifica campanyes setmanals; no necessita saber la probabilitat d'abandonament en aquest mil·lisegon, sinó una taula fresca cada matí. Un scoring nocturn és més simple, més barat i més fàcil d'operar:
# score_nocturn.py — s'executa cada nit a les 02:00 (cron / orquestrador)
import joblib
import pandas as pd
from datetime import date
pipeline = joblib.load("model_churn_v3_2026-08-24.joblib")
LLINDAR = 0.42 # triat per costos a 06-04
clients = pd.read_parquet("clients_features_avui.parquet") # dades crues actualitzades
clients["prob_churn"] = pipeline.predict_proba(
clients.drop(columns=["client_id"])
)[:, 1]
clients["accio_retencio"] = clients["prob_churn"] >= LLINDAR
clients[["client_id", "prob_churn", "accio_retencio"]].to_parquet(
f"scores_churn_{date.today()}.parquet"
)El patró online es justifica quan la predicció depèn d'informació que només existeix en el moment (el contingut del cistell actual) o quan un altre sistema necessita resposta immediata. Com que molts sistemes acaben necessitant totes dues coses —i perquè és el patró tècnicament més ric— construïm ara la versió online.
Una API REST amb FastAPI
FastAPI és el framework Python més usat avui per servir models: ràpid, amb validació automàtica de dades via pydantic i documentació interactiva generada sola. La idea: el model es carrega una vegada en arrencar el servei, i cada petició HTTP a /predict retorna una predicció.
# app.py — servei de prediccio de churn
from fastapi import FastAPI
from pydantic import BaseModel, Field
import joblib
import pandas as pd
app = FastAPI(title="MercaFresh Churn API")
# S'executa UNA vegada en arrencar, no a cada peticio (carregar es lent)
pipeline = joblib.load("model_churn_v3_2026-08-24.joblib")
LLINDAR = 0.42
class DadesClient(BaseModel):
"""Esquema d'entrada: pydantic valida tipus i rangs automaticament."""
recencia_dies: int = Field(ge=0, description="Dies des de l'ultima compra")
frequencia_90d: int = Field(ge=0)
despesa_mitjana: float = Field(ge=0)
antiguitat_mesos: int = Field(ge=0)
incidencies_lliurament: int = Field(ge=0)
ciutat: str
canal_captacio: str
@app.post("/predict")
def predir_churn(dades: DadesClient):
X = pd.DataFrame([dades.model_dump()]) # dict -> DataFrame d'una fila
prob = float(pipeline.predict_proba(X)[0, 1])
return {
"prob_churn": round(prob, 4),
"accio_retencio": prob >= LLINDAR,
"model": "churn_v3",
}Punts que convé entendre línia a línia:
DadesClient(BaseModel)defineix el contracte d'entrada. Si arriba una petició ambrecencia_dies: "molts"o sense el campciutat, FastAPI la rebutja amb un error 422 clar abans de tocar el model. En producció, la meitat dels incidents són dades mal formades; la validació a la porta els converteix en errors explícits en lloc de prediccions absurdes.Field(ge=0)afegeix restriccions (aquí: més gran o igual que zero) — validació de negoci, no només de tipus.- La resposta inclou
"model": "churn_v3": identificar quina versió va respondre cada petició és or per al monitoratge (08-03).
S'aixeca i es prova així:
pip install fastapi uvicorn
uvicorn app:app --host 0.0.0.0 --port 8000
# Documentacio interactiva automatica a http://localhost:8000/docs# provar_api.py — client de prova
import requests
resposta = requests.post("http://localhost:8000/predict", json={
"recencia_dies": 45, "frequencia_90d": 2, "despesa_mitjana": 38.5,
"antiguitat_mesos": 14, "incidencies_lliurament": 3,
"ciutat": "Valencia", "canal_captacio": "app",
})
print(resposta.status_code) # 200
print(resposta.json()) # {'prob_churn': 0.61..., 'accio_retencio': True, ...}Amb això, qualsevol sistema de MercaFresh —el backend web, el sistema de campanyes— pot demanar prediccions amb una crida HTTP, sense saber res de Python ni de scikit-learn.
Empaquetatge amb Docker
Queda un problema: l'API funciona a la teva màquina, amb el teu Python i les teves versions. Docker resol el "a la meva màquina funcionava" empaquetant l'aplicació amb el seu entorn complet (sistema base, Python, biblioteques exactes, codi i artefacte) en una imatge immutable. Aquesta imatge s'executa com a contenidor de manera idèntica al teu portàtil, al servidor de MercaFresh o al núvol.
La recepta de la imatge és el Dockerfile:
# Dockerfile del servei de churn de MercaFresh
# 1. Imatge base: Python 3.12 minim sobre Debian (variant "slim")
FROM python:3.12-slim
# 2. Directori de treball dins del contenidor
WORKDIR /app
# 3. Copiar NOMES requirements primer i fer el pip install.
# Docker desa les capes en cache: si el codi canvia pero requirements no,
# no es torna a executar el pas mes lent (pip install).
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 4. Copiar el codi del servei i l'artefacte del model
COPY app.py .
COPY model_churn_v3_2026-08-24.joblib .
# 5. Port on escolta el servei
EXPOSE 8000
# 6. Ordre d'arrencada del contenidor
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]Explicació de les decisions:
FROM python:3.12-slim: es parteix d'una imatge oficial amb Python ja instal·lat;slimretalla el que és innecessari (imatge més petita, menys superfície d'atac).WORKDIR /app: totes les rutes següents són relatives a/appdins del contenidor.- L'ordre
COPY requirements.txt→RUN pip install→COPY app.pyaprofita la memòria cau per capes: en el cicle habitual (canvies codi, no dependències) reconstruir la imatge triga segons. - L'artefacte del model viatja dins de la imatge: imatge i model es versionen junts, i desplegar una versió és desplegar un binari tancat. (En sistemes grans el model es pot descarregar d'un registre en arrencar; començar amb el model a dins és més simple.)
EXPOSEdocumenta el port; 6.CMDdefineix què executa el contenidor en arrencar.
docker build -t mercafresh/churn-api:v3 . # construeix la imatge, etiquetada v3
docker run -p 8000:8000 mercafresh/churn-api:v3 # l'executa mapant el port
# L'API respon igual que abans a http://localhost:8000/predictPer què contenidors, en una frase per motiu: reproduïbilitat (l'entorn viatja amb l'app), aïllament (no interfereix amb res més del servidor), portabilitat (la mateixa imatge corre a qualsevol lloc) i escalat (més trànsit? s'arrenquen més contenidors idèntics, a mà o amb un orquestrador com Kubernetes — que queda fora d'aquest curs).
Desplegament gradual: shadow mode i test A/B
Tens la imatge v3 llesta. Error clàssic: reemplaçar de cop la versió anterior. Si v3 té un problema que la validació no va detectar —i el món real sempre en troba algun—, el descobriràs amb tots els clients afectats. El desplegament professional és gradual:
Fase 1 — Shadow mode (mode ombra). La v3 es desplega al costat de la v2 i rep còpia de totes les peticions, però les seves prediccions no es fan servir: només es registren. Durant una o dues setmanes compares: respon amb la latència esperada? Falla amb algun tipus d'entrada? Les seves probabilitats tenen una distribució raonable respecte de les de v2? Tot el risc d'un desplegament, sense cap impacte en clients.
Fase 2 — Test A/B del model. Superada l'ombra, la v3 comença a decidir per a una fracció aleatòria de clients (p. ex. el 10 %), i la v2 per a la resta. És exactament el test A/B de la lliçó 02-04, amb el model com a tractament: es compara la mètrica de negoci (taxa de retenció després de campanya, cost per client retingut) entre grups, i s'exigeix significància estadística abans de concloure que v3 és millor al món real — no només al conjunt de prova. Nota important: la mètrica offline (AUC, F1) i la de negoci no sempre es mouen juntes; l'A/B és el jutge final.
Fase 3 — Rollout complet. v3 passa al 100 % del trànsit. La v2 no s'esborra: queda llesta per a un rollback immediat si alguna cosa va malament.
graph LR
A[v3 validada offline] --> B["Shadow mode<br/>prediu, no decideix"]
B --> C["Test A/B<br/>10 per cent del transit"]
C --> D["Rollout 100 per cent"]
B -- problema --> X[Descartar / corregir]
C -- sense millora significativa --> X
D -- incident --> R[Rollback a v2]
Checklist de posada en producció
Abans de donar per desplegat el model de churn, repassa:
- [ ] L'artefacte és el pipeline complet (preprocessament inclòs) i accepta dades crues.
- [ ] Artefacte versionat, amb metadades (dades, mètriques, llindar,
pip freeze) i versió anterior conservada. - [ ] Versions de biblioteques idèntiques entre entrenament i servei (requirements fixat, 08-01).
- [ ] Patró de servei triat conscientment (batch vs. online) segons la necessitat real.
- [ ] Validació d'entrades a la porta (pydantic): tipus, rangs, camps obligatoris.
- [ ] El llindar de decisió és configuració explícita, justificada per costos (06-04).
- [ ] Imatge Docker construïda i provada en local; port i ordre d'arrencada correctes.
- [ ] Cada resposta registra quina versió del model l'ha generada.
- [ ] Pla de desplegament gradual: ombra → A/B → rollout, amb rollback preparat.
- [ ] Logs de peticions i prediccions activats — són la matèria primera del monitoratge (08-03).
Errors Comuns i Consells
- Serialitzar només el model i reimplementar el preprocessament "a mà" en producció. Tard o d'hora les dues implementacions divergeixen i les prediccions es corrompen en silenci. Sempre el pipeline complet.
- Carregar el model dins de l'endpoint.
joblib.loada cada petició multiplica la latència per cent. Es carrega una vegada, en arrencar el procés. - Ignorar els avisos de versió de scikit-learn en carregar un artefacte. Aquell warning de "unpickling from a different version" no és soroll: és el símptoma exacte del risc d'incompatibilitat. Alinea versions.
- Muntar una API online quan un batch nocturn n'hi havia prou. És l'error d'arquitectura més car: pagues disponibilitat 24/7, escalat i guàrdies per a una necessitat que era una taula diària.
- Desplegar al 100 % sense fase d'ombra. El conjunt de prova no conté les sorpreses del trànsit real (camps nous, valors extrems, pics de càrrega). L'ombra les troba gratis.
- Consell: afegeix a l'API un endpoint
/healthque retorni la versió del model i un "ok". Els sistemes de desplegament el fan servir per saber si el servei és viu, i a tu t'estalvia el clàssic "quina versió hi ha en producció?".
Exercicis
Exercici 1. Per a cada escenari de MercaFresh, decideix batch o online i justifica-ho en una frase: (a) llista diària de clients en risc per a l'equip de retenció per correu electrònic; (b) decidir, mentre el client navega, si mostrar-li un bàner amb descompte de retenció; (c) informe mensual d'evolució del risc mitjà de churn per ciutat.
Exercici 2. L'API de l'exemple rep aquesta petició: {"recencia_dies": -5, "frequencia_90d": 2, "despesa_mitjana": 38.5, "antiguitat_mesos": 14, "incidencies_lliurament": 3, "ciutat": "Valencia", "canal_captacio": "app"}. Què passa i per què és preferible aquest comportament al fet que el pipeline predigués sense més?
Exercici 3. Escriu (sense executar-lo) el pla de desplegament gradual de la versió v4 del model de churn, indicant: què es compara en shadow mode, quina mètrica decideix el test A/B i quina condició dispararia un rollback després del rollout.
Solucions
Solució 1. (a) Batch: la llista es necessita una vegada al dia; un scoring nocturn programat és suficient i molt més simple. (b) Online: la decisió depèn del moment de la visita i algú (el frontend) espera la resposta en mil·lisegons; requereix l'API. (c) Batch: és agregació mensual sobre scores ja calculats; ni tan sols necessita prediccions noves, només els resultats del batch diari.
Solució 2. Pydantic rebutja la petició amb un error 422 abans d'arribar al model, perquè recencia_dies té la restricció Field(ge=0) i arriba -5. És preferible perquè una recència negativa és impossible: gairebé segur que hi ha un bug al sistema que crida (o a la integració). Si el pipeline predigués igualment, retornaria un número amb aparença legítima calculat sobre dades corruptes — una fallada silenciosa que ningú detectaria. Un error explícit a la porta converteix el problema en visible i atribuïble.
Solució 3. Pla tipus: (1) Ombra, 2 setmanes: v4 rep còpia del trànsit real; es comparen amb v3 la taxa d'errors, la latència (p. ex. percentil 95) i la distribució de probabilitats predites (si v4 prediu sistemàticament més alt/baix, investigar abans de continuar). (2) A/B, 4-6 setmanes al 10 %: mètrica de decisió de negoci — per exemple, taxa de retenció dels clients contactats (o cost per client retingut) al grup v4 davant del grup v3, amb test de significància com a 02-04; si no hi ha millora significativa, v4 no avança. (3) Rollout 100 % conservant la imatge v3; rollback si es dispara la taxa d'errors del servei, la latència supera el llindar acordat, o la distribució de prediccions/mètrica de negoci es desvia bruscament del que es va veure a l'A/B.
Conclusió
El model de churn de MercaFresh ja no viu en un notebook: és un pipeline serialitzat i versionat amb joblib, servit cada nit en batch per a les campanyes i disponible en temps real darrere d'una API FastAPI que valida cada entrada, tot empaquetat en una imatge Docker que s'executa igual a qualsevol màquina, i desplegat amb xarxa de seguretat — ombra, test A/B com el de 02-04 i rollback preparat. Però posar el model en producció no és el final de la feina, sinó el principi d'una etapa nova: el món canvia, els clients canvien, i un model entrenat amb el passat es degrada en silenci. Detectar aquesta degradació i decidir quan i com reentrenar és el tema de la propera lliçó: manteniment i monitoratge de models.
Curs de Machine Learning
Mòdul 1: Introducció al Machine Learning
- Què és el Machine Learning?
- Història i evolució del Machine Learning
- Tipus de Machine Learning
- Aplicacions del Machine Learning
- El flux de treball d'un projecte de Machine Learning
Mòdul 2: Fonaments d'Estadística i Probabilitat
- Conceptes bàsics d'estadística
- Distribucions de probabilitat
- Correlació i covariància
- Inferència estadística
- Teorema de Bayes
Mòdul 3: Preprocessament de Dades
- Neteja de dades
- Gestió de dades mancants
- Transformació de dades
- Codificació de variables categòriques
- Normalització i estandardització
- Enginyeria de característiques
Mòdul 4: Algorismes de Machine Learning Supervisat
- Regressió lineal
- Regressió logística
- Arbres de decisió
- Màquines de suport vectorial (SVM)
- K veïns més propers (K-NN)
- Naive Bayes
- Xarxes neuronals
Mòdul 5: Algorismes de Machine Learning No Supervisat
- Clustering: K-means
- Clustering jeràrquic
- Anàlisi de components principals (PCA)
- Anàlisi d'agrupament DBSCAN
- Visualització de dades amb t-SNE i UMAP
Mòdul 6: Avaluació i Validació de Models
- Divisió de dades: entrenament, validació i prova
- Mètriques d'avaluació
- Validació creuada
- Corba ROC i AUC
- Overfitting i underfitting
Mòdul 7: Tècniques Avançades i Optimització
- Regularització: Ridge, Lasso i Elastic Net
- Ensemble Learning
- Gradient Boosting
- Xarxes neuronals profundes (Deep Learning)
- Optimització d'hiperparàmetres
Mòdul 8: Implementació i Desplegament de Models
- Frameworks i biblioteques populars
- Implementació de models en producció
- Manteniment i monitoratge de models
- Consideracions ètiques i de privadesa
Mòdul 9: Projectes Pràctics
- Projecte 1: Predicció de preus d'habitatges
- Projecte 2: Classificació d'imatges
- Projecte 3: Anàlisi de sentiments a les xarxes socials
- Projecte 4: Detecció de fraus
- Projecte 5: Segmentació de clients
