Confident Learning: Auditando y Corrigiendo Etiquetas Ruidosas#

Open In Colab

Objetivos#

  • Cuestionar el dogma del Ground Truth: comprender que en la industria real, las etiquetas humanas contienen habitualmente entre un 5% y un 15% de errores.

  • Introducir el paradigma de Confident Learning para identificar matemáticamente qué etiquetas de nuestro dataset son probablemente incorrectas.

  • Implementar la librería cleanlab para auditar un dataset ruidoso utilizando las probabilidades cruzadas (Cross-Validated Probabilities) de un modelo base.

  • Criterio de Ingeniería: Demostrar cómo limpiar el dataset de entrenamiento mejora el rendimiento del modelo en producción mucho más que ajustar hiperparámetros durante semanas.

Prerrequisitos#


Configuración del Entorno#

Nota: cleanlab suelen requerir instalación adicional, no está en Colab por defecto (al menos hasta ahora).

En la celda de abajo, se detecta automáticamente y se intenta instalar.

Nota2: En entornos locales, este tipo de frameworks los recomiendo instalar en un entorno virtual dedicado, así no se “rompen” otras cosas. Suelen usar muchas dependencias con versiones muy concretas…

Hide code cell source

# @title *Esta celda clona el repositorio (en Colab) e importa las utilidades comunes*
import sys
import os

IN_COLAB = "google.colab" in sys.modules

if IN_COLAB:
    import subprocess
    REPO_NAME = "applied-ai-engineering"
    if not os.path.exists(REPO_NAME):
        subprocess.run(["git", "clone", f"https://github.com/AxelSkrauba/{REPO_NAME}.git"], check=True)
    os.chdir(f"/content/{REPO_NAME}")
    sys.path.append(f"/content/{REPO_NAME}")

    # Instalamos cleanlab
    subprocess.run(["pip", "install", "cleanlab"], check=True)
else:
    # Repositorio en local, apuntar path a la raiz
    os.chdir(f"../../")

from utils.plots import setup_plot_style
setup_plot_style()

Introducción Teórica#

En el notebook anterior, planteamos un dilema de presupuesto: ¿Alquilo servidores para correr Aprendizaje Semi-Supervisado (SSL) o le pago a humanos en Amazon Mechanical Turk para que etiqueten 10.000 imágenes a mano?

A menudo, la respuesta correcta es pagarle a los humanos. Pero esto abre una pregunta de ingeniería mucho más “oscura”: ¿Puedo confiar en esas etiquetas humanas?

Los humanos se cansan, se distraen o simplemente no son expertos. Estudios recientes de MIT y MIT CSAIL han demostrado que incluso los datasets más famosos del mundo (como ImageNet o MNIST) tienen un porcentaje significativo de etiquetas incorrectas.

Si se aplica SSL sobre 40 etiquetas iniciales y 5 de ellas están mal, el algoritmo propagará ese error a miles de puntos. Si se entrena un Random Forest sobre 10.000 etiquetas ruidosas, el modelo memorizará el ruido.

Confident Learning (Aprendizaje Confiable)#

En lugar de intentar crear un modelo más complejo para lidiar con el ruido, Confident Learning propone usar modelos simples para encontrar y corregir el ruido en los datos.

La intuición es brillante:

  1. Entrenamos un modelo usando Validación Cruzada (K-Fold).

  2. Para cada dato en el fold de validación, el modelo emite una probabilidad (ej. “Estoy 95% seguro de que esto es un Perro”).

  3. Comparamos esa probabilidad con la etiqueta humana (ej. El humano dijo “Gato”).

  4. Si el modelo está extremadamente seguro de una clase, pero el humano anotó otra, marcamos ese dato como un Error de Etiqueta Potencial (Label Issue).

La librería estándar de la industria para esto es cleanlab.

Desarrollo e Implementación#

Vamos a generar un dataset sintético de clasificación binaria. Luego, corrompemos intencionalmente el 15% de las etiquetas para simular el trabajo de anotadores humanos cansados.

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, cross_val_predict
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import cleanlab

