Cerramos el módulo anterior con una pregunta pendiente: "¿puedo hacer mi modelo aún mejor?". La primera respuesta de este módulo es la regularización, la técnica que doma el overfitting con precisión quirúrgica en lugar de a martillazos. En la lección 06-05 vimos que un modelo sobreajustado memoriza el ruido de los datos de entrenamiento; en los modelos lineales ese sobreajuste tiene una firma muy reconocible: coeficientes enormes y de signos opuestos que se compensan entre sí. La regularización ataca directamente esa firma añadiendo a la función de coste un castigo por complejidad. En esta lección entenderás Ridge (L2), Lasso (L1) y Elastic Net, el papel del hiperparámetro alpha, y aplicarás las tres técnicas a la predicción del gasto mensual de los clientes de MercaFresh.

Contenido

  1. El overfitting en modelos lineales: coeficientes desbocados
  2. La idea central: penalizar la complejidad
  3. Ridge (L2): encoger sin anular
  4. Lasso (L1): selección automática de features
  5. Elastic Net: lo mejor de ambos mundos
  6. El hiperparámetro alpha y las trayectorias de coeficientes
  7. Escalar antes de regularizar
  8. Regularización en MercaFresh: predicción del gasto mensual
  9. La misma idea en clasificación: el parámetro C
  10. Tabla comparativa y criterios de elección

El overfitting en modelos lineales: coeficientes desbocados

En 04-01 entrenamos regresiones lineales minimizando el error cuadrático medio, y ya avisamos de que la regularización extendería aquella idea. ¿Por qué hace falta? Porque la regresión lineal ordinaria (OLS) tiene un punto débil: las features correlacionadas.

Recuerda la lección 02-03: en MercaFresh, num_pedidos_mes y num_productos_mes están fuertemente correlacionadas (quien hace más pedidos compra más productos). Cuando dos columnas contienen casi la misma información, el modelo lineal tiene infinitas maneras casi equivalentes de repartir el peso entre ellas:

  • gasto = 20·num_pedidos + 5·num_productos predice casi lo mismo que
  • gasto = 500·num_pedidos - 155·num_productos

Ambas se ajustan igual de bien al train, pero la segunda es una bomba de relojería: sus coeficientes gigantes amplifican cualquier pequeña variación del dato de entrada, y en datos nuevos el error se dispara. Es exactamente el síntoma de alta varianza que diagnosticamos en 06-05: el modelo es hipersensible a la muestra concreta con la que se entrenó.

import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression

rng = np.random.default_rng(42)
n = 60

# Dos features casi redundantes (correlación ~0.98)
num_pedidos = rng.poisson(4, n).astype(float)
num_productos = num_pedidos * 5 + rng.normal(0, 1, n)

# El gasto real depende sobre todo del número de pedidos
gasto = 22 * num_pedidos + rng.normal(0, 8, n)

X = np.column_stack([num_pedidos, num_productos])
ols = LinearRegression().fit(X, gasto)
print(ols.coef_)   # p. ej. [17.3, 0.95] — o valores mucho más extremos según la muestra

Con solo 60 clientes y features casi duplicadas, si repites el experimento cambiando la semilla verás que los coeficientes bailan de forma salvaje entre ejecuciones. Un modelo cuyos parámetros dependen tanto del azar de la muestra no es de fiar.

La idea central: penalizar la complejidad

La solución es sorprendentemente simple. En lugar de minimizar solo el error, minimizamos:

$$\text{Coste} = \underbrace{\text{Error de ajuste (MSE)}}{\text{¿predigo bien?}} + \alpha \cdot \underbrace{\text{Penalización(coeficientes)}}{\text{¿soy simple?}}$$

  • El primer término empuja al modelo a ajustarse a los datos (lo de siempre).
  • El segundo término castiga los coeficientes grandes: cuanto mayores sean los pesos, mayor el coste.
  • alpha (α) es el hiperparámetro que regula el equilibrio: el "precio" que paga el modelo por cada unidad de complejidad.

El modelo ya no puede permitirse coeficientes de 500 y -155 "porque sí": solo mantendrá un peso grande si la mejora en el error lo compensa. Esto reduce la varianza a cambio de un pequeño aumento del sesgo — el trade-off que formalizamos en 06-05, ahora con un mando de control continuo.

