Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Monitoratge i reentrenament

Un model desplegat no falla com falla el codi. No llança cap excepció: continua responent, però les seves prediccions empitjoren a poc a poc perquè el món ha canviat i les dades ja no s’assemblen a les de l’entrenament. Aquest document explica què cal registrar, com detectar-ho i com actualitzar el model quan cal.

Per què canvien les dades

Imagina un model que valora cotxes de segona mà entrenat amb anuncis del 2023. Un any després:

  • Hi ha més cotxes elèctrics, i el model n’havia vist pocs.
  • La inflació ha pujat tots els preus un 8%.
  • L’aplicació ha afegit un camp nou i ha canviat el format d’un altre.

Res d’això trenca l’API, però totes tres coses fan que les prediccions s’allunyin dels preus reals. Aquest fenomen s’anomena drift (deriva): un canvi entre les dades d’entrenament i les de producció.

Tipus de drift

Distingim tres tipus segons què canvia. Per descriure’ls, anomenem entrades les features que rep el model i sortida el que prediu.

Covariate shift (canvi en les entrades): canvia la distribució de les features, però la relació entre entrades i sortida es manté. Exemple: arriben molts més cotxes elèctrics. Un elèctric de 50.000 km continua valent el mateix que abans, però el model n’ha vist pocs i les seves prediccions per a aquest grup són poc fiables.

Label shift (canvi en la sortida): canvia la proporció dels resultats. Exemple: un model de diagnòstic entrenat amb un 5% de casos positius rep, durant un brot, un 20% de positius. Els seus llindars estaven calibrats per a una situació diferent.

Concept drift (canvi de concepte): canvia la relació entre entrades i sortida. Les mateixes features porten a un resultat diferent. Exemple: després d’una crisi, clients amb bons ingressos i bon historial ja no paguen els préstecs com abans.

TipusQuè canviaCom es detecta
Covariate shiftDistribució de les featuresComparant les features de producció amb les de referència
Label shiftProporció de resultatsComparant la distribució de les prediccions al llarg del temps
Concept driftLa relació entre features i resultatNomés amb el resultat real: comparant prediccions amb el que ha passat de veritat

El resultat real verificat s’anomena ground truth (veritat de referència). En valoració de cotxes, és el preu al qual s’ha venut finalment el cotxe; en frau, la confirmació que la transacció era fraudulenta. Sovint arriba amb dies o setmanes de retard, o no arriba mai. Per això els mètodes estadístics que veurem detecten sobretot el covariate shift: és l’únic que es pot veure només mirant les entrades.

En un sistema LLM el canvi equivalent és que el model mateix canviï de comportament davant d’entrades similars; es detecta amb evals, no amb tests estadístics (vegeu Detecció de regressions).

Què cal registrar

Per detectar qualsevol canvi, primer cal saber què ha rebut i què ha respost el model. Cada predicció s’ha de guardar amb la informació que permeti analitzar-la després:

CampPer a què serveix
timestampAgrupar per finestres de temps
request_idTrobar una predicció concreta i lligar-la amb el ground truth quan arribi
featuresDetectar covariate shift i reproduir la predicció
predictionDetectar canvis en la distribució de les prediccions
model_versionComparar versions i saber quina va fer cada predicció
latency_msVigilar el temps de resposta

El format més senzill és un fitxer JSON Lines (.jsonl), amb una predicció per línia, fàcil de llegir amb pandas:

{"timestamp": "2026-10-01T10:30:45", "request_id": "a1b2c3", "features": {"year": 2015, "odometer": 80000, "manufacturer": "toyota"}, "prediction": 14250.0, "model_version": "v2", "latency_ms": 45.2}

Aquest registre són dades de l’aplicació, no un log del servei: ha de sobreviure als redesplegaments, així que va fora del contenidor, en un volum o una base de dades (vegeu Docker Compose). Un fitxer JSON Lines és el més senzill per començar; amb molt volum o diverses instàncies, una base de dades com SQLite o PostgreSQL és més pràctica.

Privacitat: no guardis dades personals sense necessitat. Si cal un identificador d’usuari per relacionar prediccions, guarda’n un hash:

import hashlib

SENSITIVE = {"email", "phone"}

