¿Puede una IA revisar papers científicos como lo haría un humano?#

500 papers de ICLR 2024. Decisiones de revisores humanos. Claude-3.5-Sonnet y GPT-4o evaluaron los mismos papers. Veamos cuánto coinciden — y dónde fallan.


Paper: Lu et al. (2026) The AI Scientist: Towards Fully Automated Open-Ended Scientific Discovery. Nature. DOI: 10.1038/s41586-026-10265-5 Video: Ver en YouTube

Abrir en Colab

El benchmark: revisores humanos vs IA#

El paper presenta The AI Scientist — un sistema que genera papers científicos de punta a punta. Pero también incluye The Automated Reviewer: una IA que evalúa papers como lo haría un revisor de conferencia.

Para medir qué tan bien funciona, usaron 500 papers reales de ICLR 2024. ⚠️ Los experimentos se hicieron en 2024 con Claude-3.5-Sonnet (junio 2024) y GPT-4o (mayo 2024) — versiones de hace 4 generaciones (una de las conferencias más importantes en machine learning). Cada paper fue evaluado por 3-7 revisores humanos y también por modelos de lenguaje (Claude-3.5-Sonnet y GPT-4o). Los scores van de 1 a 10.

¿Cuánto se parecen las evaluaciones de una IA a las de un humano?

# ══════════════════════════════════════════════════════════════
# Configuración — modifica estos valores para explorar
# ══════════════════════════════════════════════════════════════
UMBRAL_ACEPTACION = 6.0     # Score mínimo para considerar "aceptable"
COLOR_CLAUDE = '#7C3AED'    # Violeta — Claude
COLOR_GPT4O = '#059669'     # Emerald — GPT-4o
COLOR_HUMANO = '#2563EB'    # Azul CaM — Humanos
COLOR_RECHAZO = '#DC2626'   # Rojo — Rechazados
FUENTE = 'Fuente: Lu et al. (2026), Nature | Datos: github.com/SakanaAI/AI-Scientist'

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
import os, urllib.request

# Estilo CaM
style_file = '../../cam.mplstyle'
if not os.path.exists(style_file):
    style_file = '/tmp/cam.mplstyle'
    if not os.path.exists(style_file):
        urllib.request.urlretrieve(
            'https://raw.githubusercontent.com/Ciencia-a-Mordiscos/lab/main/cam.mplstyle',
            style_file)
plt.style.use(style_file)

# Cargar datos
df = pd.read_csv('datos/revisiones_comparadas.csv')
scores_dec = pd.read_csv('datos/scores_por_decision.csv')

print(f"Papers: {len(df)}")
print(f"Aceptados: {df['accepted'].sum()} ({df['accepted'].mean()*100:.1f}%)")
print(f"Rechazados: {(~df['accepted'].astype(bool)).sum()} ({(1-df['accepted'].mean())*100:.1f}%)")
print(f"Revisores por paper: {df['n_reviewers'].median():.0f} (mediana)")
Papers: 500
Aceptados: 184 (36.8%)
Rechazados: 316 (63.2%)
Revisores por paper: 4 (mediana)

¿Cuánto coinciden IA y humanos?#

Cada punto es un paper. Si la IA fuera perfecta, todos caerían sobre la diagonal.

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))

for ax, model, col, color, name in [
    (ax1, 'claude', 'claude_overall', COLOR_CLAUDE, 'Claude-3.5-Sonnet'),
    (ax2, 'gpt4o', 'gpt4o_overall', COLOR_GPT4O, 'GPT-4o')
]:
    # Separate by decision
    accepted = df[df['accepted'] == 1]
    rejected = df[df['accepted'] == 0]
    
    np.random.seed(42)
    jitter = np.random.normal(0, 0.08, len(df))
    
    ax.scatter(rejected['human_mean'], rejected[col] + jitter[:len(rejected)],
               color=COLOR_RECHAZO, s=20, alpha=0.3, label='Rechazados')
    ax.scatter(accepted['human_mean'], accepted[col] + jitter[len(rejected):len(rejected)+len(accepted)],
               color=color, s=20, alpha=0.4, label='Aceptados')
    
    # Diagonal
    ax.plot([1, 10], [1, 10], '--', color='#999999', linewidth=1, alpha=0.5, zorder=0)
    
    # Correlation
    rho, p = stats.spearmanr(df['human_mean'], df[col])
    ax.text(0.05, 0.95, f'ρ = {rho:.3f}', transform=ax.transAxes,
            fontsize=12, fontweight='bold', color=color, va='top')
    
    ax.set_xlabel('Score medio humano', fontsize=11)
    ax.set_ylabel(f'Score {name}', fontsize=11)
    ax.set_title(name, fontsize=13, fontweight='bold', pad=20)
    ax.set_xlim(1.5, 9.5)
    ax.set_ylim(1.5, 10.5)
    ax.legend(fontsize=9, loc='lower right')

