Pular para o conteúdo
iauaiiauai — portal de tecnologia, IA e Cloud
IAIntermediário

Viés em modelos de IA: como medir e corrigir

Viés é do dataset, não de um example. Meça com fairness metrics, entenda trade-offs, corrija de verdade.

Por Equipe iauai · 19 de junho de 2026 · 11 min de leitura

Nesta página

O problema real

Uma empresa brasileira lança modelo de classificação de crédito. Treina em 10 anos de dados históricos de aprovações/rejeições. Modelo fica muito preciso — 92% de acurácia no teste. Em produção, descobre: modelo nega crédito 40% mais em aplicações de pessoas negras que de pessoas brancas, pra mesmo perfil de renda.

A culpa? Não do algoritmo (funciona mecanicamente igual pra todos). Culpa: dataset histórico. 40 anos atrás, banco rejeitava mais gente negra. Dados históricos codificam esse preconceito. Modelo aprendeu: "traços X indicam rejeição" porque historicamente foi assim.

Viés não é acaso. É padrão sistemático em favor/desfavor de certo grupo. E afeta negócio (risco legal, percepção de marca) e pessoas (discriminação).

Viés é propriedade da distribuição, não de um example

Um example isolado ({renda: $5k, raça: negra, aprovado: não}) não prova viés. Precisam de padrão. A pergunta certa é: "qual é a taxa de aprovação por grupo?"

Tipos comuns de viés:

Viés de amostragem

Dataset tem representação desigual de grupos.

import pandas as pd

# 10k aplicações, mas 90% são homens
df = pd.read_csv('credit_applications.csv')

print(df['genero'].value_counts(normalize=True))
# genero
# Masculino    0.90
# Feminino     0.10
# Outro        0.00

Treina modelo nessa distribuição, 90% de exemplos é homem. Modelo aprende features mais bem representadas. Pra mulher, extrapolação (pode quebrar).

Viés de rótulo

Labels errados ou inconsistentes pra certos grupos.

# Banco historicamente aprovava homens com risco alto
# Rejeitava mulheres com risco similar

# Label "aprovado" é inconsistente:
# {renda: $3k, homem}   → label: aprovado    (risco ~50%)
# {renda: $3k, mulher}  → label: rejeitado   (risco ~50%)

# Modelo treina: "ser homem importa pra aprovação"

Viés histórico

Dataset codifica discriminações passadas. Mesmo que labels sejam "corretos" pra época, herdam preconceito:

# 1985: banco rejeita negros por preconceito puro
# 2025: dados históricos refletem isso
# Modelo treina, aprende: "raça correlaciona com rejeição"

Viés de agregação

Diferentes subgrupos têm padrões diferentes, mas você treina modelo único.

# Renda é bom preditor pra homens (quanto mais, mais aprovado)
# Renda é fraco preditor pra mulheres (aprovação depende de outros fatores)

# Modelo único mistura: "renda tem peso X"
# Subestima importância pra mulheres, superestima pra homens

Como MEDIR viés: fairness metrics

Não é opinião. Mede-se com números. Principais métricas (todas de 0 a 1, sendo 1 = perfeição):

Paridade demográfica (Demographic Parity)

Taxa de aprovação deve ser igual entre grupos.

from sklearn.metrics import confusion_matrix
import numpy as np

# Dataset com 1000 aplicações
# 500 mulheres, 500 homens
y_true = [1, 0, 1, 1, 0, ...]  # real (aprovado=1, rejeitado=0)
y_pred = [1, 0, 1, 0, 0, ...]  # predição do modelo
genero = ['F', 'M', 'F', 'M', 'F', ...]  # característica sensível

# Taxa de aprovação por gênero
approval_rate_f = y_pred[np.array(genero) == 'F'].mean()
approval_rate_m = y_pred[np.array(genero) == 'M'].mean()

print(f"Mulheres aprovadas: {approval_rate_f:.2%}")
print(f"Homens aprovados: {approval_rate_m:.2%}")

# Paridade demográfica = 1 se iguais
parity = min(approval_rate_f, approval_rate_m) / max(approval_rate_f, approval_rate_m)
print(f"Paridade demográfica: {parity:.2%}")

# 0.8 (80%) significa: grupo minoritário tem 80% da taxa do grupo majoritário
# < 0.6 (< 60%) = disparidade significativa

Exemplo:

Mulheres aprovadas: 45%
Homens aprovados: 65%
Paridade: 45% / 65% = 69%

Diagnóstico: modelo aprova homens 1.44x mais que mulheres
(disparidade clara)

Igualdade de oportunidade

Modelos devem ter mesma taxa de falso negativo (FNR) entre grupos. Falso negativo = pessoa que deveria ser aprovada, mas foi rejeitada.

# Verdadeiros positivos (aprovado, era bom) por grupo
tp_f = ((y_true == 1) & (y_pred == 1) & (np.array(genero) == 'F')).sum()
tp_m = ((y_true == 1) & (y_pred == 1) & (np.array(genero) == 'M')).sum()