# 1. Generamos un dataset limpio (Ground Truth real)
X, y_limpio = make_classification(n_samples=2000, n_features=10, n_informative=5, random_state=42)

# 2. Dividimos en Train y Test el dataset limpio
# y_train_limpio_temp contendrá las etiquetas limpias para el conjunto de entrenamiento antes de inyectar ruido
X_train, X_test, y_train_limpio_temp, y_test_limpio = train_test_split(X, y_limpio, test_size=0.2, random_state=42)

# 3. Simulamos el error humano: Volteamos el 15% de las etiquetas AL AZAR, PERO SOLO EN EL CONJUNTO DE ENTRENAMIENTO
np.random.seed(42)
ruido_idx_train = np.random.choice(len(y_train_limpio_temp), size=int(0.15 * len(y_train_limpio_temp)), replace=False)
y_train_ruidoso = np.copy(y_train_limpio_temp)
y_train_ruidoso[ruido_idx_train] = 1 - y_train_ruidoso[ruido_idx_train] # 0 se vuelve 1, 1 se vuelve 0

# Asignamos las etiquetas limpias del Train para verificación posterior (el modelo NO las verá durante el entrenamiento)
y_train_limpio = y_train_limpio_temp

print(f"Etiquetas corruptas inyectadas en el Train Set: {sum(y_train_ruidoso != y_train_limpio)}")
Etiquetas corruptas inyectadas en el Train Set: 240

El Baseline (Entrenando a Ciegas)#

Veamos qué pasa si un ingeniero junior toma este dataset ruidoso y entrena un modelo directamente, asumiendo que las etiquetas son perfectas.

# Entrenamos un Random Forest con las etiquetas ruidosas
modelo_ciego = RandomForestClassifier(n_estimators=100, random_state=42)
modelo_ciego.fit(X_train, y_train_ruidoso)

# Evaluamos en el Test Set (que asumimos está limpio para medir el rendimiento real)
y_pred_ciego = modelo_ciego.predict(X_test)
acc_ciego = accuracy_score(y_test_limpio, y_pred_ciego)

print(f"Accuracy del Modelo entrenado con Ruido: {acc_ciego:.4f}")
Accuracy del Modelo entrenado con Ruido: 0.8875

El modelo alcanza un ~89% de Accuracy. El ingeniero junior podría pasar las próximas 3 semanas ajustando hiperparámetros (max_depth, min_samples_split) intentando subir ese número al 90%. Es una pérdida de tiempo. El techo de cristal no es el modelo, son los datos.

La Auditoría con Cleanlab#

Vamos a usar cleanlab para encontrar las etiquetas que los “humanos” anotaron mal.

Para ello, necesitamos generar probabilidades Out-of-Fold (OOF). Esto significa que para predecir la probabilidad de la fila 100, el modelo debe haber sido entrenado con todas las filas excepto la fila 100. (Validación Cruzada pura, como vimos en notebooks anteriores).

# 1. Generamos probabilidades cruzadas (OOF Probabilities)
# cv=5 significa que divide el train en 5 partes, entrena en 4 y predice en la 5ta.
probabilidades_oof = cross_val_predict(
    # Hay un modelo de base, otro "juez" que decide... si esto está mal... CHAU
    estimator=RandomForestClassifier(n_estimators=100, random_state=42),
    X=X_train,
    y=y_train_ruidoso,
    cv=5,
    method='predict_proba'
)

# 2. ¡La Magia de Cleanlab!
# Le pasamos las etiquetas ruidosas y las probabilidades que calculó el modelo
etiquetas_con_problemas = cleanlab.filter.find_label_issues(
    labels=y_train_ruidoso,
    pred_probs=probabilidades_oof,
    return_indices_ranked_by='self_confidence' # Devuelve los índices ordenados del peor error al más dudoso
)

print(f"Cleanlab detectó {len(etiquetas_con_problemas)} posibles errores de etiquetado.")

