Regularización L1 y L2
Aprende a prevenir overfitting penalizando coeficientes grandes. Ridge, Lasso, y Elastic Net desde su geometría matemática.
01. El Problema del Overfitting
Modelo Sin Regularización
Problema 2: Coeficientes \(\beta_i\) pueden ser extremadamente grandes e inestables.
β = [2.3, -145.7, 892.1, -3210.5, ...]
Pequeño cambio en datos →
β' = [1.8, 523.4, -1072.3, 4891.2, ...]
β_regularizado = [1.2, -3.4, 5.6, -2.1, ...]
Pequeño cambio en datos →
β' = [1.1, -3.2, 5.8, -2.3, ...]
02. Ridge Regression (Regularización L2)
Matemática: Penalización Cuadrática
Geometría: Restricción Esférica
β₂
↑
| ⊙ (Círculo: ||β||₂ ≤ t)
| /|\
| / | \
| / | \
| / ★ \ ← Solución Ridge
|/____│____\___→ β₁
|
Elipses = Contornos de MSE
Ridge busca el punto donde las elipses de MSE tocan el círculo de restricción \(||\boldsymbol{\beta}||_2 \leq t\). Como es un círculo, nunca toca los ejes → coeficientes se encogen pero nunca llegan exactamente a 0.
Código: Ridge en PySpark
from pyspark.ml.regression import LinearRegression
ridge = LinearRegression(
featuresCol="features",
labelCol="label",
regParam=0.1, # λ (lambda)
elasticNetParam=0.0 # 0 = Ridge (L2 puro)
)
model = ridge.fit(train_df)
# Coeficientes "encogidos"
print(f"β Ridge: {model.coefficients}")
# β Ridge: [1.2, -0.8, 2.3, ...]
# (más pequeños que sin regularización)
03. Lasso Regression (Regularización L1)
Matemática: Penalización Absoluta
Geometría: Restricción en Forma de Diamante
β₂
↑
| /\ (Diamante: ||β||₁ ≤ t)
| / \
| / \
| / \
| ★ \ ← Solución Lasso
| / \
|/____________\___→ β₁
Elipses = Contornos de MSE
Lasso busca donde las elipses tocan el diamante. Los vértices del diamante están en los ejes
→ alta probabilidad de que la solución tenga algunos \(\beta_j = 0\) exactamente.
Resultado: Lasso hace selección automática de features.
Código: Lasso en PySpark
from pyspark.ml.regression import LinearRegression
lasso = LinearRegression(
featuresCol="features",
labelCol="label",
regParam=0.1, # λ (lambda)
elasticNetParam=1.0 # 1 = Lasso (L1 puro)
)
model = lasso.fit(train_df)
# Coeficientes con algunos exactamente 0
print(f"β Lasso: {model.coefficients}")
# β Lasso: [0.0, -0.8, 2.3, 0.0, 1.5, 0.0, ...]
# ↑ ↑ ↑
# Features eliminadas
# Contar features no-cero
non_zero = sum(1 for c in model.coefficients if abs(c) > 1e-6)
print(f"Features seleccionadas: {non_zero}/{len(model.coefficients)}")
04. Elastic Net: Combinación L1 + L2
Lo Mejor de Ambos Mundos
• \(\lambda\) (regParam): Fuerza de regularización total
• \(\alpha\) (elasticNetParam): Balance entre L1 y L2
Casos especiales:
• \(\alpha = 0\): Ridge puro (solo L2)
• \(\alpha = 1\): Lasso puro (solo L1)
• \(\alpha = 0.5\): 50% L1 + 50% L2
✗ No elimina features
✗ Si features correlacionadas, distribuye peso entre ellas
✓ Sparse β
✗ Si features muy correlacionadas, elige una al azar
✓ Maneja correlaciones
✓ Más estable que Lasso
Cuándo Usar Cada Regularización
| Escenario | Recomendación | Por qué |
|---|---|---|
| Muchas features, pocas relevantes | Lasso (α=1) | Elimina automáticamente las irrelevantes |
| Todas las features son importantes | Ridge (α=0) | Mantiene todas, solo encoge |
| Features muy correlacionadas | Elastic Net (α=0.5) | Lasso es inestable, EN balancea |
| p >> n (más features que datos) | Elastic Net (α=0.7) | Lasso puede seleccionar máx n features |
| No sabes qué usar | Cross-Validation | Prueba α ∈ {0, 0.25, 0.5, 0.75, 1} |
05. Selección de λ con Cross-Validation
Grid Search + k-Fold CV
from pyspark.ml.tuning import ParamGridBuilder, CrossValidator
from pyspark.ml.evaluation import RegressionEvaluator
from pyspark.ml.regression import LinearRegression
# Modelo base
lr = LinearRegression(
featuresCol="features",
labelCol="label"
)
# Grid de hiperparámetros
paramGrid = ParamGridBuilder() \
.addGrid(lr.regParam, [0.001, 0.01, 0.1, 1.0, 10.0]) \
.addGrid(lr.elasticNetParam, [0.0, 0.25, 0.5, 0.75, 1.0]) \
.build()
# Total: 5 × 5 = 25 combinaciones
# Evaluador (RMSE)
evaluator = RegressionEvaluator(
labelCol="label",
predictionCol="prediction",
metricName="rmse"
)
# Cross-Validator (5-fold)
cv = CrossValidator(
estimator=lr,
estimatorParamMaps=paramGrid,
evaluator=evaluator,
numFolds=5, # k-fold
parallelism=4 # Entrenar 4 modelos en paralelo
)
# Entrenar (prueba todas las combinaciones)
cv_model = cv.fit(train_df)
# Mejor modelo
best_model = cv_model.bestModel
print(f"Mejor λ (regParam): {best_model.getRegParam()}")
print(f"Mejor α (elasticNetParam): {best_model.getElasticNetParam()}")
print(f"RMSE validación: {cv_model.avgMetrics[0]:.4f}")
# Predecir en test set
test_predictions = best_model.transform(test_df)
test_rmse = evaluator.evaluate(test_predictions)
print(f"RMSE test: {test_rmse:.4f}")
Mejor λ: 0.1
Mejor α: 0.75 (75% Lasso + 25% Ridge)
RMSE validación: 2345.12
RMSE test: 2387.45
Visualización: Curva de Regularización
Error
↑
|
10k │ Underfitting
│ ╱
│ ╱
│ ╱
5k │ ╱ Óptimo (λ=0.1)
│ ╱ ★
│ ╱ ╲ Overfitting
│ ╱ ╲
2k │__╱_______________╲___________
| ╲
└──────────────────────────────→ λ
0 0.001 0.01 0.1 1.0 10.0
— Training Error
··· Validation Error
λ pequeño: Poca regularización → overfitting (memoriza ruido)
λ grande: Mucha regularización → underfitting (modelo muy simple)
λ óptimo: Balance (minimiza validation error)
06. Comparación Visual: Sin Reg vs Ridge vs Lasso
Path de Coeficientes vs λ
|β_j|
↑
100 │β₁ ────────╲
│β₂ ──────╲ ╲╲
│β₃ ────╲ ╲╲ ╲╲╲
50 │β₄ ──╲ ╲╲ ╲╲ ╲╲╲ RIDGE
│ ╲╲ ╲╲ ╲╲ ╲╲╲╲ (todos → 0, pero nunca exactamente 0)
│ ╲╲ ╲╲ ╲╲ ╲╲╲╲╲
0 └───────────────────────────→ λ
0.01 0.1 1.0 10.0
|β_j|
↑
100 │β₁ ────────╲
│β₂ ────────╲╲___0
│β₃ ──────╲___0 LASSO
50 │β₄ ────╲___0 (algunos → 0 exactamente)
│β₅ __╲___0
│β₆ ___0
0 └───────────────────────────→ λ
0.01 0.1 1.0 10.0
• Nunca llegan exactamente a 0
• Path continuo y suave
• No hace selección de features
• Features menos importantes → 0 primero
• Path con "quiebres" (no diferenciable en 0)
• Hace selección automática
07. Regularización en Regresión Logística
Mismo Principio, Diferente Función de Costo
from pyspark.ml.classification import LogisticRegression
# Logística con Elastic Net
lr_log = LogisticRegression(
featuresCol="features",
labelCol="label",
regParam=0.01, # λ
elasticNetParam=0.5 # α = 0.5 (50% L1 + 50% L2)
)
model = lr_log.fit(train_df)
# Efecto: Previene overfitting en clasificación
# Especialmente útil con clases desbalanceadas