# Falsos negativos (rejeitado, era bom) por grupo
fn_f = ((y_true == 1) & (y_pred == 0) & (np.array(genero) == 'F')).sum()
fn_m = ((y_true == 1) & (y_pred == 0) & (np.array(genero) == 'M')).sum()

# Taxa de recall (acerto em positivos) = TP / (TP + FN)
recall_f = tp_f / (tp_f + fn_f)
recall_m = tp_m / (tp_m + fn_m)

print(f"Recall mulheres (detecção de bom crédito): {recall_f:.2%}")
print(f"Recall homens: {recall_m:.2%}")

# Igualdade de oportunidade: recalls iguais
opportunity_gap = abs(recall_f - recall_m)
print(f"Gap de oportunidade: {opportunity_gap:.2%}")

Exemplo:

Recall mulheres: 60% (dos bons créditos femininos, 60% aprovados)
Recall homens: 80%
Gap: 20%

Diagnóstico: modelo falha mais em detectar bom crédito em mulheres
(mais falsos negativos = mais mulheres boas rejeitadas)

Paridade preditiva

Probs de sucesso devem ser iguais entre grupos entre pessoas aprovadas. Se modelo aprova mulher, chance dela dar certo deve ser mesma que homem aprovado.

# Dentre aprovados, qual % realmente teve sucesso?

true_positives_f = ((y_true == 1) & (y_pred == 1) & (np.array(genero) == 'F')).sum()
approved_f = (y_pred == 1)[np.array(genero) == 'F'].sum()
precision_f = true_positives_f / approved_f if approved_f > 0 else 0

true_positives_m = ((y_true == 1) & (y_pred == 1) & (np.array(genero) == 'M')).sum()
approved_m = (y_pred == 1)[np.array(genero) == 'M'].sum()
precision_m = true_positives_m / approved_m if approved_m > 0 else 0

print(f"Precisão em mulheres aprovadas: {precision_f:.2%}")
print(f"Precisão em homens aprovados: {precision_m:.2%}")

parity = abs(precision_f - precision_m)
print(f"Diferença de paridade preditiva: {parity:.2%}")

Exemplo:

Dentre mulheres aprovadas: 75% realmente obtém sucesso
Dentre homens aprovados: 82% realmente obtém sucesso
Gap: 7%

Diagnóstico: modelo é mais exigente (inconsistentemente) com mulheres

Problema: métricas conflitam

Você não consegue otimizar simultaneamente paridade demográfica (mesma taxa de aprovação) + igualdade de oportunidade (mesmo recall) + paridade preditiva (mesma precisão).

Exemplo:

Cenário: 60% de candidatos bons em ambos grupos.

Paridade demográfica (mesma taxa aprovação):
Aprova 70% mulheres, 70% homens
→ Mas se 60% são bons, haverá 10% falsos positivos

Igualdade de oportunidade (mesmo recall):
Aprova 60% mulheres boas, 60% homens bons (só bons)
→ Taxa de aprovação é diferente se há viés de rótulo

Paridade preditiva (mesma precisão):
Aprova mulheres com score > 0.75, homens com score > 0.72
→ Taxas de aprovação vão ser diferentes

Você escolhe qual métrica importa pro negócio.

Crédito: igualdade de oportunidade é legalmente mais defensável (LGPD Art. 9). Significa: oportunidade de aprovação é justa, mesmo que taxas finais sejam diferentes.

Contratação: paridade demográfica pode ser requerida (ação afirmativa). Significa: grupos sub-representados têm chances iguais de sendo contratados.

Diagnóstico de doença: paridade preditiva importa mais. Se você aprova teste em mulheres 90% confiável e em homens 85%, tem problema (inconsistência em cuidado).

Corrigindo viés: técnicas práticas

1. Rebalanceamento (resampling)

Aumentar exemplos do grupo sub-representado.

from sklearn.utils import resample

df = pd.read_csv('credit_applications.csv')

# Separa por grupo
df_majority = df[df['genero'] == 'Masculino']
df_minority = df[df['genero'] == 'Feminino']

print(f"Antes: {len(df_majority)} homens, {len(df_minority)} mulheres")

# Resample minority com reposição até igualar
df_minority_upsampled = resample(
    df_minority,
    n_samples=len(df_majority),
    random_state=42,
    replace=True
)

df_balanced = pd.concat([df_majority, df_minority_upsampled])
print(f"Depois: {len(df_balanced) / 2} homens, {len(df_balanced) / 2} mulheres")

# Treina em df_balanced
# Resultado: modelo vê 50-50, aprende features de ambos grupos melhor

Tradeoff: se minority class é raro (2% do dataset), upsampling cria dados duplicados. Modelo memoriza.

2. Reponderação (weighting)

Em vez de duplicar, aumenta peso de exemplos minority em loss.

from sklearn.utils.class_weight import compute_sample_weight

X = df[['renda', 'idade', 'score_credit', ...]]
y = df['aprovado']
grupos = df['genero']

# Compute weight: cada grupo raro pesa mais
class_weight = 'balanced'  # Sklearn balanceia automaticamente
sample_weight = compute_sample_weight(class_weight, y)