# Verificamos qué tan bueno fue Cleanlab encontrando nuestro ruido inyectado
errores_reales_encontrados = sum(y_train_ruidoso[etiquetas_con_problemas] != y_train_limpio[etiquetas_con_problemas])
print(f"De los detectados, {errores_reales_encontrados} eran errores reales que inyectamos.")
Cleanlab detectó 289 posibles errores de etiquetado.
De los detectados, 203 eran errores reales que inyectamos.

Resultados y Discusión: El Ciclo MLOps#

¡Impresionante! cleanlab encontró casi todos los errores que inyectamos artificialmente, simplemente analizando dónde el modelo discrepaba fuertemente con el humano. También, marcó algunos que no eran errores. Así que hay que tomar decisiones, más cuando no tenemos manera de saber qué tan bien se hizo el proceso (no hay etiquetas sin ruido para validar en la vida real). Entonces:

¿Qué hace un Ingeniero Senior con esta lista de índices?

  1. No los borra a ciegas.

  2. Toma el Top 100 de los errores más graves detectados por cleanlab.

  3. Se los envía de vuelta al equipo de expertos humanos (médicos, analistas) y les dice: “El modelo está 99% seguro de que se equivocaron en estas 100 imágenes. Por favor, revísenlas de nuevo”.

  4. Los humanos corrigen las etiquetas.

  5. Se re-entrena el modelo.

Para simular este proceso, vamos a “corregir” automáticamente las etiquetas que cleanlab detectó, devolviéndoles su valor limpio original, y reentrenamos el modelo.

# Simulamos la corrección humana:
# Reemplazamos las etiquetas ruidosas detectadas por sus valores limpios reales
y_train_corregido = np.copy(y_train_ruidoso)
y_train_corregido[etiquetas_con_problemas] = y_train_limpio[etiquetas_con_problemas]

# Re-entrenamos el modelo con el dataset "limpiado"
modelo_limpio = RandomForestClassifier(n_estimators=100, random_state=42)
modelo_limpio.fit(X_train, y_train_corregido)

# Evaluamos
y_pred_limpio = modelo_limpio.predict(X_test)
acc_limpio = accuracy_score(y_test_limpio, y_pred_limpio)

print(f"Accuracy ANTES de Cleanlab:   {acc_ciego:.4f}")
print(f"Accuracy DESPUÉS de Cleanlab: {acc_limpio:.4f}")

# Visualizamos la mejora
plt.figure(figsize=(6, 4))
sns.barplot(x=['Modelo con Ruido', 'Modelo Limpio (Cleanlab)'], y=[acc_ciego, acc_limpio], palette=['#d62728', '#2ca02c'])
plt.axhline(y=acc_ciego, color='black', linestyle='--', alpha=0.5)
plt.title("Impacto de la Calidad de los Datos en el Rendimiento")
plt.ylabel("Accuracy en Test Set")
plt.ylim(0.80, 0.95)
plt.show()
Accuracy ANTES de Cleanlab:   0.8875
Accuracy DESPUÉS de Cleanlab: 0.9075
../../_images/c8e81c93809fec5d0098d44b4e3a818507d4aff859b9249c4e0b50da98395ff6.png

Conclusión de Ingeniería: Data-Centric AI#

Subimos el Accuracy un par de puntos. Lograr este salto ajustando hiperparámetros en un Random Forest es prácticamente imposible si los datos están mal. Así que esto, en combinación con la búsqueda de hiperparámetros más un pipeline adecuado de preprocesamiento… implica un plus en el rendimiento global.

Este notebook demuestra el cambio de paradigma más importante de la última década en la Inteligencia Artificial: Data-Centric AI (IA Centrada en los Datos).

Durante años, la comunidad se obsesionó con crear modelos matemáticos cada vez más complejos (Model-Centric). Hoy sabemos que un modelo simple (como un Random Forest o una Regresión Logística) entrenado con datos impecablemente limpios, superará sistemáticamente a una Red Neuronal profunda entrenada con datos ruidosos.

Garbage In, Garbage Out

Conclusión del Capítulo#

