English · Español
Lab 02 — Detección de drift (KL + PSI sobre la distribución de tokens de verbo)¶
Objetivo: implementar
scripts/mlops/drift.pyy demostrar que dispara ante un desplazamiento conocido de la distribución de tokens de verbo.Tiempo estimado: 3–4 horas.
Prerequisito: lab 00 hecho; el corpus de verbos de la Fase 12 + su histograma de tokens de entrenamiento son accesibles vía DVC.
Lo que produces¶
experiments/38-drift-detection/ conteniendo:
inject.py— script que construye el desplazamiento sintético sobre la rejilla de verbos.measure.py— calcula KL y PSI sobre distribuciones baseline y desplazada.results.json— KL/PSI a niveles variables de perturbación, más grid-PSI por bucket.sensitivity.png— KL y PSI vs magnitud de perturbación (matplotlib).manifest.json.README.md.
El escenario¶
Tienes:
- \(P\) = el histograma de tokens de entrenamiento de la Fase 12 para el corpus de verbos (alrededor de 2.000 tokens tras BPE; conteos sumando unos pocos millones).
- \(P_{\text{grid}}\) = la tabla de frecuencia de celdas (tiempo × persona) en tiempo de entrenamiento (5 × 3 = 15 celdas).
- \(Q_0\) = una muestra limpia de tráfico en vivo desde la misma distribución (1.000 tokens muestreados de \(P\)).
- \(Q_\rho\) = una muestra desplazada: 1.000 tokens, de los cuales una fracción \(\rho \in \{0, 0.05, 0.10, 0.15, 0.25, 0.50\}\) han sido reemplazados con tokens de un régimen de tiempo desplazado (p. ej., cuando el entrenamiento era pesado en presente simple, la muestra perturbada es pesada en participio pasado).
Calcula KL y PSI (tanto token-PSI como grid-PSI) para \(P\) vs cada \(Q_\rho\). Confirma que las métricas suben monotónicamente con \(\rho\) y cruzan los umbrales documentados (PSI 0.10, 0.25) en valores sensatos de \(\rho\).
TODOs¶
Bloque A — ensambla \(P\) y \(P_{\text{grid}}\)¶
-
dvc pull data/processed/train.jsonl.dvcpara fetchear el corpus de la Fase 12. - Tokeniza con el tokenizer BPE de la Fase 11. Construye un vector de conteos de longitud \(|V|\)
P_countsy normaliza a un vector de probabilidadP(con suavizado \(\alpha = 10^{-6}\) pertheory/03). - Construye
P_grid: una tabla de frecuencia de 5 × 3 = 15 celdas clasificando cada frase del corpus por su tiempo de verbo primario y persona. (Usa las etiquetas en el manifest del corpus —gen_corpus.pyde la Fase 12 las emite.) - Persiste
Pcomobaseline_histogram.jsonyP_gridcomobaseline_grid.jsonen el directorio del experimento. Serán reutilizados por la Fase 39.
Bloque B — construye \(Q_\rho\)¶
- Samplea 1.000 tokens de \(P\) → ese es \(Q_0\).
- Construye una distribución OOD
Rsobre el mismo vocabulario, sesgada hacia tokens asociados con un tiempo desplazado (p. ej., sufijos de participio pasado-ed,-en, más los participios pasados irregularesgone,been,done,written,seen,come,eaten). Si la distribución de entrenamiento era pesada en presente simple, esteRsimula una afluencia repentina de queries en participio pasado. - Para cada \(\rho\), construye \(Q_\rho\): una muestra de 1.000 tokens donde cada posición se muestrea independientemente de \(P\) con probabilidad \(1-\rho\) y de
Rcon probabilidad \(\rho\). - Similarmente construye un
Q_grid_\rhopor nivel de perturbación: un histograma de 15 celdas donde las celdas de participio pasado reciben masa extra \(\rho\). - Persiste cada \(Q_\rho\) como
q_rho_<rho>.json.
Bloque C — calcula KL y PSI¶
- Implementa
kl_divergence(P, Q)enscripts/mlops/drift.py. Usa NumPy. Aplica suavizado \(\alpha = 10^{-6}\) a \(P\) y \(Q\). - Implementa
psi(P, Q)en el mismo fichero. Usa el bucketing estratificado por frecuencia detheory/03: top 20 tokens en 5 buckets de 4, siguientes 200 en 5 buckets de 40, cola restante en 5 buckets de ~360. Eso es un token-PSI de 15 buckets para el corpus de verbos. - Implementa
grid_psi(P_grid, Q_grid): PSI sobre las celdas (tiempo, persona). 15 celdas. - Para cada \(\rho\) en \(\{0, 0.05, 0.10, 0.15, 0.25, 0.50\}\): calcula
kl = kl_divergence(P, Q_rho),psi = psi(P, Q_rho),grid_psi = grid_psi(P_grid, Q_grid_rho). Guarda aresults.json.
Bloque D — plot e interpreta¶
- Plotea KL, token-PSI y grid-PSI vs \(\rho\) en el mismo eje x, con los umbrales PSI (0.10, 0.25) como líneas horizontales.
- Identifica (y documenta en
README.md): ¿a qué \(\rho\) cruza cada métrica 0.10? ¿0.25? ¿Cuál dispara primero? Para un desplazamiento concentrado en un tiempo (participio pasado), grid-PSI debería disparar antes que token-PSI — verifica esto. - Confirma que las tres métricas son monotónicamente crecientes en \(\rho\). Si no lo son, el suavizado, el bucketing o la distribución OOD
Restán mal — debuggéalo antes de continuar.
Bloque E — chequeo de cordura de tamaño de muestra¶
- Repite la medición \(\rho = 0.15\) con tamaños de \(Q\) de \(\{100, 1{,}000, 10{,}000, 100{,}000\}\) tokens. Plotea KL, token-PSI y grid-PSI vs tamaño de \(Q\) a \(\rho\) fijo.
- Confirma que las tres métricas se estabilizan para \(|Q| \geq 1{,}000\) y son ruidosas debajo. Esta es la racional de la cota inferior para la cadencia de drift en producción.
Bloque F — receta Justfile + manifest + README¶
- Añade
just drift-checkpara invocarscripts/mlops/run_drift_check.pysobre elrequests.logen vivo de una corrida de Fase 33/34, comparando contrabaseline_histogram.json+baseline_grid.json. La receta escribe undrift_reports/YYYY-MM-DD.jsony sale no-cero si cualquier PSI ≥ 0.25. -
manifest.jsonlista: seed, SHA del tokenizer de la Fase 11, hash DVC del corpus de la Fase 12, lista de valores \(\rho\), suavizado \(\alpha\), esquema de buckets PSI. -
README.md(300–500 palabras): - La tabla de umbral para este corpus: \(\rho_{0.10}, \rho_{0.25}\) por métrica.
- La cota inferior de tamaño de muestra.
- Un párrafo: por qué grid-PSI disparó antes que token-PSI (o no lo hizo — registra de cualquier modo).
- Un párrafo: qué harían las próximas 24h de detección de drift en producción con estos umbrales calibrados.
Restricciones¶
- Solo NumPy. Sin funciones
entropyopside SciPy — escribe las fórmulas tú mismo. (Verificar contra SciPy a posteriori está bien.) - Muestras determinísticas. Usa un
np.random.default_rng(seed)con seed. La misma corrida de lab con la misma semilla debe producir valores idénticos de KL y PSI. - Sin
mlflow.log_metricpara tracking — escribe amanifest.jsonyresults.jsonsolamente. El tracking MLflow entra en el gate de CI (lab 04), no en el análisis de drift offline. - Sin nuevo
src/<module>/.drift.pyvive enscripts/mlops/. La CLIdrift_checkes un wrapper fino.
Condiciones de parada¶
Hecho cuando:
- KL, token-PSI y grid-PSI suben monotónicamente con \(\rho\).
- Los umbrales PSI 0.10 y 0.25 cruzan en valores \(\rho\) razonables (típicamente \(\rho_{0.10} \approx 0.05\)–\(0.10\) y \(\rho_{0.25} \approx 0.15\)–\(0.25\) para esta forma de corpus, pero el lab calibrará).
- El chequeo de cordura de tamaño de muestra muestra estabilización por encima de \(|Q| = 1{,}000\).
just drift-checkcorre end-to-end sobre un log en vivo sintético y produce un reporte.manifest.jsonyREADME.mdestán commiteados.
Pitfalls¶
- \(\log(0)\). Si algún \(Q(t) = 0\) para \(t\) con \(P(t) > 0\), KL diverge. El suavizado debe aplicarse a ambas distribuciones, cada vez.
- Dirección equivocada. \(D_{KL}(P \| Q) \neq D_{KL}(Q \| P)\). Usa la convención "entrenamiento primero" de
theory/03. Chequea que la fórmula coincide consum P * log(P / Q), no al revés. - Elección de tamaño de bucket. Buckets PSI de ancho-igual sobre una distribución de tokens de verbo en ley de potencias ponen casi toda la masa en los buckets de tokens raros. Usa el esquema de 15 buckets estratificados por frecuencia del Bloque C, no ancho-igual ingenuo.
- Mismatch de vocabulario. Si los tokens OOD no están en \(V\), no puedes representarlos. Mapea todos los tokens OOD a un id especial
[UNK](o a tokens raros existentes). Chequeo de cordura: cada token en \(Q_\rho\) tiene un slot de conteo en tu vector de longitud \(|V|\). - Precisión flotante. Las probabilidades suavizadas son diminutas (\(\sim 10^{-7}\)). Usa
float64todo el tiempo.float32hace underflow. - Confundir celdas de la rejilla. La rejilla (tiempo, persona) tiene 15 celdas exactamente per §A13. No añadas una celda "negación" o "interrogativa" — esas son expansiones de trabajo futuro. Quédate con la forma del corpus.
Cuándo consultar solutions/¶
Tras los seis bloques. solutions/02-drift-ref.md (apertura de fase) revisa tu esquema de bucketing, el suavizado y los umbrales calibrados.
Siguiente lab: lab/03-finops-table.md.