Volver a la lista

Reproducción de las pruebas de referencia de Med-LLM: HealthBench, MedQA, PubMedQA y MMLU-Medical — Validación de la diferencia entre las cifras reportadas en la reproducción interna y las presentaciones, y las mediciones reales.

OpenAI HealthBench, MedQA (USMLE), PubMedQA, MMLU-Medical: los puntos de referencia médicos de dominio público se reproducen internamente. Se miden las métricas de rendimiento de varios proveedores (Claude, GPT, Gemini) y modelos de código abierto (Meditron, MedGemma, Med42) y se comparan con los números publicados en artículos y anuncios corporativos. Se incluye una canalización completa que abarca la contaminación de datos, el sesgo de LLM como juez, el impacto de la presencia o ausencia de CoT y el cumplimiento de la licencia.

Intermedio
|
40min
|
Verificado (2026-07)
Progreso0/15 (0%)

Reproducción de benchmarks de Med-LLM: HealthBench, MedQA, PubMedQA y MMLU-Medical — Verificación de la diferencia entre las cifras publicadas y las mediciones propias

El rendimiento de los Med-LLM se actualiza mensualmente y cada proveedor afirma obtener los mejores resultados (SOTA) en sus propios benchmarks. Sin embargo, ¿es realmente posible reproducir estas mediciones? ¿En qué medida coinciden los resultados de los benchmarks propios del usuario con las cifras publicadas en artículos científicos y presentaciones corporativas? Este capítulo presenta una solución práctica que reproduce internamente benchmarks médicos públicos (HealthBench, MedQA-USMLE, PubMedQA, MMLU-Medical, MedMCQA) y mide el rendimiento de varios proveedores y modelos de código abierto para compararlos con las cifras publicadas. Si el capítulo 09 se centró en "comparar proveedores mediante tareas prácticas idénticas", este capítulo se centra en la "verificación de la reproducibilidad mediante benchmarks estándar idénticos", es decir, una herramienta para determinar si se pueden confiar en las afirmaciones de los proveedores.

📚 Se recomienda revisar los capítulos anteriores (muy recomendado)

Este capítulo pertenece a la serie avanzada y especializada de IA y biología. Antes de profundizar en él, se recomienda encarecidamente revisar primero los siguientes capítulos de DryBench:

Si se accede a este capítulo sin haber completado los anteriores, se comenzará directamente con el código práctico sin volver a explicar la reproducción de prompts "few-shot", "Chain-of-Thought", la evaluación "LLM-as-judge" y la carga de conjuntos de datos de Hugging Face, por lo que será difícil seguirlo.


¿Qué aprendimos en DryBench?

En DryBench ai-native #7, aprendimos que los prompts influyen significativamente en los resultados; en #11, cómo las alucinaciones pueden generar sesgos en la evaluación de los benchmarks; y en #13, que es posible reproducir los estándares mediante bibliotecas como Hugging Face datasets y evaluate.

Los benchmarks de Med-LLM son un área particularmente sensible a estos tres principios. Los sistemas de preguntas y respuestas médicas (QA) combinan diversas tareas: preguntas de opción múltiple con respuestas correctas (MedQA, MMLU-Medical, MedMCQA), resumen de evidencias (PubMedQA) y diagnóstico de texto libre (HealthBench), cada una con sus propios indicadores de evaluación. Además, las cifras publicadas por los proveedores suelen derivarse de múltiples variantes, como prompts optimizados, subconjuntos específicos, "self-consistency" o "CoT", lo que hace que la reproducción no siempre sea posible. Este capítulo aborda este problema de reproducibilidad de forma directa y especializada.

Definición del problema de nivel avanzado

Requisitos prácticos

  • Reproducción de evaluaciones estándar: Cálculo de métricas de precisión en MedQA, PubMedQA, MMLU-Medical, MedMCQA y HealthBench.
  • Ejecución con múltiples modelos: Claude Opus/Sonnet, GPT-4o, o1, Gemini 2.5, Meditron, MedGemma y Med42; un mínimo de 6 modelos.
  • Garantía de reproducibilidad: Registro de la versión del conjunto de datos, la versión del modelo, el prompt, el momento de la ejecución, si se utilizó CoT y el número de ejemplos few-shot.
  • Informe de la diferencia entre los resultados publicados y los obtenidos: Matriz con el formato "El artículo X reportó un 76%, nuestra reproducción obtuvo un 71% (diferencia de -5 puntos porcentuales)".
  • Comparación entre modelos de código abierto, propietarios y especializados: Diferencia en la precisión entre las API comerciales, los modelos de código abierto locales y los modelos especializados en medicina.

