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:

  1. Pre-processing: modificar training data antes de entrenar.
  2. In-processing: modificar el algoritmo de training (cápsula 07).
  3. 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:

  1. Re-balancing: igualar tamaño de subgrupos en training set.
  2. Re-sampling: oversampling de subgrupos minoritarios.
  3. Re-weighting: dar más peso a samples sub-representadas.
  4. 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

  1. Más simple: cambios en data, no en modelo.
  2. Compatible con cualquier modelo: aplica antes de elegir architecture.
  3. Interpretable: podés inspeccionar el data resultante. "Antes 80/20, ahora 50/50".
  4. Reusable: data balanceada se reusa entre experiments.

Razones éticas

  1. Trata el problema en su raíz: bias entra por data, fix en data.
  2. Transparente: stakeholders entienden "balanceamos el dataset".
  3. No requiere claims sobre el modelo: el modelo es lo mismo, el data cambió.

Limitations

  1. Funciona solo si tenés acceso al training data: si usás API externa (OpenAI), no podés modificar su training data.
  2. No siempre suficiente: bias estructural puede requerir in-processing/post-processing también.
  3. 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

  1. Augmentar grupo minoritario con paraphrasing/SMOTE.
  2. 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 sizeRecommended
Very small (<100)Oversampling con SMOTE/augmentation
Small (100-1000)Oversampling o re-weighting
Medium (1000-10000)Re-weighting o re-balancing
Large but skewedRe-balancing o re-weighting
2. ¿Por qué re-weighting es a menudo preferible a duplicación?

Tres razones:

  1. Memory efficiency: no duplicar samples = menos RAM/storage.

  2. 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.

  3. 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:

  1. Domain extremely sensitive: medical data, legal records — synthetic samples pueden ser legalmente problemáticos o factualmente engañosos.

  2. Augmentation method introduces bias: si tu augmentation tool (LLM, GAN) tiene su propio bias, lo propagás al dataset.

  3. Quality verification difícil: para text augmentation, verificar que synthetic samples preservan meaning requiere humano review (no escala).

  4. Regulatory restrictions: algunas jurisdicciones requieren que training data sea "real" — synthetic samples no qualifies.

  5. 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:

  1. Aplicar pre-processing (re-balance/oversample/re-weight).

  2. Re-train modelo con data mitigated.

  3. Medir fairness metrics en test set.

  4. 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

  1. imbalanced-learn library — SMOTE y otras técnicas.
  2. Fairlearn — Reweighting preprocessor — implementaciones.
  3. AIF360 — Reweighing — reference implementation.
  4. 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