UD06 · Notebook 3 — Auditoría de sesgos con Fairlearn¶
Objetivo: medir el sesgo de un modelo real con Fairlearn, mitigarlo y comprobar el precio que se paga por hacerlo (RA6-f, con conexión a RA6-b y RA6-e).
Todo el código de este taller está ejecutado
Las cifras que verás abajo son las que salen de verdad al ejecutarlo con fairlearn 0.14.0, scikit-learn 1.9.0 y pandas 3.0.5. Si tus números difieren un poco, revisa las versiones: no revises tu razonamiento.
Es una entrega evaluable: se corrige con la rúbrica de su tarea en Moodle.
Fase 1 — Prepara el entorno y carga los datos¶
Usaremos el conjunto UCI Adult (48.842 filas), que predice si una persona gana más de 50 000 $ al año a partir de datos demográficos y laborales. Fairlearn lo trae ya empaquetado, así que no hay que descargar nada a mano.
La primera lección aparece antes de entrenar
Fíjate en lo que acabas de hacer: has quitado el sexo de las características y lo has guardado aparte. Eso no es un truco: es la única forma de auditar. Si borras el atributo del todo —«equidad por desconocimiento», §9.2— pierdes la capacidad de comprobar si discriminas. Y eso choca de frente con la minimización del RGPD: es la paradoja de los sesgos del §9.8, en la primera celda del taller.
Antes de seguir, mide las tasas base de cada grupo, porque de eso depende todo lo demás:
Anota este dato
El 10,93 % de las mujeres del conjunto gana más de 50 000 $, frente al 30,38 % de los hombres. Las tasas base son muy distintas, y esa es exactamente la condición del resultado de imposibilidad de Kleinberg et al. (2016) (§9.4). Recuérdalo en la Fase 5, cuando veas que no puedes arreglarlo todo a la vez.
%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¶
sparse_output=False no es opcional
HistGradientBoostingClassifier no acepta matrices dispersas. Si dejas el OneHotEncoder por defecto, el fit revienta 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¶
- Diferencia ≈ 0 significa que la métrica se cumple entre grupos.
- Paridad demográfica 0,1687: hay 17 puntos de diferencia en la tasa de selección. Mucho.
- Igualdad de oportunidades 0,0731: 7 puntos. Menos, pero no cero.
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 (y desconfía de la métrica global)¶
Aquí está el corazón del taller
La exactitud global era 0,8753, un número respetable. Pero por grupo:
- El modelo acierta más con las mujeres (0,9358) que con los hombres (0,8453). ¿Es que funciona mejor para ellas? No. Acierta más porque solo el 10,93 % de las mujeres son positivas, así que decir «no» casi siempre ya acierta. La exactitud es una métrica tramposa con clases desequilibradas.
- La tasa de selección es del 8,5 % para las mujeres y del 25,4 % para los hombres: el modelo propone a un hombre para el grupo de altos ingresos tres veces más a menudo.
Una sola cifra global habría ocultado las dos cosas. Esto es, en la práctica, lo que enseña la paradoja de Simpson (§9.5) y por qué el checklist del §9.7 insiste en medir por subgrupo.
# 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 el sesgo 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 el modelo.
Rellena esta tabla en tu informe con tus números:
| Métrica | Antes | Después | ¿Mejoró? |
|---|---|---|---|
| Exactitud global | 0,8753 | 0,8611 | |
| Igualdad de oportunidades (dif) | 0,0731 | 0,0014 | |
| Paridad demográfica (dif) | 0,1687 | 0,0950 | |
| Tasa de selección · mujeres | 0,0850 | 0,1017 | |
| Tasa de selección · hombres | 0,2538 | 0,1967 |
Las tres cosas que hay que ver en esta tabla
- La igualdad de oportunidades se ha resuelto: de 0,0731 a 0,0014, prácticamente cero. Es lo que le pedimos con
constraints="equalized_odds", y lo cumple. - Se paga un precio: la exactitud global baja de 0,8753 a 0,8611, 1,4 puntos. No es gratis, y hay que poder justificar ese coste ante quien paga el sistema.
- La paradoja demográfica NO se ha resuelto: mejora de 0,1687 a 0,0950, pero sigue lejos de cero. Y no es un fallo de la herramienta. Las tasas base son 10,93 % y 30,38 % (Fase 1), así que por Kleinberg et al. (2016) no puedes tener a la vez calibración e igualdad de oportunidades, y forzar la igualdad de oportunidades no te da paridad demográfica.
Prueba a cambiar constraints="demographic_parity" y observa qué se rompe entonces. Eso es el ejercicio 79 hecho con datos.
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¶
Responde en el informe:
- Has quitado
sexde las características pero lo has usado para auditar. ¿Cómo justificarías ese tratamiento ante el principio de minimización del RGPD (§5.2)? ¿Qué base legal y qué plazo pondrías? - Si no pudieras tratar el sexo de ninguna manera, ¿cómo auditarías el modelo con variables proxy (§9.8)? Nombra dos columnas del conjunto que podrían servir y explica el riesgo de usarlas.
- Este sistema, aplicado a selección de personal, ¿qué nivel de riesgo tendría en el AI Act y qué obligaciones concretas le tocarían (§6.1)?
- Bajo la Ley 15/2022, si una candidata denuncia discriminación, ¿quién tiene que demostrar qué? ¿Qué documento de este taller usarías como prueba?
- La exactitud bajó 1,4 puntos al mitigar. Redacta en tres frases cómo se lo explicarías a un responsable que solo mira la exactitud global.
- Aplica una técnica del §8.2 a este conjunto de datos: si tuvieras que publicarlo para que otro centro replicara el estudio, ¿qué harías y por qué no basta con borrar el nombre?
Entrega¶
Sube a Moodle un informe con la tabla de la Fase 5 rellena, la salida por grupo de la Fase 4 y las seis respuestas de la Fase 6. Extensión orientativa: dos o tres páginas.
Corrección
Las soluciones no se publican: se corrigen y comentan en clase.