Tenim els tres ingredients versionats —codi, entorn i dades—, però la recepta encara es cuina a mà: algú ha de recordar-se d'executar la neteja, després les features, després l'entrenament, després l'avaluació, en aquest ordre, i de repetir els passos correctes quan alguna cosa canvia. En aquesta lliçó convertim aquest ritual en un pipeline declarat: un fitxer dvc.yaml on cada etapa diu quina ordre executa, de què depèn i què produeix, i una sola ordre —dvc repro— que ho reconstrueix tot re-executant únicament el que ha canviat. És la peça que tanca el mòdul i la promesa que perseguim des del diagnòstic: que qualsevol persona de l'equip pugui clonar el repositori i obtenir el mateix model que Laura, sense Laura.
Contingut
- Què és un pipeline d'entrenament i per què encadenar a mà és fràgil
- El pipeline de CineClick a
dvc.yaml params.yaml: els hiperparàmetres com a dependènciadvc repro: el graf de dependències en acciódvc dagidvc metrics diff: veure el pipeline i comparar execucions- Tancament del mòdul: què hem resolt i què continua obert
Què és un pipeline d'entrenament i per què encadenar a mà és fràgil
Un pipeline d'entrenament és la seqüència de passos que transforma dades crues en un model avaluat: en el nostre cas, netejar → construir features → entrenar → avaluar. La seqüència ja existeix implícitament al nostre codi des de 02-01; el problema és com s'executa. Avui, el procediment és mental: Laura (o tu) llança els passos a mà. I l'encadenament manual falla de maneres molt concretes:
- Ordre i omissions: res no impedeix entrenar amb unes features velles perquè se't va oblidar re-executar el pas anterior després de canviar la neteja. El model surt, no dona error, i és subtilment incorrecte — la pitjor classe de bug.
- Re-execucions innecessàries: l'alternativa defensiva ("per si de cas, ho rellanço tot des de zero") malgasta temps, i quan la preparació de dades trigui hores, aquest "per si de cas" serà insostenible.
- Coneixement no transferible: l'ordre correcte, les ordres exactes i les seves condicions viuen al cap d'algú o en un README que envelleix. És el problema núm. 10 del diagnòstic amb corbata nova.
- Sense registre de què va produir què: encara que les dades estiguin versionades, res no lliga aquesta versió del model a aquella execució amb aquells paràmetres.
La solució és la mateixa que apliquen make o els sistemes de build des de fa dècades: declarar els passos amb les seves dependències i sortides, i deixar que l'eina calculi què cal executar. DVC ho porta de sèrie, integrat amb el versionat de dades de la lliçó anterior: les sortides de cada etapa queden automàticament sota control de DVC.
El pipeline de CineClick a dvc.yaml
Primer, una petita adaptació del codi: cada mòdul del paquet guanya una funció main() executable (python -m cineclick_churn.data, etc.) que llegeix la seva configuració i escriu les seves sortides a disc — el patró que vam deixar preparat a 02-01. Els passos es comuniquen per fitxers, no per variables en memòria: data.py escriu el CSV net, features.py el llegeix i escriu la matriu de features, train.py la llegeix i escriu el model més el conjunt de test reservat, i evaluate.py llegeix tots dos i escriu les mètriques. Aquesta comunicació per fitxers és just el que permet a DVC saber qui depèn de qui.
Amb això, el pipeline complet es declara a dvc.yaml, a l'arrel del repo:
# dvc.yaml — el pipeline d'entrenament de CineClick
stages:
preparar_dades:
cmd: python -m cineclick_churn.data
deps:
- data/raw/clients_churn.csv
- src/cineclick_churn/data.py
outs:
- data/processed/clients_net.csv
construir_features:
cmd: python -m cineclick_churn.features
deps:
- data/processed/clients_net.csv
- src/cineclick_churn/features.py
outs:
- data/processed/features.csv
entrenar:
cmd: python -m cineclick_churn.train
deps:
- data/processed/features.csv
- src/cineclick_churn/train.py
params:
- entrenar.test_size
- entrenar.random_state
- entrenar.n_estimators
outs:
- models/model_churn.pkl
- data/processed/test.csv
avaluar:
cmd: python -m cineclick_churn.evaluate
deps:
- models/model_churn.pkl
- data/processed/test.csv
- src/cineclick_churn/evaluate.py
metrics:
- metrics.json:
cache: falseDesgranem l'anatomia d'una etapa (stage):
cmd: l'ordre que l'executa. Qualsevol ordre de shell val; fem servir els mòduls del paquet, que és el nostre codi testejat.deps: les seves dependències — fitxers de dades i també el codi font del pas. Això és clau: si algú modificadata.py, DVC sap que la neteja (i tot el que en penja) s'ha de repetir. El codi és una dependència més.params: dependències de gra fi sobre valors delparams.yaml(apartat següent). Només els valors llistats afecten l'etapa.outs: el que produeix. DVC pren el control d'aquestes sortides: les caceja per hash (com feiadvc add, però ara automàticament) i les afegeix al.gitignore. El modelmodel_churn.pklpassa a estar versionat per DVC sense que fem res més — el seu resguard viu ara advc.lock, el fitxer que DVC genera amb els hashes exactes de cada execució i que sí que es commiteja.metrics: una sortida especial — un petit JSON de mètriques que sí que volem llegible i comparable (per aixòcache: false: és diminut i ens interessa veure'l a Git, amb els seus diffs).
L'etapa avaluar escriu el metrics.json amb les mètriques honestes de 02-01:
Un detall de disseny que mereix una pausa: entrenar produeix dues sortides, el model i test.csv (la partició de test). Per què? Perquè avaluar ha de mesurar sobre exactament les files que l'entrenament no va veure; si cada etapa repetís l'split pel seu compte, un descuit als paràmetres podria avaluar sobre dades d'entrenament. Materialitzar el test com a fitxer fa la frontera explícita i auditable.
params.yaml: els hiperparàmetres com a dependència
A 02-01 vam posar els paràmetres a configs/config.yaml. DVC introdueix una distinció útil que adoptem ara: les rutes (on és cada cosa) ja estan declarades al mateix dvc.yaml via deps/outs, mentre que els hiperparàmetres (els valors que canvies quan experimentes) es muden a params.yaml, el fitxer que DVC llegeix per convenció:
# params.yaml — hiperparàmetres del pipeline entrenar: test_size: 0.2 random_state: 42 n_estimators: 100
El codi de train.py el carrega igual que carregava el config (yaml.safe_load), així que el canvi és menor. El guany és conceptual i pràctic alhora: en declarar params: [entrenar.n_estimators, ...] a l'etapa, cada hiperparàmetre es converteix en una dependència rastrejada individualment. DVC no vigila "el fitxer params.yaml ha canviat" sinó "el valor entrenar.n_estimators ha canviat" — i només invalida les etapes que depenen d'aquell valor concret. Editar un comentari del YAML no re-executa res.
dvc repro: el graf de dependències en acció
Tot declarat. L'ordre estrella:
A la primera execució, DVC recorre el graf en ordre i executa les quatre etapes, cacejant cada sortida i anotant a dvc.lock els hashes de totes les dependències i sortides de cada etapa. Aquest dvc.lock es commiteja al costat de dvc.yaml i params.yaml: és la constància exacta de "amb aquestes entrades es van produir aquestes sortides".
La màgia apareix a la segona execució. Suposa que vols provar un bosc més gran — edites params.yaml i canvies n_estimators: 100 per n_estimators: 300:
Stage 'preparar_dades' didn't change, skipping Stage 'construir_features' didn't change, skipping Running stage 'entrenar': > python -m cineclick_churn.train Running stage 'avaluar': > python -m cineclick_churn.evaluate Updating lock file 'dvc.lock'
DVC ha comparat hashes: les dades crues no han canviat, data.py i features.py tampoc, així que la neteja i les features se salten — les seves sortides cacejades continuen sent vàlides. Només entrenar (el paràmetre del qual ha canviat) i avaluar (que depèn del model nou) es re-executen. Amb datasets grans, això és la diferència entre iterar en minuts o en hores. I funciona en totes les direccions: si en lloc del paràmetre canvia data/raw/clients_churn.csv (extracte nou via dvc add... o directament com a dependència), es re-executa tot, perquè tot penja de les dades; si només canvia evaluate.py, es re-executa únicament l'avaluació. Mai de més, mai de menys: el "què he de rellançar?" ha deixat de ser una decisió humana.
dvc dag i dvc metrics diff: veure el pipeline i comparar execucions
Per veure el graf que DVC ha construït a partir de les dependències declarades:
+----------------+
| preparar_dades |
+----------------+
*
+--------------------+
| construir_features |
+--------------------+
*
+----------+
| entrenar |
+----------+
*
+---------+
| avaluar |
+---------+Que en versió bonica és aquest DAG (graf dirigit acíclic — dirigit perquè les dades flueixen en un sentit, acíclic perquè cap pas no pot dependre de si mateix):
flowchart TD
RAW[("data/raw/<br/>clients_churn.csv<br/>(DVC)")] --> P
P["preparar_dades<br/><small>data.py</small>"] --> |clients_net.csv| F
F["construir_features<br/><small>features.py</small>"] --> |features.csv| T
PARAMS[["params.yaml<br/>n_estimators, test_size,<br/>random_state"]] --> T
T["entrenar<br/><small>train.py</small>"] --> |model_churn.pkl| E
T --> |test.csv| E
E["avaluar<br/><small>evaluate.py</small>"] --> M[["metrics.json"]]I per a l'experiment dels 300 arbres, la pregunta de negoci és "ha millorat?". Com que metrics.json està declarat com a mètrica, DVC sap comparar-lo entre l'estat actual i l'últim commit:
Path Metric HEAD workspace Change metrics.json accuracy 0.86 0.8613 0.0013 metrics.json f1 0.5 0.512 0.012 metrics.json precision 0.61 0.618 0.008 metrics.json recall 0.43 0.44 0.01
Veredicte: triplicar els arbres esgarrapa una millora marginal en totes les mètriques — probablement no compensa el triple de temps d'entrenament i inferència, així que revertim el paràmetre (o commitegem el canvi si decidim que sí). L'important és el flux: canviar un valor, dvc repro, dvc metrics diff, decidir amb dades. Ara bé, siguem honestos sobre els límits: això compara el workspace contra un commit, d'un en un. Quan vulguis explorar vint combinacions d'hiperparàmetres, veure-les en una taula, ordenar-les per recall i recuperar-ne qualsevol, aquest mecanisme es queda curt — aquesta comoditat és exactament el que aporta MLflow, i és la primera parada del mòdul 3.
El tancament del cercle és el flux complet d'un company nou. Marc, en una màquina neta:
git clone <repo> && cd cineclick-churn python -m venv .venv && source .venv/bin/activate pip install pip-tools && pip-sync requirements.lock && pip install -e . --no-deps dvc pull # porta les dades (i sortides cacejades) del remote dvc repro # "Data and pipelines are up to date."
Aquest missatge final — tot al dia, res a re-executar — és la reproduïbilitat feta frase: els hashes de dades, codi i paràmetres de la màquina de Marc coincideixen exactament amb els del dvc.lock commitejat, per tant el seu pipeline produiria el mateix model que el de Laura. I si esborra models/ i rellança dvc repro, ho comprova empíricament: mateixes mètriques, decimal a decimal. CineClick mai no havia estat aquí.
Tancament del mòdul: què hem resolt i què continua obert
Passem revista al diagnòstic de la lliçó 01-04, que era la nostra llista de tasques:
| # | Problema | Estat després del mòdul 2 |
|---|---|---|
| 1 | Rutes absolutes i personals | ✅ Resolt (02-01): rutes relatives a configuració i dvc.yaml |
| 2 | Sense llavor aleatòria | ✅ Resolt (02-01): random_state=42 a split i model, split estratificat |
| 3 | Dades sense versionar | ✅ Resolt (02-03): DVC + remote; viatge en el temps amb git checkout + dvc checkout |
| 8 | Dependències implícites | ✅ Resolt (02-02): pyproject.toml + requirements.lock + .python-version |
| 9 | Preprocessament no reutilitzable | ✅ Encarrilat (02-01): features.py importable; el servei del mòdul 4 el consumirà |
| 10 | Procés 100% manual | 🟡 Parcial (02-04): repetible amb dvc repro, però continua llançant-se a mà i en local — programar-lo i orquestrar-lo arriba a 05-03 |
| 5 | Mètrica única i enganyosa | 🟡 Parcial (02-01): precision/recall/F1 a metrics.json; comparar molts experiments amb comoditat → mòdul 3 |
| 4 | Model sense versionar ni metadades | 🟡 Parcial: el .pkl és a la caché de DVC lligat al seu commit, però sense registre formal de versions/estats → model registry, mòdul 3 |
| 7 | Sense registre d'experiments | ❌ Obert: dvc metrics diff compara de dos en dos; l'historial còmode i consultable → MLflow, mòdul 3 |
| 6 | Sense tests | ❌ Obert (llevat de la llavor plantada a 02-01): tests de dades i model en CI → mòdul 5 |
Quatre problemes tancats, tres ben encarrilats, i els tres restants amb propietari i data. Gens malament per a un mòdul.
Errors Comuns i Consells
- Error: oblidar el codi a les
deps. Sientrenarnomés declarafeatures.csvcom a dependència, un canvi atrain.pyno re-executarà res i continuaràs servint el model del codi vell. Regla: cada etapa depèn de les seves dades d'entrada i del seu codi. - Error: passos que es comuniquen per variables en memòria. Si un sol script fa features+entrenament "perquè és més còmode", DVC no pot saltar-se la part cara quan només canvia la barata. La granularitat del pipeline la marquen els fitxers intermedis: si vols poder saltar-te un pas, materialitza'n la sortida.
- Error: editar a mà una sortida (
outs). Retocardata/processed/features.csvamb Excel dura fins al següentdvc repro, que el regenerarà des de les seves dependències. Tot canvi ha d'entrar pel principi del graf: dades crues, codi o paràmetres. - Error: no commitejar
dvc.lock. Sense ell, la resta de l'equip no sap quins hashes corresponen a l'última execució bona, i eldvc reprode Marc no pot verificar que està al dia.dvc.yaml,dvc.lockiparams.yamlviatgen junts al commit. - Consell: mantén
dvc reprobarat. La disciplina de "ha millorat?" ambdvc metrics diffnomés es practica si iterar costa minuts. Si un pas es torna lent, trosseja'l (la part cara i estable separada de la barata i canviant) perquè la caché treballi a favor teu.
Exercicis
Exercici 1
Per a cadascun d'aquests canvis, indica quines etapes re-executarà el següent dvc repro (preparar_dades / construir_features / entrenar / avaluar) i per què: (a) canvies entrenar.test_size de 0.2 a 0.25 a params.yaml; (b) el data engineer lliura l'extracte d'agost i substitueixes data/raw/clients_churn.csv; (c) corregeixes un docstring a evaluate.py; (d) afegeixes una regla nova de neteja a data.py; (e) reordenes els comentaris de params.yaml sense tocar cap valor.
Exercici 2
L'equip vol provar de limitar la profunditat dels arbres per accelerar la inferència. Descriu els canvis exactes necessaris per incorporar l'hiperparàmetre max_depth al pipeline (quins fitxers toques i què hi afegeixes a cadascun), i el flux complet per avaluar si max_depth: 10 val la pena davant de la línia base.
Exercici 3
Un directiu escèptic pregunta: "i com em demostreu que el model que teniu és reproduïble, i no una altra vegada el 0.87 de Laura?". Dissenya la demostració: la seqüència d'ordres que executaries davant seu en una màquina neta, què esperaries veure a cada pas, i quin és el missatge/resultat concret que constitueix la prova.
Solucions
Solució 1: (a) entrenar i avaluar — el paràmetre és dependència declarada d'entrenar, i avaluar depèn de les seves sortides (model i test); les dues primeres etapes no en depenen i se salten. (b) Les quatre — tot el graf penja de les dades crues. (c) Només avaluar — evaluate.py és dependència únicament d'aquesta etapa (sí, DVC re-executa encara que el canvi sigui un docstring: DVC compara hashes de fitxer, no semàntica; és el preu de la seguretat). (d) Les quatre — data.py és dependència de preparar_dades, la seva sortida canvia (o pot canviar), i la invalidació es propaga aigües avall. (e) Cap — les etapes depenen de valors concrets del params.yaml (entrenar.n_estimators, etc.), no del fitxer com a text, i cap valor no ha canviat.
Solució 2: canvis: (1) a params.yaml, afegir max_depth: 10 sota entrenar:; (2) a src/cineclick_churn/train.py, llegir el valor i passar-lo al RandomForestClassifier(..., max_depth=params["max_depth"]); (3) a dvc.yaml, afegir entrenar.max_depth a la llista params de l'etapa entrenar. Flux d'avaluació: dvc repro (només re-executa entrenar+avaluar; nota: la primera vegada re-entrena de totes maneres perquè train.py ha canviat), dvc metrics diff per comparar contra HEAD; si les mètriques aguanten (recall similar) amb arbres més petits, commit de params.yaml+train.py+dvc.yaml+dvc.lock i dvc push; si no, git checkout -- . i dvc checkout per descartar.
Solució 3: en una màquina neta (o un contenidor): (1) git clone + creació de l'entorn amb pip-sync requirements.lock + pip install -e . --no-deps — s'espera instal·lació sense errors amb versions exactes; (2) dvc pull — descarrega les dades exactes referenciades pel commit; (3) dvc repro — s'espera "Data and pipelines are up to date", que ja és en si la verificació de hashes; (4) per a la prova forta: esborrar models/model_churn.pkl i metrics.json i rellançar dvc repro — el pipeline re-entrena des de zero; (5) mostrar metrics.json i comparar-lo amb el commitejat (o dvc metrics diff, esperant canvis buits). La prova concreta: les mètriques regenerades coincideixen decimal a decimal amb les commitejades (accuracy 0.86, precision 0.61, recall 0.43, F1 0.50), en una màquina on Laura no ha tocat res. Això és exactament el que el 0.87 original no podia oferir.
Conclusió
El mòdul 2 acaba on prometia: el notebook churn_final_v3_DEFINITIU.ipynb ja és un projecte amb paquet instal·lable i testejat, entorn fixat amb lock, dades versionades amb DVC i un pipeline declarat a dvc.yaml —preparar_dades → construir_features → entrenar → avaluar— que qualsevol reconstrueix amb git clone, dvc pull i dvc repro, obtenint el mateix model i les mateixes mètriques honestes (precision 0.61, recall 0.43, F1 0.50) decimal a decimal. La reproduïbilitat, el fonament de tota la resta, ja està posada. Però en repassar el diagnòstic hem vist el que falta, i l'experiment dels 300 arbres ho ha fet tangible: canviar un paràmetre i comparar una execució contra una altra funciona; explorar desenes de combinacions, veure-les en una taula ordenada per recall, saber què es va provar fa tres setmanes i amb quin resultat, i gestionar quina versió del model està "beneïda" per a producció — això demana eines dedicades. Al mòdul 3 les donem a CineClick: MLflow per al tracking d'experiments, el seu model registry per versionar i promocionar models, i una mirada crítica als feature stores. Laura podrà respondre per fi, amb dades, la pregunta que va quedar flotant al mòdul 1: què era exactament "la v2", i si de debò era pitjor.
Curs de MLOps
Mòdul 1: Fonaments de MLOps
- Què és MLOps i per què els models moren al notebook
- El cicle de vida d'un model de ML en producció
- Nivells de maduresa MLOps i rols de l'equip
- El projecte del curs: del notebook a producció
Mòdul 2: Del notebook al codi reproduïble
- Estructura d'un projecte de ML: del notebook al paquet
- Entorns reproduïbles i gestió de dependències
- Versionat de dades amb DVC
- Pipelines d'entrenament reproduïbles
Mòdul 3: Experiments i registre de models
- Tracking d'experiments amb MLflow
- Model registry: versionar i promocionar models
- Feature stores: quan i per a què
Mòdul 4: Servir models en producció
- Patrons de desplegament: batch, online i streaming
- Un servei de predicció amb FastAPI
- Empaquetatge amb Docker
- Escalat i desplegament: Kubernetes i serverless
- Optimització de la inferència: latència i cost
Mòdul 5: Automatització: CI/CD i orquestració
- CI per a ML: tests de codi, dades i models
- CD: automatitzar el desplegament del model
- Orquestració de pipelines de ML
- Estratègies de release: shadow, canary i A/B