# Mais peso ainda pra minority
for i, group in enumerate(grupos):
    if group == 'Feminino':
        sample_weight[i] *= 1.5  # Mulheres pesam 50% mais

model = LogisticRegression()
model.fit(X, y, sample_weight=sample_weight)

Tradeoff: não cria dados fake, mas modelo pode underfittar (peso demais em minoria causa overfitting nela).

3. Ajuste de threshold por grupo

Treina modelo igual pra todos, mas threshold de decisão varia.

from sklearn.linear_model import LogisticRegression

# Treina modelo único
model = LogisticRegression()
model.fit(X, y)

# Produz scores (probabilidade, 0 a 1)
scores = model.predict_proba(X)[:, 1]

# Threshold padrão: 0.5
# Mas para igualdade de oportunidade, ajusta por grupo

def predict_fair(scores, genero, target_recall=0.80):
    predictions = []
    for score, g in zip(scores, genero):
        if g == 'Feminino':
            threshold = 0.45  # Mais tolerante com mulheres
        else:
            threshold = 0.50  # Padrão com homens
        
        predictions.append(1 if score > threshold else 0)
    
    return np.array(predictions)

y_pred_fair = predict_fair(scores, grupos)

Tradeoff: muda a semântica de "score de risco". Score 0.48 em mulher significa "aprova", mas 0.48 em homem significa "rejeita", mesmo pessoa. Defensável legalmente? Depende da lei.

4. Pós-processamento (post-processing)

Após treinar, aplica regra corretiva.

# Modelo treina normalmente
model.fit(X, y)
y_pred = model.predict(X)

# Diagnóstico: mulheres aprovadas com recall 60%, homens com 80%
# Pós-processamento: "inverte alguns rejeitados de mulheres"

target_recall = 0.80

# Mulheres rejeitadas com score alto
rejected_women_high_score = (
    (y_pred == 0) & (grupos == 'Feminino') & (scores > 0.40)
)

# Aprova 25% delas (a mais pra aumentar recall)
n_to_approve = int(rejected_women_high_score.sum() * 0.25)
indices_to_flip = np.random.choice(
    np.where(rejected_women_high_score)[0],
    n_to_approve,
    replace=False
)

y_pred[indices_to_flip] = 1

# Agora recall mulheres aumentou

Tradeoff: pode aumentar falsos positivos.

Armadilhas comuns

Ignorar contexto empresarial: "paridade demográfica" é impossível de atingir se base tem 60% de homens bons e 40% de mulheres boas. Você vai rejeitando homens bons pra igualar, loss function cresce.

Medir em whole dataset, não em subgrupos: accuracy global 92% mascara que accuracy pra mulher negra é 70%.

# RUIM: acurácia global
accuracy = (y_true == y_pred).mean()

# BOM: acurácia por subgrupo
for genero in df['genero'].unique():
    mask = df['genero'] == genero
    acc = (y_true[mask] == y_pred[mask]).mean()
    print(f"Acurácia {genero}: {acc:.2%}")

Confundir correlação com causalidade: se modelo aprova mais homens, não é culpa de estar "homem", é culpa de dataset ter correlações históricas.

Conexão com LGPD e decisão automatizada

Lei Geral de Proteção de Dados (LGPD Art. 9-10) proíbe decisão exclusivamente automatizada que afete direitos. Crédito é um direito. Você tem que:

  1. Informar pessoa que decisão foi automatizada
  2. Oferecer explicação compreensível
  3. Oferecer direito de contestação (humano revê)

Se modelo tem viés flagrante (gap > 20%), é munição pra processo. Melhor prevenir: meça, corrija, documente.

Quando NÃO corrigir agressivamente

Se corrigir viés piora performance do modelo no geral, trade-off não vale. Exemplo: credit scoring com 5% de acurácia só pra ter paridade demográfica perfeita.

Se dados naturalmente têm diferenças (não viés). Exemplo: taxa de devolução de empréstimo genuinamente é maior pra certo grupo por razões econômicas legítimas (não discriminatórias). Não força paridade artificial.

Se impacto do viés é pequeno (gap < 5%) e população é pequena (< 100 exemplos). Ruído estatístico.

Próximos passos

Estude técnicas avançadas com Engenharia de prompt em produção, que inclui viés em respostas de LLMs.

Implemente observabilidade de fairness com Observabilidade em aplicações com LLM, monitorando métricas por grupo em produção.

Use dados para decidir: RAG, fine-tuning ou prompt? Guia de decisão analisa trade-offs similares em IA.

Comece simples: calcule recall e precisão por grupo mensalmente. Se gap > 10%, invira em correção. Dados falam.

Aprenda jogando

Detetive de Viés

Triagem de amostras para manter o dataset balanceado antes do treino.

Jogar Detetive de Viés

Teste seu conhecimento

Teste o que você aprendeu sobre viés em modelos de IA

Pergunta 1 de 5

Seu retriever devolve um trecho que corta no meio de uma frase importante, perdendo contexto. Qual é a causa mais provável?

Continue lendo