¡Felicidades! Ahora sí, completo el Capítulo 3. Pasamos de trazar de trazar líneas rectas simples a dominar ensamblados de árboles, pipelines a prueba de fugas, detección de anomalías en alta dimensionalidad, propagación de etiquetas en grafos y, finalmente, la auditoría de calidad de datos con Confident Learning.

Tienes el criterio completo de un Ingeniero de Machine Learning Clásico.

Recursos Recomendados para Profundizar#

  1. Paper Fundacional: Confident Learning: Estimating Uncertainty in Dataset Labels (Northcutt et al., 2021). La base matemática detrás de cleanlab.

  2. cleanlab - Repositorio original

  3. cleanlab examples - Repositorio con ejemplos para múltiples tipos de datos


Entorno de Ejecución#

Hide code cell source

from utils.environment import environment_table
environment_table(include_all=True)

Hide code cell output

Reproducibility Environment Information
Package Version
Python 3.12.13
Platform Linux-6.6.113+-x86_64-with-glibc2.35
Cython 3.0.12
IPython 7.34.0
PIL 11.3.0
aiohappyeyeballs 2.6.1
aiohttp 3.13.4
aiosignal 1.4.0
anyio 4.13.0
anywidget 0.9.21
argparse 1.1
attr 26.1.0
backcall 0.2.0
bottleneck 1.4.2
brotli 1.2.0
certifi 2026.2.25
chardet 5.2.0
charset_normalizer 3.4.6
cleanlab 2.9.0
click 8.3.1
cloudpickle 3.1.2
cramjam 2.11.0
csv 1.0
ctypes 1.1.0
cycler 0.12.1
cython 3.0.12
datasets 4.0.0
dateutil 2.9.0.post0
debugpy 1.8.15
decimal 1.70
decorator 4.4.2
defusedxml 0.7.1
dill 0.3.8
entrypoints 0.4
filelock 3.25.2
frozenlist 1.8.0
fsspec 2025.3.0
google 3.0.0
h11 0.16.0
h2 4.3.0
hpack 4.1.0
http 0.6
httpcore 1.0.9
httplib2 0.31.2
httpx 0.28.1
huggingface_hub 1.8.0
hyperframe 6.1.0
idna 16.0.0
ipaddress 1.0
ipykernel 6.17.1
ipython_genutils 0.2.0
ipywidgets 7.7.1
jinja2 3.1.6
joblib 1.5.3
json 2.0.9
jupyter_client 7.4.9
jupyter_core 5.9.1
kiwisolver 1.5.0
logging 0.5.1.2
markupsafe 3.0.3
matplotlib 3.10.0
matplotlib_inline 0.2.1
multidict 6.7.1
multiprocess 0.70.16
numexpr 2.14.1
numpy 2.0.2
packaging 26.0
pandas 2.2.2
patsy 1.0.2
pexpect 4.9.0
pickleshare 0.7.5
platformdirs 4.9.4
prompt_toolkit 3.0.52
propcache 0.4.1
psutil 5.9.5
psygnal 0.15.1
ptyprocess 0.7.0
pyarrow 18.1.0
pydantic 2.12.3
pydantic_core 2.41.4
pydevd 3.2.3
pygments 2.20.0
pyparsing 3.3.2
pytz 2025.2
re 2.2.1
requests 2.32.4
rich 13.9.4
scipy 1.16.3
seaborn 0.13.2
simplejson 3.20.2
six 1.17.0
sklearn 1.5.3
snappy 0.7.3
sniffio 1.3.1
socketserver 0.4
socks 1.7.1
soundfile 0.13.1
statsmodels 0.14.6
termcolor 3.3.0
threadpoolctl 3.6.0
tornado 6.5.1
tqdm 4.67.3
traitlets 5.7.1
typing_extensions 4.15.0
typing_inspection 0.4.2
urllib 3.12
urllib3 2.5.0
wcwidth 0.6.0
xmlrpc 3.12
xxhash 3.6.0
yaml 6.0.3
yarl 1.23.0
zlib 1.0
zmq 26.2.1
zstandard 0.25.0