def anonymize(features: dict) -> dict:
    """Elimina els camps sensibles i substitueix l'identificador d'usuari pel seu hash."""
    safe = {k: v for k, v in features.items() if k not in SENSITIVE}
    if "user_id" in safe:
        safe["user_id"] = hashlib.sha256(str(safe["user_id"]).encode()).hexdigest()[:16]
    return safe

Detectar el drift

Per detectar drift, comparem dues mostres: les dades de referència (el production set, o un període en què el model funcionava bé) i les dades recents de producció.

flowchart LR
    A[Referència<br/>production set] --> C[Comparar]
    B[Recents<br/>última setmana] --> C
    C --> D{Drift?}
    D -->|Sí| E[Alerta]
    D -->|No| F[Tot correcte]

La finestra recent pot ser d’hores, dies o setmanes. Una finestra massa curta té poques dades i dona falses alarmes; una de massa llarga detecta els canvis tard.

Test de Kolmogorov-Smirnov

El test KS respon una pregunta: aquestes dues mostres d’una variable numèrica podrien venir de la mateixa distribució? Retorna un p-valor: com més baix, menys probable és que les dues mostres siguin iguals.

from scipy import stats

statistic, p_value = stats.ks_2samp(reference["odometer"], recent["odometer"])
if p_value < 0.05:
    print(f"Drift in odometer (p={p_value:.4f})")

Per a variables categòriques (fabricant, combustible), el test chi-quadrat (scipy.stats.chisquare) compara les freqüències de cada categoria.

Amb moltes dades, aquests tests detecten diferències minúscules que no afecten el model. Per això el p-valor sol, sense mirar la magnitud del canvi, dona massa alarmes.

Population Stability Index

El PSI mesura quant ha canviat una distribució, i té llindars coneguts. La idea: es divideix la variable en trams (per exemple, deu trams de quilometratge), es calcula quin percentatge de dades cau a cada tram a la referència i a les dades recents, i se sumen les diferències.

import numpy as np

def calculate_psi(reference, current, bins: int = 10) -> float:
    """Population Stability Index entre dues mostres d'una feature numèrica."""
    # Límits dels trams a partir de la distribució de referència
    edges = np.percentile(reference, np.linspace(0, 100, bins + 1))
    edges[0], edges[-1] = -np.inf, np.inf

    ref_pct = np.histogram(reference, edges)[0] / len(reference)
    cur_pct = np.histogram(current, edges)[0] / len(current)

    # Evita log(0) als trams buits
    ref_pct = np.clip(ref_pct, 1e-4, None)
    cur_pct = np.clip(cur_pct, 1e-4, None)

    return float(np.sum((cur_pct - ref_pct) * np.log(cur_pct / ref_pct)))
PSIInterpretació
Menys de 0,1Canvi insignificant
De 0,1 a 0,2Canvi moderat: cal investigar
Més de 0,2Canvi significatiu: cal actuar

Simular drift per provar el codi

Abans de tenir dades de producció, com saps si el codi de detecció funciona? Creant un drift conegut: agafes una còpia del production set, la modifiques a propòsit i comproves que el detector reacciona.

EscenariModificacióQuè esperes
Sense driftLes dades originalsCap alarma
Lleuyear + 1Potser cap alarma: ajusta el llindar
Moderatodometer × 1,15Alarma de nivell “investigar”
Fortodometer × 2, un 30% de “gas” passa a “electric”Alarma clara

Aquests escenaris es poden convertir en tests automàtics del detector. És una eina per provar el codi i calibrar els llindars, no un substitut del monitoratge amb dades reals.

Què vigilar

Un servei de ML es vigila en dos nivells. El primer és el de qualsevol servei web: latència, errors, ús de recursos i health checks, amb alertes quan se superen uns llindars (vegeu Operació en producció). El segon és específic del model:

  • Distribució de les features: PSI o KS per feature, comparat amb la referència.
  • Distribució de les prediccions: si el preu mitjà predit puja un 30% en una setmana, alguna cosa ha canviat, encara que no tinguem el ground truth.
  • Mètriques del model: les de Avaluació de models (MAE, F1), quan arriba el ground truth. És l’única manera de detectar concept drift.