ax1.text(0.5, 1.02, 'Da ~7 a casi todo — incluidos los rechazados',
         transform=ax1.transAxes, fontsize=10, color='#666666', ha='center')
ax2.text(0.5, 1.02, 'Más calibrado, pero pierde papers buenos',
         transform=ax2.transAxes, fontsize=10, color='#666666', ha='center')

plt.tight_layout()
fig.text(0.13, -0.04, FUENTE, fontsize=7.5, color='#999999', style='italic')
plt.savefig('figuras/scatter_scores.png', dpi=200, bbox_inches='tight')
plt.show()
../../_images/6e31c4b029f061f5d2cae039aeaef645baf03156c646d186ec157ff7818e98df.png

La correlación entre IA y humanos es modesta: ρ = 0,323 (Claude) y ρ = 0,285 (GPT-4o). Eso significa que la concordancia entre IA y humanos es baja — los rankings coinciden poco.

Pero lo más llamativo es el patrón de cada modelo. Claude da ~7 a casi todo: su score medio para papers rechazados por humanos es 7,00 — apenas por debajo de los aceptados (7,35). GPT-4o es más selectivo (5,17 para rechazados vs 5,79 para aceptados), pero su gap de 0,62 puntos sigue siendo pequeño comparado con el humano (1,82 puntos).

¿Aciertan en aceptar o rechazar?#

Una cosa es el score numérico. Otra es la decisión binaria: ¿el paper se acepta o se rechaza?

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5.5))

for ax, col_accept, color, name in [
    (ax1, 'claude_accepted', COLOR_CLAUDE, 'Claude-3.5-Sonnet'),
    (ax2, 'gpt4o_accepted', COLOR_GPT4O, 'GPT-4o')
]:
    tp = ((df['accepted']==1) & (df[col_accept]==1)).sum()
    tn = ((df['accepted']==0) & (df[col_accept]==0)).sum()
    fp = ((df['accepted']==0) & (df[col_accept]==1)).sum()
    fn = ((df['accepted']==1) & (df[col_accept]==0)).sum()
    
    matrix = np.array([[tn, fp], [fn, tp]])
    im = ax.imshow(matrix, cmap='Blues', alpha=0.7)
    
    labels = [['Rechazados\ncorrectos', 'Falsos\naceptados'],
              ['Aceptados\nperdidos', 'Aceptados\ncorrectos']]
    for i in range(2):
        for j in range(2):
            val = matrix[i, j]
            pct = val / len(df) * 100
            text_color = 'white' if val > 200 else '#333333'
            ax.text(j, i, f'{labels[i][j]}\n{val} ({pct:.1f}%)',
                    ha='center', va='center', fontsize=10, fontweight='bold',
                    color=text_color)
    
    acc = (tp + tn) / len(df)
    ax.set_title(f'{name}\nAccuracy: {acc:.1%}', fontsize=12, fontweight='bold', pad=15)
    ax.set_xticks([0, 1])
    ax.set_xticklabels(['Rechazar', 'Aceptar'], fontsize=10)
    ax.set_yticks([0, 1])
    ax.set_yticklabels(['Rechazado\n(humano)', 'Aceptado\n(humano)'], fontsize=10)
    ax.set_xlabel('Decisión de la IA', fontsize=11)

fig.suptitle('¿Acierta la IA al aceptar o rechazar?',
             fontsize=14, fontweight='bold', y=1.05)
fig.text(0.5, 1.01, 'Matriz de confusión: 500 papers ICLR 2024',
         fontsize=10, color='#666666', ha='center')

plt.tight_layout()
fig.text(0.13, -0.04, FUENTE, fontsize=7.5, color='#999999', style='italic')
plt.savefig('figuras/confusion_matrix.png', dpi=200, bbox_inches='tight')
plt.show()
../../_images/d99ff60f9517d9d3c664facaa086938b638b897397bdde2848824547d9abaec6.png

Dos estrategias opuestas. Claude acepta 485 de 500 papers — nunca pierde un aceptado (recall 100%), pero acepta 301 que deberían haberse rechazado. GPT-4o es conservador: rechaza correctamente 280 de 316, pero se lleva por delante 124 papers que los humanos sí aceptaron.

Si un comité de conferencia usara a Claude como filtro, pasaría casi todo. Si usara a GPT-4o, rechazaría buenos papers.

¿Cuántos puntos se desvía cada modelo?#

¿Cuántos puntos se desvía cada modelo?

df['diff_claude'] = df['claude_overall'] - df['human_mean']
df['diff_gpt4o'] = df['gpt4o_overall'] - df['human_mean']

fig, ax = plt.subplots(figsize=(12, 5.5))
bins = np.arange(-5, 6, 0.5)

