SECCIÓN 14

MÓDULO 2.Regularization()

CONTROLANDO LA COMPLEJIDAD DEL MODELO

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

$$ J(\boldsymbol{\beta}) = \frac{1}{2n} \sum_{i=1}^{n} (y_i - \boldsymbol{\beta}^T \mathbf{x}_i)^2 $$
Problema 1: Si \(p > n\) (más features que datos), existen infinitas soluciones.
Problema 2: Coeficientes \(\beta_i\) pueden ser extremadamente grandes e inestables.
Síntoma: Coeficientes Inestables
β = [2.3, -145.7, 892.1, -3210.5, ...]

Pequeño cambio en datos →
β' = [1.8, 523.4, -1072.3, 4891.2, ...]
                        
Los coeficientes varían salvajemente. El modelo "memoriza" ruido en vez de aprender patrones generalizables.
Solución: Regularización
β_regularizado = [1.2, -3.4, 5.6, -2.1, ...]

Pequeño cambio en datos →
β' = [1.1, -3.2, 5.8, -2.3, ...]
                        
Coeficientes más pequeños y estables. Penalizar ||β|| controla complejidad.

02. Ridge Regression (Regularización L2)

Matemática: Penalización Cuadrática

$$ J_{\text{Ridge}}(\boldsymbol{\beta}) = \frac{1}{2n} \sum_{i=1}^{n} (y_i - \boldsymbol{\beta}^T \mathbf{x}_i)^2 + \lambda \sum_{j=1}^{p} \beta_j^2 $$ $$ = \underbrace{\frac{1}{2n} ||\mathbf{y} - X\boldsymbol{\beta}||^2}_{\text{Ajuste a datos (MSE)}} + \underbrace{\lambda ||\boldsymbol{\beta}||_2^2}_{\text{Penalización L2}} $$
Donde: \(||\boldsymbol{\beta}||_2^2 = \sum_{j=1}^{p} \beta_j^2\) (norma L2 al cuadrado)
Gradiente:
$$ \nabla J_{\text{Ridge}} = \frac{1}{n} X^T (X\boldsymbol{\beta} - \mathbf{y}) + 2\lambda \boldsymbol{\beta} $$
El término \(2\lambda \boldsymbol{\beta}\) "empuja" los coeficientes hacia 0.
Solución Analítica:
$$ \hat{\boldsymbol{\beta}}_{\text{Ridge}} = (X^T X + \lambda I)^{-1} X^T \mathbf{y} $$
Ventaja: \(X^T X + \lambda I\) siempre es invertible (incluso si \(p > n\)). El término \(\lambda I\) garantiza que la matriz sea positiva definida.

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)
💡 Efecto: Todos los β disminuyen, pero ninguno es exactamente 0. Ridge no hace selección de features.

03. Lasso Regression (Regularización L1)

Matemática: Penalización Absoluta

$$ J_{\text{Lasso}}(\boldsymbol{\beta}) = \frac{1}{2n} \sum_{i=1}^{n} (y_i - \boldsymbol{\beta}^T \mathbf{x}_i)^2 + \lambda \sum_{j=1}^{p} |\beta_j| $$ $$ = \underbrace{\frac{1}{2n} ||\mathbf{y} - X\boldsymbol{\beta}||^2}_{\text{MSE}} + \underbrace{\lambda ||\boldsymbol{\beta}||_1}_{\text{Penalización L1}} $$
Donde: \(||\boldsymbol{\beta}||_1 = \sum_{j=1}^{p} |\beta_j|\) (norma L1, suma de valores absolutos)
⚠️ No hay solución analítica para Lasso (debido al valor absoluto). Se requiere optimización iterativa (e.g., coordinate descent, proximal gradient).
Subgradiente (en lugar de gradiente):
$$ \partial J_{\text{Lasso}} = \frac{1}{n} X^T (X\boldsymbol{\beta} - \mathbf{y}) + \lambda \cdot \text{sign}(\boldsymbol{\beta}) $$
Donde: \(\text{sign}(\beta_j) = \begin{cases} +1 & \text{si } \beta_j > 0 \\ -1 & \text{si } \beta_j < 0 \\ [-1, 1] & \text{si } \beta_j = 0 \end{cases}\)

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)}")
💡 Ventaja: Lasso puede reducir 100 features a 15 automáticamente. Modelo más interpretable y eficiente.

04. Elastic Net: Combinación L1 + L2

Lo Mejor de Ambos Mundos

$$ J_{\text{ElasticNet}}(\boldsymbol{\beta}) = \frac{1}{2n} ||\mathbf{y} - X\boldsymbol{\beta}||^2 + \lambda \left[\alpha ||\boldsymbol{\beta}||_1 + \frac{(1-\alpha)}{2} ||\boldsymbol{\beta}||_2^2\right] $$
Parámetros:
• \(\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
Ridge (α=0)
✓ Encoge todos los β
✗ No elimina features
✗ Si features correlacionadas, distribuye peso entre ellas
Lasso (α=1)
✓ Selecciona features
✓ Sparse β
✗ Si features muy correlacionadas, elige una al azar
Elastic Net (α=0.5)
✓ Selecciona features
✓ 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}")
Resultado ejemplo:
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
                        
Ridge:
• Todos los coeficientes disminuyen gradualmente
• Nunca llegan exactamente a 0
• Path continuo y suave
No hace selección de features
Lasso:
• Coeficientes llegan a 0 en orden
• 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

$$ J_{\text{Logística}}(\boldsymbol{\beta}) = \underbrace{-\frac{1}{n} \sum_{i=1}^{n} [y_i \log p_i + (1-y_i) \log(1-p_i)]}_{\text{Cross-Entropy}} + \lambda \cdot \text{Penalty}(\boldsymbol{\beta}) $$
La regularización se aplica igual (L1, L2, o Elastic Net), pero sobre la función de cross-entropy en vez de MSE.
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
💡 Aplicación común: En clasificación de texto con 10,000+ features (bag-of-words), Lasso elimina palabras irrelevantes automáticamente, mejorando interpretabilidad y velocidad.