Skip to content

English · Español

03 — Detección de drift: KL y PSI sobre distribuciones de verbos

🇪🇸 Dos métricas, dos escalas. La divergencia KL sobre histogramas de formas verbales cuantifica cuán diferentes son las distribuciones; PSI sobre features escalares (longitud, tasa de rechazos, ratio persona/tiempo) es la versión bucketizada con umbrales fijos. Ambas exigen tamaño mínimo de muestra — un KL sobre 50 tokens es ruido, no señal. Para el corpus microscópico (20 verbos × 5 tiempos × 3 personas), el espacio de tokens es lo bastante pequeño como para que la bucketización por frecuencia entrenada importe más que el conteo bruto.


Qué significa "drift" aquí

El tutor de gramática se entrena sobre una distribución \(P\) de entradas — frases en inglés que usan los 20 verbos en la rejilla de 5 tiempos × 3 personas de §A13. En producción, los aprendices envían frases desde una distribución \(Q\). Si \(P \approx Q\), el rendimiento en evaluación de la Fase 20 del tutor se traslada. Si \(Q\) se ha desplazado de \(P\) — por ejemplo, los aprendices envían frases con verbos que no entrenamos, o usan el participio pasado en contextos que no vimos durante el entrenamiento — la evaluación deja de ser predictiva, y el tutor puede degradarse en silencio.

Importan dos sabores de drift:

  1. Drift de entrada. Los verbos, tiempos, personas, longitudes de frase o patrones sintácticos de \(Q\) se han desplazado. Detectable solo desde los logs de request, sin ground truth.
  2. Drift de salida. Incluso con \(P = Q\), las propias salidas del tutor (tasa de rechazo, confianza, longitud de respuesta) se han desplazado. Suele ser síntoma de un deploy o cambio ambiental, no de drift de datos.

La Fase 38 se centra en drift de entrada. El drift de salida es más fácil — el stack de observabilidad de la Fase 34 lo marca directamente vía paneles de métricas.

El setup

Tenemos dos distribuciones sobre un alfabeto finito \(V\) (el vocabulario del tokenizer, \(|V|\) dependiendo de la configuración BPE de la Fase 11 — para el corpus de verbos en inglés, probablemente \(|V| \approx 2{,}000\), mucho menor que un tokenizer de propósito general):

  • \(P\): la frecuencia de tokens en tiempo de entrenamiento, calculada una vez al entrenar y guardada en el registry junto a eval_report.json (como train_token_histogram.json).
  • \(Q\): la frecuencia de tokens del tráfico en vivo, calculada sobre una ventana deslizante (p. ej., las últimas 24 horas de entradas).

Queremos un único número que mida "cuán diferentes son \(P\) y \(Q\)". Dos opciones baratas y bien comprendidas: divergencia KL y PSI.

Divergencia KL

\[D_{KL}(P \| Q) = \sum_{t \in V} P(t) \log \frac{P(t)}{Q(t)}\]

Propiedades:

  1. \(D_{KL}(P \| Q) \geq 0\), con igualdad si y solo si \(P = Q\).
  2. No es simétrica. \(D_{KL}(P \| Q) \neq D_{KL}(Q \| P)\) en general. La convención "distribución de entrenamiento primero" importa.
  3. No acotada. Si \(Q(t) = 0\) para algún \(t\) con \(P(t) > 0\), el término explota a infinito. El suavizado es obligatorio.
  4. Unidades. Con \(\log\) como logaritmo natural, KL está en nats. Con \(\log_2\), en bits. Elige uno y mantenlo; nosotros usamos logaritmo natural.

Por qué \(P\) delante, no \(Q\)

La elección "entrenamiento primero" es convención más razón práctica: \(P\) es conocida y estable (el corpus de entrenamiento es fijo); \(Q\) se estima a partir de muestras en vivo ruidosas. Poner \(P\) en el denominador haría la fórmula sensible a tokens con frecuencia cero en la estimación de \(Q\) (lo cual ocurre todo el tiempo con tokens raros en ventanas pequeñas). Poner \(P\) delante significa sumar sobre el soporte de \(P\) — tokens que esperamos ver — y ponderar por la frecuencia de entrenamiento. El drift en tokens raros contribuye poco; el drift en tokens comunes contribuye mucho. Esto suele ser lo que queremos.

Para el tutor de gramática en concreto: \(P\) tiene masa casi uniforme sobre los 20 verbos (el corpus de la Fase 12 enumera la rejilla). Merges BPE comunes como -ed, -s, -ing dominan por frecuencia superficial. El drift sobre estos tokens de alta frecuencia (p. ej., los aprendices usan -ing 3× más a menudo de lo que entrenamos) aparece con fuerza; el drift sobre tokens raros (un verbo que el usuario escribió mal) aparece débilmente. Este sesgo es apropiado.

Suavizado

Ambas distribuciones necesitan suavizado para evitar \(\log 0\):

\[P_{\text{smooth}}(t) = \frac{c_P(t) + \alpha}{N_P + \alpha |V|}, \quad \alpha = 10^{-6}\]