ax.hist(df['diff_claude'], bins=bins, color=COLOR_CLAUDE, alpha=0.5,
        edgecolor=COLOR_CLAUDE, linewidth=0.8, label='Claude-3.5-Sonnet')
ax.hist(df['diff_gpt4o'], bins=bins, color=COLOR_GPT4O, alpha=0.5,
        edgecolor=COLOR_GPT4O, linewidth=0.8, label='GPT-4o')

# Vertical lines at means
mean_c = df['diff_claude'].mean()
mean_g = df['diff_gpt4o'].mean()
ax.axvline(mean_c, color=COLOR_CLAUDE, linewidth=2.5, linestyle='-')
ax.axvline(mean_g, color=COLOR_GPT4O, linewidth=2.5, linestyle='-')
ax.axvline(0, color='#333333', linewidth=1.5, linestyle='--', alpha=0.5)

ax.annotate(f'Claude: +{mean_c:.2f}', xy=(mean_c, ax.get_ylim()[1]*0.85),
            fontsize=11, fontweight='bold', color=COLOR_CLAUDE, ha='left',
            xytext=(mean_c+0.3, ax.get_ylim()[1]*0.85))
ax.annotate(f'GPT-4o: {mean_g:+.2f}', xy=(mean_g, ax.get_ylim()[1]*0.75),
            fontsize=11, fontweight='bold', color=COLOR_GPT4O, ha='right',
            xytext=(mean_g-0.3, ax.get_ylim()[1]*0.75))

ax.set_xlabel('Score IA − Score humano', fontsize=11)
ax.set_ylabel('Número de papers', fontsize=11)
ax.set_title('¿Cuánto se desvían del humano?',
             fontsize=14, fontweight='bold', pad=20)
ax.text(0.5, 1.02, 'Positivo = la IA puntúa más alto que los humanos',
        transform=ax.transAxes, fontsize=10, color='#666666', ha='center')
ax.legend(fontsize=10, loc='upper right')

fig.text(0.13, -0.03, FUENTE, fontsize=7.5, color='#999999', style='italic')
plt.savefig('figuras/sesgo_scores.png', dpi=200, bbox_inches='tight')
plt.show()
../../_images/89e7d6f1b4f295ccf69e53ace75f7fee913d67038345ebf74a3e18273f99eb22.png

Claude tiene un sesgo sistemático: puntúa +1,70 puntos por encima de los humanos en promedio. En una escala de 1-10, eso es enorme — equivale a dar un aprobado a un paper que los humanos consideran mediocre. GPT-4o está centrado en cero (sesgo de −0,02), pero tiene más dispersión (σ = 1,35): en un 8% de papers se desvía 3+ puntos.

¿Para qué tipo de papers falla más?#

Veamos cómo le va a cada modelo según la categoría de decisión humana.

fig, ax = plt.subplots(figsize=(12, 5.5))

categories = ['Reject', 'Accept (Poster)', 'Accept (Spotlight)', 'Accept (Oral)']
cat_labels = ['Rechazado', 'Poster', 'Spotlight', 'Oral']
x = np.arange(len(categories))
width = 0.25

human_means = [df[df['decision']==c]['human_mean'].mean() for c in categories]
claude_means = [df[df['decision']==c]['claude_overall'].mean() for c in categories]
gpt4o_means = [df[df['decision']==c]['gpt4o_overall'].mean() for c in categories]

bars1 = ax.bar(x - width, human_means, width, color=COLOR_HUMANO, alpha=0.8, label='Humanos')
bars2 = ax.bar(x, claude_means, width, color=COLOR_CLAUDE, alpha=0.8, label='Claude')
bars3 = ax.bar(x + width, gpt4o_means, width, color=COLOR_GPT4O, alpha=0.8, label='GPT-4o')

# Inline values
for bars in [bars1, bars2, bars3]:
    for bar in bars:
        height = bar.get_height()
        ax.text(bar.get_x() + bar.get_width()/2., height + 0.08,
                f'{height:.1f}', ha='center', va='bottom', fontsize=8, fontweight='bold')

ax.set_xticks(x)
ax.set_xticklabels(cat_labels, fontsize=11, fontweight='bold')
ax.set_ylabel('Score medio', fontsize=11)
ax.set_ylim(0, 9)
ax.legend(fontsize=10, loc='upper left')
ax.set_title('¿Discrimina la IA entre categorías de aceptación?',
             fontsize=14, fontweight='bold', pad=20)
ax.text(0.5, 1.02, 'Los humanos discriminan 1,82 puntos entre rechazados y aceptados. ¿Y la IA?',
        transform=ax.transAxes, fontsize=10, color='#666666', ha='center')