Las tres técnicas de esta lección se diferencian únicamente en cómo miden el tamaño de los coeficientes:

Técnica Penalización Fórmula del término
Ridge L2 (cuadrados) $\alpha \sum_j w_j^2$
Lasso L1 (valores absolutos) $\alpha \sum_j |w_j|$
Elastic Net Mezcla L1 + L2 $\alpha \left( r \sum_j |w_j| + \frac{1-r}{2} \sum_j w_j^2 \right)$

Nota: la penalización nunca incluye el término independiente (intercepto); castigar el nivel base de la predicción no tendría sentido.

Ridge (L2): encoger sin anular

Ridge penaliza la suma de cuadrados de los coeficientes. Como elevar al cuadrado castiga desproporcionadamente los valores grandes, Ridge odia los coeficientes extremos y prefiere repartir el peso de forma equilibrada entre features correlacionadas.

Propiedades clave:

  • Encoge todos los coeficientes hacia cero, pero nunca los hace exactamente cero: reducir un peso pequeño aporta cada vez menos ahorro cuadrático, así que nunca compensa anularlo del todo.
  • Es la opción más estable cuando hay multicolinealidad: en el ejemplo anterior, Ridge repartiría el peso entre num_pedidos y num_productos de forma robusta.
  • Tiene solución analítica cerrada, por lo que es rápido y numéricamente estable.
from sklearn.linear_model import Ridge

ridge = Ridge(alpha=1.0).fit(X, gasto)
print(ridge.coef_)   # coeficientes moderados y estables entre ejecuciones

Lasso (L1): selección automática de features

Lasso penaliza la suma de valores absolutos. La diferencia geométrica parece menor, pero tiene una consecuencia enorme: con L1, reducir un coeficiente de 0.1 a 0 ahorra exactamente lo mismo que reducirlo de 5.1 a 5. Por eso a Lasso sí le compensa poner coeficientes exactamente a cero cuando una feature aporta poco.

El resultado es que Lasso hace selección automática de features: el modelo final usa solo un subconjunto de columnas, y el resto quedan literalmente eliminadas (coeficiente = 0). Esto conecta directamente con la ingeniería de características de 03-06: allí creamos features a mano (RFM, ratios, agregados) sin saber cuáles serían útiles; Lasso nos da un mecanismo basado en datos para descartar las que no aportan.

from sklearn.linear_model import Lasso

lasso = Lasso(alpha=1.0).fit(X, gasto)
print(lasso.coef_)   # p. ej. [21.8, 0.0] — ¡num_productos eliminada!

Matiz importante: cuando dos features están muy correlacionadas, Lasso tiende a quedarse con una y anular la otra, y cuál elige puede depender del azar de la muestra. Ridge, en cambio, reparte el peso entre ambas. Ninguna estrategia es "la buena" en abstracto: depende de si quieres un modelo compacto (Lasso) o estable (Ridge).

Elastic Net: lo mejor de ambos mundos

Elastic Net combina ambas penalizaciones con un segundo hiperparámetro, l1_ratio (r en la fórmula anterior):

  • l1_ratio=1 → Lasso puro.
  • l1_ratio=0 → Ridge puro.
  • Valores intermedios → sigue anulando features irrelevantes (herencia L1) pero trata las features correlacionadas en grupo en lugar de elegir una arbitrariamente (herencia L2).
from sklearn.linear_model import ElasticNet

enet = ElasticNet(alpha=1.0, l1_ratio=0.5).fit(X, gasto)
print(enet.coef_)

Es la opción recomendable cuando tienes muchas features, sospechas que sobran algunas y además hay grupos correlacionados — una situación muy habitual tras una sesión generosa de ingeniería de características.

El hiperparámetro alpha y las trayectorias de coeficientes

alpha controla la intensidad del castigo:

  • alpha = 0: sin penalización → regresión lineal ordinaria (con todos sus problemas).
  • alpha pequeño: penalización suave, coeficientes casi libres.
  • alpha grande: penalización dura, coeficientes cada vez más pequeños; en el extremo, todos tienden a cero y el modelo predice casi la media (underfitting puro, como el DummyRegressor de 06-02).

La mejor manera de visualizarlo es el gráfico de trayectorias: cómo evoluciona cada coeficiente al aumentar alpha.

import matplotlib.pyplot as plt