Características de cada evaluación

  • MedQA (USMLE) [1]: Evaluación de tipo examen nacional para médicos de EE. UU., con preguntas de opción múltiple. Se seleccionan entre 4 y 5 opciones. Métrica de precisión (accuracy).
  • PubMedQA [2]: Preguntas de respuesta sí/no/tal vez basadas en resúmenes de PubMed. Incluye un resumen de la evidencia. Métricas de precisión (accuracy) y F1.
  • MMLU-Medical (subconjunto) [3]: Subconjuntos médicos de MMLU, como conocimiento clínico, anatomía, medicina universitaria y medicina profesional. Métrica de precisión (accuracy).
  • MedMCQA [4]: Evaluación de tipo examen nacional para médicos de la India (AIIMS/NEET), con preguntas de opción múltiple de 4 opciones. Métrica de precisión (accuracy).
  • HealthBench (OpenAI 2025) [5]: Respuestas abiertas basadas en escenarios clínicos reales. Puntuación basada en un modelo de lenguaje grande como evaluador (LLM-as-judge) o en rúbricas.
  • También existen nuevas evaluaciones como CasesMD y DiagnosisBench.

Objetivos de este capítulo

  • Completar una matriz con un mínimo de 24 a 40 celdas, con 4 a 5 evaluaciones × 6 a 8 modelos.
  • Indicar en cada celda la precisión, el intervalo de confianza del 95% y la diferencia en la reproducción (en comparación con los resultados publicados).
  • Analizar las causas de las celdas en las que la reproducción falla (diferencia superior a 5 puntos porcentuales con respecto a los resultados publicados), considerando factores como el prompt, el subconjunto, la consistencia interna (self-consistency) y CoT.
  • Permitir que cualquiera pueda reproducir el proceso mediante código de código abierto (respetando las licencias).

Conjunto de herramientas e infraestructura requerida

HerramientaFunciónLicencia
HuggingFace datasetsCarga de conjuntos de datos para las evaluacionesApache 2.0
HuggingFace evaluateMétricas de evaluación estándarApache 2.0
VendorAdapter del capítulo 09 (Anthropic, OpenAI, Google)Llamadas a las API comercialesLicencia de cada SDK
Hugging Face transformers + vLLM (opcional)Inferencia local con modelos de código abiertoApache 2.0
pandas · matplotlib · seabornTablas de resultados · mapa de calorBSD
jsonlines · pyyamlAlmacenamiento de registros de resultadosMIT · MIT
scipy · statsmodelsIntervalos de confianza · pruebas de significanciaBSD

Requisitos de infraestructura:

  • No se requiere GPU (se priorizan las API comerciales). Para la ejecución local de modelos de código abierto, se necesitan GPU pequeñas a medianas/grandes (Meditron 7B para GPU pequeña, 70B requiere una GPU de centro de datos con +24 GB de VRAM).
  • RAM de al menos 16 GB.
  • Disco: cada conjunto de datos del benchmark suma aproximadamente 500 MB; los pesos de los modelos de código abierto se incluyen por separado (Meditron 70B ocupa aproximadamente 140 GB en fp16).

Costo estimado para la reproducción por parte del estudiante: 6 proveedores × 4 benchmarks × número de ítems por benchmark (~500~2000) = total de APIs comerciales de aproximadamente 30~120 USD.

Implementación práctica del flujo de trabajo

Flujo completo:

mermaid

Paso 1. Cargar el conjunto de datos de referencia

Cargar cada prueba de evaluación desde Hugging Face Hub. Las licencias varían según la prueba, por lo que es imprescindible verificarlas individualmente.

