🎯 Objetivo del Post: Aprenderás qué es la regularización, por qué es crucial para evitar overfitting, cómo funciona el parámetro C en regresión logística, y cómo encontrar el valor óptimo que maximice el rendimiento en validación.
🎯 El Problema del Overfitting
Imagina que estás estudiando para un examen. Puedes:
Opción A: Memorizar todas las respuestas del libro de práctica
- ✅ Perfecto en práctica (100%)
- ❌ Malo en el examen real (60%)
Opción B: Entender los conceptos fundamentales
- ✅ Bueno en práctica (85%)
- ✅ Bueno en el examen real (82%)
Opción A = Overfitting (memorización)
Opción B = Generalización (comprensión)
Overfitting en ML
# Modelo sobreajustado
Accuracy Train: 99% ✅ ¡Excelente!
Accuracy Val: 70% ❌ ¡Terrible!
# → El modelo memorizó el training set pero no generalizó
# Modelo bien regularizado
Accuracy Train: 85% ✓ Bueno
Accuracy Val: 84% ✓ Bueno
# → El modelo aprendió patrones generales
🔧 ¿Qué es la Regularización?
Regularización es una técnica para penalizar la complejidad del modelo, forzándolo a ser más simple y generalizar mejor.
Analogía: Simplificar para Entender Mejor
Sin Regularización | Con Regularización |
---|---|
"Si llueve Y es lunes Y es marzo Y el viento sopla del norte Y... → lleva paraguas" | "Si llueve → lleva paraguas" |
Regla compleja, específica | Regla simple, general |
Overfitting | Generalización |
Tipos de Regularización
-
L1 (Lasso)
- Penaliza la suma absoluta de coeficientes
- Algunos coeficientes se vuelven exactamente cero
- Selección automática de features
-
L2 (Ridge) ← Usado en Logistic Regression por defecto
- Penaliza la suma de cuadrados de coeficientes
- Coeficientes se vuelven pequeños pero no cero
- Reduce magnitud de todos los coeficientes
-
ElasticNet
- Combina L1 y L2
- Balance entre ambas técnicas
📊 El Parámetro C en Logistic Regression
En scikit-learn, el parámetro C controla la fuerza de la regularización:
Definición
C = 1 / λ
Donde λ (lambda) es el parámetro de regularización tradicional.
Interpretación de C
Valor de C | λ | Regularización | Efecto |
---|---|---|---|
C muy pequeño (0.01) | λ muy grande | Fuerte | Modelo muy simple, puede underfit |
C pequeño (0.1) | λ grande | Fuerte | Modelo simple |
C medio (1.0) | λ medio | Moderada | Balance |
C grande (10) | λ pequeño | Débil | Modelo complejo |
C muy grande (100) | λ muy pequeño | Muy débil | Modelo muy complejo, puede overfit |
Visualización del Efecto de C
Complejidad del Modelo
│
│ ╱ Overfitting
│ ╱
│ ╱
│ ╱
│ ╱
│ ╱ ← Sweet spot
│╱ Underfitting
└────────────────────────────────────────────
0.01 0.1 1.0 10 100 C
←── Más regularización Menos regularización ──→
💻 Implementación: Encontrar el Mejor C
Paso 1: Preparar los Datos
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction import DictVectorizer
from sklearn.metrics import accuracy_score
# Cargar y limpiar datos
url = "https://raw.githubusercontent.com/alexeygrigorev/datasets/master/course_lead_scoring.csv"
df = pd.read_csv(url)
categorical_cols = df.select_dtypes(include=['object']).columns.tolist()
numerical_cols = df.select_dtypes(include=['int64', 'float64']).columns.tolist()
df_clean = df.copy()
for col in categorical_cols:
df_clean[col] = df_clean[col].fillna('NA')
for col in numerical_cols:
if col != 'converted':
df_clean[col] = df_clean[col].fillna(0.0)
# División de datos
df_train_full, df_temp = train_test_split(df_clean, test_size=0.4, random_state=42)
df_val, df_test = train_test_split(df_temp, test_size=0.5, random_state=42)
y_train = df_train_full['converted'].values
y_val = df_val['converted'].values
y_test = df_test['converted'].values
X_train_df = df_train_full.drop('converted', axis=1).reset_index(drop=True)
X_val_df = df_val.drop('converted', axis=1).reset_index(drop=True)
X_test_df = df_test.drop('converted', axis=1).reset_index(drop=True)
# One-hot encoding
dv = DictVectorizer(sparse=False)
X_train = dv.fit_transform(X_train_df.to_dict(orient='records'))
X_val = dv.transform(X_val_df.to_dict(orient='records'))
X_test = dv.transform(X_test_df.to_dict(orient='records'))
print("✅ Datos preparados")
print(f" Train: {X_train.shape}")
print(f" Val: {X_val.shape}")
print(f" Test: {X_test.shape}")
Paso 2: Probar Diferentes Valores de C
print("\nQUESTION 6: BÚSQUEDA DEL MEJOR PARÁMETRO C")
print("=" * 70)
# Valores de C a probar (según el homework)
C_values = [0.01, 0.1, 1, 10, 100]
# Diccionario para guardar resultados
results = {}
print(f"\n{'C':>8} {'Acc Train':>12} {'Acc Val':>12} {'Acc Val (3 dec)':>18} {'Nota'}")
print("-" * 70)
for C in C_values:
# Entrenar modelo con C específico
model = LogisticRegression(
solver='liblinear',
C=C,
max_iter=1000,
random_state=42
)
model.fit(X_train, y_train)
# Evaluar en train y validation
y_pred_train = model.predict(X_train)
y_pred_val = model.predict(X_val)
accuracy_train = accuracy_score(y_train, y_pred_train)
accuracy_val = accuracy_score(y_val, y_pred_val)
# Redondear a 3 decimales (según homework)
accuracy_val_rounded = round(accuracy_val, 3)
# Guardar resultado
results[C] = accuracy_val_rounded
# Determinar nota sobre overfitting
diff = accuracy_train - accuracy_val
if diff < 0.02:
nota = "✓ Bien balanceado"
elif diff < 0.05:
nota = "~ Leve overfit"
else:
nota = "⚠ Overfit"
print(f"{C:>8.2f} {accuracy_train:>12.6f} {accuracy_val:>12.6f} "
f"{accuracy_val_rounded:>18.3f} {nota}")
print("-" * 70)
Salida esperada:
QUESTION 6: BÚSQUEDA DEL MEJOR PARÁMETRO C
======================================================================
C Acc Train Acc Val Acc Val (3 dec) Nota
----------------------------------------------------------------------
0.01 0.847765 0.845890 0.846 ✓ Bien balanceado
0.10 0.849260 0.849315 0.849 ✓ Bien balanceado
1.00 0.849315 0.849315 0.849 ✓ Bien balanceado
10.00 0.849315 0.849315 0.849 ✓ Bien balanceado
100.00 0.849315 0.849315 0.849 ✓ Bien balanceado
----------------------------------------------------------------------
Paso 3: Identificar el Mejor C
print("\nANÁLISIS DE RESULTADOS")
print("=" * 70)
# Encontrar el máximo accuracy
max_accuracy = max(results.values())
# Encontrar todos los C con máximo accuracy
best_Cs = [c for c, acc in results.items() if acc == max_accuracy]
# Si hay empate, seleccionar el menor C (más regularización)
best_C = min(best_Cs)
print(f"\nResultados ordenados por accuracy:")
for c, acc in sorted(results.items(), key=lambda x: (-x[1], x[0])):
marker = " ← MEJOR" if c == best_C else ""
print(f" C = {c:>6.2f} → Accuracy = {acc:.3f}{marker}")
print(f"\n🎯 Mejor C: {best_C}")
print(f" Accuracy: {max_accuracy:.3f}")
if len(best_Cs) > 1:
print(f"\n💡 Nota: Empate entre C = {best_Cs}")
print(f" Seleccionamos el menor C ({best_C}) → Más regularización")
print(f" Razón: Modelo más simple con el mismo rendimiento")
print(f"\n✅ RESPUESTA QUESTION 6: {best_C}")
Paso 4: Visualizar el Efecto de C
# Preparar datos para visualización
Cs_list = sorted(results.keys())
accuracies = [results[c] for c in Cs_list]
# Crear gráfico
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
# Gráfico 1: Accuracy vs C (escala logarítmica)
ax1.plot(Cs_list, accuracies, 'o-', linewidth=2, markersize=8, color='steelblue')
ax1.axhline(y=max_accuracy, color='red', linestyle='--',
label=f'Máximo: {max_accuracy:.3f}', alpha=0.7)
ax1.set_xscale('log')
ax1.set_xlabel('C (escala logarítmica)', fontweight='bold')
ax1.set_ylabel('Accuracy en Validación', fontweight='bold')
ax1.set_title('Accuracy vs Parámetro C', fontsize=14, fontweight='bold')
ax1.grid(alpha=0.3)
ax1.legend()
# Marcar el mejor C
best_idx = Cs_list.index(best_C)
ax1.plot(best_C, accuracies[best_idx], 'r*', markersize=20,
label=f'Mejor C = {best_C}')
# Gráfico 2: Diferencia respecto al mejor
differences = [acc - max_accuracy for acc in accuracies]
colors = ['green' if d == 0 else 'orange' if d > -0.01 else 'red'
for d in differences]
ax2.bar(range(len(Cs_list)), differences, color=colors, alpha=0.7)
ax2.set_xticks(range(len(Cs_list)))
ax2.set_xticklabels([f'{c}' for c in Cs_list])
ax2.set_xlabel('C', fontweight='bold')
ax2.set_ylabel('Diferencia vs Máximo', fontweight='bold')
ax2.set_title('Pérdida de Accuracy vs Mejor Modelo',
fontsize=14, fontweight='bold')
ax2.axhline(y=0, color='black', linestyle='-', linewidth=1)
ax2.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
📊 Análisis Detallado del Efecto de C
Comparar Train vs Validation
print("\nCOMPARACIÓN TRAIN vs VALIDATION POR C")
print("=" * 80)
print(f"\n{'C':>8} {'Train Acc':>12} {'Val Acc':>12} {'Diferencia':>12} {'Interpretación'}")
print("-" * 80)
for C in C_values:
model = LogisticRegression(solver='liblinear', C=C,
max_iter=1000, random_state=42)
model.fit(X_train, y_train)
acc_train = accuracy_score(y_train, model.predict(X_train))
acc_val = accuracy_score(y_val, model.predict(X_val))
diff = acc_train - acc_val
if diff < 0.01:
interp = "Excelente generalización"
elif diff < 0.03:
interp = "Buena generalización"
elif diff < 0.05:
interp = "Leve overfitting"
else:
interp = "Overfitting significativo"
print(f"{C:>8.2f} {acc_train:>12.6f} {acc_val:>12.6f} "
f"{diff:>12.6f} {interp}")
Analizar Coeficientes
print("\nEFECTO DE C EN LOS COEFICIENTES")
print("=" * 70)
# Entrenar modelos con C extremos
model_low_C = LogisticRegression(solver='liblinear', C=0.01,
max_iter=1000, random_state=42)
model_high_C = LogisticRegression(solver='liblinear', C=100,
max_iter=1000, random_state=42)
model_low_C.fit(X_train, y_train)
model_high_C.fit(X_train, y_train)
# Comparar magnitud de coeficientes
coef_low = model_low_C.coef_[0]
coef_high = model_high_C.coef_[0]
print(f"\nEstadísticas de coeficientes:")
print(f"\n{'Métrica':<25} {'C = 0.01':>15} {'C = 100':>15}")
print("-" * 60)
print(f"{'Media absoluta':<25} {np.mean(np.abs(coef_low)):>15.6f} "
f"{np.mean(np.abs(coef_high)):>15.6f}")
print(f"{'Máximo absoluto':<25} {np.max(np.abs(coef_low)):>15.6f} "
f"{np.max(np.abs(coef_high)):>15.6f}")
print(f"{'Std de coeficientes':<25} {np.std(coef_low):>15.6f} "
f"{np.std(coef_high):>15.6f}")
print(f"\n💡 Observación:")
print(f" C pequeño (0.01) → Coeficientes más pequeños (más regularización)")
print(f" C grande (100) → Coeficientes más grandes (menos regularización)")
🎯 Evaluación Final en Test Set
Una vez encontrado el mejor C, evaluamos en el conjunto de test:
print("\nEVALUACIÓN FINAL EN TEST SET")
print("=" * 70)
# Entrenar modelo final con el mejor C
final_model = LogisticRegression(
solver='liblinear',
C=best_C,
max_iter=1000,
random_state=42
)
final_model.fit(X_train, y_train)
# Evaluar en todos los conjuntos
y_pred_train_final = final_model.predict(X_train)
y_pred_val_final = final_model.predict(X_val)
y_pred_test_final = final_model.predict(X_test)
acc_train_final = accuracy_score(y_train, y_pred_train_final)
acc_val_final = accuracy_score(y_val, y_pred_val_final)
acc_test_final = accuracy_score(y_test, y_pred_test_final)
print(f"\nModelo final (C = {best_C}):")
print(f" Accuracy Train: {acc_train_final:.6f} ({acc_train_final*100:.2f}%)")
print(f" Accuracy Val: {acc_val_final:.6f} ({acc_val_final*100:.2f}%)")
print(f" Accuracy Test: {acc_test_final:.6f} ({acc_test_final*100:.2f}%)")
# Verificar consistencia
if abs(acc_val_final - acc_test_final) < 0.02:
print(f"\n✅ Excelente: Val y Test son muy similares")
print(f" El modelo generaliza bien a datos nuevos")
elif abs(acc_val_final - acc_test_final) < 0.05:
print(f"\n✓ Bueno: Val y Test son razonablemente similares")
else:
print(f"\n⚠️ Advertencia: Val y Test difieren significativamente")
print(f" Puede haber sobreajuste en validation")
💡 Mejores Prácticas en Regularización
✅ DO (Hacer)
- Probar múltiples valores de C
C_values = [0.001, 0.01, 0.1, 1, 10, 100, 1000]
- Usar validación cruzada para mayor robustez
from sklearn.model_selection import GridSearchCV
param_grid = {'C': [0.01, 0.1, 1, 10, 100]}
grid_search = GridSearchCV(LogisticRegression(), param_grid, cv=5)
- Evaluar múltiples métricas
# No solo accuracy, también precision, recall, F1
- Verificar en test set al final
# Usar test solo una vez, al final
❌ DON'T (No Hacer)
- No usar validation set
# ❌ MAL: Optimizar en train
# ✅ BIEN: Optimizar en validation
- Optimizar en test set
# ❌ MAL: Elegir C basándose en test
# Esto introduce data leakage
- Ignorar la diferencia train-val
# Si train >> val → overfitting
# Necesitas más regularización (C menor)
- No considerar el contexto
# Balance entre interpretabilidad y rendimiento
📝 Código Completo para Referencia
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction import DictVectorizer
from sklearn.metrics import accuracy_score
# 1. Preparar datos
url = "https://raw.githubusercontent.com/alexeygrigorev/datasets/master/course_lead_scoring.csv"
df = pd.read_csv(url)
categorical_cols = df.select_dtypes(include=['object']).columns.tolist()
numerical_cols = df.select_dtypes(include=['int64', 'float64']).columns.tolist()
df_clean = df.copy()
for col in categorical_cols:
df_clean[col] = df_clean[col].fillna('NA')
for col in numerical_cols:
if col != 'converted':
df_clean[col] = df_clean[col].fillna(0.0)
# 2. División
df_train_full, df_temp = train_test_split(df_clean, test_size=0.4, random_state=42)
df_val, df_test = train_test_split(df_temp, test_size=0.5, random_state=42)
y_train = df_train_full['converted'].values
y_val = df_val['converted'].values
X_train_df = df_train_full.drop('converted', axis=1)
X_val_df = df_val.drop('converted', axis=1)
# 3. One-hot encoding
dv = DictVectorizer(sparse=False)
X_train = dv.fit_transform(X_train_df.to_dict(orient='records'))
X_val = dv.transform(X_val_df.to_dict(orient='records'))
# 4. Probar diferentes C
C_values = [0.01, 0.1, 1, 10, 100]
results = {}
for C in C_values:
model = LogisticRegression(solver='liblinear', C=C, max_iter=1000, random_state=42)
model.fit(X_train, y_train)
y_pred_val = model.predict(X_val)
accuracy_val = accuracy_score(y_val, y_pred_val)
accuracy_val_rounded = round(accuracy_val, 3)
results[C] = accuracy_val_rounded
print(f"C={C}: accuracy = {accuracy_val_rounded}")
# 5. Encontrar mejor C
max_accuracy = max(results.values())
best_C = min([c for c, acc in results.items() if acc == max_accuracy])
print(f"\nMejor C: {best_C} (accuracy: {max_accuracy:.3f})")
🎯 Resumen del Homework Completo
Ahora tenemos todas las respuestas:
print("\n" + "=" * 70)
print("RESUMEN COMPLETO - HOMEWORK 3: CLASIFICACIÓN")
print("=" * 70)
print(f"\nQuestion 1 - Moda de 'industry':")
print(f" Respuesta: retail")
print(f"\nQuestion 2 - Mayor correlación:")
print(f" Respuesta: annual_income y interaction_count")
print(f"\nQuestion 3 - Mayor Mutual Information Score:")
print(f" Respuesta: employment_status")
print(f"\nQuestion 4 - Accuracy de Regresión Logística:")
print(f" Respuesta: 0.85")
print(f"\nQuestion 5 - Feature menos útil:")
print(f" Respuesta: employment_status")
print(f"\nQuestion 6 - Mejor valor de C:")
print(f" Respuesta: {best_C}")
print("\n" + "=" * 70)
print("✅ HOMEWORK COMPLETADO")
print("=" * 70)
🎓 Conclusión
En este post final aprendimos sobre regularización:
- ✅ Qué es la regularización y por qué previene overfitting
- ✅ Cómo funciona el parámetro C en regresión logística
- ✅ Cómo encontrar el mejor C mediante búsqueda sistemática
- ✅ Cómo evaluar el modelo final en test set
Puntos clave:
- 🎯 C controla la fuerza de regularización (C pequeño = más regularización)
- 📊 Siempre optimizar en validation, no en test
- 🔍 Si hay empate, elegir el C más pequeño (modelo más simple)
- ⚖️ Balance entre train y validation accuracy es crucial
🚀 Siguientes Pasos
¡Felicitaciones! Has completado el Homework 3 de Clasificación. Ahora puedes:
- ✅ Experimentar con otros datasets de clasificación
- ✅ Probar otros algoritmos (Random Forest, Gradient Boosting)
- ✅ Explorar técnicas avanzadas (SMOTE para desbalance, ensemble methods)
- ✅ Aplicar lo aprendido a problemas reales de tu trabajo
¿Qué valor de C encontraste óptimo? ¿Notaste diferencias significativas entre valores? ¡Comparte tus resultados!
Top comments (0)