alphas = np.logspace(-2, 3, 100)   # de 0.01 a 1000, en escala logarítmica
coefs_ridge, coefs_lasso = [], []

for a in alphas:
    coefs_ridge.append(Ridge(alpha=a).fit(X, gasto).coef_)
    coefs_lasso.append(Lasso(alpha=a, max_iter=10000).fit(X, gasto).coef_)

fig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)
for ax, coefs, titulo in [(axes[0], coefs_ridge, "Ridge"),
                          (axes[1], coefs_lasso, "Lasso")]:
    ax.plot(alphas, coefs)
    ax.set_xscale("log")
    ax.set_xlabel("alpha (escala log)")
    ax.set_title(f"Trayectorias de coeficientes — {titulo}")
    ax.axhline(0, color="gray", lw=0.5)
axes[0].set_ylabel("Valor del coeficiente")
plt.show()

Lo que verás en el gráfico:

  • En Ridge, las curvas descienden suavemente hacia cero pero no lo tocan: se acercan asintóticamente.
  • En Lasso, las curvas chocan contra cero y se quedan ahí: a partir de cierto alpha, cada feature "muere" y el modelo se va simplificando por etapas.

¿Y cómo se elige el mejor alpha? Se prueba una rejilla de valores con validación cruzada (06-03). En esta lección lo haremos con un bucle manual; la búsqueda sistemática y automatizada (GridSearchCV y compañía) es el tema de la lección 07-05.

Escalar antes de regularizar

Punto crítico que retoma la lección 03-05: la regularización castiga el tamaño numérico de los coeficientes, y ese tamaño depende de la escala de cada feature.

Si gasto_total se mide en euros (valores de miles) y frecuencia en pedidos/mes (valores de unidades), la primera necesitará un coeficiente diminuto y la segunda uno grande para el mismo efecto real. La penalización machacaría injustamente a frecuencia solo por su escala. Conclusión:

Siempre estandariza las features (StandardScaler) antes de Ridge, Lasso o Elastic Net. Y, como aprendimos en el módulo 3, dentro de un Pipeline para que el escalado se ajuste solo con el train y no haya fugas (06-01).

Regularización en MercaFresh: predicción del gasto mensual

Apliquemos todo al problema de regresión de MercaFresh: predecir el gasto mensual de un cliente a partir de features de comportamiento, varias de ellas correlacionadas entre sí (como descubrimos en la matriz de correlación de 02-03).

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet

# --- Dataset sintético de MercaFresh (features de comportamiento) ---
rng = np.random.default_rng(7)
n = 300

frecuencia = rng.gamma(3, 1.5, n)                      # pedidos/mes
productos = frecuencia * 6 + rng.normal(0, 2, n)       # correlada con frecuencia
antiguedad = rng.uniform(1, 60, n)                     # meses como cliente
ticket_medio = rng.normal(35, 8, n)                    # € por pedido
visitas_web = frecuencia * 4 + rng.normal(0, 3, n)     # correlada con frecuencia
ruido_1 = rng.normal(0, 1, n)                          # features irrelevantes
ruido_2 = rng.normal(0, 1, n)

# Gasto real: depende de frecuencia y ticket; el resto es redundante o ruido
gasto_mensual = frecuencia * ticket_medio + rng.normal(0, 15, n)

X = pd.DataFrame({
    "frecuencia": frecuencia, "productos": productos,
    "antiguedad": antiguedad, "ticket_medio": ticket_medio,
    "visitas_web": visitas_web, "ruido_1": ruido_1, "ruido_2": ruido_2,
})
y = gasto_mensual

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42)

# --- Torneo: OLS vs Ridge vs Lasso vs Elastic Net, siempre con escalado ---
modelos = {
    "OLS":         LinearRegression(),
    "Ridge":       Ridge(alpha=10),
    "Lasso":       Lasso(alpha=1.0, max_iter=10000),
    "Elastic Net": ElasticNet(alpha=1.0, l1_ratio=0.5, max_iter=10000),
}

for nombre, modelo in modelos.items():
    pipe = Pipeline([("scaler", StandardScaler()), ("reg", modelo)])
    # RMSE por validación cruzada de 5 pliegues, como en 06-03
    rmse = -cross_val_score(pipe, X_train, y_train, cv=5,
                            scoring="neg_root_mean_squared_error").mean()
    pipe.fit(X_train, y_train)
    coefs = pipe.named_steps["reg"].coef_.round(1)
    print(f"{nombre:12s} RMSE-CV: {rmse:6.2f} €   coefs: {coefs}")

