Módulo 2: Bias and Fairness — Detection, Measurement, Mitigation
6. Mitigation: Pre-processing Techniques
Descripción de la cápsula
Detectaste bias (cápsulas 02-04). Entendiste los trade-offs (cápsula 05). Ahora la pregunta accionable: ¿qué hacés para reducirlo?
Las técnicas de mitigation se organizan en tres categorías según dónde intervienen:
- Pre-processing: modificar training data antes de entrenar.
- In-processing: modificar el algoritmo de training (cápsula 07).
- Post-processing: modificar outputs del modelo entrenado (cápsula 07).
Esta cápsula cubre pre-processing — la categoría más simple, más interpretable, y a menudo la más efectiva. Si tu data está sesgada, el primer fix es la data.
Vas a aprender 4 técnicas concretas:
- Re-balancing: igualar tamaño de subgrupos en training set.
- Re-sampling: oversampling de subgrupos minoritarios.
- Re-weighting: dar más peso a samples sub-representadas.
- Data augmentation: generar samples sintéticas para grupos under-represented.
Todas implementadas desde cero, con código Python ejecutable.
Por qué empezar por pre-processing
Razones técnicas
- Más simple: cambios en data, no en modelo.
- Compatible con cualquier modelo: aplica antes de elegir architecture.
- Interpretable: podés inspeccionar el data resultante. "Antes 80/20, ahora 50/50".
- Reusable: data balanceada se reusa entre experiments.
Razones éticas
- Trata el problema en su raíz: bias entra por data, fix en data.
- Transparente: stakeholders entienden "balanceamos el dataset".
- No requiere claims sobre el modelo: el modelo es lo mismo, el data cambió.
Limitations
- Funciona solo si tenés acceso al training data: si usás API externa (OpenAI), no podés modificar su training data.
- No siempre suficiente: bias estructural puede requerir in-processing/post-processing también.
- Puede reducir signal predictive: si oversampleás minorías que tienen patrones distintos, modelo aprende patrones mezclados.
Técnica 1: Re-balancing
Concepto
Si tu dataset tiene 10,000 samples con 90% group A y 10% group B, subsampleá group A hasta tener 50/50.
Antes:
Group A: 9,000 samples
Group B: 1,000 samples
Total: 10,000
Después:
Group A: 1,000 samples (random subsample)
Group B: 1,000 samples
Total: 2,000
Implementación
import pandas as pd
import numpy as np
def rebalance_dataset(df, group_column, target_size_per_group=None,
random_state=42):
"""
Re-balance dataset por subsampling.
Args:
df: DataFrame con features + group column.
group_column: nombre de la columna del atributo protegido.
target_size_per_group: tamaño desired per group. Si None, usa min(group_sizes).
Returns:
DataFrame balanceado.
"""
group_sizes = df.groupby(group_column).size()
if target_size_per_group is None:
target_size_per_group = group_sizes.min()
balanced_dfs = []
for group_name, group_df in df.groupby(group_column):
if len(group_df) > target_size_per_group:
# Subsample
sampled = group_df.sample(target_size_per_group, random_state=random_state)
else:
# Use all
sampled = group_df
balanced_dfs.append(sampled)
balanced = pd.concat(balanced_dfs, ignore_index=True)
return balanced.sample(frac=1, random_state=random_state).reset_index(drop=True)
Uso
# Original dataset
print(df['gender'].value_counts())
# M 9000
# F 1000
balanced = rebalance_dataset(df, group_column='gender')
print(balanced['gender'].value_counts())
# M 1000
# F 1000
Trade-offs
Pros:
- Simple.
- Garantiza equal representation.
Cons:
- Pierde data: 8,000 samples descartadas. Pierdes signal.
- No funciona si grupo minoritario tiene < N samples para training significativo.
Cuándo usar: cuando tenés mucho data y la diferencia entre grupos es muy grande. Si tenés 1M samples con 80/20 split, podés balancear a 50/50 sin perder demasiado total.
Técnica 2: Re-sampling (Oversampling)
Concepto
En lugar de descartar majority, replicar minority. Si group B tiene 1,000 samples, multiplicalas hasta tener 9,000 (igualar a A).
Antes:
Group A: 9,000
Group B: 1,000
Después (oversampling):
Group A: 9,000 (sin cambios)
Group B: 9,000 (cada uno duplicado 9x, o sample con replacement)
Total: 18,000
Implementación: simple oversampling con replacement
def oversample_minority(df, group_column, random_state=42):
"""
Oversample minorities a tamaño de majority.
"""
group_sizes = df.groupby(group_column).size()
target_size = group_sizes.max()
sampled_dfs = []
for group_name, group_df in df.groupby(group_column):
if len(group_df) < target_size:
# Oversample con replacement
extra_needed = target_size - len(group_df)
extra_samples = group_df.sample(extra_needed, replace=True,
random_state=random_state)
sampled = pd.concat([group_df, extra_samples])
else:
sampled = group_df
sampled_dfs.append(sampled)
result = pd.concat(sampled_dfs, ignore_index=True)
return result.sample(frac=1, random_state=random_state).reset_index(drop=True)
Trade-offs
Pros:
- No pierde data (mantiene majority).
- Simple.
Cons:
- Repetición exacta de samples puede causar overfitting al patrón del grupo minoritario.
- Si grupo minoritario es muy chico (< 50), repetición tiene poco diversity.
SMOTE: Synthetic Minority Over-sampling Technique
Versión más sophisticated. En lugar de repetir, interpolar entre samples cercanos para generar synthetic samples:
from imblearn.over_sampling import SMOTE
def smote_oversample(X, y, group_labels, random_state=42):
"""
SMOTE oversampling.
Genera samples sintéticas en lugar de repetir.
Note: SMOTE original es para class imbalance.
Para bias, podés usar sobre group_labels o combinar.
"""
smote = SMOTE(random_state=random_state)
X_resampled, y_resampled = smote.fit_resample(X, group_labels)
return X_resampled, y_resampled
SMOTE genera samples en el "espacio" entre samples reales del grupo minoritario. Más diverso que pure repetición.
Limitations de SMOTE:
- Solo funciona para features numéricas (no text directly).
- Synthetic samples pueden no ser realistic.
- Para bias correction (no class imbalance), aplicación es indirecta.
Técnica 3: Re-weighting
Concepto
En lugar de duplicar samples, dar más peso a samples sub-representadas durante training.
Si group A es 90% y group B es 10%, weight de samples de B = 9x weight de A.
Modelos como sklearn aceptan sample_weight parameter. Loss se calcula ponderado, así que samples con peso 9 cuentan 9 veces más en gradiente.
Implementación
def compute_sample_weights(df, group_column):
"""
Compute weights inversamente proporcional a frecuencia del grupo.
"""
group_counts = df[group_column].value_counts()
total = len(df)
n_groups = len(group_counts)
# Weight = total / (n_groups * count_group)
# Esto da weight ~1 si grupos balanceados, > 1 si under-represented
weights = df[group_column].apply(
lambda g: total / (n_groups * group_counts[g])
)
return weights.values
# Uso con sklearn
from sklearn.linear_model import LogisticRegression
weights = compute_sample_weights(df, 'gender')
model = LogisticRegression()
model.fit(X_train, y_train, sample_weight=weights)
Trade-offs
Pros:
- No modifica data físicamente.
- Más eficiente que duplicar.
- Compatible con most ML libraries (sklearn, xgboost, lightgbm).
Cons:
- No todos los modelos aceptan sample_weights (algunos NN frameworks requieren custom code).
- Outlier samples con peso alto pueden dominar training.
Cuándo elegir re-weighting sobre oversampling
- Data muy grande: re-weighting más memory-efficient.
- Modelos que aceptan weights: sklearn, xgboost, lightgbm.
- Querés mantener diversidad real sin synthetic samples.
Técnica 4: Data Augmentation
Concepto
Para grupos sub-representados, generar samples sintéticas más realistas que SMOTE simple.
Aplicación depende del data type:
Para tabular data
Generar variaciones de samples reales modificando features no-protected ligeramente:
def augment_tabular(df, group_column, minority_label, n_augment=1000,
noise_scale=0.05):
"""
Augment data tabular agregando noise gaussiano a features numéricas.
"""
minority = df[df[group_column] == minority_label]
numeric_cols = minority.select_dtypes(include=[np.number]).columns
augmented_samples = []
for _ in range(n_augment):
sample = minority.sample(1).copy()
# Add gaussian noise to numeric features
for col in numeric_cols:
std = minority[col].std()
noise = np.random.normal(0, std * noise_scale)
sample[col] += noise
augmented_samples.append(sample)
augmented_df = pd.concat(augmented_samples + [df], ignore_index=True)
return augmented_df
Para text data
Augmentation paraphrasing:
- Synonym replacement.
- Back-translation (English → French → English).
- Style transfer.
# Pseudo-code (requires NLP library)
def augment_text(text):
"""Augment text mediante back-translation."""
intermediate = translate(text, source='en', target='fr')
augmented = translate(intermediate, source='fr', target='en')
return augmented
Con LLMs:
# Generar paraphrases con GPT
def llm_paraphrase(text, n=5):
prompt = f"""Generate {n} paraphrases of this text, preserving meaning:
{text}
Return as JSON array."""
response = llm.complete(prompt)
return json.loads(response)
Para image data
Standard augmentations (rotation, flip, brightness) + más sophisticated:
- Generative augmentation con StyleGAN o similar.
- For demographic balance: generar samples de under-represented demographics.
Caveat ético: si generás caras sintéticas de minorías para training, asegurate de que el generador NO heredó bias del dataset original.
Trade-offs
Pros:
- Más diversity que pure repetition.
- Puede expandir distribution covered.
Cons:
- Quality depende del augmentation method.
- Synthetic data puede introducir nuevos artifacts.
- Computacionalmente caro.
Combinando técnicas
En práctica, raramente usás solo una. Combinaciones típicas:
Combo 1: Re-weighting + Augmentation
- Augmentar grupo minoritario con paraphrasing/SMOTE.
- Aplicar re-weighting sobre el dataset augmentado.
Resultado: más diversity + balance.
Combo 2: Stratified rebalancing por intersectional groups
def rebalance_intersectional(df, attributes, target_per_combo=None):
"""
Re-balance por combinación de attributes (intersectional).
"""
combo_sizes = df.groupby(attributes).size()
if target_per_combo is None:
target_per_combo = combo_sizes.min()
balanced = df.groupby(attributes).apply(
lambda x: x.sample(min(len(x), target_per_combo))
).reset_index(drop=True)
return balanced
Útil para resolver bias intersectional (mujeres negras under-represented), no solo single-attribute.
Validar la mitigation
Después de aplicar pre-processing, medí de nuevo las métricas:
def validate_mitigation(df_original, df_mitigated, model, test_set,
group_column):
"""
Re-train modelo en data mitigated y compará métricas.
"""
# Train en data original
model_original = clone(model)
model_original.fit(df_original.drop('label', axis=1), df_original['label'])
pred_original = model_original.predict(test_set.drop('label', axis=1))
# Train en data mitigated
model_mitigated = clone(model)
model_mitigated.fit(df_mitigated.drop('label', axis=1), df_mitigated['label'])
pred_mitigated = model_mitigated.predict(test_set.drop('label', axis=1))
# Compare
dp_original = demographic_parity(pred_original, test_set[group_column])
dp_mitigated = demographic_parity(pred_mitigated, test_set[group_column])
acc_original = accuracy_score(test_set['label'], pred_original)
acc_mitigated = accuracy_score(test_set['label'], pred_mitigated)
return {
'parity_improvement': dp_mitigated['parity_ratio'] - dp_original['parity_ratio'],
'accuracy_change': acc_mitigated - acc_original,
'recommend_deploy': (
dp_mitigated['parity_ratio'] >= 0.80 and
acc_mitigated >= acc_original - 0.05
),
}
Si fairness mejora pero accuracy cae > 5%: investigar otras técnicas. Pre-processing puede no ser suficiente; ir a in-processing (cápsula 07).
Si fairness mejora y accuracy similar: deploy mitigated version.
Si fairness no mejora: la técnica eligida no es apropiada. Try otra.
Trampas y errores comunes
1. Aplicar mitigation sin medir post-hoc
Aplicaste re-balancing, "ahora es fair". ¿Verificaste? Sin re-medir, no sabés.
2. Re-balancing pierde demasiado data
Si grupo minoritario es 1% (rare), re-balancing perfecto descarta 99% del data. Inutil.
Solution: usar oversampling o re-weighting cuando minority es muy pequeña.
3. Synthetic samples no realistic
SMOTE en data tabular puede generar samples imposibles (ej., persona con income negativo). Validar synthetic samples antes de training.
4. Augmentation que cambia el label
Si paraphrasing de text cambia significado, label se invalida. Validar.
5. No considerar el cost downstream
Re-balancing puede mejorar fairness en train pero degradar accuracy en production deployment con distribution diferente. Validar en test set realístico.
Auto-verificación
1. ¿Cuándo elegirías oversampling sobre rebalancing?
Cuando tu grupo minoritario es muy chico absolutamente. Re-balancing por subsampling de majority te dejaría con un dataset muy chico para training significativo.
Ejemplo:
- Group A: 100,000 samples
- Group B: 500 samples
Re-balancing → 500 + 500 = 1,000 samples total. Probablemente insuficiente para train un modelo decente.
Oversampling → 100,000 + 100,000 (B replicated 200x) = 200,000 samples. Modelo entrena con full majority + B oversampled. Risk: overfitting en patterns de B porque cada sample aparece muchas veces. Mitigated con SMOTE (synthetic) en lugar de duplicación.
Alternativamente, re-weighting es viable: sin modificar data, dar peso 200x a samples de B durante training.
Decision matrix:
| Minority size | Recommended |
|---|---|
| Very small (<100) | Oversampling con SMOTE/augmentation |
| Small (100-1000) | Oversampling o re-weighting |
| Medium (1000-10000) | Re-weighting o re-balancing |
| Large but skewed | Re-balancing o re-weighting |
2. ¿Por qué re-weighting es a menudo preferible a duplicación?
Tres razones:
-
Memory efficiency: no duplicar samples = menos RAM/storage.
-
Mantiene diversidad real: con duplication, modelo ve los mismos exact samples múltiples veces, introduciendo overfitting risk. Con re-weighting, modelo ve cada sample una vez con weight ajustado.
-
Training más rápido: menos samples = menos gradient computations.
Caveat: requiere que el modelo soporte sample_weights. Sklearn, XGBoost, LightGBM sí. Algunos custom NN frameworks pueden no soportar nativamente — requeriría custom loss function.
En modern stack, re-weighting es la opción default cuando model lo soporta.
3. ¿Cuándo data augmentation NO es viable?
Casos:
-
Domain extremely sensitive: medical data, legal records — synthetic samples pueden ser legalmente problemáticos o factualmente engañosos.
-
Augmentation method introduces bias: si tu augmentation tool (LLM, GAN) tiene su propio bias, lo propagás al dataset.
-
Quality verification difícil: para text augmentation, verificar que synthetic samples preservan meaning requiere humano review (no escala).
-
Regulatory restrictions: algunas jurisdicciones requieren que training data sea "real" — synthetic samples no qualifies.
-
Domain-specific complexity: augmenting medical images requires expert validation que synthetic doesn't introduce false features.
Cuando augmentation no es viable, alternativas: re-weighting, in-processing techniques (cápsula 07), o data collection más balanceada upstream.
4. ¿Cómo decidir si pre-processing es suficiente o necesitás también in/post-processing?
Procedimiento:
-
Aplicar pre-processing (re-balance/oversample/re-weight).
-
Re-train modelo con data mitigated.
-
Medir fairness metrics en test set.
-
Decidir:
- Si fairness pasa thresholds y accuracy ~ original: pre-processing sufficient. Deploy.
- Si fairness pasa pero accuracy cae > 5%: trade-off explicito. Considerar otras técnicas (in/post-processing pueden tener menor accuracy cost).
- Si fairness no pasa thresholds: pre-processing insuficiente. Combinar con in/post-processing.
General rule: empezá por pre-processing porque es más simple. Si insuficiente, escalá complexity con in/post-processing. Combinaciones (pre + post) son comunes en sistemas production.
Resumen y siguiente paso
- Pre-processing modifica training data. Más simple, interpretable, model-agnostic.
- Técnicas:
- Re-balancing: subsample majority (pierde data).
- Oversampling: replicate o SMOTE minority.
- Re-weighting: weights al training (most efficient si model soporta).
- Augmentation: synthetic samples (depends on data type).
- Combiná técnicas para mejor balance + diversity.
- Validá post-mitigation: medí métricas de nuevo, NO asumas que aplicar mitigation = fair.
- Pre-processing puede no ser suficiente: si insuficiente, combinar con in/post-processing.
Checkpoint: deberías poder elegir y aplicar la técnica de pre-processing apropiada según tu data characteristics.
Puente a la siguiente cápsula: la cápsula 07 cubre in-processing (modificar el algoritmo de training: fairness constraints en loss, adversarial debiasing) y post-processing (modificar predictions: threshold tuning per group, calibration adjustment, reject option). Son técnicas más sophisticated que aplicas cuando pre-processing solo no es suficiente, o cuando no podés modificar el training data.
Recursos
- imbalanced-learn library — SMOTE y otras técnicas.
- Fairlearn — Reweighting preprocessor — implementaciones.
- AIF360 — Reweighing — reference implementation.
- Synthetic Data Vault — augmentation tabular.
Siguiente: 07-mitigacion-in-post-processing.md — In-processing y post-processing mitigations.
Cápsula 06 de 08 — Módulo 2 — AI Ethics & Compliance Guide