MLOps — Módulo 2: reproducibilidad — versionar datos y no solo código
Segundo módulo del Curso de MLOps: por qué un resultado de ML es código más datos más entorno más hiperparámetros más seed, por qué git no sirve para datasets grandes, y cómo DVC versiona los datos con un puntero liviano en git mientras guarda los archivos en S3 o GCS.
☰ Índice del contenido
En el módulo 1 vimos la diferencia central: un modelo de ML depende de datos, no solo de código. De ahí sale una consecuencia incómoda que este módulo ataca de frente. Un colega te pregunta: “¿me puedes regenerar exactamente el modelo que está en producción?”. Si tu respuesta es “creo que sí, pero no estoy seguro de con qué datos lo entrené”, no tienes MLOps: tienes un experimento suelto. Reproducir un modelo bit a bit es la base sobre la que se apoya todo lo demás, y empieza por versionar algo que git no sabe manejar: los datos.
La tesis del módulo
Un resultado de ML no es solo código: es código + datos + entorno + hiperparámetros + seed. Si te falta versionar cualquiera de esas cinco piezas, no puedes reproducir el modelo — y si no lo puedes reproducir, no lo puedes arreglar, comparar ni auditar.
Las cinco piezas de un resultado reproducible
Cuando entrenas un modelo, el resultado depende de cinco cosas. Cambia una y el modelo cambia:
- Código — el script de entrenamiento. Git ya lo versiona bien.
- Datos — el dataset exacto con que entrenaste. Git no lo maneja bien; ese es el foco del módulo.
- Entorno — las versiones de las librerías (scikit-learn 1.4 no da lo mismo que 1.2).
- Hiperparámetros — la configuración del entrenamiento (profundidad del árbol, learning rate).
- Seed — la semilla del generador aleatorio. Sin fijarla, dos corridas idénticas dan modelos distintos.
El seed merece un segundo. Muchos algoritmos usan azar (barajar los datos, inicializar pesos). Si no fijas la semilla, cada corrida es irrepetible. Fijarla vuelve el azar determinista:
import numpy as np
import random
SEED = 42
random.seed(SEED) # aleatoriedad de Python
np.random.seed(SEED) # aleatoriedad de NumPy
# y en el modelo, pasar el seed explícito:
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators=200, random_state=SEED)
Ojo: fijar el seed hace la corrida reproducible en la misma máquina y versiones. Si cambias la versión de la librería o el hardware, el resultado puede variar igual. Por eso el entorno también se versiona (lo vemos más abajo).
Por qué git no sirve para datasets
Git fue diseñado para texto: archivos de código de kilobytes que cambian línea por línea. Un dataset es lo opuesto: un CSV de 5 GB o una carpeta de 200.000 imágenes. Meter eso en git rompe todo:
- El repo explota. Git guarda cada versión del archivo completo. Diez versiones de un CSV de 5 GB son 50 GB en tu
.git, y clonar el repo se vuelve eterno. - No hay diff útil. Git no te dice “cambiaron estas 3 filas”: ve un binario gigante distinto y lo guarda entero de nuevo.
- GitHub lo rechaza. El límite por archivo es 100 MB. Tu dataset ni siquiera entra.
La solución no es meter los datos en git, sino guardar en git un puntero liviano a los datos, y los datos de verdad en un almacenamiento pensado para archivos grandes (S3, GCS, un disco de red).
DVC: versionar datos con un puntero en git
DVC (Data Version Control, control de versiones para datos) hace exactamente eso. Funciona encima de git: cuando le pides versionar un dataset, DVC calcula un hash del archivo, guarda un archivito de metadatos (.dvc) de unos cien bytes — ese sí va a git — y sube los datos reales a un almacenamiento remoto que tú configuras.
GIT (liviano) ALMACENAMIENTO REMOTO
(S3 / GCS)
┌───────────────────┐ ┌───────────────────┐
│ train.py │ │ │
│ params.yaml │ │ datos.csv 5 GB │
│ datos.csv.dvc ───┼──hash──┼─► (archivo real) │
│ (~100 bytes) │ │ │
└───────────────────┘ └───────────────────┘
versionas esto dvc push sube esto
El .dvc es un puntero: adentro guarda el hash del dataset. Cuando alguien hace git checkout de un commit viejo y luego dvc pull, DVC lee el hash del .dvc y descarga exactamente la versión de los datos que correspondía a ese commit. Así, un commit de git queda amarrado a una versión precisa de los datos.
Los comandos del día a día:
# 1. Inicializar DVC dentro de un repo git ya existente
dvc init
git add .dvc .dvcignore
git commit -m "Inicializa DVC"
# 2. Configurar dónde viven los datos (un bucket S3, por ejemplo)
dvc remote add -d almacen s3://mi-bucket/datos
git add .dvc/config
git commit -m "Configura remote de datos"
# 3. Poner un dataset bajo control de DVC
# Crea datos.csv.dvc (el puntero) y deja de trackear el CSV en git
dvc add datos/train.csv
git add datos/train.csv.dvc datos/.gitignore
git commit -m "Versiona dataset de entrenamiento v1"
# 4. Subir los datos reales al almacenamiento remoto
dvc push
# --- Otra persona, otra máquina ---
git clone <repo> && cd repo
dvc pull # descarga los datos que corresponden a este commit
La combinación clave es git checkout + dvc pull: git te lleva a la versión del código y del puntero; DVC te trae la versión de los datos que ese puntero indica. Vuelves a cualquier punto de la historia con datos y código en sincronía.
Qué versionar (y con qué herramienta)
Reproducir de verdad significa versionar las cinco piezas, cada una con la herramienta adecuada:
| Pieza | Con qué se versiona | Cómo |
|---|---|---|
| Código | Git | Commits normales |
| Datos | DVC | dvc add + dvc push |
| Entorno | requirements.txt / Docker | Versiones pineadas, imagen inmutable |
| Hiperparámetros | Git (params.yaml) | Archivo de config versionado |
| Modelo entrenado | DVC o Model Registry | Módulo 4 lo formaliza |
El entorno se versiona pineando versiones exactas en requirements.txt (scikit-learn==1.4.2, no scikit-learn) y, mejor aún, empaquetando todo en una imagen de contenedor inmutable. Si esa parte te suena nueva, el módulo de Contenerización con Docker del curso de Deploy la cubre en detalle: la misma imagen que usas para desplegar te sirve para reproducir el entorno de entrenamiento.
Ojo: sin versionar los datos no puedes reproducir ni auditar un modelo. El día que un modelo de crédito rechace a alguien y te pregunten “¿con qué datos se entrenó esto?”, la respuesta tiene que ser un hash exacto recuperable, no un “estaba en una carpeta que ya no existe”.
Errores comunes
- Versionar solo el código y creer que basta. El mismo
train.pysobre un dataset distinto produce otro modelo. Sin versionar los datos, el código solo cuenta la mitad de la historia. - Meter el dataset directo en git. Infla el repo, rompe los clones y GitHub lo rechaza sobre 100 MB. Para eso existe DVC.
- No fijar el seed. Dos corridas idénticas dan modelos distintos y te vuelves loco buscando una diferencia que es puro azar.
- Dejar las versiones de librerías sin pinear.
scikit-learnsin versión instala hoy una y mañana otra; el modelo deja de ser reproducible sin que cambies una línea tuya. - Guardar los datos en el remoto pero olvidar
dvc push. El.dvcviaja en git, pero si no subiste los datos, eldvc pulldel colega falla. Puntero sin datos no reproduce nada.
La frase para llevarse
“Git versiona la receta; DVC versiona los ingredientes. Reproducir un modelo necesita las dos, más el entorno, los hiperparámetros y el seed. Falta una y la cocina no se repite.”
Qué viene
Módulo 3: experiment tracking. Ya sabes reproducir un resultado; ahora entrenarás decenas de variantes y necesitas registrar cuál fue cuál sin terminar con archivos llamados modelo_final_v2_bueno.pkl. Ahí entra MLflow.
Material relacionado
Preguntas sobre este curso
Regístrate gratis para hacer preguntas y guardar tu progreso.
✉ Entérate de lo nuevo
Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.