Regresión lineal con funciones base y Regularización
Contenido
import holoviews as hv
hv.extension('bokeh')
hv.opts.defaults(hv.opts.Curve(width=500),
hv.opts.Scatter(width=500, size=4),
hv.opts.Histogram(width=500),
hv.opts.Slope(color='k', alpha=0.5, line_dash='dashed'),
hv.opts.HLine(color='k', alpha=0.5, line_dash='dashed'))
import pandas as pd
import numpy as np
import scipy.stats
import statsmodels.api as sm
print(f"NumPy versión: {np.__version__}")
print(f"Scipy versión: {scipy.__version__}")
print(f"StatsModels versión: {sm.__version__}")
NumPy versión: 1.21.2
Scipy versión: 1.7.3
StatsModels versión: 0.13.2
16. Regresión lineal con funciones base y Regularización¶
16.1. Modelo general de regresión lineal¶
Anteriormente aprendimos como ajustar lineas e hiperplanos a datos continuos. Sin embargo, la forma más general de un modelo lineal es:
donde \(\phi_k: \mathbb{R}^D \to \mathbb{R}\) es una transformación que pueder no-lineal. Notemos además que \(K\) y \(D\) no necesariamente están relacionados. A continuación veremos algunos ejemplos.
Base Polinomial: Para una variable unidimensional \(x_i \in \mathbb{R}, i=1,\ldots,N\) se puede definir una base polinomial como:
que resulta en un model polinomial de grado \(K\):
Base Trigonométrica: Si queremos modelar un comportamiento periódico en la variable independiente una buena opción es utilizar una base trigonométrica, e.g. una serie de Fourier.
Para una variable unidimensional \(x_i \in \mathbb{R}, i=1,\ldots,N\), una base trigonométrica con periodo \(P=1/f_0\) sería:
que resulta en un modelo con \(2K+1\) parámetros:
Modelar interacciones entre variables: Podemos crear una base que modela interacciones no lineales entre las variables independientes.
Por ejemplo, si tienemos una variable independiente bidimensional (dos columnas) \(x_i = (w_{i}, v_{i}), i=1,\ldots,N\) un modelo con interacciones hasta de segundo orden sería:
16.2. Regresión polinomial con statsmodels
¶
Podemos hacer una regresión lineal con funciones base utilizando el método from_formula
de OLS
. Este método recibe una formula como string y un DataFrame de Pandas con los datos.
Por ejemplo, si nuestro DataFrame tiene columnas llamadas Y
y X
, y queremos ajustar un modelo que prediga Y
en función de X
, la fórmula sería: Y ~ X
.
Ver también
La fórmula anterior está escrita en un “mini-lenguaje” implementado por la librería patsy
. Este lenguaje para definir modelos estadísticos está inspirado en R. Revise la documentación si tiene interés en definir modelos más avanzados que los que se verán a continuación.
Veamos el siguiente dataset sintético:
np.random.seed(1234)
x = np.random.randn(30)
y = -0.5*x**4 + 2*x**3 -3 + np.random.randn(len(x))
df = pd.DataFrame({'X': x, 'Y': y})
Así se utilizaría from_formula
para crear un modelo que prediga la variable Y
dado X
result = sm.OLS.from_formula(formula='Y ~ X', data=df).fit()
result.params
Intercept -5.445831
X 6.764197
dtype: float64
Nota
Por defecto, statsmodels
agrega un parámetro de intercepta en el modelo lineal
Luego podemos hacer una predicción con:
hatX = pd.DataFrame({'X': np.linspace(-3, 3, num=100)})
hatY = result.predict(hatX)
hv.Overlay([hv.Curve((hatX, hatY), 'X', 'Y', label='model'),
hv.Scatter((x, y), label='data').opts(color='k')]).opts(legend_position='top_left')
Podemos extender el modelo anterior agregamos más términos a la fórmula. Por ejemplo podríamos agregar términos cuadráticos y cúbicos con:
result = sm.OLS.from_formula(formula='Y ~ X + I(X ** 2) + I(X ** 3)', data=df).fit()
display(result.params)
hatY = result.predict(hatX)
Intercept -2.731240
X -0.602724
I(X ** 2) -1.039263
I(X ** 3) 2.659100
dtype: float64
hv.Overlay([hv.Curve((hatX, hatY), 'X', 'Y', label='model'),
hv.Scatter((x, y), label='data').opts(color='k')]).opts(legend_position='top_left')
¿Qué ocurre si aumentamos el grado del polinomio arbitrariamente?
hatY = {}
for k in range(1, 16):
many_terms = "".join([f'+ I(X ** {j})' for j in range(2, k+1)])
result = sm.OLS.from_formula(formula='Y ~ X'+many_terms, data=df).fit()
hatY[k] = result.predict(hatX)
hMap = hv.HoloMap(kdims=['degree'])
for degree, haty_ in hatY.items():
p_model = hv.Curve((hatX, haty_), 'X', 'Y', label='model')
p_data = hv.Scatter((x, y), label='data').opts(color='k')
hMap[degree] = hv.Overlay([p_model, p_data]).opts(legend_position='top_left', ylim=(-40., 20.))
hMap
Peligro
A medida que el grado del polinomio crece, el modelo se sobreajusta los datos cada vez más.
A continuación definiremos el concepto de sobreajuste y luego veremos como evitarlo utilizando regularización.
16.3. Sobreajuste y regularización¶
En el ejemplo anterior, el grado del modelo polinomial representa la complejidad del modelo. En general mientras más complejo es un modelo, mayor es su flexibilidad para acomodarse a los datos.
Pero demasiada complejidad puede devenir en sobreajuste
Sobreajuste se refiere cuando ajustamos los datos con un error cercano a cero.
Lo anterior es perjudicial pues, en general estaríamos ajustando el ruido en los datos y perdiendo capacidad de generalizar a datos nuevos.
Para evitar sobreajuste podemos
Usar modelos de baja complejidad
Decidir la complejidad utilizando técnicas de validación cruzada
Penalizar la complejidad mediante regularización
Ver también
Para una tratamiento más extenso sobre sobreajuste y estrategias de validación cruzada recomiendo revisar el siguiente material
16.3.1. Compromiso sesgo-varianza¶
Asumamos que nuestros datos pueden modelarse con una función suave y ruido aditivo gaussiano:
y que ajustaremos los datos con un modelo lineal. Podemos medir la calidad del ajuste en base al error cuadrático medio (mean square error, MSE), que puede descomponerse como sigue:
donde
el primer término está asociado al error irreducible (ruido de los datos)
el segundo término es el cuadrado del sesgo del estimador
el tercer término es la varianza del estimador
Demostración
De la descomposición es fácil ver que el MSE puede ser pequeño si el sesgo es pequeño o si la varianza es pequeña.
Los modelos simples suelen tener alto sesgo y baja varianza
Los modelos complejos suelen tener bajo sesgo y alta varianza
El teorema de Gauss-Markov dice que OLS es el estimador insesgado de mínima varianza. Pero los modelos con sesgo cero no son necesariamente buenos si la complejidad es alta (sobreajuste).
Importante
Si estamos en un régimen sobreajustado, puede ser interesante disminuir la varianza a costa de aumentar un poco el sesgo. Esto se puede lograr penalizando la complejidad.
16.3.2. Mínimos cuadrados penalizado o Ridge Regression¶
Si asumimos una verosimilitud Gaussiana y un prior Gaussiano podemos escribir el logaritmo de la distribución conjunta:
En este caso, el estimador MAP de \(\theta\) está dado por:
donde \(\alpha = \frac{\sigma^2}{\sigma_0^2}\)
Cuya solución se obtiene derivado en función de \(\theta\) e igualando a cero:
y finalmente:
que se conoce como la solución de mínimos cuadrados regularizado, ridge regression o regularización de Tikhonov.
Nota
El prior Gaussiano es equivalente a implementar una penalización con norma \(L_2\) de \(\theta\). Distintos priors dan a lugar distintas penalizaciones, por ejemplo un prior Laplaciano resulta en una penalización con norma \(L_1\).
Importante
Regularizar un modelo consiste en añadir un término penalizador o restricción a la función de costo a minimizar, con tal de evitar sobreajuste o problemas numéricos relacionados a problemas “mal puestos” (ill-posed).
En el caso de los mínimos cuadrados regularizados, el hiperparámetro \(\alpha\) controla el compromiso entre minimizar el error cuadrático y minimizar la norma de los parámetros.
Si \(\alpha\) es muy pequeño es como ignorar la penalización (se reduce a OLS)
Si \(\alpha\) es muy grande entonces se puede inducir una penalización excesiva, donde el modelo no es capaz de ajustarse a los datos
Podemos seleccionar \(\alpha\) utilizando validación cruzada o el método de la “curva L”, el cual consiste en encontrar el codo de la curva de \( \log (Y-X\theta)^T (Y - X\theta)\) vs \( \log \|\theta\|^2\).
16.4. Mínimos cuadrados regularizados con statsmodels
¶
Podemos ajustar un modelo de mínimos cuadrados regularizados utilizando la función fit_regularized
de OLS. En particular, si utilizamos el argumento L1_wt=0
entonces estaremos haciendo regresión ridge. Si utilizamos L1_wt=1
estaremos haciendo regularización con norma \(L_1\) (que se conoce como LASSO).
Veamos como cambia el resultado de mínimos cuadrados regularizados en función de \(\alpha\) para un polinomio de grado 15
many_terms = "".join([f'+ I(X ** {j})' for j in range(2, 16)])
hatY = {}
for alpha in [0, 1e-4, 1e-2, 1., 1e+2, 1e+4, 1e+10]:
result = sm.OLS.from_formula(formula='Y ~ X'+many_terms, data=df).fit_regularized(L1_wt=0, alpha=alpha)
hatY[alpha] = result.predict(hatX)
hMap = hv.HoloMap(kdims=['alpha'])
for alpha, haty_ in hatY.items():
p_model = hv.Curve((hatX, haty_), 'X', 'Y', label='model')
p_data = hv.Scatter((x, y), label='data').opts(color='k')
hMap[alpha] = hv.Overlay([p_model, p_data]).opts(legend_position='top_left', ylim=(-40.0, 20.0))
hMap
Observaciones
Aplicar regularización puede disminuir el sobreajuste en modelos complejos
Pero, si la penalización es excesiva, se puede llegar a una solución trivial (linea horizontal)