Vam tancar el mòdul 7 amb el taller muntat: un paquet src/novamarket/ amb dades.py, entrenar.py i predir.py, tests que passen, un entorn reproduïble i un model desat amb el seu JSON de mètriques. El que faltava, dèiem, era un mètode: com es porta un cas d'ús des de la idea fins a un sistema que funciona en producció i continua funcionant mesos després. Aquesta lliçó el dona. Reprenem el diagrama del flux de treball que vam presentar a 04-01 com a mapa (problema → dades → preparació → entrenament → avaluació → desplegament → monitoratge) i el desenvolupem com a projecte, amb les preguntes que cal respondre a cada fase, els rols que hi participen i els errors que enfonsen projectes sencers. I ho fem sobre el cas que ens acompanya des del mòdul 4: la Marta i el Diego porten el predictor de devolucions (cas d'ús 3) de la idea a producció dins de novamarket_ia/, amb un document de definició, una decisió de desplegament per lots amb banda de revisió humana, una model card generada des d'entrenar.py, un predir_lot.py que reparteix les comandes de cada nit en tres cues i una comprovació de deriva que avisa quan toca reentrenar. És important perquè la majoria de projectes d'IA no fracassen per l'algorisme, sinó per saltar-se alguna d'aquestes fases; i perquè a 09-04 faràs tu el mateix recorregut amb el teu propi projecte: aquí tens el mètode i l'exemple resolt.
Contingut
- Del mapa al mètode: el cicle de vida i els marcs CRISP-DM i CRISP-ML(Q)
- Fase 1: definir el problema de negoci i l'èxit
- Fase 2: dades (fonts, disponibilitat, fuites, divisió temporal, governança)
- Fase 3: exploració i anàlisi
- Fase 4: prototip ràpid (línia base → model simple → iterar)
- Fase 5: avaluació amb criteri de negoci i amb les parts interessades
- Fase 6: desplegament
- Fase 7: monitoratge i manteniment
- Fase 8: documentació i comunicació de resultats
- Rols de l'equip i errors típics de projecte
- Exemple resolt: el predictor de devolucions a
novamarket_ia/ - Errors Comuns i Consells
- Exercicis
- Conclusió
- Del mapa al mètode: el cicle de vida i els marcs CRISP-DM i CRISP-ML(Q)
A 04-01 vam dibuixar el flux de treball de l'aprenentatge automàtic com una seqüència amb tornades enrere. Aquí el redibuixem com a cicle de vida d'un projecte, afegint-hi el que llavors vam deixar en l'aire: la definició de negoci al principi, el desplegament i la vigilància al final, i el fet que les fletxes de tornada no són excepcions sinó la norma.
flowchart LR
A[1. Problema de negoci<br/>i definició de l'èxit] --> B[2. Dades]
B --> C[3. Exploració<br/>i anàlisi]
C --> D[4. Prototip:<br/>línia base → model → iterar]
D --> E[5. Avaluació<br/>amb criteri de negoci]
E -->|no compensa| A
E -->|falten dades| B
E -->|compensa| F[6. Desplegament]
F --> G[7. Monitoratge<br/>i manteniment]
G -->|deriva| B
G -->|canvia el negoci| A
H[8. Documentació i comunicació] -.-> A & E & F & G
Aquest cicle no és cap invenció del curs: és la forma que han pres dos marcs molt usats a la indústria.
- CRISP-DM (Cross-Industry Standard Process for Data Mining, finals dels anys noranta) defineix sis fases: comprensió del negoci, comprensió de les dades, preparació de les dades, modelatge, avaluació i desplegament. El seu gran encert va ser posar la comprensió del negoci abans que les dades i dibuixar les fletxes de tornada.
- CRISP-ML(Q) és una actualització pensada per a l'aprenentatge automàtic modern: hi afegeix el monitoratge i el manteniment com a fase pròpia (un model desplegat es degrada) i una garantia de qualitat (Q) transversal: a cada fase es defineixen requisits, riscos i comprovacions. El nostre cicle de vuit passos és essencialment CRISP-ML(Q) amb la documentació explicitada com a fase 8.
| Fase del nostre cicle | CRISP-DM | CRISP-ML(Q) | Lliçó del curs on s'ha vist la tècnica |
|---|---|---|---|
| 1. Problema i èxit | Comprensió del negoci | Comprensió de negoci i dades | 01-03 (valor/viabilitat/risc), 02-01 (mesura de rendiment), 02-04 (llista de verificació) |
| 2. Dades | Comprensió de les dades | Comprensió de negoci i dades | 02-03, 04-03 |
| 3. Exploració | Comprensió de les dades | Enginyeria de dades | 07-02 |
| 4. Prototip | Preparació + modelatge | Enginyeria del model | 04-04, 04-06, 05-x |
| 5. Avaluació | Avaluació | Avaluació del model | 04-05 |
| 6. Desplegament | Desplegament | Desplegament | 07-03, 07-04 |
| 7. Monitoratge | (no explícita) | Monitoratge i manteniment | aquesta lliçó |
| 8. Documentació | transversal | Garantia de qualitat (Q) | aquesta lliçó |
Una idea que convé fixar des d'ara: el temps d'un projecte d'IA no es reparteix a parts iguals. En projectes reals és habitual que la definició i les dades consumeixin més de la meitat de l'esforç, el modelatge una fracció petita, i el desplegament i el monitoratge la resta. Qui planifica "dues setmanes de dades i dos mesos de model" està planificant el projecte a l'inrevés.
- Fase 1: definir el problema de negoci i l'èxit
És la fase que més projectes salva i més se salta. Es tracta de respondre per escrit, en una pàgina, aquestes preguntes abans de tocar cap dada:
- Quina pregunta respon el sistema? No "predir devolucions", sinó "quina probabilitat té aquesta comanda de ser retornada, coneguda en el moment de preparar-la?".
- Quina decisió canvia? Si la predicció no canvia cap acció, el projecte no val res per bona que sigui la mètrica. A NovaMarket la decisió és: aprovar la comanda sense més, passar-la a una persona o preparar per endavant la logística inversa (embalatge reutilitzable, etiqueta de devolució, no tancar la reposició d'estoc).
- Quina és la mètrica de negoci i quina la tècnica? La de negoci s'expressa en la unitat de qui decideix: euros (cost de falsos positius i falsos negatius, 04-05), hores de feina, clients retinguts. La tècnica (AUC, F1, MAE) és un mitjà. Cal escriure totes dues i la relació entre elles.
- Quina és la línia base? El que es fa avui: res, una regla del Diego (">300 €"), un procés manual. Sense línia base no es pot dir si el model aporta res.
- Quines restriccions hi ha? Legals (RGPD: base jurídica, decisions automatitzades; Reglament d'IA: nivell de risc, 02-04), ètiques (grups afectats, proxies), operatives (quantes comandes pot revisar una persona al dia?), tècniques (en quant de temps cal la resposta?).
- Quan considerarem que ha funcionat? Un criteri d'èxit verificable: "reduir el cost de devolucions no anticipades almenys un 25 % respecte a la línia base en tres mesos, sense superar 300 revisions manuals al dia".
Aquí és on s'aplica la llista de verificació ètica de nou punts de 02-04 i, si el sistema decideix sobre persones, l'avaluació d'impacte. No com a tràmit posterior: les seves respostes condicionen quines dades es poden fer servir (punt 2), quin model (explicabilitat, punt 4) i quina revisió humana cal (punt 5).
El resultat de la fase és un document de definició: una taula d'una pàgina signada per negoci i dades. El veurem fet per al predictor de devolucions a la secció 11.
- Fase 2: dades (fonts, disponibilitat, fuites, divisió temporal, governança)
Amb el problema definit, toca inventariar les dades. Les preguntes:
- Fonts: en quins sistemes són? (
comandes.csviclients.csvde l'ERP,incidencies.csvdel CRM, logs del web). Amb quina freqüència s'actualitzen? Qui n'és el propietari? Cal unir-les (SQL, 07-01) i amb quina clau? - Disponibilitat en el moment de predir: la pregunta més important i més oblidada. Cada característica ha d'existir quan es farà servir el model, no quan s'entrena.
motiu_devolucioexisteix a l'històric, però només després de la devolució: és una fuita (04-03).dies_lliuramentreal tampoc no es coneix en preparar la comanda; es coneix el promès. Icomandes_previesd'un client s'ha de calcular contra el seu historial fins aquell dia, no contra tot el fitxer. - Etiquetes: com s'obté la veritat? Una devolució es registra setmanes després de la compra: el model s'entrena amb comandes "madures" i en producció l'etiqueta arriba amb retard, cosa que afecta com es mesura (fase 7).
- Divisió temporal: en un problema amb dates, el test ha de ser posterior a l'entrenament (
TimeSeriesSplit, 04-05); una divisió aleatòria deixa que el model "vegi el futur" i dona mètriques optimistes. - Volum i qualitat: quants casos positius hi ha (el 16,4 % de 3.000 comandes són 492 devolucions: suficient per a una logística, poc per a una xarxa profunda), NaN, duplicats, atípics (04-03).
- Governança: base jurídica i minimització (RGPD), qui accedeix a què, on es desen les dades crues (mai a Git, 07-04), què s'anonimitza, quant de temps es conserven. I una fitxa de dades (datasheet): origen, període, columnes, biaixos coneguts, restriccions d'ús.
Una regla que estalvia disgustos: construeix el conjunt de dades d'entrenament amb la mateixa consulta que faràs servir en producció. Si l'entrenament surt d'un Excel manual i la producció d'una consulta SQL, les columnes acaben volent dir coses diferents (l'anomenat training-serving skew). Ho veurem amb dies_des_inici a la secció 11.
- Fase 3: exploració i anàlisi
Abans de modelar, mirar. L'exploració (07-02) té tres objectius en un projecte:
- Confirmar que el problema existeix a les dades: la taxa de devolució varia de debò amb l'import, la categoria, el tipus de client? Si res no varia, no hi ha senyal per aprendre.
- Detectar problemes de qualitat i de fuita: columnes amb massa NaN, valors impossibles (l'import de 99.999 €), columnes que prediuen "massa bé" (sospita de fuita).
- Alimentar la definició: l'exploració sovint canvia la pregunta. Si el 60 % de les devolucions són d'electrònica, potser el primer desplegament s'hauria de limitar a aquesta categoria.
El lliurable és un quadern d'exploració (Jupyter, 07-04) amb gràfics i una llista de troballes i de decisions que provoquen. És la fase adequada per al notebook; el que maduri passa a src/.
- Fase 4: prototip ràpid (línia base → model simple → iterar)
L'error clàssic és començar pel model més potent. L'ordre correcte:
- Línia base sense model: "mai no es retorna" (3.690 € al test de 750 comandes), la regla del Diego ">300 €" (3.195 €). Qualsevol model ha de batre això en euros.
- Model simple, interpretable, amb el pipeline complet: la regressió logística dins d'un
Pipelineque imputa, escala i codifica (04-03, 04-04). Que funcioni d'extrem a extrem abans de millorar res. - Iterar amb un pressupost: cada iteració canvia una cosa (una característica nova, un algorisme, un hiperparàmetre, 04-06) i s'anota amb la seva mètrica. La Marta es va donar un pressupost de dues setmanes per al prototip: la logística va donar AUC 0,844; el bosc ajustat 0,838; l'MLP 21→16→8→1, 0,834. Cap no va millorar de manera significativa la logística, que a més explica les seves decisions. S'atura: el pressupost de temps protegeix del "una prova més".
| Iteració | Canvi | AUC test | Cost amb llindar 0,2 | Decisió |
|---|---|---|---|---|
| 0 | Línia base "mai" | — | 3.690 € (sense llindar) | referència |
| 0b | Regla ">300 €" | — | 3.195 € | referència operativa |
| 1 | Logística + pipeline (21 columnes) | 0,844 | 1.610 € | candidata |
| 2 | Bosc aleatori ajustat | 0,838 | similar | no millora |
| 3 | MLP 21→16→8→1 | 0,834 | similar | no millora, opac |
Regla pràctica: el prototip s'acaba quan dues iteracions seguides no mouen la mètrica de negoci, no quan s'acaben les idees.
- Fase 5: avaluació amb criteri de negoci i amb les parts interessades
L'avaluació tècnica (04-05) és necessària però no suficient. En un projecte s'avalua amb les persones que hauran de conviure amb el sistema:
- Mètrica de negoci sobre el test reservat: cost 2.485 € amb llindar 0,5 → 1.610 € amb llindar 0,2 (un 35 % menys), davant de 3.690 € sense fer res.
- Capacitat operativa: amb llindar 0,2 es marquen 200 comandes de 750 (el 27 %); el Diego diu que el seu equip no pot revisar-ne tantes. D'aquí la banda de revisió humana 0,2-0,5: 60 comandes automàtiques, 140 a revisar, 550 aprovades.
- Equitat: paritat de taxes de marcatge entre zones de codi postal (02-04) sobre el test.
- Casos concrets: ensenyar al Diego deu comandes de cada cua amb l'explicació (els coeficients de la logística) perquè jutgi si el model "té sentit". Aquest pas descobreix fuites i errors que cap mètrica no ensenya.
- Riscos i pla B: què passa si el model falla (s'apaga i es torna al procés manual, vegeu la reversió a la fase 6).
La sortida és una decisió explícita: seguir cap al desplegament, tornar a les dades, o aturar-se. Aturar-se és un resultat vàlid d'un projecte ben fet; el que no és vàlid és desplegar perquè ja s'hi ha invertit molt.
- Fase 6: desplegament
Desplegar és integrar el model en un flux real. Les decisions:
| Decisió | Opcions | Criteri | NovaMarket (devolucions) |
|---|---|---|---|
| Quan es prediu | Per lots (batch: cada nit, cada hora) vs temps real (per petició) | Quant pot esperar la decisió? | La comanda es prepara l'endemà: n'hi ha prou amb un lot nocturn |
| Com s'integra | API (FastAPI, servir.py de 07-03) vs fitxer/taula que consumeix el procés existent |
Qui consumeix la predicció i com treballa avui? | Un CSV/taula de cues que carrega l'eina de magatzem; l'API queda per al futur assistent |
| Grau d'automatització | Totalment automàtic vs human-in-the-loop per banda | Impacte en persones, cost dels errors (02-04) | Tres cues: aprovar / revisar / marcar |
| Com es prova en real | Mode ombra (prediu però no actua; es compara amb el que passa) → A/B o desplegament gradual (un magatzem primer) → total | Risc del canvi | Dues setmanes en ombra a Getafe, després banda de revisió a Getafe, després Zaragoza |
| Pla de reversió | Interruptor per tornar al procés anterior; versió prèvia del model desada | Sempre | La variable de configuració MODEL_ACTIU=off ho torna tot a "aprovar" i la regla del Diego |
| Empaquetat | Script en cron, contenidor Docker (07-04), servei | Infraestructura disponible | Script predir_lot.py llançat pel planificador nocturn |
Dos apunts. Mode ombra vol dir executar el model en producció sense que les seves sortides afectin res, només per registrar-les i comparar-les després amb la realitat: és la manera més barata de descobrir que les dades de producció no són com les de l'entrenament. I la prova A/B (una part de les comandes amb el model, una altra sense, comparant el cost) és l'única manera de mesurar l'impacte causal; a nivell conceptual n'hi ha prou de saber que existeix i que exigeix assignar els grups a l'atzar i esperar a tenir prou casos.
- Fase 7: monitoratge i manteniment
Un model desplegat comença a caducar el primer dia. Cal vigilar tres coses:
- Salut tècnica: s'ha executat el lot? quantes comandes? quant ha trigat? hi ha hagut errors o columnes absents?
- Deriva de dades (data drift): la distribució de les entrades canvia (imports més alts per Nadal, més clients nous després d'una campanya, una categoria nova). Es detecta comparant la distribució recent amb la de l'entrenament: diferència de mitjanes, un índex com el PSI (Population Stability Index) per trams, o simplement la taxa de marcatge del model (si de sobte marca el 50 % de les comandes, alguna cosa ha canviat).
- Deriva de concepte (concept drift): canvia la relació entre entrades i sortida (una nova política de devolucions gratuïtes fa que les mateixes comandes es retornin més). Només es detecta quan arriben les etiquetes reals, amb retard: cal recalcular AUC i cost sobre les comandes de fa unes setmanes les devolucions de les quals ja es coneixen.
I actuar: alertes amb llindars (avís / alerta), un calendari de reentrenament (periòdic o disparat per deriva), versionat de models (v1.0.0, v1.1.0, amb el seu JSON de mètriques) i un registre de decisions (quin model ha puntuat quin lot, quantes comandes han anat a cada cua, què ha decidit la revisora): serveix per auditar, per explicar-ho a un client i per reentrenar amb etiquetes humanes. Les eines específiques d'aquest terreny (MLflow, panells de deriva) es van esmentar a 07-03; aquí en tenim prou d'entendre què es mesura i de programar-ho a mà.
- Fase 8: documentació i comunicació de resultats
Dos documents curts i un hàbit:
- Model card (fitxa del model): què és, per a què es pot fer servir i per a què no, amb quines dades s'ha entrenat, com rendeix (mètriques tècniques i de negoci, per grups si escau), limitacions conegudes, responsable, versió i data. Va néixer com a proposta acadèmica i avui és pràctica habitual i, per a sistemes de risc, part del que demana la regulació (02-04). La generarem automàticament des d'
entrenar.py. - Fitxa de dades (datasheet): origen, període, columnes, com s'ha etiquetat, biaixos coneguts, base jurídica.
- Comunicació: al Diego no se li porta un AUC; se li porta "1.610 € davant de 3.690 € per cada 750 comandes, 140 revisions per cada 750, i una llista de casos que pots mirar". A direcció, tres xifres i un risc. A l'equip tècnic, el repositori i la model card.
- Rols de l'equip i errors típics de projecte
Fins i tot en una empresa de 180 persones, un projecte d'IA necessita quatre barrets (de vegades sobre poques testes):
| Rol | Qui a NovaMarket | Responsabilitat |
|---|---|---|
| Negoci / propietari | Diego | Defineix la decisió, els costos, la capacitat operativa; accepta o rebutja |
| Dades / modelatge | Marta | Dades, exploració, prototip, avaluació, model card |
| Enginyeria | equip de sistemes | Integració, planificador nocturn, entorn, reversió, alertes tècniques |
| Legal / compliment | assessoria externa | RGPD, Reglament d'IA, avaluació d'impacte, conservació de dades |
I els errors que més projectes enfonsen:
- Començar pel model ("farem deep learning") sense decisió ni mètrica de negoci.
- Sense línia base: un AUC de 0,84 no vol dir res si la regla del Diego ja aconseguia gairebé el mateix.
- Mètrica equivocada: optimitzar l'exactitud amb classes desequilibrades (04-05), o una mètrica tècnica que no es mou en euros.
- Dades que no existiran en producció: fuites i columnes calculades de manera diferent en servir.
- No tancar el cicle: desplegar sense monitorar, sense reentrenament, sense registre; o quedar-se en un notebook que mai no arriba a producció.
- No implicar qui decideix: el Diego s'assabenta del sistema el dia que li arriben 641 comandes a revisar.
- Exemple resolt: el predictor de devolucions a
novamarket_ia/
novamarket_ia/Recorrem les vuit fases sobre el projecte que ja tenim a novamarket_ia/. Tot el codi d'aquesta secció s'ha executat a l'entorn del curs; els fitxers nous són predir_lot.py, deriva.py, les funcions de model card a entrenar.py i un test.
11.1 Document de definició (fase 1)
| Camp | Contingut |
|---|---|
| Pregunta | Quina probabilitat té una comanda de ser retornada, coneguda en preparar-la? |
| Decisió que canvia | Cada nit, cada comanda del dia va a una de tres cues: aprovar (flux normal), revisar (una persona decideix si prepara logística inversa o contacta amb el client), marcar (es prepara logística inversa i no es tanca la reposició) |
| Mètrica de negoci | Cost = 5 € per falsa alarma + 30 € per devolució no anticipada (+ 3 € per revisió manual) sobre comandes madures |
| Mètrica tècnica | AUC (comparació de models); cost per llindar (tria del llindar) |
| Línia base | "Mai": 3.690 € / 750 comandes. Regla del Diego ">300 €": 3.195 € |
| Criteri d'èxit | ≥ 25 % menys de cost que la línia base en 3 mesos; ≤ 300 revisions/dia; ràtio d'impacte entre zones ≥ 0,8 |
| Restriccions | RGPD: no és decisió automatitzada amb efectes jurídics (banda de revisió, sense denegar compres); Reglament d'IA: risc limitat/mínim; no fer servir codi_postal_zona per decidir sense revisió; dades crues fora de Git |
| Dades | comandes.csv (3 mesos), clients.csv (historial); etiqueta = devolució en 30 dies |
| Desplegament | Lot nocturn; sortida = taula de cues; ombra 2 setmanes a Getafe; reversió per configuració |
| Propietari / responsable | Diego (negoci), Marta (model), sistemes (integració), assessoria (legal) |
| Pressupost | Prototip: 2 setmanes; pilot: 6 setmanes |
11.2 Dades, exploració, prototip i avaluació (fases 2-5)
Aquestes fases ja estan fetes als mòduls 4 i 7 i no les repetim: dades.py genera i prepara les comandes (fuites fora, Pipeline de 21 columnes), i la logística va guanyar el prototip amb AUC 0,844 i cost 1.610 € amb llindar 0,2. Només una correcció que apareix en preparar el desplegament, típica de la fase 2: preparar_comandes calculava dies_des_inici respecte a la primera comanda del fitxer. En entrenament això és l'1 de setembre de 2025; en un lot nocturn de l'1 de desembre seria... l'1 de desembre, i la columna valdria 0 en lloc de 91. És un cas de llibre de training-serving skew. La correcció: un paràmetre data_inici que en producció es passa explícitament.
# src/novamarket/dades.py (fragment modificat)
def preparar_comandes(brut, data_inici=None):
"""... data_inici: data de referència per a 'dies_des_inici'. En entrenament es pren la
primera comanda de l'històric; en producció S'HA DE passar la mateixa data de l'entrenament."""
net = brut.drop_duplicates()
...
if data_inici is None:
data_inici = net["data_comanda"].min()
net["dies_des_inici"] = (net["data_comanda"] - pd.Timestamp(data_inici)).dt.days
...També movem a dades.py la funció cost_negoci(matriu, cost_fp=5, cost_fn=30) de 04-05, perquè la model card la necessita.
11.3 Codi (a): la model card generada des d'entrenar.py (fase 8)
Ampliem entrenar.py perquè, a més del model i el seu JSON de versions, escrigui la fitxa del model en dos formats (JSON per a màquines, Markdown per a persones) i una referència de deriva que farà servir el monitoratge. entrenar() retorna ara un tercer valor amb el context (particions i probabilitats de test).
# src/novamarket/entrenar.py (parts noves; la resta és la de 07-04)
from datetime import date
import numpy as np
from sklearn.metrics import confusion_matrix, roc_auc_score
from novamarket.dades import cost_negoci, ...
LLINDAR_BAIX, LLINDAR_ALT = 0.2, 0.5 # banda de revisió humana decidida a 04-05
VERSIO_MODEL = "1.0.0"
def entrenar(n=3000, llavor=42):
"""Genera les dades, entrena i retorna (pipeline, AUC de test, context per a la fitxa)."""
X, y = preparar_comandes(embrutar_comandes(generar_comandes_ml(n, llavor), llavor))
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.25, random_state=llavor, stratify=y)
pipe = Pipeline([("prep", crear_preparacio()),
("model", LogisticRegression(max_iter=1000))]).fit(Xtr, ytr)
prob = pipe.predict_proba(Xte)[:, 1]
auc = roc_auc_score(yte, prob)
return pipe, auc, {"Xtr": Xtr, "Xte": Xte, "yte": yte, "prob_test": prob}
def escriure_model_card(auc, context, ruta_dir, n, llavor):
"""Model card mínima: què és, amb quines dades, com rendeix, límits. JSON + Markdown."""
yte, prob = context["yte"], context["prob_test"]
costos = {}
for u in (0.5, LLINDAR_BAIX): # cost de negoci amb els dos llindars
m = confusion_matrix(yte, (prob >= u).astype(int))
costos[f"llindar_{u}"] = {"cost_eur": cost_negoci(m), "marcats": int(m[:, 1].sum())}
fitxa = {
"nom": "Predictor de devolucions NovaMarket (cas d'ús 3)",
"versio": VERSIO_MODEL,
"data_entrenament": date.today().isoformat(),
"responsable": "Marta (dades); propietari de negoci: Diego (operacions)",
"us_previst": "Puntuar cada nit les comandes del dia i repartir-les en tres cues: "
"aprovar (<0,2), revisar per una persona (0,2-0,5) i marcar (>=0,5).",
"us_no_previst": "Denegar compres, penalitzar clients o decidir sense revisió humana "
"per sobre de la banda.",
"algorisme": "Pipeline scikit-learn: imputació + escalat + one-hot (21 columnes) "
"+ regressió logística",
"dades": {"origen": "comandes.csv sintètic (generar_comandes_ml)", "n_total": n,
"n_entrenament": int(len(context["Xtr"])), "n_test": int(len(yte)),
"taxa_devolucio_test": round(float(yte.mean()), 4), "llavor": llavor,
"caracteristiques": list(context["Xtr"].columns)},
"metriques": {"auc_test": round(float(auc), 4), "cost_negoci_test": costos,
"cost_fp_eur": 5, "cost_fn_eur": 30},
"limitacions": [
"Entrenat amb dades sintètiques de tres mesos: no ha vist ni Black Friday ni rebaixes.",
"'dies_des_inici' extrapola en producció; es vigila i es reentrena periòdicament.",
"'comandes_previes' i 'taxa_devolucio_previa' s'han de calcular contra l'històric "
"complet del client, no només dins del lot.",
"codi_postal_zona no es fa servir per decidir sense revisió: es mesura la paritat de taxes "
"entre zones cada mes (02-04).",
],
"entorn": {"python": platform.python_version(), "scikit_learn": sklearn.__version__},
}
ruta_dir = Path(ruta_dir)
(ruta_dir / "model_card.json").write_text(json.dumps(fitxa, indent=2, ensure_ascii=False))
md = [f"# Model card: {fitxa['nom']} v{fitxa['versio']}", "",
f"- **Data**: {fitxa['data_entrenament']} | **Responsable**: {fitxa['responsable']}",
f"- **Ús previst**: {fitxa['us_previst']}", f"- **Ús NO previst**: {fitxa['us_no_previst']}",
f"- **Algorisme**: {fitxa['algorisme']}",
f"- **Dades**: {fitxa['dades']['n_entrenament']} comandes d'entrenament, "
f"{fitxa['dades']['n_test']} de test (taxa de devolució {fitxa['dades']['taxa_devolucio_test']:.1%})",
"", "## Mètriques", "", "| Mètrica | Valor |", "|---|---|",
f"| AUC (test) | {fitxa['metriques']['auc_test']} |"]
for k, v in costos.items():
md.append(f"| Cost de negoci {k.replace('_', ' ')} | {v['cost_eur']} € ({v['marcats']} marcats) |")
md += ["", "## Limitacions", ""] + [f"- {l}" for l in fitxa["limitacions"]]
(ruta_dir / "model_card.md").write_text("\n".join(md) + "\n")
return fitxa
def escriure_referencia_deriva(context, ruta_dir):
"""Desa com eren les dades i les sortides en entrenar, per comparar-les cada setmana (deriva.py)."""
import_comanda = context["Xtr"]["import_comanda"].dropna()
vores = np.quantile(import_comanda, np.linspace(0, 1, 11)) # 10 trams amb el 10 % de les comandes cadascun
ref = {"import_mitja": round(float(import_comanda.mean()), 2),
"import_vores": [None] + [round(float(b), 2) for b in vores[1:-1]] + [None], # None = infinit
"import_frequencies": [0.1] * 10,
"client_nou_taxa": round(float(context["Xtr"]["client_nou"].mean()), 4),
"taxa_marcatge": round(float((context["prob_test"] >= LLINDAR_BAIX).mean()), 4),
"taxa_marcatge_alt": round(float((context["prob_test"] >= LLINDAR_ALT).mean()), 4)}
(Path(ruta_dir) / "referencia_deriva.json").write_text(json.dumps(ref, indent=2))
return ref
if __name__ == "__main__":
... # argparse igual que a 07-04
pipe, auc, ctx = entrenar(args.n, args.llavor)
meta = desar(pipe, auc, args.sortida, args.n, args.llavor)
fitxa = escriure_model_card(auc, ctx, Path(args.sortida).parent, args.n, args.llavor)
ref = escriure_referencia_deriva(ctx, Path(args.sortida).parent)
print(f"AUC test: {auc:.3f} -> {args.sortida}")
print("Cost de negoci en test:", fitxa["metriques"]["cost_negoci_test"])Explicació de les peces noves:
- La fitxa reuneix en un sol lloc el que un auditor, un company nou o el Diego necessiten: ús previst i no previst (la frase "no denegar compres" és una decisió ètica convertida en document), dades, mètriques tècniques i de negoci, limitacions honestes i versió.
- La referència de deriva desa com era l'import a l'entrenament (mitjana i els deu trams que deixen el 10 % de comandes a cadascun), la proporció de clients nous i quina fracció de comandes marcava el model al test. És el que el monitoratge compararà cada setmana.
- Les vores extremes es desen com a
None(infinit): així una comanda més cara que qualsevol de l'entrenament cau a l'últim tram en lloc de perdre's.
Sortida real de python -m novamarket.entrenar a l'entorn del curs:
AUC test: 0.844 -> models/model_devolucions.joblib
Cost de negoci en test: {'llindar_0.5': {'cost_eur': 2485, 'marcats': 60}, 'llindar_0.2': {'cost_eur': 1610, 'marcats': 200}}I models/model_card.md queda així (extracte):
# Model card: Predictor de devolucions NovaMarket (cas d'ús 3) v1.0.0
- **Data**: 2026-08-18 | **Responsable**: Marta (dades); propietari de negoci: Diego (operacions)
- **Ús previst**: Puntuar cada nit les comandes del dia i repartir-les en tres cues: aprovar (<0,2), revisar per una persona (0,2-0,5) i marcar (>=0,5).
- **Ús NO previst**: Denegar compres, penalitzar clients o decidir sense revisió humana per sobre de la banda.
- **Dades**: 2249 comandes d'entrenament, 750 de test (taxa de devolució 16.4%)
| Mètrica | Valor |
|---|---|
| AUC (test) | 0.8444 |
| Cost de negoci llindar 0.5 | 2485 € (60 marcats) |
| Cost de negoci llindar 0.2 | 1610 € (200 marcats) |Les xifres són exactament les de 04-05 (2.485 € → 1.610 €, 60 i 200 marcats): la model card no s'inventa res, documenta el que el codi calcula. models/ conté ara model_devolucions.joblib, model_devolucions.json, model_card.json, model_card.md i referencia_deriva.json; els tres JSON i el Markdown sí que van a Git (07-04).
11.4 Codi (b): predir_lot.py, les tres cues de cada nit (fase 6)
El lot nocturn rep un CSV amb les comandes del dia ja preparades per la mateixa consulta que alimenta l'entrenament (id_comanda més les 15 columnes de preparar_comandes), les puntua amb el model desat i produeix les cues.
# src/novamarket/predir_lot.py
"""Puntua per lots (cada nit) un CSV de comandes noves i les reparteix en tres cues.
Ús: python -m novamarket.predir_lot --entrada data/raw/comandes_2025-12-01.csv \
--sortida data/processed/cues_2025-12-01.csv"""
import argparse, json
from datetime import datetime
from pathlib import Path
import pandas as pd
from novamarket.predir import carregar
LLINDAR_BAIX, LLINDAR_ALT = 0.2, 0.5 # banda de revisió humana (04-05); la mateixa que a entrenar.py
def assignar_cua(prob, baix=LLINDAR_BAIX, alt=LLINDAR_ALT):
"""Tradueix una probabilitat en una de les tres cues operatives del Diego."""
if prob >= alt:
return "marcar" # es prepara la logística inversa; ningú no ho revisa a mà
if prob >= baix:
return "revisar" # una persona decideix (human-in-the-loop, 02-04)
return "aprovar" # segueix el flux normal
def puntuar_lot(model, comandes):
"""comandes: DataFrame amb id_comanda + les 15 columnes de preparar_comandes. Retorna id, prob i cua."""
columnes = [c for c in comandes.columns if c != "id_comanda"]
prob = model.predict_proba(comandes[columnes])[:, 1]
sortida = pd.DataFrame({"id_comanda": comandes["id_comanda"].values,
"prob_devolucio": prob.round(4)})
sortida["cua"] = sortida["prob_devolucio"].map(assignar_cua)
# De més a menys risc: si l'equip del Diego no arriba a tota la cua "revisar", comença pel pitjor
return sortida.sort_values("prob_devolucio", ascending=False).reset_index(drop=True)
def registrar_decisio(resum, ruta_log="logs/decisions.jsonl"):
"""Registre de decisions: una línia JSON per lot (quin model, quan, quantes a cada cua)."""
Path(ruta_log).parent.mkdir(parents=True, exist_ok=True)
with open(ruta_log, "a") as f:
f.write(json.dumps(resum) + "\n")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Puntua un lot de comandes i genera les cues")
parser.add_argument("--entrada", required=True)
parser.add_argument("--sortida", required=True)
parser.add_argument("--model", default="models/model_devolucions.joblib")
args = parser.parse_args()
model = carregar(args.model)
versio = json.loads(Path(args.model).with_suffix(".json").read_text()).get("versio", "?")
comandes = pd.read_csv(args.entrada)
cues = puntuar_lot(model, comandes)
Path(args.sortida).parent.mkdir(parents=True, exist_ok=True)
cues.to_csv(args.sortida, index=False)
recompte = cues["cua"].value_counts().to_dict()
resum = {"data_execucio": datetime.now().isoformat(timespec="seconds"), "lot": args.entrada,
"model_versio": versio, "n": int(len(cues)),
"aprovar": recompte.get("aprovar", 0), "revisar": recompte.get("revisar", 0),
"marcar": recompte.get("marcar", 0), "llindars": [LLINDAR_BAIX, LLINDAR_ALT]}
registrar_decisio(resum)
print(f"{len(cues)} comandes puntuades amb el model v{versio} -> {args.sortida}")
for cua in ("aprovar", "revisar", "marcar"):
print(f" {cua:8s}: {recompte.get(cua, 0):4d} ({recompte.get(cua, 0) / len(cues):.1%})")Explicació:
assignar_cuaés la banda de revisió humana de 04-05 i 06-04 feta codi: tres zones i dos llindars, definits com a constants amb nom perquè un canvi es faci en un sol lloc (i quedi a Git).puntuar_lotno toca el model: elPipelinedesat ja imputa, escala i codifica (per això vam insistir a 04-03 a posar la preparació a dins). Ordena per risc descendent: si l'equip només arriba a 300 revisions, revisa les 300 més arriscades.registrar_decisioés el registre de decisions de la fase 7: una línia per lot amb la versió del model (llegida del JSON que va deixardesar), la data i els recomptes. És el mínim per auditar i per dibuixar l'evolució setmana a setmana.- La sortida és un CSV que l'eina de magatzem ja sap carregar: integrar en el flux existent en lloc d'obligar el Diego a cridar una API.
Per provar-ho sense dades reals, simulem el fitxer que produiria la consulta SQL per a l'1 de desembre de 2025 (3.000 comandes, un dia de NovaMarket), sense l'etiqueta retornat, passant la mateixa data_inici que l'entrenament:
# simular_dia.py (només per al curs; en producció aquest CSV el produeix SQL sobre el magatzem de dades)
import numpy as np, pandas as pd
from novamarket.dades import generar_comandes_ml, embrutar_comandes, preparar_comandes
DATA_INICI_ENTRENAMENT = "2025-09-01" # la mateixa referència que va fer servir entrenar.py
def simular_dia(data, n=3000, llavor=1, factor_import=1.0, taxa_nous=None, sortida=None):
comandes = generar_comandes_ml(n, llavor)
if factor_import != 1.0: # escenari de deriva: comandes més cares
comandes["import_comanda"] = (comandes["import_comanda"] * factor_import).round(2)
if taxa_nous is not None: # escenari de deriva: campanya de captació
rng = np.random.default_rng(llavor)
comandes["client_nou"] = (rng.random(n) < taxa_nous).astype(int)
brut = embrutar_comandes(comandes, llavor)
brut["data_comanda"] = pd.Timestamp(data) # totes les comandes són del mateix dia
X, _ = preparar_comandes(brut, data_inici=DATA_INICI_ENTRENAMENT) # mateixa referència!
X.insert(0, "id_comanda", brut.loc[X.index, "id_comanda"].values)
if sortida:
X.to_csv(sortida, index=False)
return X
simular_dia("2025-12-01", llavor=2025, sortida="data/raw/comandes_2025-12-01.csv") # dia normal
simular_dia("2025-12-08", llavor=2026, factor_import=1.4, taxa_nous=0.45, # dia amb deriva
sortida="data/raw/comandes_2025-12-08.csv")Sortida real del lot del dia normal:
$ python -m novamarket.predir_lot --entrada data/raw/comandes_2025-12-01.csv --sortida data/processed/cues_2025-12-01.csv 2999 comandes puntuades amb el model v1.0.0 -> data/processed/cues_2025-12-01.csv aprovar : 2050 (68.4%) revisar : 641 (21.4%) marcar : 308 (10.3%)
I les primeres files de cues_2025-12-01.csv (ordenades per risc) i la línia del registre:
id_comanda,prob_devolucio,cua
P101388,0.9978,marcar
P102496,0.9818,marcar
P102743,0.9817,marcar
...
{"data_execucio": "2026-08-18T01:00:31", "lot": "data/raw/comandes_2025-12-01.csv", "model_versio": "1.0.0", "n": 2999, "aprovar": 2050, "revisar": 641, "marcar": 308, "llindars": [0.2, 0.5]}Fixa't en el número que el Diego veu primer: 641 comandes a revisar en un dia. Al test de 750 comandes, la banda donava 140 revisions (el 19 %); a escala de 3.000 comandes diàries són més de 600, el doble del que el seu equip pot absorbir (300, segons el document de definició). Això no invalida el model: és exactament la mena de troballa per a la qual existeix el mode ombra, i es resol a l'avaluació amb les parts interessades: pujar el llindar inferior (0,25 o 0,3) a canvi de deixar escapar més devolucions, o mantenir 0,2 i revisar en ordre de risc fins a esgotar la capacitat. A l'exercici 1 ho quantificaràs.
11.5 Codi (c): deriva.py, la comprovació setmanal (fase 7)
Cada dilluns es compara l'últim lot (o la unió dels set lots de la setmana) amb la referència desada en entrenar. Mesurem quatre coses: el PSI de l'import, la mitjana de l'import, la taxa de clients nous i la taxa de marcatge del model (proporció de comandes que no van a "aprovar").
# src/novamarket/deriva.py
"""Comprovació setmanal de deriva: compara el lot recent amb la referència desada en entrenar.
Ús: python -m novamarket.deriva --entrada data/raw/comandes_2025-12-01.csv --cues data/processed/cues_2025-12-01.csv"""
import argparse, json
from pathlib import Path
import numpy as np
import pandas as pd
PSI_AVIS, PSI_ALERTA = 0.10, 0.25 # convenció habitual: <0,1 estable, 0,1-0,25 vigilar, >0,25 actuar
DIF_TAXA_MARCATGE = 0.05 # 5 punts percentuals de diferència en la taxa de marcatge
def psi(freq_ref, freq_nou, eps=1e-4):
"""Population Stability Index entre dues distribucions per trams (llistes de freqüències que sumen 1)."""
ref = np.clip(np.asarray(freq_ref, float), eps, None)
nou = np.clip(np.asarray(freq_nou, float), eps, None)
return float(np.sum((nou - ref) * np.log(nou / ref)))
def frequencies_per_tram(valors, vores):
"""Reparteix 'valors' en els trams definits per 'vores' (None = infinit) i retorna freqüències."""
b = [-np.inf if x is None else x for x in vores]
b[-1] = np.inf
recompte, _ = np.histogram(pd.Series(valors).dropna(), bins=b)
return recompte / recompte.sum()
def comprovar_deriva(comandes, cues, referencia):
"""Retorna una llista d'indicadors amb el seu valor, la referència i el nivell: ok / avis / alerta."""
informe = []
freq = frequencies_per_tram(comandes["import_comanda"], referencia["import_vores"])
v = psi(referencia["import_frequencies"], freq)
informe.append({"indicador": "PSI import", "valor": round(v, 3), "referencia": 0.0,
"nivell": "alerta" if v > PSI_ALERTA else "avis" if v > PSI_AVIS else "ok"})
mitjana = float(comandes["import_comanda"].mean())
informe.append({"indicador": "import mitjà", "valor": round(mitjana, 2),
"referencia": referencia["import_mitja"],
"nivell": "avis" if abs(mitjana / referencia["import_mitja"] - 1) > 0.15 else "ok"})
nous = float(comandes["client_nou"].mean())
informe.append({"indicador": "taxa client_nou", "valor": round(nous, 3),
"referencia": referencia["client_nou_taxa"],
"nivell": "avis" if abs(nous - referencia["client_nou_taxa"]) > 0.10 else "ok"})
marcatge = float((cues["cua"] != "aprovar").mean()) # revisar + marcar = prob >= 0,2
dif = abs(marcatge - referencia["taxa_marcatge"])
informe.append({"indicador": "taxa de marcatge (>=0,2)", "valor": round(marcatge, 3),
"referencia": referencia["taxa_marcatge"],
"nivell": "alerta" if dif > 2 * DIF_TAXA_MARCATGE else "avis" if dif > DIF_TAXA_MARCATGE else "ok"})
return informe
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Comprovació de deriva del predictor de devolucions")
parser.add_argument("--entrada", required=True)
parser.add_argument("--cues", required=True)
parser.add_argument("--referencia", default="models/referencia_deriva.json")
args = parser.parse_args()
referencia = json.loads(Path(args.referencia).read_text())
informe = comprovar_deriva(pd.read_csv(args.entrada), pd.read_csv(args.cues), referencia)
print(pd.DataFrame(informe).to_string(index=False))
nivells = {fila["nivell"] for fila in informe}
if "alerta" in nivells:
print("\nALERTA: deriva significativa -> obrir tasca de reentrenament i avisar la Marta i el Diego")
elif "avis" in nivells:
print("\nAVÍS: vigilar la setmana que ve; si es repeteix, reentrenar")
else:
print("\nOK: sense deriva apreciable")Explicació:
- El PSI compara dos histogrames amb els mateixos trams: per a cada tram, la diferència de freqüències multiplicada pel logaritme del seu quocient, sumat. Val 0 si són idèntics i creix amb la diferència; la convenció de la indústria (heretada de l'scoring de crèdit) és que per sota de 0,1 no passa res, entre 0,1 i 0,25 cal vigilar i per sobre de 0,25 cal actuar. Els trams són els decils de l'entrenament (10 % de comandes a cadascun), així que la referència és simplement deu vegades 0,1.
- La taxa de marcatge és l'indicador més barat i més útil: no necessita etiquetes i reflecteix qualsevol canvi a les entrades que afecti el model. Si de sobte marca el 51 % en lloc del 27 %, l'equip de revisió es desborda i alguna cosa ha canviat.
- Els llindars d'avís i alerta són decisions, no lleis: es fixen amb el Diego i s'anoten al document.
Sortida real de les dues execucions:
$ python -m novamarket.deriva --entrada data/raw/comandes_2025-12-01.csv --cues data/processed/cues_2025-12-01.csv
indicador valor referencia nivell
PSI import 0.007 0.0000 ok
import mitjà 124.800 126.1500 ok
taxa client_nou 0.308 0.3024 ok
taxa de marcatge (>=0,2) 0.316 0.2667 ok
OK: sense deriva apreciable
$ python -m novamarket.deriva --entrada data/raw/comandes_2025-12-08.csv --cues data/processed/cues_2025-12-08.csv
indicador valor referencia nivell
PSI import 0.224 0.0000 avis
import mitjà 175.130 126.1500 avis
taxa client_nou 0.453 0.3024 avis
taxa de marcatge (>=0,2) 0.510 0.2667 alerta
ALERTA: deriva significativa -> obrir tasca de reentrenament i avisar la Marta i el DiegoEl primer lot s'assembla a l'entrenament i tot queda en verd. Al segon (simulem una campanya de Nadal: comandes un 40 % més cares i un 45 % de clients nous), l'import mitjà ha pujat a 175 €, el PSI és a la zona de vigilància i el model marca el 51 % de les comandes: 1.530 comandes a les cues de revisar i marcar en un sol dia. L'alerta salta abans de conèixer cap devolució real; això és deriva de dades. Si d'aquí a un mes, amb les etiquetes ja madures, l'AUC sobre aquestes comandes hagués caigut, seria a més deriva de concepte, i la resposta seria reentrenar amb les dades de la campanya incloses (python -m novamarket.entrenar amb l'històric ampliat, versió 1.1.0, nova model card, comparar en ombra, substituir).
Els tests del projecte s'amplien amb dos per al lot (tests/test_predir_lot.py: les fronteres d'assignar_cua amb 0,19/0,20/0,49/0,50 i que puntuar_lot retorna una fila per comanda amb probabilitats entre 0 i 1) i pytest passa 7 tests.
11.6 Calendari de monitoratge
| Freqüència | Què es mira | Qui | Llindar d'acció |
|---|---|---|---|
| Cada nit | El lot s'ha executat; n de comandes, temps, errors; recompte per cua a logs/decisions.jsonl |
Sistemes (alerta automàtica) | Lot absent o n fora de ±30 % de l'habitual → avís immediat |
| Cada setmana | deriva.py: PSI i mitjana d'import, taxa de clients nous, taxa de marcatge |
Marta | Avís → vigilar; alerta → tasca de reentrenament |
| Cada setmana | Cua "revisar": comandes revisades, decisió de la revisora, temps mitjà | Diego | Cua > 300/dia durant 3 dies → revisar llindar inferior |
| Cada mes | Amb etiquetes madures (comandes de fa ≥ 30 dies): AUC, cost real per llindar, cost davant de línia base; paritat de taxes entre zones (02-04) | Marta + Diego | AUC < 0,80 o cost ≥ línia base → reentrenar o aturar; ràtio d'impacte < 0,8 → revisar equitat |
| Cada trimestre | Revisió de la model card, del document de definició i de l'avaluació d'impacte; reentrenament programat encara que no hi hagi alerta | Marta, Diego, legal | Sempre (manteniment preventiu) |
Amb això el projecte ha tancat el cicle: definició, dades, prototip, avaluació, desplegament per lots amb banda de revisió i reversió, monitoratge amb alertes i documentació. A 09-04 recorreràs tu aquest mateix camí amb un cas propi.
Errors Comuns i Consells
- Començar pel model. Escriu el document de definició (una taula) abans d'obrir el notebook. Si no pots omplir "decisió que canvia" i "línia base", el projecte encara no existeix.
- Mètrica tècnica sense traducció. Un AUC sol no convenç ningú; acompanya'l sempre del cost en euros i del nombre de casos que arriben a les persones.
- Dades que no existiran o canviaran en servir. Comprova columna a columna que existeix en el moment de predir i que es calcula igual (
dies_des_inicin'era un exemple silenciós). Fes servir la mateixa consulta per entrenar i servir. - Divisió aleatòria en problemes temporals. Test posterior a l'entrenament; si no, mètriques optimistes.
- Desplegar de cop. Ombra → pilot en un magatzem → tot, sempre amb interruptor de reversió.
- Automatitzar del tot decisions sobre persones. Banda de revisió humana proporcional a l'impacte (02-04) i registre del que decideix la persona.
- No monitorar perquè "el model ja funciona". Programa la taxa de marcatge i un PSI des del primer dia; són quinze línies.
- Llindars d'alerta trets d'un manual. 0,1/0,25 per al PSI i ±5 punts de taxa de marcatge són punts de partida; ajusta'ls amb la variabilitat real de les teves setmanes i anota'ls.
- Documentar al final. La model card que es genera des del codi no costa res de mantenir; la que s'escriu a mà al final no s'escriu.
- Consell: desa cada versió del model amb el seu JSON i la seva fitxa, i no sobreescriguis mai l'anterior fins que la nova hagi passat per ombra.
Exercicis
Exercici 1: capacitat de revisió
El Diego només pot revisar 300 comandes al dia. Amb el fitxer cues_2025-12-01.csv (o generant-lo amb simular_dia), calcula quantes comandes anirien a "revisar" si el llindar inferior fos 0,25 i 0,30 (mantenint 0,5 com a superior). Proposa una decisió raonada. Pista: puntuar_lot ja retorna prob_devolucio; n'hi ha prou de comptar ((p >= u) & (p < 0.5)).sum().
Exercici 2: deriva de concepte sense deriva de dades
Descriu (sense codi) un canvi a NovaMarket que faria que deriva.py no donés cap alerta i, tanmateix, el model empitjorés de debò. Quin indicador del calendari de monitoratge ho detectaria, i amb quant de retard? Què afegiries a la comprovació setmanal per reduir aquest retard?
Exercici 3: document de definició del recomanador
Omple la taula de definició de la secció 11.1 per al recomanador de productes (cas d'ús 1) de NovaMarket. Para una atenció especial a "decisió que canvia", "mètrica de negoci", "línia base" i "desplegament" (lot o temps real?, mode ombra o A/B?).
Solucions
Solució 1. Executat sobre cues_2025-12-01.csv (2.999 comandes), comptant ((p >= u) & (p < 0.5)).sum() per a cada u: amb llindar inferior 0,2 hi ha 641 comandes a "revisar"; amb 0,25 en queden 484; amb 0,3 en queden 355 (la cua "marcar" no canvia: 308). Cap de les tres no cap en 300, però amb 0,3 l'excés és petit (55 comandes). El preu es veu al test de 04-05 (750 comandes, revisió a 3 € i revisora que encerta): amb banda 0,2-0,5 el cost era 1.555 € i s'escapaven 35 devolucions; amb 0,25-0,5 puja a 1.795 € (47 s'escapen) i amb 0,3-0,5 a 1.966 € (56 s'escapen). És a dir, pujar el llindar inferior a 0,3 estalvia unes 290 revisions diàries però costa uns 400 € més per cada 750 comandes: capacitat davant de cost, la mena d'intercanvi que decideix el Diego, no el model. Decisió raonable: llindar inferior 0,3 al pilot, cua ordenada per risc (les 55 que no s'arriben a revisar són les de menys probabilitat, entre 0,30 i 0,32, i s'aproven), mesurar el cost real al cap d'un mes amb les etiquetes madures i, si compensa, demanar capacitat de revisió addicional per tornar a 0,2. Alternativa igual de vàlida: mantenir 0,2, revisar les 300 més arriscades i aprovar la resta; com que la cua està ordenada, el resultat és pràcticament el mateix que pujar el llindar, però deixa registrat quantes comandes "de risc mitjà" queden sense revisar cada dia. L'important no és el número sinó el procés: la banda és una decisió operativa que es pren amb qui la pateix, no un paràmetre del model.
Solució 2. Exemple: NovaMarket llança "devolucions gratuïtes i sense preguntes durant 60 dies". Les comandes que entren són iguals (mateixos imports, mateixos clients, mateixa taxa de marcatge del model), però ara es retornen més i per motius diferents: la relació entre entrades i sortida ha canviat (deriva de concepte). deriva.py no hi veuria res. Ho detectaria la revisió mensual amb etiquetes madures (AUC i cost real per llindar), amb un retard d'entre 30 i 60 dies (el que triguen les devolucions a registrar-se). Per escurçar-lo: (a) afegir a la comprovació setmanal la taxa de devolució real de les comandes de fa 3-4 setmanes comparada amb la de l'entrenament (16,4 %), encara que sigui amb etiquetes parcials; (b) fer servir la cua de revisió com a sensor: la proporció de comandes que la revisora confirma com a risc canvia abans que les xifres globals; (c) subscriure el projecte als canvis de política comercial (una fila al registre de decisions: "canvi de política el dia X"), perquè les derives de concepte gairebé sempre tenen una causa coneguda per negoci.
Solució 3. Una proposta: Pregunta: quins productes tenen més probabilitat d'interessar aquest client ara. Decisió que canvia: quins 6 productes es mostren al bloc "També et pot interessar" de la fitxa i del correu setmanal (avui: els més venuts de la categoria). Mètrica de negoci: ingressos addicionals per sessió i taxa de clic/compra del bloc; tècnica: precisió al top-6 (proporció de recomanacions que acaben en clic o compra) mesurada sobre sessions passades. Línia base: "més venuts de la categoria" (el que hi ha avui) i "els mateixos que va comprar el client l'última vegada". Criteri d'èxit: +X % d'ingressos del bloc en un A/B de 4 setmanes sense pujar la taxa de devolució. Restriccions: RGPD (perfilat: informació i possibilitat d'oposar-s'hi), no recomanar per codi postal, no empènyer productes amb devolució alta (connexió amb el cas 3), risc mínim al Reglament d'IA. Dades: comandes.csv, clients.csv, productes.csv, navegació web; fuita típica: fer servir compres posteriors a la sessió. Desplegament: la llista de recomanats per client es pot calcular en lot cada nit (n'hi ha prou per al correu i per a la majoria de fitxes) i servir-se des d'una taula; el web la llegeix per API; A/B obligatori perquè l'efecte només es mesura causalment. Monitoratge: cobertura (a quants clients se'ls recomana alguna cosa?), diversitat, clics, i que no apareguin sempre els mateixos cinc productes. Propietaris: màrqueting (negoci), Marta, sistemes.
Conclusió
Aquesta lliçó ha convertit el mapa de 04-01 en un mètode. El cicle de vida d'un projecte d'IA (definició del problema i de l'èxit, dades, exploració, prototip, avaluació, desplegament, monitoratge i documentació) és la forma que han pres CRISP-DM i CRISP-ML(Q), amb les fletxes de tornada com a norma i la qualitat com a fil transversal. Hem vist quines preguntes respon cada fase (la decisió que canvia, la mètrica en euros davant de la tècnica, la línia base, la disponibilitat de cada dada en el moment de predir, la divisió temporal, el pressupost del prototip, l'avaluació amb les persones afectades, lot davant de temps real, ombra i A/B, reversió, deriva de dades i de concepte, model card i fitxa de dades), qui hi participa (negoci, dades, enginyeria, legal) i quins errors enfonsen projectes (començar pel model, no tenir línia base, mètrica equivocada, dades que no existiran en producció, no tancar el cicle). I ho hem fet de debò amb el predictor de devolucions dins de novamarket_ia/: un document de definició d'una pàgina, la correcció d'un training-serving skew a dies_des_inici, una model card en JSON i Markdown generada des d'entrenar.py amb les xifres de sempre (AUC 0,844; 2.485 € → 1.610 €), un predir_lot.py que reparteix 2.999 comandes d'una nit en 2.050 aprovades, 641 a revisar i 308 marcades i en deixa constància en un registre, un deriva.py que dona verd en un dia normal i alerta en un dia de campanya (PSI 0,224, taxa de marcatge del 51 %), i un calendari de monitoratge amb responsables i llindars.
La Marta i el Diego tenen ara un mètode i un projecte que el segueix. Però un mètode s'aprèn també mirant com l'han aplicat, bé o malament, altres: què van fer les plataformes de recomanació, els sistemes de diagnòstic per imatge, AlphaGo i AlphaFold, les eines de selecció de personal que discriminaven, els assistents que es van inventar polítiques i els projectes cèlebres que van fracassar. És la següent lliçó, 08-02, Casos d'Estudi en IA, on cada cas acaba amb la mateixa pregunta: què n'aprèn NovaMarket.
Fonaments d'Intel·ligència Artificial (IA)
Mòdul 1: Introducció a la Intel·ligència Artificial
Mòdul 2: Principis Bàsics de la IA
- Conceptes Fonamentals: Agents, Entorns i Racionalitat
- Tipus d'Intel·ligència Artificial
- Les Dades com a Matèria Primera de la IA
- Ètica i Consideracions en IA
Mòdul 3: Algorismes en IA
- Introducció als Algorismes
- Algorismes de Cerca
- Cerca amb Adversari: Jocs i Minimax
- Algorismes d'Optimització
Mòdul 4: Aprenentatge Automàtic (Machine Learning)
- Conceptes Bàsics de Machine Learning
- Tipus d'Aprenentatge Automàtic
- Preparació de Dades i Característiques
- Algorismes de Machine Learning
- Avaluació i Validació de Models
- Sobreajust, Regularització i Ajust d'Hiperparàmetres
Mòdul 5: Xarxes Neuronals i Deep Learning
- Introducció a les Xarxes Neuronals
- Arquitectura de Xarxes Neuronals
- Com Aprèn una Xarxa: Descens del Gradient i Retropropagació
- Deep Learning i les seves Aplicacions
- Transformers, Grans Models de Llenguatge i IA Generativa
Mòdul 6: Lògica i Sistemes Experts
- Lògica en IA
- Sistemes Experts
- Raonament amb Incertesa: Probabilitat i Xarxes Bayesianes
- Aplicacions dels Sistemes Experts
Mòdul 7: Eines i Llenguatges de Programació en IA
- Llenguatges de Programació per a IA
- Python Científic: NumPy, pandas i Matplotlib
- Eines i Llibreries Populars
- Entorns de Desenvolupament
Mòdul 8: Projectes i Casos d'Estudi
Mòdul 9: Exercicis i Pràctiques
- Exercicis d'Algorismes
- Pràctiques de Machine Learning
- Projectes de Xarxes Neuronals
- Projecte Integrador: de la Idea al Prototip