donde \(c_P(t)\) es el conteo del token \(t\) en el corpus de entrenamiento y \(N_P = \sum_t c_P(t)\) es el total. Lo mismo para \(Q\). Con \(\alpha = 10^{-6}\), el suavizado apenas perturba los tokens de alta frecuencia, mientras mantiene los de baja frecuencia finitos.

Umbrales

KL no tiene umbral universal — depende de \(|V|\), del suavizado, y de qué cuenta como drift "significativo" en el dominio. Para el corpus de verbos con \(|V| \approx 2{,}000\):

  • \(D_{KL} < 0.03\) → tranquilo. Misma distribución.
  • \(0.03 \leq D_{KL} < 0.15\) → notable. Investigar.
  • \(D_{KL} \geq 0.15\) → significativo. Probablemente justifica reentrenar.

Vienen de la calibración empírica del lab 02 — Borja perturbará el corpus y medirá KL en niveles de perturbación conocidos (p. ej., inyectar formas de participio pasado en un stream dominado por presente simple).

Cota inferior de tamaño de muestra

Un KL calculado a partir de \(N_Q\) tokens en vivo tiene un error de muestreo de \(O(|V| / N_Q)\) (corrección Miller-Madow para el sesgo de muestra finita). Para \(|V| = 2{,}000\) y un KL "fiable" dentro de 0.01 en valor absoluto, necesitamos \(N_Q \gtrsim 2 \times 10^5\) tokens.

El lab 02 fija la ventana mínima a 1.000 tokens para la demo de desplazamiento sintético. En producción, la ventana debería ajustarse al corpus y al tráfico — esto es una palanca de tuning, no una constante.

PSI: Population Stability Index

PSI es la divergencia KL, bucketizada, con una tabla de interpretación fija. Usada de forma intensiva en finanzas (credit-scoring) desde los 90. La fórmula:

\[\text{PSI} = \sum_{i=1}^{B} (Q_i - P_i) \log \frac{Q_i}{P_i}\]

sobre \(B\) bins. Cada \(P_i\) es la fracción de muestras de entrenamiento en el bin \(i\); \(Q_i\) es la fracción de muestras en vivo en el bin \(i\).

Relación con KL

\[\text{PSI} = D_{KL}(Q \| P) + D_{KL}(P \| Q)\]

PSI es el KL simetrizado. Esto hace los umbrales agnósticos a la dirección — útil para monitoring donde no quieres comprometerte a "drift hacia" vs "drift desde".

Los umbrales canónicos

A diferencia del KL puro, PSI tiene umbrales estándar de la industria, aplicables a través de dominios:

  • \(\text{PSI} < 0.10\)sin desplazamiento significativo.
  • \(0.10 \leq \text{PSI} < 0.25\)desplazamiento moderado. Monitorizar.
  • \(\text{PSI} \geq 0.25\)desplazamiento significativo. Investigar, probablemente reentrenar.

Estos umbrales funcionan porque PSI opera sobre bins (típicamente 10), no sobre el vocabulario completo. El número de bins acota la magnitud.

Qué va en los bins

Para features escalares (longitud de frase, tasa de rechazo, tasa de request, longitud de respuesta, ratios persona/tiempo): bins de igual frecuencia de la distribución de entrenamiento. Diez bins es lo estándar.

Para la distribución de tokens de verbos: PSI sobre un bucketing estratificado por frecuencia — agrupar tokens por frecuencia de entrenamiento. Para el vocabulario pequeño del tutor de gramática, funcionan tres estratos de frecuencia:

  • Top 20 tokens más frecuentes (palabras función, merges BPE comunes) → 5 buckets de 4 tokens cada uno.
  • 200 tokens medios (raíces verbales, sufijos, puntuación) → 5 buckets de 40 tokens cada uno.
  • Cola (los ~1.800 tokens restantes) → 5 buckets de ~360 tokens cada uno.

Un PSI de 15 buckets estratificado por frecuencia maneja la forma del corpus de verbos mejor que ancho-igual.

Cuándo preferir PSI sobre KL

  • Comunicación entre equipos. "PSI = 0.31" es interpretable para ingenieros de ops; "KL = 0.18 nats" no.
  • Múltiples features. Las puntuaciones PSI se pueden agregar entre features (frecuencia de verbo, distribución persona/tiempo, longitud de frase, ...). KL también, pero los umbrales varían.
  • Muestras pequeñas. El bucketing de PSI lo hace más robusto al ruido de muestra — especialmente relevante para el pequeño corpus de verbos.

Cuándo preferir KL sobre PSI

  • Monitoring de alta resolución. El binning de PSI pierde información sobre qué token se desplazó.
  • Comparar con una distribución teórica. KL es el objeto natural de teoría de la información; PSI es una heurística.

Para el tutor de gramática, calculamos ambos y reportamos ambos. PSI es el umbral de alerta (operabilidad); KL es el diagnóstico (comprensión).

