
Este artículo fue escrito por Sarah Khatry y Haniyeh Mahmoudian, científicas de datos en DataRobot.
La cuestión del sesgo en los modelos de machine learning ha recibido mucha atención en los últimos años. Las historias de modelos que fallan ocupan titulares, y juristas especializados en derechos humanos, políticos y periodistas han contribuido al debate sobre qué ética y qué valores queremos reflejar en los modelos que construimos.
Aunque el sesgo humano es un tema espinoso y no siempre fácil de definir, el sesgo en machine learning, al fin y al cabo, es matemático. Hay muchos tipos de pruebas que puedes aplicar a tu modelo para identificar distintas formas de sesgo en sus predicciones. Qué prueba conviene usar depende sobre todo de lo que te preocupa y del contexto en el que se emplea el modelo.
Una de las pruebas más versátiles es la paridad estadística, que vamos a ver paso a paso en este tutorial práctico. El sesgo siempre se evalúa en relación con distintos grupos de personas identificados por un atributo protegido en tus datos, por ejemplo, raza, género, edad, orientación sexual, nacionalidad, etc.
Con la paridad estadística, tu objetivo es medir si los distintos grupos tienen la misma probabilidad de obtener un resultado favorable. Un ejemplo clásico es un modelo de contratación, en el que querrías asegurar que candidatas y candidatos tengan la misma probabilidad de ser contratados. En un modelo sesgado, verás que un grupo está privilegiado con una probabilidad mayor de contratación, mientras que el otro está en desventaja.
Para mostrar cómo funciona en la práctica, primero generaremos datos sintéticos con un sesgo predefinido, luego confirmaremos mediante el análisis que los datos reflejan la situación prevista y, por último, aplicaremos la prueba de paridad estadística.
Generación de datos sintéticos
En este tutorial usaremos el paquete pandas en Python, pero cada paso se puede reproducir también en R.
import pandas as pd
Para generar datos sintéticos con un atributo protegido y predicciones del modelo, primero necesitamos especificar algunas entradas: el número total de registros, el propio atributo protegido (aquí dos valores genéricos, A y B) y la predicción del modelo asociada al resultado favorable, en este ejemplo el valor 1.
num_row = 1000 # number of rows in the data
prot_att = 'prot_att' # column name for protected attribute
model_pred_bin = 'prediction' # column name for predictions
pos_label = 1 # prediction value associated to positive/favorable outcome
prot_group = ['A','B'] # two groups in our protected attribute
Como en la vida real, es posible que los grupos A y B no estén distribuidos de forma uniforme en nuestros datos. En el siguiente código, hemos decidido que el 60% de la población pertenezca al grupo privilegiado B, que tiene un 30% de probabilidad de recibir el resultado favorable. El grupo no privilegiado A conformará el 40% restante y tendrá solo un 15% de probabilidad de lograr el resultado favorable.
priv_g = 'B' # privileged group
priv_g_rate = 60 # 60% of the population is from group B
priv_p_rate = 30 # 30% of the predictions for group B was for favorable outcome 1
unpriv_p_rate = 15 # 15% of the predictions for group A was for favorable outcome 1
biased_list_priv = [prot_group[0]] * (100 - priv_g_rate) + [prot_group[1]] * priv_g_rate
biased_list_pos_priv = [0] * (100 - priv_p_rate) + [1] * priv_p_rate
biased_list_pos_unpriv = [0] * (100 - unpriv_p_rate) + [1] * unpriv_p_rate
Para cada registro, asignamos aleatoriamente un grupo protegido y una predicción, usando como pesos el sesgo que definimos antes, y luego creamos un dataframe a partir de la lista de registros.
list_df = [] # empty list to store the synthetic records
for i in range(0, num_row):
# generating random value representing protected groups with bias towards B
prot_rand = random.choices(biased_list_priv)[0]
if prot_rand == priv_g:
prot_g = priv_g
# generating random binary value representing prediction with bias towards 0
pred = random.choices(biased_list_pos_priv)[0]
# adding the new record to the list
list_df.append([prot_g,pred])
else:
prot_g = prot_group[0]
pred = random.choices(biased_list_pos_unpriv)[0]
list_df.append([prot_g,pred])
# create a dataframe from the list
df = pd.DataFrame(list_df,columns=['prot_att','prediction'])
Interpretación de los datos
Ahora que tenemos nuestros datos sintéticos, analicemos qué hemos construido. Para cada grupo, A frente a B, ¿cuál es su probabilidad de clase de lograr el resultado favorable o desfavorable?
df_group = (df.groupby([prot_att])[model_pred_bin].value_counts() / df.groupby([prot_att])[
model_pred_bin].count()).reset_index(name='probability')
print(df_group)
prot_att prediction probability
0 A 0 0.849490
1 A 1 0.150510
2 B 0 0.713816
3 B 1 0.286184
Si revisas la tabla, verás claramente que el grupo B tiene casi el doble de probabilidades de obtener el resultado favorable, con una probabilidad del 28,6%. Nuestros datos sintéticos se diseñaron para tener una probabilidad del 30%, así que estamos cerca del objetivo. A continuación, guardamos las probabilidades en un diccionario.
Como se genera aleatoriamente, tu código puede dar un resultado ligeramente distinto.
prot_att_dic = {}
for att in prot_group:
temp = df_group[(df_group[prot_att] == att) & (df_group[model_pred_bin] == pos_label)]
prob = float(temp['probability'])
prot_att_dic[att] = prob
La prueba de paridad estadística
Para cada grupo, la paridad estadística devuelve la razón entre su probabilidad de obtener el resultado favorable y la probabilidad del grupo privilegiado de obtener ese mismo resultado. Iteramos sobre el diccionario de cada grupo protegido con su probabilidad de resultado favorable para construir las razones.
prot_test_res = {}
for k in prot_att_dic.keys():
res = float(prot_att_dic[k] / prot_att_dic[priv_g])
prot_test_res[k] = res
for key in prot_test_res.keys():
value = prot_test_res[key]
print(key, ' : ', value)
A : 0.5259207131128314
B : 1.0
Para el grupo privilegiado, B, la puntuación de paridad estadística es 1, como debe ser. Para el otro grupo, A, su puntuación es 0,526, lo que indica que tiene aproximadamente la mitad de probabilidades de lograr el resultado favorable que el grupo B.
La prueba de sesgo estadístico ofrece una evaluación sencilla de hasta qué punto pueden diferir los resultados previstos para determinados grupos en tus datos. El objetivo de medir el sesgo es doble. Por un lado, esta prueba ofrece una métrica transparente, más fácil y concreta de comunicar. Pero, idealmente, identificar el sesgo es el primer paso para empezar a mitigarlo en tu modelo. Es un área muy activa de investigación en machine learning, con muchas técnicas en desarrollo para adaptarse a distintos tipos de sesgo y enfoques de modelado.
Con la combinación adecuada de pruebas y técnicas de mitigación, es posible mejorar tu modelo de forma iterativa, reducir el sesgo y mantener la precisión. Puedes diseñar sistemas de machine learning que no solo aprendan de resultados históricos, sino que también reflejen tus valores en la toma de decisiones futura.
Descubre cómo crear una IA en la que puedas confiar con DataRobot.