fig.text(0.13, -0.03, FUENTE, fontsize=7.5, color='#999999', style='italic')
plt.savefig('figuras/scores_por_categoria.png', dpi=200, bbox_inches='tight')
plt.show()
../../_images/a1c49ec3d9f5140f549dc917597501af576ea8fcea5986f821499f79d7ed8228.png

Lo que los datos soportan#

Afirmación

¿Soportada?

Detalle

La correlación entre IA y humanos es modesta

ρ = 0,323 (Claude), 0,285 (GPT-4o). Significativas (p < 10⁻¹⁰). Correlaciones modestas — para referencia, ρ² = 0,10 (Claude) y 0,08 (GPT-4o)

Claude acepta casi todo

485/500 aceptados. Solo rechaza 15 papers. Recall = 100%, Precision = 37,9%

GPT-4o es más selectivo pero pierde papers buenos

Accuracy 68%, pero recall = 32,6%. Pierde 124 de 184 aceptados

Claude tiene sesgo positivo sistemático

+1,70 puntos de media. Da 7,00 a rechazados (humanos: 4,75)

GPT-4o está mejor calibrado en media

Sesgo = −0,02. Pero dispersión mayor (σ = 1,35 vs 1,18 de Claude)

Ninguna IA discrimina bien entre categorías

Humanos: gap de 1,82 puntos. Claude: 0,36. GPT-4o: 0,62

Limitaciones: (1) Solo 500 papers de ICLR 2024 — una conferencia de ML, no representativa de toda la ciencia. (2) Los modelos son de 2024 (Claude-3.5-Sonnet de junio 2024, GPT-4o de mayo 2024). El paper se publicó en Nature en 2026 pero los experimentos se hicieron en 2024 — los modelos actuales (Claude 4.6, GPT-4o actualizado) podrían comportarse muy diferente como revisores. (3) Este benchmark evalúa al Automated Reviewer (parte 2 del paper), no la calidad de los papers generados por The AI Scientist (parte 1).


Ahora tú#

  1. ¿Hay un umbral mejor? Cambia UMBRAL_ACEPTACION en la celda de configuración. Si usas el score de Claude >7 para aceptar, ¿mejora la accuracy?

  2. ¿Qué paper tiene la mayor discrepancia? Busca el paper donde la diferencia Claude-humano es máxima. ¿Qué score le dieron los humanos?

  3. Otros sub-scores: El dataset tiene claude_soundness, claude_originality, etc. ¿Cuál de estos sub-scores tiene mayor correlación con la decisión humana?

# --- EXPERIMENTA AQUÍ ---
# ¿Con qué umbral de score de Claude se maximiza la accuracy?

best_acc = 0
best_threshold = 0

for threshold in np.arange(5.0, 9.5, 0.5):
    predicted_accept = (df['claude_overall'] >= threshold).astype(int)
    acc = (predicted_accept == df['accepted']).mean()
    if acc > best_acc:
        best_acc = acc
        best_threshold = threshold

print(f"Mejor umbral para Claude: ≥ {best_threshold}")
print(f"Accuracy con ese umbral: {best_acc:.1%}")
print()

# Comparar con GPT-4o
for threshold in np.arange(3.0, 9.0, 0.5):
    predicted_accept = (df['gpt4o_overall'] >= threshold).astype(int)
    acc = (predicted_accept == df['accepted']).mean()
    if acc > best_acc:
        best_acc = acc
        best_threshold = threshold
        print(f"  GPT-4o @ ≥{threshold}: accuracy = {acc:.1%}")

# Sub-score más predictivo
print(f"\nCorrelación de sub-scores de Claude con decisión humana:")
for col in ['claude_soundness', 'claude_originality', 'claude_quality',
            'claude_clarity', 'claude_significance', 'claude_contribution']:
    rho, p = stats.spearmanr(df[col], df['accepted'])
    print(f"  {col.replace('claude_', ''):15}: ρ = {rho:.3f}")
Mejor umbral para Claude: ≥ 7.5
Accuracy con ese umbral: 64.8%

  GPT-4o @ ≥6.5: accuracy = 66.8%

Correlación de sub-scores de Claude con decisión humana:
  soundness      : ρ = 0.257
  originality    : ρ = 0.110
  quality        : ρ = 0.232
  clarity        : ρ = 0.154
  significance   : ρ = 0.162
  contribution   : ρ = 0.235

Datos: Benchmark de revisión automatizada del repositorio AI-Scientist (SakanaAI). 500 papers ICLR 2024 con scores de revisores humanos y revisiones de Claude-3.5-Sonnet y GPT-4o. Paper: 10.1038/s41586-026-10265-5 Código: github.com/SakanaAI/AI-Scientist Licencia: Apache 2.0 (código), datos bajo licencia original del paper. Repo: github.com/Ciencia-a-Mordiscos/lab