Per començar, n’hi ha prou amb una arquitectura senzilla: un script programat (per exemple, cada nit) llegeix el .jsonl, calcula aquestes mesures i envia una alerta si se supera un llindar.

flowchart LR
    A[API] --> B[predictions.jsonl]
    B --> C[Script diari]
    C --> D{Llindars<br/>superats?}
    D -->|Sí| E[Alerta]
    D -->|No| F[Informe]
MesuraLlindar d’exempleSeveritat
PSI d’una featureMés de 0,2Avís
Preu mitjà preditCanvi de més del 20% respecte a la referènciaAvís
MAE amb ground truth10% pitjor que en validarCrítica

Calibra els llindars amb cura: un sistema que avisa cada dia acaba ignorat (fatiga d’alertes).

Qualitat de les dades

A banda del drift, les dades poden simplement estar malament. Abans d’usar-les per predir o per entrenar, comprova:

  • Valors absents: quin percentatge de cada feature arriba buit. Un salt sobtat sol indicar un canvi a l’origen.
  • Valors fora de rang: un any 3500, un quilometratge negatiu.
  • Categories noves: valors que el model no ha vist mai.
  • Esquema: columnes que falten, tipus incorrectes.

El drift mesura canvis en la distribució; la qualitat mesura dades errònies. Un problema greu de qualitat ha d’aturar el pipeline i avisar.

Reentrenar

Quan el monitoratge detecta que el model s’ha degradat, cal actualitzar-lo. També hi ha altres motius: hi ha més dades disponibles, una feature nova, o un bug corregit al preprocessament.

Dades noves o model nou

Hi ha dues respostes diferents a una degradació:

SituacióCausa probableResposta
Funcionava bé i empitjora amb el tempsLes dades han canviatIteració de dades: reentrenar el mateix model amb dades recents
No ha arribat mai al nivell esperatEl model o les features no són prou bonsIteració de model: canviar d’algorisme o afegir features
No millora tot i tenir més dadesLes features no tenen prou informacióIteració de model

La iteració de dades és la que es fa amb regularitat i la que es pot automatitzar. La iteració de model és un projecte en si: es fa de tant en tant, amb els datasets fixats per poder comparar candidats.

Amb quina freqüència

EstratègiaQuan es reentrenaAdequada per a
ManualQuan algú ho decideixDades estables, canvis rars
ProgramadaCada setmana o cada mesCanvis graduals i previsibles
Per disparadorQuan una alerta detecta drift o degradacióCanvis imprevisibles

Comença amb un reentrenament manual o programat. Automatitzar el disparador només val la pena quan les dades canvien de pressa, arriben prou dades noves i la validació és fiable sense revisió humana.

Del ground truth al dataset

Per reentrenar amb dades de producció cal convertir les prediccions en exemples d’entrenament:

  1. Guardar cada predicció amb un identificador (request_id), com hem vist a Què cal registrar.
  2. Lligar-hi el ground truth quan arribi. Pot ser automàtic (el preu de venda final, un clic) o manual (un expert que revisa). Les prediccions que no reben mai el resultat real no entren al dataset d’entrenament.
  3. Triar la finestra de dades:
    • Expansiva: totes les dades des del principi. Més dades, però els patrons antics pesen tant com els recents.
    • Lliscant: només els últims N dies. S’adapta de pressa, però oblida patrons estacionals.
    • Híbrida: una mostra de dades antigues més totes les recents.
  4. Validar el dataset: prou exemples, cap salt estrany en la distribució de resultats, i cap resultat amb data anterior a la predicció (seria una fuita d’informació del futur).

El pipeline d’actualització

L’actualització segueix el mateix pipeline que el primer entrenament:

flowchart LR
    A[Dades noves] --> B[Validar dades]
    B --> C[Entrenar candidat]
    C --> D{Compleix criteris<br/>i és millor que l'actual?}
    D -->|Sí| E[Nova versió<br/>aprovada]
    D -->|No| F[Descartar i avisar]
    E --> G[Promocionar]

Dues condicions per acceptar el candidat:

  • Compleix els criteris de desplegament, els mateixos de Validació i CI/CD.
  • No és pitjor que el model actual, avaluats tots dos sobre les mateixes dades. Un candidat amb un MAE de 800 sembla bo fins que veus que l’actual en fa 750.

Si s’accepta, l’entrenament crea una versió nova i la promoció segueix el mateix camí que qualsevol altra versió (vegeu Quina versió se serveix). Per programar el reentrenament n’hi ha prou amb cron:

# Cada dilluns a les 03:00
0 3 * * 1 cd /opt/project && make train >> logs/cron.log 2>&1

Una variant és continuar entrenant el model existent només amb les dades noves (entrenament incremental) en lloc de reentrenar des de zero. És més ràpid, però el model pot oblidar el que havia après abans. Per als models de scikit-learn d’aquests materials, reentrenar des de zero és el més senzill i el més segur.

Provar en producció

Les proves offline amb dades històriques no capturen tot: el comportament real dels usuaris, l’efecte en el negoci. Per això un model nou es pot provar amb tràfic real de manera controlada. Les estratègies generals (shadow, canary, A/B) es descriuen a Estratègies de desplegament; aquí veiem què aporta cadascuna a un model:

  • Shadow: el model nou rep una còpia de cada petició, però la seva predicció no arriba a l’usuari, només es registra. Permet comparar les prediccions dels dos models sobre les mateixes dades, sense cap risc. No diu res de l’efecte en el negoci.
  • Canary: el model nou serveix una fracció petita del tràfic (un 5%) i, si les mètriques aguanten, es va ampliant. Respon la pregunta “el model nou falla?”.
  • A/B testing: el tràfic es reparteix entre els dos models durant prou temps per comparar una mètrica de negoci (vendes, clics). Respon la pregunta “quin és millor?”. Per repartir de manera consistent, es fa un hash de l’identificador d’usuari: el mateix usuari sempre rep el mateix model. Cada predicció registra la model_version per analitzar els resultats per grup.

Una seqüència raonable per a un equip petit: primer shadow, per veure que el model nou no falla; després canary, per veure’l amb tràfic real; i un A/B només si cal mesurar l’impacte en el negoci. Hi ha variants que mouen el tràfic automàticament cap al model que va millor (multi-armed bandits), però són més complexes i poques vegades calen.

Practicar amb un dataset fix

Sense un flux real de dades, el cicle de monitoratge i reentrenament es pot practicar dividint el production set en lots que simulen l’arribada de dades al llarg del temps:

  1. Entrenament inicial: entrenar amb el training set, validar amb el validation set i desplegar la versió v1.
  2. Lot 1: predir-lo, calcular-ne les mètriques i guardar-les com a referència.
  3. Lot 2: comparar-ne la distribució amb el lot 1 (PSI) i avaluar si el rendiment cau. Per fer-ho més realista, s’hi pot introduir un drift simulat.
  4. Decidir: si el rendiment cau més d’un llindar (per exemple, un 5%), reentrenar amb el training set més el lot 1, comparar el candidat amb v1 sobre el lot 2, i crear v2 si és millor.
  5. Repetir amb els lots següents, acumulant dades i versions.

Aquesta simulació valida el pipeline, però no reprodueix el retard real del ground truth ni els patrons estacionals de les dades reals.

Resum

  • El drift fa que un model empitjori sense cap error visible. El covariate shift es detecta mirant les entrades; el concept drift necessita el ground truth.
  • Cal registrar cada predicció amb un identificador, les features, la predicció i la versió del model, sense dades personals.
  • KS i chi-quadrat diuen si dues distribucions són diferents; el PSI diu quant, amb llindars coneguts.
  • Es vigila el servei com qualsevol altre i, a més, les distribucions de features i prediccions, i les mètriques quan arriba el ground truth.
  • El reentrenament segueix el mateix pipeline: el candidat ha de complir els criteris i no ser pitjor que l’actual. Promocionar-lo és un canvi de configuració.
  • Shadow, canary i A/B proven un model nou amb tràfic real, cadascun responent una pregunta diferent.

A Sistemes LLM en producció recorrem aquestes mateixes etapes (desplegar, validar, observar i actualitzar) per a un sistema que fa servir un LLM.

Referències

Last change: , commit: 2280835