Introducción
Este módulo es la segunda parte de un caso de uso completo que abarca 4 sesiones de 90 minutos (módulos 5 y 6 conjuntamente). Se recomienda trabajar con ambos módulos de forma coordinada.
La Inteligencia Artificial es un campo de la informática que busca desarrollar sistemas capaces de realizar tareas que normalmente requieren inteligencia humana. Uno de los enfoques más destacados es el aprendizaje automático (ML), donde los sistemas mejoran su rendimiento a través de la experiencia y la retroalimentación.
Tema 1: Introducción al Balanceo de Datos
El problema del desbalanceo
En la mayoría de los casos de uso reales, los conjuntos de datos con los que trabajamos los científicos de datos no están bien balanceados. Normalmente en el mundo real la mayoría de los eventos siempre tienen el mismo valor, y solo un pequeño porcentaje son los eventos que queremos detectar.
Ejemplos de tasas de desbalanceo en sectores reales:
- Ciberseguridad: Tasas de ataque de 1 a 10.000 eventos normales
- Detección de fraude: Tasas de 1 a 10 o 1 a 100
- Medicina: Tasas similares al fraude para enfermedades raras
Si los datos no se balancean correctamente, los modelos algorítmicos se convierten en vagos y siempre intentarán predecir que no se ha detectado el tipo de anomalía. El reto consiste en balancear correctamente las muestras para forzar a los modelos a detectar las anomalías dentro del conjunto elevado de datos.
Un porcentaje menor al 4-5% de la clase minoritaria puede servir para detectar este tipo de patrones, pero por debajo de ese umbral el desbalanceo se vuelve crítico.
Tema 2: Submuestreo de Datos (Undersampling)
El submuestreo reduce el número de muestras de la clase mayoritaria para equilibrar el dataset. La idea es maximizar la información de las muestras de la clase minoritaria.
Técnica básica de submuestreo
La forma más sencilla consiste en dividir la muestra en dos grupos según la clase y crear un dataset donde el número de filas de cada grupo sea exactamente igual. Por ejemplo, si tenemos 400 muestras con clase 0 y 10.000 con clase 1, pasamos a 400 muestras de clase 0 y 400 de clase 1.
Ventaja: Aumenta la información que tiene el modelo sobre las variables a detectar.
Desventaja: Perdemos información al reducir el número de muestras de entrenamiento. La selección aleatoria no siempre extrae las muestras más adecuadas.
Tema 3: NearMiss
NearMiss es una familia de métodos de submuestreo que seleccionan ejemplos de la clase mayoritaria en función de su distancia euclidiana con respecto a los ejemplos de la clase minoritaria. La idea básica es conservar los ejemplos que están cerca de la clase minoritaria y descartar los que están lejos.
Variantes de NearMiss
- NearMiss-1: Selecciona ejemplos de la clase mayoritaria cuya distancia media a los k vecinos más cercanos de la clase minoritaria es mínima.
- NearMiss-2: Selecciona ejemplos de la clase mayoritaria cuya distancia media a los k vecinos más lejanos de la clase minoritaria es mínima.
- NearMiss-3: Para cada muestra minoritaria, selecciona un número dado de los vecinos más cercanos de la clase mayoritaria.
from imblearn.under_sampling import NearMiss
# Aplicar NearMiss
nm = NearMiss(version=1, n_neighbors=3)
X_resampled, y_resampled = nm.fit_resample(X_train, y_train)
print(f"Distribución después de NearMiss: {dict(zip(*np.unique(y_resampled, return_counts=True)))}")Tema 4: Sobremuestreo de Datos (Oversampling)
El sobremuestreo genera más datos sintéticos de la clase minoritaria para equilibrar el dataset. A diferencia del submuestreo, no perdemos información; en su lugar, creamos nuevas muestras.
Esta técnica es más peligrosa que el submuestreo, ya que generamos datos que no son reales y, por tanto, probablemente sean del valor de fraude pero no asegurado desde el punto de vista real.
SMOTE (Synthetic Minority Over-sampling Technique)
SMOTE crea ejemplos sintéticos de la clase minoritaria interpolando entre instancias existentes y sus vecinos más cercanos. A diferencia del sobremuestreo aleatorio (que simplemente duplica muestras), SMOTE crea ejemplos nuevos y variados.
from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)
print(f"Distribución después de SMOTE: {dict(zip(*np.unique(y_resampled, return_counts=True)))}")Caso Práctico: Credit Card Fraud Detection
El Dataset
Dataset de Kaggle: Credit Card Fraud Detection (Machine Learning Group - ULB). Contiene transacciones realizadas con tarjetas de crédito europeas en septiembre de 2013.
- Total: 284.807 transacciones
- Fraudes: 492 (0,172% del total)
- Variables: 28 componentes PCA anónimas (V1-V28), Time, Amount, Class
- Variable objetivo:
Class(0 = legítima, 1 = fraude)
Carga del Dataset
import pandas as pd
from sklearn.model_selection import train_test_split
data = pd.read_csv('creditcard.csv')
print(data.Class.value_counts())
# 0 284315
# 1 492
X = data.drop('Class', axis=1)
y = data['Class']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)Modelo Básico (Sin Balanceo)
Primero entrenamos un Random Forest básico sin ninguna técnica de balanceo para establecer la línea base:
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score, confusion_matrix
clf_base = RandomForestClassifier(n_estimators=100, random_state=42)
clf_base.fit(X_train, y_train)
y_pred_base = clf_base.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred_base):.6f}") # 0.9995
print(f"F1-Score: {f1_score(y_test, y_pred_base):.4f}") # ~0.84
print(confusion_matrix(y_test, y_pred_base))
# [[85256 44]
# [ 55 88]]Resultados del modelo básico:
- Accuracy: 0.9995 — Alta pero engañosa
- F1-Score: ~0.84 — Razonable pero mejorable
- La matriz de confusión revela: 55 fraudes no detectados (FN) y 44 falsas alarmas (FP)
Modelo con NearMiss (Submuestreo)
from imblearn.under_sampling import NearMiss
from sklearn.metrics import classification_report
nm = NearMiss(version=1)
X_nm, y_nm = nm.fit_resample(X_train, y_train)
print(f"Distribución NearMiss: {dict(zip(*np.unique(y_nm, return_counts=True)))}")
clf_nm = RandomForestClassifier(n_estimators=100, random_state=42)
clf_nm.fit(X_nm, y_nm)
y_pred_nm = clf_nm.predict(X_test)
print(classification_report(y_test, y_pred_nm))Modelo con SMOTE (Sobremuestreo)
from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=42)
X_smote, y_smote = smote.fit_resample(X_train, y_train)
print(f"Distribución SMOTE: {dict(zip(*np.unique(y_smote, return_counts=True)))}")
# Ambas clases con ~199.000 muestras
clf_smote = RandomForestClassifier(n_estimators=100, random_state=42)
clf_smote.fit(X_smote, y_smote)
y_pred_smote = clf_smote.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred_smote):.4f}")
print(f"F1-Score: {f1_score(y_test, y_pred_smote):.4f}")
print(f"Recall: {recall_score(y_test, y_pred_smote):.4f}")Comparación de Técnicas
| Técnica | Accuracy | F1-Score | Recall | Observación |
|---|---|---|---|---|
| Modelo Base | 0.9995 | ~0.84 | ~0.62 | Alta accuracy, pero pierde fraudes |
| NearMiss | Variable | Variable | Alto | Mejor detección, más FP posibles |
| SMOTE | Alto | Mayor | Mayor | Mejor equilibrio general |
Conclusión
La accuracy es una medida muy peligrosa para los modelos muy desbalanceados. Un modelo que siempre devuelve 0 (no fraude) tiene accuracy del 99.83% pero F1-Score de 0. El objetivo no es maximizar la accuracy, sino maximizar la detección de fraudes (recall) sin generar demasiadas falsas alarmas (precisión). El equilibrio óptimo entre ambas depende del contexto de negocio y los costes asociados a cada tipo de error.