Un resultado típico de este experimento:

  • OLS reparte peso entre frecuencia, productos y visitas_web (redundantes) e incluso asigna algo a las features de ruido.
  • Ridge reduce todos los pesos y estabiliza el reparto entre las correlacionadas; suele mejorar ligeramente el RMSE de validación.
  • Lasso anula ruido_1, ruido_2 y a menudo alguna de las redundantes: el modelo resultante es más corto y más legible para el equipo de negocio de MercaFresh ("el gasto lo explican frecuencia y ticket medio").
  • Elastic Net queda entre ambos: elimina el ruido pero conserva parte del grupo correlacionado.

Fíjate en que la comparación se hace con validación cruzada sobre el train, reservando el test para el veredicto final — la disciplina del módulo 6 no se abandona por usar técnicas más avanzadas.

La misma idea en clasificación: el parámetro C

La regularización no es exclusiva de la regresión. De hecho, ya la has estado usando sin saberlo:

  • En 04-02 vimos que LogisticRegression de sklearn tiene un parámetro C. Pues bien: LogisticRegression viene regularizada con L2 por defecto, y C es exactamente el inverso de alpha: C = 1/α.
  • En 04-04, el parámetro C de las SVM cumplía el mismo papel: controlar el equilibrio entre ajustar bien el train y mantener un modelo simple (margen amplio).
Parámetro Valor pequeño Valor grande
alpha (Ridge/Lasso/EN) Poca regularización → riesgo de overfitting Mucha regularización → riesgo de underfitting
C (LogisticRegression/SVM) Mucha regularización → underfitting Poca regularización → overfitting

Cuidado con la inversión: subir alpha y subir C tienen efectos opuestos. Además, LogisticRegression(penalty="l1", solver="liblinear") te da un clasificador con selección de features al estilo Lasso — útil en el churn de MercaFresh si quieres un modelo que use pocas variables RFM.

from sklearn.linear_model import LogisticRegression

# Churn con regularización L1: coeficientes a cero = features descartadas
clf = Pipeline([
    ("scaler", StandardScaler()),
    ("logreg", LogisticRegression(penalty="l1", C=0.1, solver="liblinear")),
])

Tabla comparativa y criterios de elección

Criterio Ridge (L2) Lasso (L1) Elastic Net
Coeficientes a cero exacto No
Selección de features No Automática Automática (por grupos)
Features muy correlacionadas Reparte el peso (estable) Elige una casi al azar Tiende a conservar el grupo
Nº de hiperparámetros 1 (alpha) 1 (alpha) 2 (alpha, l1_ratio)
Cuándo usarla Muchas features útiles y correlacionadas; prioridad: estabilidad Sospechas que pocas features importan; prioridad: interpretabilidad Muchas features, algunas irrelevantes y otras en grupos correlacionados

Regla práctica: empieza con Ridge como opción segura; pasa a Lasso si necesitas un modelo compacto y explicable; usa Elastic Net cuando Lasso se comporte de forma inestable por la correlación entre features.

Errores Comunes y Consejos

  • Regularizar sin escalar. El error número uno. Sin StandardScaler, alpha castiga a las features por su unidad de medida, no por su relevancia. Usa siempre un Pipeline.
  • Confundir alpha y C. En regresión (Ridge/Lasso), más alpha = más regularización. En clasificación (C), es al revés. Comprueba siempre en qué dirección mueves el mando.
  • Interpretar el cero de Lasso como "esta variable no influye en la realidad". Lasso dice que la feature no aporta dado que ya están las demás; con features correlacionadas, la eliminada puede ser tan causal como la superviviente. La inferencia causal es otra disciplina.
  • Elegir alpha mirando el error de entrenamiento. El train siempre preferirá alpha = 0. El alpha se elige con validación (cruzada), nunca con el error de train.
  • Olvidar max_iter en Lasso/ElasticNet. Usan optimización iterativa (descenso por coordenadas) y con alphas pequeños pueden no converger; si ves warnings, sube max_iter.
  • Consejo: explora alpha siempre en escala logarítmica (np.logspace), porque su efecto es multiplicativo: la diferencia relevante está entre 0.01, 0.1, 1 y 10, no entre 1 y 2.

