
Este artigo foi escrito por Sarah Khatry e Haniyeh Mahmoudian, cientistas de dados na DataRobot.
A questão do viés em modelos de machine learning tem recebido muita atenção nos últimos anos. Histórias de modelos que falham viram manchete, e juristas, políticos e jornalistas têm contribuído para o debate sobre quais princípios e valores queremos ver refletidos nos modelos que construímos.
Embora o viés humano seja um tema complexo e nem sempre fácil de delimitar, o viés em machine learning é, no fim das contas, matemático. Existem vários tipos de testes que você pode aplicar ao seu modelo para identificar diferentes vieses nas previsões. Qual teste usar depende principalmente do que você valoriza e do contexto de uso do modelo.
Um dos testes mais amplamente aplicáveis é o de paridade estatística, que vamos explorar passo a passo neste tutorial prático. Vale lembrar: o viés é sempre avaliado em relação a grupos distintos de pessoas identificados por um atributo protegido nos seus dados, como raça, gênero, idade, orientação sexual, nacionalidade etc.
Com a paridade estatística, o objetivo é medir se os diferentes grupos têm a mesma probabilidade de alcançar um resultado favorável. Um exemplo clássico é um modelo de contratação, no qual você quer garantir que candidatos homens e mulheres tenham a mesma probabilidade de serem aprovados. Em um modelo enviesado, você identifica que um grupo é privilegiado com maior probabilidade de contratação, enquanto o outro fica em desvantagem.
Para mostrar isso na prática, primeiro vamos construir dados sintéticos com um viés pré-definido, depois confirmar por meio de análise que os dados refletem o cenário desejado e, por fim, aplicar o teste de paridade estatística.
Gerando dados sintéticos
Neste tutorial, vamos usar o pacote pandas em Python, mas todas as etapas também podem ser reproduzidas em R.
import pandas as pd
Para gerar dados sintéticos com um atributo protegido e previsões do modelo, primeiro precisamos definir algumas entradas: o número total de registros, o próprio atributo protegido (aqui com dois valores genéricos, A e B) e a previsão do modelo associada ao resultado favorável, neste exemplo o 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 na vida real, os grupos A e B podem não estar distribuídos uniformemente nos dados. No código abaixo, decidimos que 60% da população será do grupo privilegiado B, que tem 30% de chance de receber o resultado favorável. O grupo A, não privilegiado, compõe os 40% restantes e tem apenas 15% de probabilidade de obter o resultado favorável.
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, atribuimos aleatoriamente um grupo protegido e uma previsão, usando os vieses definidos como pesos, e então criamos um dataframe a partir da 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'])
Interpretando os dados
Agora que temos nossos dados sintéticos, vamos analisar o que construímos. Para cada grupo, A versus B, qual é a probabilidade de classe de alcançar o resultado favorável ou desfavorável?
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
Analisando a tabela, não é difícil ver que o grupo B tem quase o dobro da chance de alcançar o resultado favorável, com probabilidade de 28,6%. Nossos dados sintéticos foram projetados para ter probabilidade de 30%, então estamos bem próximos. Em seguida, salvamos as probabilidades em um dicionário.
Como é gerado aleatoriamente, o seu código pode apresentar um resultado um pouco diferente.
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
O teste de paridade estatística
Para cada grupo, a paridade estatística calcula a razão entre sua probabilidade de alcançar o resultado favorável e a probabilidade do grupo privilegiado alcançar esse mesmo resultado. Iteramos sobre o dicionário de cada grupo protegido com sua probabilidade de resultado favorável para construir essas razões.
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 o grupo privilegiado, B, o score de paridade estatística é 1, como esperado. Para o outro grupo, A, o score é 0,526, indicando que eles têm cerca de metade da chance de alcançar o resultado favorável em comparação com o grupo B.
O teste de viés estatístico oferece uma avaliação simples de quão diferentes podem ser os resultados previstos para determinados grupos nos seus dados. Medir o viés tem dois objetivos. De um lado, o teste gera uma métrica transparente, facilitando a comunicação de forma objetiva. Idealmente, porém, identificar o viés é o primeiro passo para começar a mitigá-lo no seu modelo. Esta é uma área quente de pesquisa em machine learning, com várias técnicas sendo desenvolvidas para lidar com diferentes tipos de viés e abordagens de modelagem.
Com a combinação certa de testes e técnicas de mitigação, é possível melhorar seu modelo de forma iterativa, reduzir o viés e manter a acurácia. Você pode projetar sistemas de machine learning que não apenas aprendam com resultados históricos, mas também reflitam seus valores nas decisões futuras.
Descubra como construir uma IA em que você pode confiar com a DataRobot.
