Capítulo 14 de 37 · intermedio
Regresión ridge
Qué cubre este capítulo
La regresión ridge es regresión lineal con correa. Ajusta el mismo modelo — una suma ponderada de las features más un intercepto — pero cambia lo que significa "mejor": en lugar de los pesos que minimizan solo el error cuadrático, tomas los pesos que minimizan el error cuadrático más una penalización sobre qué tan grandes se vuelven esos pesos. Ese único término extra es toda la idea, y arregla los dos lugares donde mínimos cuadrados ordinarios hace el ridículo: features correlacionadas que se reparten entre sí coeficientes enormes que se cancelan, y un ajuste que se aferra tanto a la muestra de entrenamiento que no puede generalizar.
Este capítulo construye directamente sobre la semana 8. Si no has leído la
página de regresión lineal, léela primero — predict, r2_score y la ecuación
normal regresan sin cambios, y ridge es una edición de dos caracteres a esa
ecuación normal. Construimos el objetivo penalizado, la solución en forma
cerrada que lo minimiza, y el camino de coeficientes: la imagen de cada peso
encogiéndose hacia cero conforme crece la penalización. Luego lo corremos contra
el Ridge de scikit-learn y confirmamos que aterrizan en los mismos
coeficientes hasta trece decimales.
Los datos reales son el dataset de diabetes incluido en scikit-learn: 442 pacientes, diez features — edad, sexo, IMC, presión arterial y seis mediciones de suero sanguíneo — y un target que mide la progresión de la enfermedad un año después. Varias de esas features de suero están fuertemente correlacionadas, que es exactamente el desorden que ridge existe para limpiar.
Un poco de historia
La regresión ridge tiene dos historias de origen independientes que resultaron ser las mismas matemáticas. El nombre y el planteamiento estadístico vienen de Arthur Hoerl y Robert Kennard, dos ingenieros químicos de DuPont, en un par de artículos de 1970 en Technometrics — "Ridge Regression: Biased Estimation for Nonorthogonal Problems". Su problema era práctico e industrial: cuando las variables predictoras de una regresión son casi colineales, las estimaciones de mínimos cuadrados se vuelven locas — coeficientes enormes, signos disparatados, números que oscilan violentamente si agregas un dato más. Hoerl y Kennard mostraron que podías cambiar un poco de sesgo por un gran recorte en varianza agregando una constante pequeña a la diagonal de la matriz antes de invertirla. Llamaron "ridge trace" a la gráfica de los coeficientes contra esa constante, y de ahí sacó su nombre el método.
La misma idea ya había aparecido del lado de las matemáticas. Andrey Tikhonov, trabajando en la Unión Soviética durante los años cuarenta y formalizando en los sesenta, resolvía problemas inversos mal planteados — casos donde los datos no determinan una respuesta única — agregando un término de regularización que prefiere soluciones más pequeñas y suaves. Por eso verás que a ridge le llaman "regularización de Tikhonov", y por eso el término de abajo a veces se escribe con una matriz más general. La estadística lo encontró por regresiones inestables; el análisis numérico lo encontró por inversas inestables. Es el mismo arreglo, y es el primer regularizador que conoce la mayoría de la gente.
La intuición
Empieza con lo que sale mal. Mínimos cuadrados quiere los pesos que mejor explican el target, y cuando dos features cargan casi la misma información no tiene razón para preferir una sobre la otra. Puede ponerle un peso positivo grande a la primera y un peso negativo grande a la segunda, o al revés, o cualquiera de mil combinaciones casi equivalentes — todas ajustan los datos de entrenamiento más o menos igual de bien. El resultado son coeficientes enormes, inestables e imposibles de leer. Aquí están dos de las features de suero de los datos de diabetes, s1 y s2. Su correlación es 0.90; se mueven juntas casi a la perfección.
Para un ajuste de mínimos cuadrados, esa línea casi diagonal es una trampa: puede intercambiar peso entre s1 y s2 libremente, y lo hará, aterrizando en cualquier par de pesos que se cancelan que le exprima la última gota de error de entrenamiento. Ridge cierra la trampa cobrando renta. Cada unidad de coeficiente ahora cuesta algo — específicamente, lambda por su cuadrado — así que un peso positivo gigante y un peso negativo gigante que casi se cancelan ya no salen gratis. El ajuste prefiere partir la diferencia: dos pesos modestos en lugar de dos enormes. Ese es todo el mecanismo. La penalización hace que el modelo prefiera coeficientes pequeños y compartidos sobre grandes y frágiles, y los coeficientes pequeños son exactamente lo que sobrevive el contacto con datos nuevos.
Las matemáticas
El modelo no cambia respecto a la regresión lineal. Apila los datos en una matriz con renglones y columnas, y cada renglón recibe una predicción como suma ponderada de sus features más un intercepto:
donde es el vector de pesos y es el intercepto escalar. Mínimos cuadrados ordinarios elige y para minimizar el error cuadrático. Ridge minimiza el error cuadrático más una penalización L2 sobre los pesos:
El primer término es la misma suma de residuales al cuadrado que minimiza
mínimos cuadrados. El segundo, , es la
penalización. El escalar es la perilla: en la
penalización desaparece y regresas a mínimos cuadrados ordinarios; conforme
la penalización domina y cada peso queda aplastado hacia
cero. Escrito con el error cuadrático medio en lugar de la suma, el mismo
objetivo es — dividir el término de error entre solo reescala lo que
significa , así que la forma es idéntica. Nos quedamos con la forma
de suma porque hace que coincida exactamente con el alpha de
scikit-learn.
El intercepto está deliberadamente ausente de la penalización. Nunca castigamos al modelo por la altura general de la línea — solo por la pendiente que le asigna a cada feature. Como el objetivo sigue siendo una cuadrática convexa, podemos igualar la derivada a cero y despejar el mínimo directamente, exactamente como con la ecuación normal. Metiendo el intercepto entre los pesos al pegarle una columna de unos a , la solución es:
Esa es la ecuación normal con una adición: en la diagonal (con un cero en el lugar del intercepto, para que quede sin penalizar). Ese único término hace la regularización. También hace algo silenciosamente esencial: puede ser singular o casi singular cuando las features son colineales, y agregar levanta cada eigenvalor en , garantizando que la matriz sea invertible. Ridge siempre tiene una respuesta, incluso donde mínimos cuadrados divide entre cero.
Una cosa que las matemáticas asumen: que las features están en escalas comparables. Como la penalización suma coeficientes al cuadrado, una feature medida en unidades pequeñas carga un coeficiente naturalmente más grande y recibe más penalización, sin ninguna buena razón. Así que primero estandarizas — centras cada feature a media cero, la escalas a varianza unitaria — y entonces una sola significa lo mismo para todas.
En qué es bueno, en qué no
Ridge se gana su lugar siempre que las features están correlacionadas o tienes más features de las que puedes sostener con comodidad, que en datos reales es la mayoría de las veces. Estabiliza coeficientes que mínimos cuadrados deja dando bandazos, nunca falla al invertir, y mejora de forma confiable el error fuera de muestra cambiando un poco de sesgo por una gran caída en varianza. Es barato — la misma resolución lineal de un solo paso que mínimos cuadrados ordinarios, más una diagonal — y tiene exactamente un hiperparámetro que ajustar. En la práctica ridge es el default al que recurro en el momento en que los coeficientes de la regresión lineal simple empiezan a verse inestables o su error de test queda detrás de su error de entrenamiento.
Lo que ridge no va a hacer es simplificar tu modelo. Encoge los coeficientes suavemente hacia cero pero esencialmente nunca pone uno exactamente en cero, así que conservas todas tus features, solo con pesos más pequeños. Si tienes cincuenta features y sospechas que cuarenta son ruido, ridge va a encoger calladamente las cincuenta en lugar de decirte cuáles diez importan — obtienes un modelo denso más estable, no uno disperso y legible. Ese trabajo le corresponde a la penalización L1 de lasso, que pone coeficientes en cero de tajo y hace selección de features, o a elastic net, que mezcla L1 y L2 para obtener un poco de ambos. Esos son los siguientes dos capítulos. La regla práctica que uso: ridge cuando crees que la mayoría de las features cargan un poco de señal y las quieres todas, más calmadas; lasso cuando crees que la mayoría de las features son inútiles y quieres un cuchillo.
Los datos
El dataset de diabetes viene incluido en scikit-learn, así que no hay nada que descargar. Cada uno de los 442 renglones es un paciente: diez mediciones de línea base — edad, sexo, índice de masa corporal, presión arterial promedio y seis mediciones de suero sanguíneo etiquetadas s1 a s6 — y un target que cuantifica la progresión de la enfermedad un año después de la línea base, con rango de 25 a 346. Es un problema de regresión pequeño y honesto, y tiene la propiedad que hace que valga la pena enseñar ridge con él: las features de suero están enredadas. Ya vimos a s1 y s2 montadas sobre una diagonal casi perfecta.
La señal individual más clara es el índice de masa corporal, que sigue la progresión tan bien como cualquier feature individual puede. Aquí está contra el target. La relación es real y claramente más o menos lineal, e igual de claramente ruidosa — una feature explica parte de la historia, nunca toda.
Constrúyelo, una función a la vez
La mayor parte de ridge es la semana 8. El modelo es la misma línea única — las predicciones son las features por los pesos más el intercepto:
def predict(X, w, b):
"""The linear model: yhat = Xw + b. Same model as ordinary least squares.
X is (n, d), w is (d,), b is a scalar; the result is (n,). Ridge changes how
we pick w and b, not what the model computes once we have them.
"""
return X @ w + b
Y R², el número que reportamos, no cambia: uno menos la razón entre nuestro error cuadrático y el error de simplemente adivinar la media.
def r2_score(X, y, w, b):
"""Coefficient of determination: the fraction of variance we explain.
1 minus (our squared error / the squared error of always guessing the mean).
1.0 is perfect, 0.0 is no better than the mean, negative is worse. This is
the number we report, unchanged from linear regression.
"""
resid = y - predict(X, w, b)
ss_res = float(np.sum(resid ** 2))
ss_tot = float(np.sum((y - y.mean()) ** 2))
return 1.0 - ss_res / ss_tot
La primera pieza genuinamente nueva es la estandarización, porque ridge la necesita. La penalización suma coeficientes al cuadrado, así que solo tiene sentido si cada feature está en la misma escala. Centramos cada feature a media cero y la escalamos a desviación estándar unitaria, ajustando esas estadísticas sobre el conjunto de entrenamiento y reusándolas en todos lados para que no se filtre información del test:
def standardize(X, mu=None, sd=None):
"""Center each feature to mean 0 and scale to unit standard deviation.
Ridge penalizes the squared length of the weight vector, so it treats every
coefficient on the same scale. If one feature is in dollars and another in
kilograms, the raw magnitudes are meaningless and the penalty falls unevenly.
Standardizing first puts every feature on equal footing so a single lambda
means the same thing everywhere. Fit mu/sd on train, reuse them everywhere.
"""
if mu is None:
mu = X.mean(axis=0)
sd = X.std(axis=0)
sd = np.where(sd == 0.0, 1.0, sd) # guard a constant column
return (X - mu) / sd, mu, sd
Ahora el objetivo — la cosa que ridge realmente minimiza. Es el error cuadrático más lambda por la longitud al cuadrado del vector de pesos, y el intercepto se queda fuera de la penalización:
def ridge_objective(X, y, w, b, lam):
"""What ridge minimizes: squared error plus lambda times the squared weights.
The first term is ordinary least squares — how badly the line misses. The
second, lam * (w . w), is the L2 penalty: it charges the fit for the length
of the weight vector, pulling every coefficient toward zero. The intercept b
is NOT in the penalty — we never punish the model for the height of the line,
only for the steepness it assigns to the features.
"""
resid = predict(X, w, b) - y
return float(resid @ resid + lam * (w @ w))
Minimizar ese objetivo tiene forma cerrada, y es la ecuación normal con un cambio. Pega la columna de unos para el intercepto, agrega a la diagonal — pero un cero en el lugar del intercepto, para no penalizarlo — y resuelve. Ese es toda la diferencia entre esto y mínimos cuadrados ordinarios:
def ridge_closed_form(X, y, lam):
"""The exact ridge solution: w = (XtX + lam I)^-1 Xt y, intercept unpenalized.
Glue a column of ones onto X so the intercept rides along as the first
weight, then build a penalty matrix that is lam on the diagonal EXCEPT the
intercept slot, which stays 0. Solving (Xb'Xb + P) theta = Xb'y minimizes the
ridge objective in one shot. The lam I term is why ridge is numerically kind:
it lifts XtX away from singular, so the inverse exists even when features are
collinear and OLS would blow up. `np.linalg.solve` factors instead of
inverting explicitly — same answer, better conditioned.
"""
ones = np.ones((X.shape[0], 1))
Xb = np.hstack([ones, X]) # (n, d+1)
P = lam * np.eye(Xb.shape[1])
P[0, 0] = 0.0 # do not penalize the intercept
theta = np.linalg.solve(Xb.T @ Xb + P, Xb.T @ y)
b = float(theta[0])
w = theta[1:]
return w, b
La última pieza es la que hace a ridge visual. Reajusta en todo un rango de lambdas y registra cada peso cada vez, y obtienes el camino de coeficientes: la traza de lo que la regularización le hace al modelo conforme subes la perilla:
def coefficient_path(X, y, lambdas):
"""Refit ridge at every lambda and record the whole weight vector each time.
This is the picture that explains ridge. At lambda near 0 you get the OLS
weights — large, and for correlated features, wild. Crank lambda up and the
penalty dominates: every weight is pulled smoothly toward 0, the big
correlated coefficients collapse first, and in the limit the whole vector
goes flat. L2 shrinks toward zero but almost never exactly to zero — that is
the contrast with L1 (lasso), which sets coefficients to exactly 0 and
selects features. Returns a (len(lambdas), d) array of weights.
"""
weights = np.zeros((len(lambdas), X.shape[1]))
for i, lam in enumerate(lambdas):
w, _ = ridge_closed_form(X, y, lam)
weights[i] = w
return weights
Corre ese camino sobre una malla de lambdas espaciada logarítmicamente y grafica cada coeficiente como una línea, y obtienes la clásica ridge trace — la imagen a la que Hoerl y Kennard le pusieron el nombre del método. A la izquierda, cerca de penalización cero, los pesos son las estimaciones OLS, bien abiertas. Barre hacia la derecha y todos se doblan suavemente hacia cero, los grandes correlacionados colapsando más rápido, sin que ninguno llegue nunca del todo:
Míralo trabajar
Aquí está el mismo barrido como animación, un reajuste real por cuadro. El panel superior es cada coeficiente como barra; el panel inferior traza el error de validación conforme crece lambda. Mira primero el panel izquierdo: con lambda pequeña las barras son los coeficientes OLS completos, y las features de suero correlacionadas resaltan — s2, s4, s5, todas grandes. Conforme lambda sube, cada barra se contrae hacia la línea punteada del cero, y las grandes y frágiles se encogen más rápido. Para el extremo derecho el modelo entero está casi plano: la penalización ganó, y ridge está prediciendo casi el mismo número para todos.
El panel inferior es la razón por la que no simplemente subes lambda hasta el infinito. El error de validación cae al principio — un poco de encogimiento ayuda de verdad — toca fondo, y luego vuelve a subir conforme el modelo se vuelve demasiado tímido para ajustar nada. La línea naranja marca la lambda en el fondo de esa curva, la que nos quedamos. La leyenda nombra la lambda actual, la norma L2 del vector de pesos y el error de validación en ese punto.
El mínimo de esa curva de validación queda en lambda ≈ 31.6. Ese es el valor que
llevamos adelante: suficiente penalización para calmar los coeficientes
correlacionados, no tanta como para que el modelo se quede flácido. Vale la pena
notar que es una U genuina — el extremo izquierdo (sin penalización) y el
extremo derecho (pura penalización) son ambos peores que el medio. Ridge no es
"menos es mejor" ni "más es mejor"; hay un punto dulce real, y encontrarlo es
para lo que sirve el barrido de validación. Dejado a su suerte, el RidgeCV de
scikit-learn, que usa validación cruzada leave-one-out en lugar de nuestro único
split apartado, aterriza cerca en alpha ≈ 20 — el mismo vecindario, alcanzado
por otro camino.
Aquí está esa curva de validación por sí sola, con la lambda elegida marcada, para que puedas leer el trade-off directamente:
La implementación completa
El archivo entero, sin librería, de arriba a abajo — el modelo, R², la estandarización, el objetivo de ridge, la resolución en forma cerrada y el camino de coeficientes. Este es el código que de verdad corrió la animación de arriba:
"""Ridge regression, built from scratch.
Ridge is ordinary least squares with one extra term: a penalty on the size of
the weights. You still fit a linear model — a weighted sum of the features plus
an intercept — but instead of chasing the smallest squared error you chase the
smallest squared error *plus* lambda times the squared length of the weight
vector. That single term is what tames multicollinearity and overfit: it makes
the fit pay for every unit of coefficient it spends, so correlated features stop
handing each other enormous offsetting weights and settle for small, stable
ones instead.
This file builds directly on week 8 (linear regression). `predict` and
`r2_score` are the same functions; the new work is the penalized objective, the
closed-form solution that minimizes it, and the coefficient path that traces
what happens to the weights as lambda grows.
Pure NumPy — no ML library anywhere in this file. Every function shows up in the
chapter one step at a time; the `# region:` markers are what the include
directives pull in.
"""
import numpy as np
import pandas as pd
# region: predict
def predict(X, w, b):
"""The linear model: yhat = Xw + b. Same model as ordinary least squares.
X is (n, d), w is (d,), b is a scalar; the result is (n,). Ridge changes how
we pick w and b, not what the model computes once we have them.
"""
return X @ w + b
# endregion
# region: r2
def r2_score(X, y, w, b):
"""Coefficient of determination: the fraction of variance we explain.
1 minus (our squared error / the squared error of always guessing the mean).
1.0 is perfect, 0.0 is no better than the mean, negative is worse. This is
the number we report, unchanged from linear regression.
"""
resid = y - predict(X, w, b)
ss_res = float(np.sum(resid ** 2))
ss_tot = float(np.sum((y - y.mean()) ** 2))
return 1.0 - ss_res / ss_tot
# endregion
# region: standardize
def standardize(X, mu=None, sd=None):
"""Center each feature to mean 0 and scale to unit standard deviation.
Ridge penalizes the squared length of the weight vector, so it treats every
coefficient on the same scale. If one feature is in dollars and another in
kilograms, the raw magnitudes are meaningless and the penalty falls unevenly.
Standardizing first puts every feature on equal footing so a single lambda
means the same thing everywhere. Fit mu/sd on train, reuse them everywhere.
"""
if mu is None:
mu = X.mean(axis=0)
sd = X.std(axis=0)
sd = np.where(sd == 0.0, 1.0, sd) # guard a constant column
return (X - mu) / sd, mu, sd
# endregion
# region: ridge_objective
def ridge_objective(X, y, w, b, lam):
"""What ridge minimizes: squared error plus lambda times the squared weights.
The first term is ordinary least squares — how badly the line misses. The
second, lam * (w . w), is the L2 penalty: it charges the fit for the length
of the weight vector, pulling every coefficient toward zero. The intercept b
is NOT in the penalty — we never punish the model for the height of the line,
only for the steepness it assigns to the features.
"""
resid = predict(X, w, b) - y
return float(resid @ resid + lam * (w @ w))
# endregion
# region: ridge_closed_form
def ridge_closed_form(X, y, lam):
"""The exact ridge solution: w = (XtX + lam I)^-1 Xt y, intercept unpenalized.
Glue a column of ones onto X so the intercept rides along as the first
weight, then build a penalty matrix that is lam on the diagonal EXCEPT the
intercept slot, which stays 0. Solving (Xb'Xb + P) theta = Xb'y minimizes the
ridge objective in one shot. The lam I term is why ridge is numerically kind:
it lifts XtX away from singular, so the inverse exists even when features are
collinear and OLS would blow up. `np.linalg.solve` factors instead of
inverting explicitly — same answer, better conditioned.
"""
ones = np.ones((X.shape[0], 1))
Xb = np.hstack([ones, X]) # (n, d+1)
P = lam * np.eye(Xb.shape[1])
P[0, 0] = 0.0 # do not penalize the intercept
theta = np.linalg.solve(Xb.T @ Xb + P, Xb.T @ y)
b = float(theta[0])
w = theta[1:]
return w, b
# endregion
# region: coef_path
def coefficient_path(X, y, lambdas):
"""Refit ridge at every lambda and record the whole weight vector each time.
This is the picture that explains ridge. At lambda near 0 you get the OLS
weights — large, and for correlated features, wild. Crank lambda up and the
penalty dominates: every weight is pulled smoothly toward 0, the big
correlated coefficients collapse first, and in the limit the whole vector
goes flat. L2 shrinks toward zero but almost never exactly to zero — that is
the contrast with L1 (lasso), which sets coefficients to exactly 0 and
selects features. Returns a (len(lambdas), d) array of weights.
"""
weights = np.zeros((len(lambdas), X.shape[1]))
for i, lam in enumerate(lambdas):
w, _ = ridge_closed_form(X, y, lam)
weights[i] = w
return weights
# endregion
def load_data(path="../data/diabetes.csv"):
"""Diabetes progression: 442 patients, 10 features, a progression score.
Returns (X, y, feature_names). y is a quantitative measure of disease
progression one year after baseline; the features are age, sex, BMI, blood
pressure, and six blood-serum measurements (s1-s6), several of which are
strongly correlated with one another.
"""
df = pd.read_csv(path)
target = "target"
features = [c for c in df.columns if c != target]
X = df[features].to_numpy(float)
y = df[target].to_numpy(float)
return X, y, features
La versión de librería
Nadie escribe a mano la ecuación normal regularizada en producción.
sklearn.linear_model.Ridge es el mismo modelo con el mismo objetivo — error
cuadrático más una penalización L2, intercepto ajustado pero no penalizado — y
su alpha es exactamente nuestra lambda, así que con el mismo valor los dos
producen los mismos coeficientes:
def sklearn_fit(X_train, y_train, alpha):
"""Fit ridge at a fixed alpha. Returns (weights, intercept) — the twin of our
ridge_closed_form, so the coefficients line up one for one at alpha == lam."""
model = Ridge(alpha=alpha) # fit_intercept=True, intercept unpenalized
model.fit(X_train, y_train)
return model.coef_, float(model.intercept_)
Por debajo, sklearn resuelve el mismo sistema regularizado que nosotros; para
datos densos factoriza directamente. La única
conveniencia que vale la pena usar es RidgeCV, que barre una malla de alphas
con validación cruzada y te devuelve el mejor — la versión automatizada de la
curva de validación que dibujamos a mano:
def sklearn_ridge_cv(X_train, y_train, alphas):
"""Let sklearn pick alpha by cross-validation over the grid. Returns the
chosen alpha — sklearn's answer to 'which lambda minimizes the error?'."""
model = RidgeCV(alphas=alphas)
model.fit(X_train, y_train)
return float(model.alpha_)
Lo que de verdad hay que mirar es lo que la penalización les hizo a los coeficientes. Aquí están los diez pesos con lambda 0 — mínimos cuadrados ordinarios — junto a los mismos diez con nuestra lambda elegida. Cada barra de ridge es más corta que su gemela OLS, y las features de suero correlacionadas son las que más se mueven: s2 se encoge de cerca de −11 a −8, s4 de cerca de 11 a 8. La norma L2 total del vector de pesos cae de 45.5 a 40.6. Nada queda en cero — eso es L2 — pero todo queda más calmado.
Desde cero contra librería
Divide los 442 pacientes 60/20/20 — 265 para entrenar, 88 para elegir lambda, 89
apartados para calificar — estandariza sobre el conjunto de entrenamiento, y
ajusta de tres maneras: nuestra línea base OLS (ridge con lambda ≈ 0), nuestro
ridge con la lambda elegida, y el Ridge de sklearn con el mismo alpha.
Califica sobre los 89 apartados:
Hay que leer dos cosas aquí. Primera, nuestro ridge y el de sklearn son la misma
barra — R² de test de 0.363 para ambos, porque sus coeficientes coinciden con
una diferencia absoluta máxima de cerca de 1.6e-13. Es la misma forma cerrada;
no hay nada en qué discrepar. Segunda, y el punto de todo el capítulo: ridge le
gana al ajuste sin regularizar, 0.363 contra el 0.339 de OLS. Esa es una
ganancia real sobre datos apartados, comprada puramente por negarse a dejar
crecer los coeficientes. Las cifras exactas viven en results.json, regenerado
cada vez que el código cambia, para que la prosa y la gráfica no puedan
desviarse de lo que el código produjo.
Un R² de 0.363 es modesto — diez mediciones de línea base explican cerca de un tercio de la varianza en cómo progresa esta enfermedad, lo cual es honesto para un problema así de difícil. Aquí está el ajuste como predicho contra real sobre los pacientes apartados; un modelo perfecto pondría cada punto sobre la diagonal punteada:
La nube se inclina a lo largo de la diagonal — el modelo claramente aprendió algo — pero es una nube ancha, y se aplana arriba: los pacientes con mayor progresión reciben predicciones demasiado bajas, con el modelo cubriéndose hacia el medio. Esa cobertura es exactamente lo que hace ridge. Encoger los coeficientes jala las predicciones hacia la media, que es el precio que pagas por la estabilidad, y en estos datos es un precio que vale la pena pagar.
Conclusiones
Recurre a ridge en el momento en que la regresión lineal simple empiece a portarse mal. Si los coeficientes son enormes, o cambian de signo cuando agregas datos, o el error de test queda detrás del error de entrenamiento, tienes varianza que gastar, y ridge la gasta bien: un hiperparámetro, la misma resolución en forma cerrada, una matriz con inversión garantizada y generalización confiablemente mejor. Es el seguro más barato de la caja de herramientas de regresión, y estandarizar las features más un barrido rápido de validación para lambda sale casi gratis. En estos datos convirtió un 0.339 en un 0.363 sin tocar la clase del modelo para nada.
Ten claro lo que ridge es y lo que no. Encoge cada coeficiente suavemente hacia cero y los conserva todos — terminas con un modelo estable y denso, no con una lista corta de las features que importan. Si lo que quieres es selección, si prefieres que el modelo te entregue diez coeficientes y ponga los otros cuarenta exactamente en cero, ridge es la penalización equivocada y lasso es la correcta. Elastic net parte la diferencia. Los tres minimizan error cuadrático más una penalización; difieren solo en la forma de esa penalización, y esa forma es toda la personalidad del método. La L2 de ridge mantiene a todos en la mesa, más pequeños. La L1 de lasso vacía el cuarto. Los siguientes dos capítulos son esas dos penalizaciones, y ahora ya viste aquella contra la que se miden.