English · Español
Break — Evaluar sobre el training set en vez de held-out; mostrar cómo miente el score¶
🇪🇸 Cambiamos un solo path en el harness — el split de evaluación pasa de val a train. Reportamos 99% de accuracy con un modelo que en realidad solo memorizó. Es la trampa más vieja y más común en evaluación; verla en frío evita caerla en caliente.
Síntoma que verá Borja¶
Dos informes de evaluación:
- Informe A (control): el harness lee
data/eval/val.jsonl(60 frases held-out). Reporta CCR-regular = 82%, CCR-irregular = 64%, ConjAcc = 76%. - Informe B (break): el harness lee
data/eval/train.jsonl(240 frases de entrenamiento). Reporta CCR-regular = 95%, CCR-irregular = 87%, ConjAcc = 88%.
El mismo checkpoint del modelo. El mismo código de métricas. Conclusión distinta.
Si Borja ve el Informe B y pega "ConjAcc 88%, listo para shippear" en el journal, acaba de cometer el error de reporting más común en ML.
El break, mecánicamente¶
En src/minieval/harness.py:
# Run A (control)
def run_eval(model, split: str = "val") -> Report:
...
# Run B (break) — one line in the config
eval_split: train # was: val
O directamente en código: hardcodea split = "train" y entierra el cambio en un commit de refactor. (Así es exactamente como aparece el bug en repos del mundo real. Un refactor renombra una clave de config, el default cae a "train", y nadie revisa el diff.)
El break entero es un intercambio de un identificador.
Por qué esto enseña el concepto¶
A escala §A13 con 103k parámetros y 240 frases de entrenamiento, el modelo tiene aproximadamente 430 parámetros por ejemplo de entrenamiento. Eso no está hambriento de parámetros — el modelo tiene capacidad de memorizar frases enteras si quiere.
El training set tiene la propiedad de que el modelo ha visto cada forma en él, a menudo varias veces. Predecir wrote tras He es, para el modelo entrenado, un lookup casi determinista. El modelo no está generalizando; está recuperando.
Cuando evalúas en train:
- La PPL sobre train está sesgada hacia abajo por la memorización que el modelo hizo durante el entrenamiento. No refleja el comportamiento del modelo en inputs novedosos.
- El CCR sobre train está sesgado hacia arriba por la misma memorización. El alto CCR-irregular del modelo (87%) es "memoricé las formas irregulares de estos 8 verbos", no "aprendí la estructura de la conjugación irregular".
- La alineación bilingüe sobre train está sesgada hacia arriba por la misma razón.
Cada métrica está inflada. Ninguna te dice nada sobre generalización.
Este es el propósito entero del split train/val. La infraestructura del portal §A12 / §A14 (Fase 41) va a forzar el split a nivel del data-loader, pero en la Fase 20 el harness sigue funcionando por honor.
Escalera diagnóstica que Borja debería recorrer¶
- Primera comprobación: lee el manifest. ¿Cuál es
eval_split? - Segunda comprobación: compara las tablas por slice. Si train y val son parecidas, o el modelo super-generaliza (raro a escala §A13) o estás evaluando sobre el split equivocado.
- Tercera comprobación: el test canónico de sanity — toma 5 frases del eval set y comprueba si aparecen en el train set. Si sí, el split está roto.
- Diagnóstico:
eval_splitestrain, noval. O bien los archivos train y val fueron concatenados por un mal paso de preprocesado.
Reproductor¶
# Control
just phase-20-eval split=val
# Break (looks like "wow we trained well")
just phase-20-eval split=train
# Compare
just phase-20-eval-compare experiments/20-eval-val experiments/20-eval-train
El script de comparación imprime ambas tablas de métricas lado a lado y calcula el sesgo (train − val) por slice. Los sesgos son el tamaño de "lo que la memorización te compró".
Cascada de pistas¶
- (Suave) "Los dos informes usan el mismo modelo. ¿Qué más podría diferir?"
- (Media) "¿Qué dice
manifest.jsonsobre los datos de eval?" - (Directa) "El eval está leyendo el training set. ¿Dónde decide el harness qué archivo abrir?"
Arreglo¶
Cambia eval_split: train de vuelta a eval_split: val. O, estructuralmente, refactoriza el harness para que la API de eval tome una ruta y train no sea un valor válido en producción. El portal de la Fase 41 va más allá: los datos held-out viven en un mount de filesystem separado al que el bucle de entrenamiento físicamente no puede llegar.
Una versión más sutil del mismo bug¶
Una variante más insidiosa: train y val están correctamente separados, pero la construcción de probes usa prompts que aparecieron en train. Por ejemplo, el probe "He went to the ___" se pregunta en eval, pero el prompt exacto "He went to the school" aparece en train. La continuación del modelo es memorización.
Defensa: asegúrate de que los prompts en data/eval/probes.jsonl sean novedosos — sus prefijos no aparecen en data/train/*.jsonl. El corpus §A13 es lo bastante pequeño como para que esto sea sencillo de garantizar. El probe-schema.md de la Fase 20 (lab/00) exige este chequeo.
Lo que este break NO es¶
- No es un bug de modelo.
- No es un bug matemático de métrica.
- No es un leak por preprocesado de datos inadvertido.
Es el error de evaluación más simple posible: leer el archivo equivocado. La lección es que los errores de evaluación más comunes no son exóticos — son "la ruta está mal" o "el slice está mal". Un harness ruidoso y simple con rutas explícitas y separadas es la defensa.
Referencias cruzadas¶
theory/04-perplexity-pitfalls-tiny-corpus.md— incluso sobre un split val limpio, la PPL sola engaña.theory/03-probe-construction.md— cómo se construyen los probes para evitar leaks.- Fase 41 §A14 — aislamiento de datos a nivel de portal.