python
from dataclasses import dataclass, field
from typing import Literal
from datasets import load_dataset
@dataclass
class BenchQuestion:
bench_name: str
qid: str
question: str
choices: list[str] | None # lista si es opción múltiple, None si es respuesta abierta
gold_answer: str # para opción múltiple "A"/"B"/..., para respuesta abierta JSON de rubric
metadata: dict = field(default_factory=dict)
def load_medqa(split: str = "test", max_samples: int | None = None) -> list[BenchQuestion]:
"""Carga de MedQA (estilo USMLE). Repositorio bigbio/med_qa."""
ds = load_dataset("bigbio/med_qa", "med_qa_en_source", split=split)
if max_samples:
ds = ds.select(range(min(max_samples, len(ds))))
questions = []
for i, row in enumerate(ds):
questions.append(BenchQuestion(
bench_name="medqa",
qid=f"medqa_{i}",
question=row["question"],
choices=[opt["value"] for opt in row["options"]],
gold_answer=row["answer_idx"], # "A", "B", ...
metadata={"meta_info": row.get("meta_info", "")},
))
return questions
def load_pubmedqa(split: str = "train", max_samples: int | None = None) -> list[BenchQuestion]:
"""PubMedQA (yes/no/maybe). qiaojin/PubMedQA labeled subset."""
ds = load_dataset("qiaojin/PubMedQA", "pqa_labeled", split=split)
if max_samples:
ds = ds.select(range(min(max_samples, len(ds))))
questions = []
for i, row in enumerate(ds):
context = " ".join(row["context"]["contexts"])
questions.append(BenchQuestion(
bench_name="pubmedqa",
qid=f"pmqa_{i}",
question=f"Contexto: {context}\n\nPregunta: {row['question']}\nLa respuesta debe ser una de las siguientes: sí/no/tal vez:",
choices=["yes", "no", "maybe"],
gold_answer=row["final_decision"],
metadata={"pmid": row.get("pubid", "")},
))
return questions
def load_mmlu_medical(split: str = "test", max_samples: int | None = None) -> list[BenchQuestion]:
"""Subconjunto médico de MMLU. cais/mmlu."""
subsets = ["clinical_knowledge", "college_medicine", "anatomy", "professional_medicine", "medical_genetics"]
questions = []
for subset in subsets:
ds = load_dataset("cais/mmlu", subset, split=split)
if max_samples:
ds = ds.select(range(min(max_samples // len(subsets), len(ds))))
for i, row in enumerate(ds):
questions.append(BenchQuestion(
bench_name=f"mmlu_{subset}",
qid=f"mmlu_{subset}_{i}",
question=row["question"],
choices=row["choices"],
gold_answer=["A", "B", "C", "D"][row["answer"]],
metadata={"subset": subset},
))
return questions
def load_medmcqa(split: str = "validation", max_samples: int | None = None) -> list[BenchQuestion]:
"""MedMCQA (examen de licencia médica de la India). openlifescienceai/medmcqa."""
try:
ds = load_dataset("openlifescienceai/medmcqa", split=split)
except Exception:
return []
if max_samples:
ds = ds.select(range(min(max_samples, len(ds))))
questions = []
for i, row in enumerate(ds):
questions.append(BenchQuestion(
bench_name="medmcqa",
qid=f"medmcqa_{i}",
question=row["question"],
choices=[row["opa"], row["opb"], row["opc"], row["opd"]],
gold_answer=["A", "B", "C", "D"][row["cop"]],
metadata={"subject_name": row.get("subject_name", "")},
))
return questions
def load_healthbench(split: str = "test", max_samples: int | None = None) -> list[BenchQuestion]:
"""HealthBench (OpenAI 2025). Verifique previamente el repositorio y la licencia.
El nombre real del repositorio debe confirmarse en el momento del anuncio. Aquí se presenta como un esqueleto conceptual.
"""
try:
ds = load_dataset("openai/healthbench", split=split)
except Exception:
# Alternativa: carga manual de escenarios de muestra del apéndice del artículo
return []
if max_samples:
ds = ds.select(range(min(max_samples, len(ds))))
questions = []
for i, row in enumerate(ds):
questions.append(BenchQuestion(
bench_name="healthbench",
qid=f"hb_{i}",
question=row["scenario"],
choices=None,
gold_answer=row.get("rubric_json", "{}"),
metadata=row.get("metadata", {}),
))
return questions

Paso 2. Plantilla de prompt (reproducción del artículo original)

Reproducir el prompt utilizado en el artículo original con la mayor fidelidad posible. Variantes separadas para zero-shot, few-shot y CoT.

python
ZERO_SHOT_MEDQA = """Responda la siguiente pregunta del examen nacional de medicina.
Pregunta: {question}
Opciones:
{choices_formatted}
Respuesta (solo una letra: A/B/C/D/E):
"""
ZERO_SHOT_COT_MEDQA = """Responda la siguiente pregunta del examen nacional de medicina. Primero, razone paso a paso y luego proporcione la respuesta final.
Problema: {question}
Opciones:
{choices_formatted}
Razonamiento paso a paso:
"""
FEW_SHOT_MEDQA = """A continuación se presentan ejemplos de preguntas y respuestas del examen nacional de medicina.
Ejemplo 1:
Problema: Paciente masculino de 65 años, con dolor torácico súbito, sudoración y náuseas hace 3 horas. Elevación del segmento ST en el ECG. ¿Cuál es el diagnóstico más probable?
Opciones: A. Infarto agudo de miocardio B. Angina inestable C. Disección aórtica D. Embolia pulmonar E. Pericarditis
Respuesta: A
Ejemplo 2:
Problema: Mujer de 45 años, con sudoración nocturna, pérdida de peso y tos crónica durante 3 meses. Nódulo en el lóbulo superior derecho en la radiografía de tórax. ¿Cuál es el examen prioritario a realizar?
Opciones: A. Tomografía computarizada B. Prueba de baciloscopia en esputo C. Cultivo para micobacterias D. Broncoscopia E. Prueba de Mantoux
Respuesta: B
Ahora responda la siguiente pregunta.
Pregunta: {question}
Opciones:
{choices_formatted}
Respuesta (solo una letra):
"""
ZERO_SHOT_PUBMEDQA = """{question}
"""
ZERO_SHOT_MMLU = """Responda la siguiente pregunta.
Pregunta: {question}
Opciones:
A. {a}
B. {b}
C. {c}
D. {d}
Respuesta (solo una letra de A/B/C/D):
"""
def format_question(q: BenchQuestion, prompt_type: str = "zero_shot") -> str:
"""Renderizado de prompts por benchmark."""
if q.bench_name == "medqa":
choices_text = "\n".join(f"{chr(65+i)}. {c}" for i, c in enumerate(q.choices or []))
template = {
"zero_shot": ZERO_SHOT_MEDQA,
"zero_shot_cot": ZERO_SHOT_COT_MEDQA,
"few_shot": FEW_SHOT_MEDQA,
}.get(prompt_type, ZERO_SHOT_MEDQA)
return template.format(question=q.question, choices_formatted=choices_text)
elif q.bench_name == "pubmedqa":
return ZERO_SHOT_PUBMEDQA.format(question=q.question)
elif q.bench_name.startswith("mmlu_"):
return ZERO_SHOT_MMLU.format(
question=q.question,
a=q.choices[0], b=q.choices[1], c=q.choices[2], d=q.choices[3],
)
elif q.bench_name == "medmcqa":
choices_text = "\n".join(f"{chr(65+i)}. {c}" for i, c in enumerate(q.choices or []))
return ZERO_SHOT_MEDQA.format(question=q.question, choices_formatted=choices_text)
elif q.bench_name == "healthbench":
return q.question # respuesta abierta
else:
raise ValueError(f"Unknown bench: {q.bench_name}")

Paso 3. Análisis de la respuesta (extracción de la respuesta correcta en preguntas de opción múltiple)

python
import re
def extract_choice(response: str, choices: list[str] | None) -> str | None:
"""Extrae una opción A/B/C/D de la respuesta del LLM.
También maneja respuestas CoT (razonamiento largo seguido de respuesta).
"""
if not choices:
return None
upper = response.upper()
# Prioritize patterns like "Answer: A" · "The correct answer is A" · "The answer is A"
for pattern in [
r"answer[:\s]+([A-E])",
r"correct answer[is:\s]+([A-E])",
r"answer[:\s]+([A-E])",
r"the answer is\s+([A-E])",
r"final answer[:\s]+([A-E])",
]:
m = re.search(pattern, upper)
if m:
return m.group(1)
# Fallback: Last standalone A~E appearing
matches = re.findall(r"\b([A-E])\b", upper)
if matches:
return matches[-1]
# Fallback: Matching option text
for i, choice in enumerate(choices):
if choice and choice.lower() in response.lower():
return chr(65 + i)
return None
def extract_yes_no_maybe(response: str) -> str | None:
"""Extract PubMedQA yes/no/maybe."""
text = response.lower().strip()
for keyword in ["yes", "no", "maybe"]:
if re.search(rf"\b{keyword}\b", text[:100]):
return keyword
return None

Paso 4. LLM como evaluador (HealthBench con respuestas abiertas)

HealthBench utiliza una puntuación basada en rúbricas y se automatiza mediante un LLM que actúa como evaluador.

python
You are an expert in clinical diagnosis and treatment evaluation.
Evaluate the following scenario and response according to the rubric.
Scenario:
{scenario}
Response:
{response}
Rubric (JSON, each item evaluated as 0 or 1):
{rubric}
Output scores for each rubric item in JSON as 0 (not met) · 1 (met):
{{
"criterion_1": 0 or 1,
"criterion_2": 0 or 1,
...
"total_score": total,
"max_score": número total de elementos del rúbrica
}}
"""
def judge_healthbench_response(
scenario: str,
response: str,
rubric_json: str,
judge_client,
judge_model: str = "claude-opus-4-5-20250219",
) -> dict:
"""Puntuación del rúbrica con LLM como juez."""
prompt = JUDGE_PROMPT.format(scenario=scenario, response=response, rubric=rubric_json)
resp = judge_client.messages.create(
model=judge_model,
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
raw = resp.content[0].text
match = re.search(r"\{.*\}", raw, re.DOTALL)
if not match:
return {"total_score": 0, "max_score": 1, "error": "Error al analizar el juicio"}
import json
try:
return json.loads(match.group(0))
except json.JSONDecodeError:
return {"total_score": 0, "max_score": 1, "error": "JSON invalid"}

Paso 5. Ejecución del benchmark · Intervalo de confianza

Reutilización del VendorAdapter del capítulo 09.

python
import asyncio
from dataclasses import dataclass
@dataclass
class BenchAnswerResult:
question: BenchQuestion
model: str
model_snapshot: str
response: str
predicted_answer: str | None
is_correct: bool
latency_ms: float
metadata: dict
async def run_bench_on_vendor(
questions: list[BenchQuestion],
adapter, # VendorAdapter del capítulo 09
prompt_type: str = "zero_shot",
max_concurrent: int = 5,
) -> list[BenchAnswerResult]:
"""Ejecutar un benchmark × un modelo."""
semaphore = asyncio.Semaphore(max_concurrent)
async def process(q: BenchQuestion) -> BenchAnswerResult:
async with semaphore:
prompt = format_question(q, prompt_type=prompt_type)
resp = await adapter.query_async(prompt)
if q.choices:
if q.bench_name == "pubmedqa":
predicted = extract_yes_no_maybe(resp.raw_text)
else:
predicted = extract_choice(resp.raw_text, q.choices)
is_correct = predicted == q.gold_answer
else:
# HealthBench y otros de respuesta libre requieren juez separado
predicted = None
is_correct = False
return BenchAnswerResult(
question=q, model=adapter.model, model_snapshot=resp.model_snapshot,
response=resp.raw_text, predicted_answer=predicted,
is_correct=is_correct, latency_ms=resp.latency_ms,
metadata={
"input_tokens": resp.input_tokens,
"output_tokens": resp.output_tokens,
"prompt_type": prompt_type,
},
)
return await asyncio.gather(*[process(q) for q in questions])

Paso 6. Informe de la diferencia entre la predicción y la medición real.

python
import numpy as np
import pandas as pd
# Número de vendedores · publicaciones en conferencias (verificar al momento de la ejecución)
PUBLISHED_NUMBERS = {
"medqa": {
"claude-opus-4-5": 0.905, # Ejemplo, anuncio de Anthropic
"gpt-4o": 0.876, # Anuncio de OpenAI
"o1": 0.947, # Anuncio de OpenAI o1
"gemini-2.5-pro": 0.895, # Anuncio de Google
"med-gemini": 0.91, # Google Med-Gemini
"meditron-70b": 0.72, # Artículo de EPFL
"med42-70b": 0.85, # Presentación M42
},
"pubmedqa": {
"claude-opus-4-5": 0.788,
"gpt-4o": 0.759,
"o1": 0.82,
"meditron-70b": 0.82, # Dominio de modelos especializados
"med-gemini": 0.81,
},
"mmlu_medical": {
"gpt-4o": 0.87,
"claude-opus-4-5": 0.89,
"gemini-2.5-pro": 0.88,
"meditron-70b": 0.75,
},
# ... otros benchmarks
}
def compute_bench_score(results: list[BenchAnswerResult]) -> dict:
"""Precisión + IC del 95% (intervalo de Wilson)."""
n = len(results)
if n == 0:
return {"accuracy": 0.0, "ci_low": 0.0, "ci_high": 0.0, "n": 0}
correct = sum(1 for r in results if r.is_correct)
p = correct / n
z = 1.96
denominator = 1 + z**2 / n
center = (p + z**2 / (2 * n)) / denominator
margin = z * np.sqrt(p * (1 - p) / n + z**2 / (4 * n**2)) / denominator
return {
"accuracy": p,
"ci_low": max(0, center - margin),
"ci_high": min(1, center + margin),
"n": n,
"correct": correct,
}
def delta_report(all_results: dict[tuple[str, str], list[BenchAnswerResult]]) -> pd.DataFrame:
"""(bench, model) → delta entre medido y anunciado."""
rows = []
for (bench, model), results in all_results.items():
score = compute_bench_score(results)
# Coincidencia de alias del modelo con números anunciados
model_key = model.split("-2024")[0].split("-2025")[0] # Eliminar instantánea
published = PUBLISHED_NUMBERS.get(bench, {}).get(model_key)
delta = (score["accuracy"] - published) if published is not None else None
reproducibility = "N/A"
if delta is not None:
if abs(delta) < 0.02:
reproducibility = "Reproducido (Δ < 2%p)"
elif abs(delta) < 0.05:
reproducibility = "Parcialmente reproducido (Δ 2~5%p)"
else:
reproducibility = f"Fallo de reproducción (Δ {delta:+.1%})"
rows.append({
"bench": bench,
"model": model,
"n": score["n"],
"correct": score["correct"],
"accuracy_measured": round(score["accuracy"], 4),
"ci_95": f"[{score['ci_low']:.3f}, {score['ci_high']:.3f}]",
"published": published,
"delta": round(delta, 4) if delta is not None else None,
"reproducibility": reproducibility,
})
return pd.DataFrame(rows)
def reproducibility_heatmap(df: pd.DataFrame, output_path: str = "reproducibility.png") -> None:
"""Mapa de calor benchmark × modelo (color delta)."""
import matplotlib.pyplot as plt
import seaborn as sns
pivot = df.pivot(index="model", columns="bench", values="delta")
fig, ax = plt.subplots(figsize=(12, 8))
sns.heatmap(
pivot, annot=True, cmap="RdYlGn_r", center=0, fmt=".3f",
cbar_kws={"label": "Medición - Publicado (positivo=mejor medición)"},
ax=ax,
)
ax.set_title("Métrica delta de reproducción del benchmark Med-LLM\n(positivo=mejor medición, negativo=fallo de reproducción)")
plt.tight_layout()
plt.savefig(output_path, dpi=150)

Paso 7. Análisis de las causas del fallo en la reproducibilidad

Cuando el valor de Delta es grande, verificar las posibles causas.

python
def diagnose_reproduction_failure(
bench: str,
model: str,
delta: float,
all_results: list[BenchAnswerResult],
) -> dict:
"""Diagnóstico de posibles causas de fallo en la reproducción."""
diagnosis = {
"bench": bench, "model": model, "delta": delta,
"possible_causes": [],
}
if abs(delta) < 0.03:
diagnosis["possible_causes"].append("Reproducido. No es necesaria una investigación de las causas.")
return diagnosis
# 1. Posible diferencia en el prompt
prompt_types = {r.metadata.get("prompt_type", "unknown") for r in all_results}
if "zero_shot_cot" not in prompt_types:
diagnosis["possible_causes"].append(
"No se utilizó el prompt CoT (Chain-of-Thought). Es posible que el artículo publicado haya utilizado CoT. Necesario reejecutar con el prompt zero_shot_cot."
)
if "few_shot" not in prompt_types:
diagnosis["possible_causes"].append(
"Sin ejemplos few-shot incluidos. Es posible que la publicación haya utilizado 5-shot, 25-shot, etc."
)
# 2. Diferencia en el subconjunto de datos
total_n = len(all_results)
diagnosis["possible_causes"].append(
f"n actual de muestras={total_n}. Es posible que la publicación se haya medido en el conjunto completo de pruebas (miles). Necesario ampliar las muestras."
)
# 3. No se utilizó la consistencia interna
diagnosis["possible_causes"].append(
"No se utilizó la consistencia interna (voto mayoritario de N muestras). Es posible que la publicación haya utilizado N=5, 10, etc."
)
# 4. Data contamination
diagnosis["possible_causes"].append(
f"{bench} es un conjunto de datos abierto. Posible inclusión en el corpus de preentrenamiento del modelo (contaminación de datos). "
"Se recomienda validar con benchmarks recientes no utilizados en el entrenamiento."
)
# 5. Diferencia en las instantáneas del modelo
snapshots = {r.model_snapshot for r in all_results}
diagnosis["possible_causes"].append(
f"Instantánea observada: {snapshots}. Puede diferir de la instantánea publicada en el momento del anuncio."
)
return diagnosis

Paso 8. Flujo de trabajo integrado

python
async def full_reproduction_bench(
benches: list[str], # ["medqa", "pubmedqa", "mmlu", "medmcqa"]
adapters: list, # Lista de VendorAdapter del capítulo 09
output_dir: Path,
sample_size: int | None = 500,
prompt_types: list[str] = ["zero_shot"],
) -> pd.DataFrame:
"""Ejecución completa del benchmark de reproducción."""
output_dir.mkdir(parents=True, exist_ok=True)
# Carga de benchmarks
all_questions = {}
if "medqa" in benches:
all_questions["medqa"] = load_medqa(max_samples=sample_size)
if "pubmedqa" in benches:
all_questions["pubmedqa"] = load_pubmedqa(max_samples=sample_size)
if "mmlu" in benches:
all_questions["mmlu_medical"] = load_mmlu_medical(max_samples=sample_size)
if "medmcqa" in benches:
all_questions["medmcqa"] = load_medmcqa(max_samples=sample_size)
print(f"Carga completada. Muestras por benchmark: " +
", ".join(f"{k}={len(v)}" for k, v in all_questions.items()))
all_results = {}
for adapter in adapters:
for bench_name, questions in all_questions.items():
for pt in prompt_types:
print(f"[{adapter.model}] {bench_name} ({pt})")
results = await run_bench_on_vendor(questions, adapter, prompt_type=pt)
key = (bench_name, adapter.model)
if pt != "zero_shot":
key = (f"{bench_name}_{pt}", adapter.model)
all_results[key] = results
# Informe
df = delta_report(all_results)
df.to_csv(output_dir / "reproduction_report.csv", index=False)
reproducibility_heatmap(df, str(output_dir / "reproducibility_heatmap.png"))
# Diagnóstico de fallo en la reproducción
diagnoses = []
for (bench, model), results in all_results.items():
row = df[(df["bench"] == bench) & (df["model"] == model)]
if not row.empty and row.iloc[0]["delta"] is not None:
diag = diagnose_reproduction_failure(bench, model, row.iloc[0]["delta"], results)
diagnoses.append(diag)
with open(output_dir / "failure_diagnoses.json", "w", encoding="utf-8") as f:
import json
json.dump(diagnoses, f, ensure_ascii=False, indent=2)
return df

Rendimiento, costo y casos de fallo conocidos

Referencias de rendimiento (citación de benchmarks públicos)

Cifras recientes publicadas para benchmarks de Med-LLM (aproximadas por modelo y año):

ModeloMedQA (USMLE)PubMedQAMMLU-MedicalMedMCQAHealthBenchFuente
Meditron 70B0.720.820.750.65Chen et al., EPFL 2023 [6]
PMC-LLaMA 13B0.610.770.650.60Wu et al. 2023 [7]
Med-PaLM 20.860.790.850.72Singhal et al., Nature 2023 [8]
Med-Gemini0.910.810.880.750.62Saab et al., Nat Med 2024 [9]
Med42 70B0.850.790.830.71Anuncio M42
GPT-4o0.880.760.870.720.65Anuncio de OpenAI [10]
Claude Opus 4.50.910.790.890.760.72Anuncio de Anthropic [11]
o10.95+0.820.920.830.78Anuncio de OpenAI [10]
o30.940.820.910.810.79Anuncio de OpenAI [10]

Costo estimado de reproducción

  • 6~8 modelos × 4 benchmarks × 500 muestras = 12,000~16,000 solicitudes, total aproximadamente 30~120 USD (verificar las tarifas de cada modelo al momento).
  • Al ejecutar modelos de código abierto localmente, el tiempo de GPU se calcula por separado.

5 casos de fallo conocidos (recopilación comunitaria y académica)

  1. Contaminación de datos del benchmark Síntoma: Posibilidad de que el modelo haya visto las preguntas del benchmark durante la etapa de preentrenamiento, lo que infla la precisión anunciada. Si al reproducir los resultados son inesperadamente bajos, no se debe a contaminación, sino a un fallo en la reproducción (diferencias en el prompt o en el subconjunto). Causa: MedQA y PubMedQA son datos abiertos antiguos, por lo que es probable que estén incluidos en el corpus extraído mediante web crawling. Evitar: (a) priorizar los benchmarks más recientes (como HealthBench), (b) utilizar herramientas de detección de contaminación (superposición de n-gramos, inferencia de pertenencia), (c) crear un benchmark independiente con preguntas nuevas, (d) utilizar una instantánea de preentrenamiento anterior a la fecha de publicación del benchmark. Fuente: Xu et al. "Benchmark Data Contamination of Large Language Models". arXiv 2024 [12].

  2. La precisión varía en más de 5 puntos porcentuales debido a ligeras diferencias en el prompt. Síntoma: La precisión disminuye o aumenta drásticamente incluso con pequeñas variaciones del prompt del artículo original. Causa: Los LLM son sensibles a la formulación del prompt. El orden, la cantidad y la relevancia del dominio de los ejemplos "few-shot" influyen. Evitar: (a) copiar exactamente el prompt del apéndice del artículo original y volver a ejecutar el código del repositorio de origen, (b) utilizar un conjunto de múltiples variaciones del prompt (consistencia interna con N=5, 10), (c) almacenar los resultados junto con el registro del prompt y los parámetros, (d) fijar las condiciones de ejecución del benchmark (temperature=0, etc.). Fuente: Lu et al. "Fantastically Ordered Prompts and Where to Find Them". ACL 2022 [13].

  3. Sesgo del LLM como juez (HealthBench, etc.). Síntoma: El modelo juez califica favorablemente las respuestas de ciertos modelos de un proveedor específico (sesgo de preferencia propia). Causa: Si el juez y los modelos evaluados pertenecen a la misma familia de modelos, se prefiere su propio estilo. Evitar: (a) alternar proveedores entre el juez y los modelos evaluados, (b) utilizar un conjunto de múltiples jueces (Claude + GPT + Gemini), (c) verificar periódicamente el coeficiente de correlación con la puntuación humana, (d) utilizar una puntuación basada en rúbricas (minimizar el juicio subjetivo). Fuente: Zheng et al. "Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena". NeurIPS 2023 [14].

  4. Gran diferencia en la precisión según el uso de CoT. Síntoma: Se observa una precisión de 0,79 en GPT-4o en MedQA con "zero-shot", en comparación con 0,88 con un prompt CoT (diferencia de 9 puntos porcentuales). Causa: Muchas preguntas de QA médico requieren un razonamiento en varias etapas. Sin CoT, se producen errores debido a la coincidencia superficial de patrones. Evitar: (a) informar sobre todos los prompts óptimos para cada benchmark (zero-shot / CoT / few-shot / self-consistency), (b) verificar las condiciones explícitas del prompt en el artículo publicado, (c) para modelos de razonamiento como o1 y o3, el "zero-shot" es óptimo dado su propio CoT interno. Fuente: Wei et al. "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models." NeurIPS 2022 [15].

  5. Problemas de licencia y acceso a los conjuntos de datos Síntoma: Algunos conjuntos de datos de referencia, como HealthBench y MedMCQA, requieren registro y certificación, lo que impide el acceso inmediato. Causa: Los datos clínicos y las preguntas de exámenes médicos plantean problemas de derechos de autor y normativas. Solución: (a) Verificar previamente la licencia de cada conjunto de datos de referencia y especificar el procedimiento de registro, (b) priorizar los conjuntos de datos de referencia con licencias abiertas (como MMLU y PubMedQA), (c) complementar con la creación de un conjunto de datos de prueba interno, (d) guiar a los usuarios en el procedimiento de registro. Fuente: Fichas de cada conjunto de datos en HuggingFace Hub y sección de licencias de los artículos originales.

Ideas para ampliar

  • Conjunto de datos de referencia médico en coreano: Crear y reproducir un conjunto de datos de referencia propio basado en KMLE (estilo del examen de licencia médica de Corea). Validación específica para el contexto nacional.
  • Robustez temporal: Ejecutar el mismo conjunto de datos de referencia mensualmente para rastrear las actualizaciones de los modelos de los proveedores. Detección de regresiones.
  • Conjunto de datos de referencia para el ajuste fino específico de tareas: Cuantificar la mejora del rendimiento en el conjunto de datos de referencia tras realizar un ajuste fino de modelos de código abierto.
  • Comparación con y sin "Chain-of-Thought": Matriz que muestra el impacto de usar o no indicaciones CoT, el número N de ejemplos "few-shot" y la consistencia interna N en el rendimiento de cada conjunto de datos de referencia.
  • Integración con RAG: Buscar resúmenes relevantes de PubMed para las preguntas del conjunto de datos de referencia e inyectarlos como contexto, y cuantificar la mejora del rendimiento.
  • Conjunto de datos de referencia a gran escala con modelos de código abierto: Cobertura exhaustiva de familias de código abierto como Meditron, Med42, MedGemma, Llama-3-Med y Qwen-Med.

Próximo episodio

  • Episodio 09 llm-vendor-benchmark: Si este episodio se centra en la reproducción estándar del conjunto de datos de referencia, el episodio 09 comparará proveedores de tareas prácticas.
  • Episodio 14 bio-mcp-agent: Mostrará los resultados de los conjuntos de datos de referencia de este episodio mediante una herramienta MCP.
  • Episodio 01 clinical-notes-ie-llm: Reexaminará el rendimiento de la extracción de información clínica del episodio 01 desde la perspectiva de los conjuntos de datos de referencia.

Referencias

  1. Jin D, Pan E, Oufattole N, et al. "What Disease Does This Patient Have? A Large-Scale Open Domain Question Answering Dataset from Medical Exams (MedQA)." arXiv 2020. https://arxiv.org/abs/2009.13081
  2. Jin Q, Dhingra B, Liu Z, et al. "PubMedQA: A Dataset for Biomedical Research Question Answering." EMNLP 2019.
  3. Hendrycks D, Burns C, Basart S, et al. "Measuring Massive Multitask Language Understanding (MMLU)." ICLR 2021. https://arxiv.org/abs/2009.03300
  4. Pal A, Umapathi LK, Sankarasubbu M. "MedMCQA: A Large-scale Multi-Subject Multi-Choice Dataset for Medical domain Question Answering." CHIL 2022.
  5. OpenAI HealthBench (publicado en 2025): https://openai.com/index/healthbench/ (la URL real deberá verificarse en el momento de la publicación)
  6. Chen Z, Cano AH, Romanou A, et al. "Meditron-70B: Scaling Medical Pretraining for Large Language Models." EPFL 2023. https://arxiv.org/abs/2311.16079
  7. Wu C, Zhang X, Zhang Y, et al. "PMC-LLaMA: Toward Building Open-source Language Models for Medicine." arXiv 2023.
  8. Singhal K, Tu T, Gottweis J, et al. "Towards Expert-Level Medical Question Answering with Large Language Models (Med-PaLM 2)." Nature 2023. https://www.nature.com/articles/s41586-023-06291-2
  9. Saab K, Tu T, Weng W-H, et al. "Capabilities of Gemini Models in Medicine (Med-Gemini)." Nature Medicine 2024. https://www.nature.com/articles/s41591-024-03246-6
  10. Puntos de referencia de los modelos de OpenAI: https://openai.com/index/gpt-4o-system-card/ · o1 · tarjetas del sistema o3
  11. Puntos de referencia de los modelos de Anthropic: https://www.anthropic.com/news/claude-3-family · tarjeta del sistema Claude 4
  12. Xu R et al. "Benchmark Data Contamination of Large Language Models: A Survey." arXiv 2024. https://arxiv.org/abs/2406.04244
  13. Lu Y, Bartolo M, Moore A, et al. "Fantastically Ordered Prompts and Where to Find Them." ACL 2022.
  14. Zheng L, Chiang W-L, Sheng Y, et al. "Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena." NeurIPS 2023.
  15. Wei J, Wang X, Schuurmans D, et al. "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models." NeurIPS 2022.
  16. HuggingFace datasets — bigbio/med_qa: https://huggingface.co/datasets/bigbio/med_qa
  17. HuggingFace datasets — qiaojin/PubMedQA: https://huggingface.co/datasets/qiaojin/PubMedQA
  18. HuggingFace datasets — cais/mmlu: https://huggingface.co/datasets/cais/mmlu
  19. Meditron GitHub: https://github.com/epfLLM/meditron
  20. HELM (Holistic Evaluation of Language Models): https://crfm.stanford.edu/helm/

💬 Preguntas y comentarios

0 comentarios

Puedes publicar sin iniciar sesión. Los comentarios de invitados no pueden editarse ni eliminarse después.

0/2000

Cargando...