Ejercicios

  1. Trayectorias sobre MercaFresh. Con el dataset de gasto mensual de esta lección (las 7 features), dibuja las trayectorias de coeficientes de Lasso para alphas = np.logspace(-2, 2, 100) (estandarizando antes). ¿En qué orden "mueren" las features? ¿Coincide con lo que sabes de cómo se generó el gasto?
  2. Ridge contra la multicolinealidad. Genera 20 muestras distintas del dataset pequeño de la sección 1 (cambiando la semilla) y ajusta en cada una OLS y Ridge con alpha=10 (con escalado). Calcula la desviación estándar del primer coeficiente entre las 20 ejecuciones para cada modelo. ¿Cuál es más estable?
  3. L1 en el churn. Sobre el dataset de churn con features RFM del módulo 3, entrena LogisticRegression(penalty="l1", solver="liblinear") con C en [0.01, 0.1, 1, 10] dentro de un pipeline con escalado. Para cada C, cuenta cuántos coeficientes quedan a cero y calcula el F1 con validación cruzada estratificada (06-03). ¿Qué C elegirías y por qué?

Soluciones

  1. Estandariza con StandardScaler y ajusta un Lasso por cada alpha, guardando coef_. Al pintar las 7 curvas verás que ruido_1 y ruido_2 mueren casi de inmediato (alphas pequeños), después caen las redundantes (visitas_web, productos — Lasso conserva normalmente frecuencia del grupo correlacionado) y las últimas supervivientes son frecuencia y ticket_medio, exactamente las dos variables con las que se generó el gasto. El orden de muerte de las trayectorias es en sí mismo un ranking de relevancia.
  2. Estructura: bucle for seed in range(20), regenerar datos con np.random.default_rng(seed), ajustar ambos modelos sobre features estandarizadas y guardar coef_[0]. Con OLS la desviación estándar del coeficiente resulta varias veces mayor que con Ridge (con datos tan correlacionados puede ser un orden de magnitud). Conclusión: Ridge no solo mejora la predicción; hace que el modelo sea reproducible, que es lo que significa reducir varianza.
  3. Con C=0.01 (regularización muy fuerte) casi todos los coeficientes quedan a cero y el F1 cae hacia el del baseline DummyClassifier (06-02): underfitting. Con C=10 no se anula casi nada y el resultado se parece a la logística sin regularizar. El punto interesante suele estar en C=0.11: F1 comparable al máximo con 2-4 features RFM eliminadas. Elegirías el C con mejor F1-CV y, en empate práctico, el más regularizado (modelo más simple). La búsqueda sistemática de este tipo de decisiones la automatizaremos en 07-05.

Conclusión

La regularización convierte el trade-off sesgo-varianza de 06-05 en un mando continuo: la función de coste pasa a premiar simultáneamente el ajuste y la simplicidad, y alpha decide el equilibrio. Ridge (L2) encoge coeficientes y estabiliza modelos con features correlacionadas; Lasso (L1) además pone coeficientes a cero exacto, regalándonos selección automática de features; Elastic Net mezcla ambas. Has visto que la idea no es nueva para ti: el parámetro C de la regresión logística y de las SVM era regularización disfrazada. Y has confirmado dos disciplinas innegociables: escalar dentro de un Pipeline y elegir alpha con validación cruzada, nunca con el train. Regularizar mejora un modelo conteniéndolo; la siguiente lección explora el camino opuesto y complementario: mejorar combinando muchos modelos para que sus errores se cancelen entre sí. Bienvenido al Ensemble Learning.

Curso de Machine Learning

Módulo 1: Introducción al Machine Learning

Módulo 2: Fundamentos de Estadística y Probabilidad

Módulo 3: Preprocesamiento de Datos

Módulo 4: Algoritmos de Machine Learning Supervisado

Módulo 5: Algoritmos de Machine Learning No Supervisado

Módulo 6: Evaluación y Validación de Modelos

Módulo 7: Técnicas Avanzadas y Optimización

Módulo 8: Implementación y Despliegue de Modelos

Módulo 9: Proyectos Prácticos

Módulo 10: Recursos Adicionales

© Copyright 2026. Todos los derechos reservados