English · Español
Lab 02 — Métricas de calibración y el slice adversarial¶
Objetivo: extender el harness para calcular ECE, Brier score, el diagrama de fiabilidad, y los scores del slice adversarial desglosados por categoría de trampa.
Tiempo estimado: 90-120 minutos.
Prerrequisito: Lab 01 hecho (el harness emite
results.jsoncon predicciones y confianzas por probe).data/eval/adversarial.jsonltiene ≥ 20 probes adversariales curados a mano.
Lo que produces¶
Extensiones:
src/eval/calibration.py— helpers de ECE, Brier, diagrama de fiabilidad.src/eval/adversarial.py— agregador del slice adversarial.tests/eval/test_calibration.py— tests sintéticos sobre predicciones de calibración conocida y miscalibración conocida.
Artefactos nuevos (añadidos a experiments/20-eval-report/<checkpoint_name>/):
reliability.png— diagrama de fiabilidad (confianza-predicha en eje x, accuracy empírica en eje y, diagonal de referencia).adversarial_by_category.csv— accuracy en cada categoría de trampa.adversarial_by_category.png— gráfico de barras.results.jsonextendido conece,brier,adversarial.overall,adversarial.by_category.
TODOs¶
Bloque A — src/eval/calibration.py¶
import numpy as np
def ece(confidences: np.ndarray, # shape (N,), in [0,1]
correct: np.ndarray, # shape (N,), 0 or 1
n_bins: int = 10) -> float:
"""Expected Calibration Error with equal-width bins."""
bin_edges = np.linspace(0.0, 1.0, n_bins + 1)
N = len(confidences)
total = 0.0
for m in range(n_bins):
lo, hi = bin_edges[m], bin_edges[m+1]
# include right edge in the last bin
if m == n_bins - 1:
mask = (confidences >= lo) & (confidences <= hi)
else:
mask = (confidences >= lo) & (confidences < hi)
if mask.sum() == 0:
continue
bin_acc = correct[mask].mean()
bin_conf = confidences[mask].mean()
total += (mask.sum() / N) * abs(bin_acc - bin_conf)
return float(total)
def brier(confidences: np.ndarray, correct: np.ndarray) -> float:
"""Binary Brier score: mean squared error between confidence and correctness."""
return float(np.mean((confidences - correct) ** 2))
def brier_multiclass(probs: np.ndarray, # shape (N, C)
labels: np.ndarray, # shape (N,), integer in [0, C)
) -> float:
"""Multi-class Brier: mean over samples of sum-of-squared-deviations across classes."""
N, C = probs.shape
one_hot = np.zeros_like(probs)
one_hot[np.arange(N), labels] = 1.0
return float(np.mean(np.sum((probs - one_hot) ** 2, axis=1) / C))
def reliability_diagram(confidences: np.ndarray,
correct: np.ndarray,
n_bins: int = 10,
out_path: str = "reliability.png") -> None:
"""Plot bin-mean-confidence vs bin-mean-accuracy. Diagonal reference."""
import matplotlib.pyplot as plt
# ... compute per-bin stats, plot
-
ecedevuelve 0 sobre un dataset sintético perfectamente calibrado. -
brierdevuelve 0 sobre predicciones perfectas (conf=1.0paracorrect=1,conf=0.0paracorrect=0). -
reliability_diagramguarda un PNG con: gráfico de barras de conteos por bin (fondo), scatter de (conf, acc) por bin, y la diagonal y=x.
Bloque B — src/eval/adversarial.py¶
def aggregate_adversarial(probes, predictions) -> dict:
"""Returns:
{
"overall": {"n": int, "correct": int, "accuracy": float, "wilson": (lo, hi)},
"by_category": {
"over_regularization": {...},
"wrong_person_agreement": {...},
"wrong_tense_marker": {...},
"auxiliary_mismatch": {...},
"en_es_mismatch": {...},
"plural_or_oos": {...},
}
}
"""
...
- Las categorías se derivan de
probe.reason_code(la etiqueta de trampa). - Las celdas con
n < 3se reportan pero se marcan conlow_sample: true.
Bloque C — extender run_eval (del Lab 01)¶
Tras el bucle de clasificación de probes, también:
adv_probes = load_probes(adversarial_path)
adv_results = classify_all(model, tokenizer, adv_probes)
results["adversarial"] = aggregate_adversarial(adv_probes, adv_results)
confidences = np.array([r.confidence for r in core_results])
correct = np.array([1 if r.predicted == p.expected else 0
for r, p in zip(core_results, core_probes)])
results["ece"] = ece(confidences, correct, n_bins=10)
results["brier"] = brier(confidences, correct)
reliability_diagram(confidences, correct, n_bins=10,
out_path=str(out_dir / "reliability.png"))
Bloque D — tests sintéticos en tests/eval/test_calibration.py¶
test_ece_perfectly_calibrated:test_ece_overconfident:test_brier_perfect:test_brier_uniform_random:test_adversarial_category_aggregation— probes fixture con reason_codes conocidos y un patrón de predicción fijo; verifica las accuracies por categoría.
Bloque E — visualizaciones¶
reliability.png:
- eje y: accuracy empírica en el bin.
- eje x: confianza media en el bin.
- Diagonal y=x.
- Overlay de barras (semitransparente) mostrando conteos por bin en un eje y secundario o anotado.
- Título: Reliability — checkpoint=<name>, ECE=<val>, N=<count>.
adversarial_by_category.png:
- Gráfico de barras horizontal, una barra por categoría, longitud = accuracy, codificado por color según conteo de muestras.
- Anota la barra con n=<count> y Wilson CI.
Restricciones¶
- Las categorías coinciden con las etiquetas
reason_codeexactamente. No inventes nuevas categorías en tiempo de agregación; las categorías están definidas entheory/03y los valores dereason_codede los probes deben encajar. - El diagrama de fiabilidad incluye bins vacíos. No los descartes; muéstralos como marcadores de altura cero para que la ausencia sea visible.
- Los probes adversariales se excluyen del cálculo de ECE/Brier núcleo. De lo contrario los ejemplos trampa envenenan la estimación de calibración. El ECE adversarial se calcula por separado y se reporta en su propia línea.
Condiciones de parada¶
Hecho cuando:
- Los cinco tests de
test_calibration.pypasan. experiments/20-eval-report/<name>/reliability.pngexiste y muestra una curva reconocible (puede ser cuasi-diagonal, puede ser sobre o subconfianza — eso es comportamiento del modelo).experiments/20-eval-report/<name>/adversarial_by_category.csvexiste con una fila por categoría.results.jsontieneece,brier,adversarial.overall.accuracy, yadversarial.by_category.*poblados.
Trampas¶
- ECE con
n_bins > N/3: cada bin tiene ≤ 3 muestras; las estimaciones de ECE se vuelven ruido. Con N=60 probes núcleo yn_bins=10, cada bin tiene ~6 muestras — al filo. Si un bin acaba con 0 o 1 muestras, ese bin contribuye 0 al ECE pero el ruido del siguiente bin aumenta. Reportan_binsymin_bin_counten el JSON. - La confianza es sobre el conjunto de candidatos, no sobre el vocabulario completo. Un probe de 4 candidatos con
confidence=0.5es mucho más débil que un probe de 2 candidatos conconfidence=0.5. Si mezclas probes con distintolen(candidates), la calibración queda enturbiada. O bien normalizas (reescalando a "por encima del azar") o reportas la calibración separadamente por tamaño del conjunto de candidatos. Documenta la elección. - Las celdas de
adversarial_by_categorycon n=1 o n=2 son casi sin significado individualmente pero contribuyen a la accuracy adversarial general. No las suprimas deloverall; sí márcalas como low-sample. - Desacuerdo entre Brier y ECE. Miden cosas relacionadas pero distintas. ECE = 0 no implica Brier = 0 (un modelo puede estar perfectamente calibrado a 0.5 de confianza con 0.5 de accuracy — ECE=0 pero Brier=0.25). Reporta ambos.
- Ejes del diagrama de fiabilidad invertidos. Convención: confianza en x, accuracy en y. Hazlo bien o todo lector leerá mal el gráfico.
Cuándo consultar solutions/¶
Después de que pasen todos los tests y los PNGs de reliability + adversarial estén producidos. La solución en solutions/02-calibration-ref.md (escrita al abrir la fase) discute cómo leer el diagrama de fiabilidad y qué significa en la práctica un modelo "sobreconfiado en adversariales".
Siguiente lab: lab/03-report-and-checkpoint-compare.md.