PSI por bucket para la rejilla verbo-tiempo

Un tutor de gramática tiene una descomposición natural de features: la rejilla (tiempo × persona). PSI calculado sobre las 5×3 = 15 celdas de la rejilla (con frecuencias de entrenamiento como distribución de referencia) da una señal de drift focalizada:

PSI_grid = sum over (tense, person) of (Q_cell - P_cell) * log(Q_cell / P_cell)

Si los aprendices se desplazan hacia enviar frases en participio pasado (porque la Fase 32 las enfatizó, pongamos), la fila de participio pasado de la rejilla pega un pico en \(Q\) mientras se mantiene plana en \(P\). PSI_grid captura esto incluso cuando el PSI puro de tokens está tranquilo.

Este es el tipo de métrica de drift consciente del dominio que se paga sola comparada con un dashboard genérico de drift de ML.

Lo que la detección de drift no hace

  1. No te dice que el tutor haya empeorado. El drift es una precondición para la degradación, no una medida de ella. El tutor puede manejar bien la distribución desplazada; solo una evaluación sobre la distribución desplazada puede decírtelo.
  2. No apunta a una causa raíz. El PSI sobre la rejilla de tiempos pegó un pico. ¿Por qué? Podría ser una nueva cohorte de usuarios, un upstream corrupto, un bug de tokenización, un ataque. El drift es la alarma, no el diagnóstico.
  3. No funciona en ventanas en vivo diminutas. Para 50 tokens de tráfico en vivo, las matemáticas siguen corriendo pero el resultado es ruido de muestreo. El piso de 1.000 tokens es una regla dura, no una guía.

Workflow de detección

El workflow de drift de la Fase 38 es un batch programado (por defecto, diario):

nightly:
  P          := train_token_histogram   (from the active registry entry)
  P_grid     := train_grid_histogram    (per (tense, person))
  Q          := tokenize(last_24h_inputs)
  Q_grid     := classify(last_24h_inputs) into (tense, person) cells
  if |Q| < 1000: skip, log "insufficient samples"
  compute:
    kl       = KL(P || Q)
    psi_tok  = PSI(P_buckets, Q_buckets)
    psi_grid = PSI(P_grid,    Q_grid)
  for each scalar feature f in {sentence_length, refusal_rate, request_rate}:
    psi_f = PSI(P_f_bins, Q_f_bins)
  emit drift_report/YYYY-MM-DD.json
  if any psi >= 0.25: alert (write to alerts.jsonl + log structured event)

El informe de drift se commitea en el árbol de experimentos, no a una base de datos separada. Los ficheros append-only son el sustrato de auditoría.

Dónde vive este código

scripts/mlops/drift.py, ~120 LOC. NumPy puro + stdlib. El script driver (scripts/mlops/run_drift_check.py) se invoca con just drift-check y mediante una GitHub Action diaria.

El módulo de drift no vive en src/miniobserve/miniobserve es para métricas online transmitidas desde el stack de servicio. El drift es análisis batch offline sobre trazas registradas. Mantenerlos separados evita acoplar el hot path online a la lógica de análisis batch.

Problemas de práctica (trabájalos antes del lab 02)

Soluciones en solutions/03-drift-ref.md — escritas en la apertura de fase.

  1. Demuestra que \(D_{KL}(P \| Q) \geq 0\) usando la desigualdad de Jensen sobre \(\log\).
  2. El histograma de tokens de verbo \(P\) tiene 2.000 entradas; un atacante envía 100 requests que contienen cada uno un único token raro nunca visto en entrenamiento. Sin suavizado, KL = \(\infty\). Calcula el KL suavizado con \(\alpha = 10^{-6}\), \(|V| = 2{,}000\), \(N_P = 10^7\).
  3. PSI sobre longitud de frase: el entrenamiento tiene \(E[L] = 8\) palabras, \(\sigma_L = 3\); el tráfico en vivo tiene \(E[L] = 8\), \(\sigma_L = 12\) (misma media, colas mucho más gordas). ¿Es PSI sensible a esto? Esboza los bins y la respuesta.
  4. El PSI de rejilla sobre (tiempo, persona) es 0.18. El PSI de tokens es 0.05. ¿Qué significa esta combinación operacionalmente? ¿Qué intervención recomendarías?

Recapitulación en un párrafo

El drift es la brecha entre la distribución de entrada de entrenamiento y la distribución de entrada en vivo. La divergencia KL lo cuantifica con unidades teórico-informacionales; PSI es su prima industrial bucketizada con umbrales fijos (0.10, 0.25). El suavizado es obligatorio para evitar el log-de-cero; los mínimos de tamaño de muestra son obligatorios para distinguir señal de ruido. Para el tutor de gramática, el PSI-grid (sobre las celdas tiempo × persona) es el número único más informativo — captura desplazamientos que el PSI de tokens puro suaviza. La detección de drift es una alarma, no un diagnóstico: un pico de PSI dice "mira", no "el tutor está roto".

Siguiente: theory/04-traffic-and-finops.md.