UD06 · Taller 2 — Auditoría de sesgos con Fairlearn¶
Objetivo: auditar un modelo real, medir su sesgo por grupos, mitigarlo y medir el precio de esa mitigación.
Rellena las celdas marcadas con # TU CÓDIGO y responde en las de texto marcadas con ✏️ Respuesta.
Fase 1 — Entorno y datos¶
Usaremos UCI Adult (48.842 filas), que predice si una persona gana más de 50.000 $ al año. Fairlearn lo trae empaquetado: no hay que descargar nada.
Fíjate en lo que vas a hacer: quitar sex de las características y guardarlo aparte. No es un truco, es la única forma de poder auditar.
%pip install fairlearn scikit-learn pandas
import pandas as pd
from fairlearn.datasets import fetch_adult
datos = fetch_adult(as_frame=True)
# TU CÓDIGO: quita «sex» de X, construye y (>50K), y conserva sexo aparte
X = ...
y = ...
sexo = ...
print("filas:", len(X), "| columnas:", len(X.columns))
print(sexo.value_counts().to_dict())
✏️ Respuesta: si borrases el atributo del todo —«equidad por desconocimiento»—, ¿qué perderías? Relaciónalo con la minimización de datos del RGPD.
(escribe aquí)
Antes de entrenar, mide las tasas base de cada grupo: de eso depende todo lo demás.
# TU CÓDIGO: la proporcion de cada grupo que gana mas de 50.000
...
✏️ Respuesta: anota las dos tasas base. ¿Son parecidas o muy distintas? Esa es la condición exacta del resultado de imposibilidad de Kleinberg et al. (2016): recuérdalo en la Fase 5, cuando veas que no puedes arreglarlo todo a la vez.
(escribe aquí)
Fase 2 — Entrena un modelo¶
Un Pipeline con OneHotEncoder para las categóricas y un HistGradientBoostingClassifier.
!!! sparse_output=False no es opcional HistGradientBoostingClassifier no acepta matrices dispersas. Si dejas el OneHotEncoder por defecto, el fit falla con TypeError: Sparse data was passed for X, but dense data is required. Es el fallo más habitual de este taller.
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
# TU CÓDIGO: prepara el pipeline, parte los datos (arrastrando `sexo`) y entrena
...
pred = ...
Fase 3 — Mide la equidad¶
Tres números: exactitud global, paridad demográfica e igualdad de oportunidades.
from sklearn.metrics import accuracy_score
from fairlearn.metrics import (MetricFrame, demographic_parity_difference,
equalized_odds_difference, selection_rate)
# TU CÓDIGO: los tres valores
...
✏️ Respuesta: la exactitud global es alta. ¿Te vale para decir que el modelo es justo?
(escribe aquí)
Fase 4 — Compara por grupo¶
Construye un MetricFrame con exactitud y tasa de selección desglosadas por sexo.
# TU CÓDIGO
mf = ...
print(mf.by_group)
✏️ Respuesta: ¿en qué grupo acierta más el modelo? ¿Y a qué grupo selecciona más? Si las dos respuestas no coinciden, explica por qué la métrica global no lo veía.
(escribe aquí)
Fase 5 — Mitiga y mide el precio¶
ThresholdOptimizer ajusta un umbral distinto por grupo para forzar la métrica que le pidas. Es mitigación de postprocesado: no toca los datos ni reentrena.
!!! Dos parámetros obligatorios Hace falta predict_method="predict_proba" y prefit=True, porque el modelo ya está entrenado.
from fairlearn.postprocessing import ThresholdOptimizer
# TU CÓDIGO: ajusta con constraints="equalized_odds" y vuelve a medir las tres metricas
...
✏️ Respuesta: rellena la tabla con tus números.
| Métrica | Antes | Después | ¿Mejoró? |
|---|---|---|---|
| Exactitud global | |||
| Igualdad de oportunidades (dif) | |||
| Paridad demográfica (dif) | |||
| Tasa de selección · mujeres | |||
| Tasa de selección · hombres |
Y responde: ¿qué has pagado por reducir la desigualdad de oportunidades? ¿Se arreglaron las dos métricas de equidad a la vez?
(escribe aquí)
Fase 6 — Reflexión ética y normativa¶
✏️ Respuesta:
- ¿Qué métrica de equidad defenderías ante un comité, y por qué esa y no otra?
- ¿Qué exige el AI Act a un sistema como este? ¿En qué nivel de riesgo lo situarías?
- ¿Es aceptable perder exactitud global para ganar equidad? ¿Quién debería decidirlo?
- Cambia la restricción a
demographic_parityy mira qué se rompe. ¿Qué te dice eso del resultado de imposibilidad?
(escribe aquí)