12  Modelos de regresión

Objetivo

El objetivo de esta clase es que los estudiantes aprendan a implementar en Python varios modelos de regresión.

La regresión lineal es el algoritmo más sencillo del aprendizaje automático y se puede entrenar de distintas formas. En este cuaderno, cubriremos los siguientes algoritmos lineales:

  1. Linear Regression
  2. Robust Regression
  3. Ridge Regression
  4. LASSO Regression
  5. Elastic Net
  6. Polynomial Regression
  7. Stochastic Gradient Descent
  8. Artificial Neural Networks
  9. Random Forest Regressor
  10. Support Vector Machine

Usaremos la base de datos USA_Housing, que contiene la siguiente información:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

pd.set_option('display.float', '{:.2f}'.format)
from decimal import Decimal, getcontext

getcontext().prec = 2

%matplotlib inline

sns.set_style("whitegrid")
plt.style.use("fivethirtyeight")

12.1 Análisis exploratorio de datos

USAhousing = pd.read_csv('USA_Housing.csv')
USAhousing.head()
USAhousing.info()
USAhousing.describe()
USAhousing.columns
sns.pairplot(USAhousing)
plt.show()
sns.heatmap(USAhousing.corr(numeric_only=True), annot=True, fmt='0.2f')
plt.show()

12.2 Preparación de los datos

X = USAhousing[['Avg. Area Income', 'Avg. Area House Age', 'Avg. Area Number of Rooms',
               'Avg. Area Number of Bedrooms', 'Area Population']]
y = USAhousing['Price']
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
from sklearn import metrics
from sklearn.model_selection import cross_val_score

def cross_val(model):
    pred = cross_val_score(model, X, y, cv=10)
    return pred.mean()

def print_evaluate(true, predicted):  
    mae = metrics.mean_absolute_error(true, predicted)
    mse = metrics.mean_squared_error(true, predicted)
    rmse = np.sqrt(metrics.mean_squared_error(true, predicted))
    r2_square = metrics.r2_score(true, predicted)
    print(f'MAE: {mae:0.2f}')
    print(f'MSE: {mse:0.2f}')
    print(f'RMSE:: {rmse:0.2f}')
    print(f'R2 Square: {r2_square:0.2f}')
    print('__________________________________')
    
def evaluate(true, predicted):
    mae = metrics.mean_absolute_error(true, predicted)
    mse = metrics.mean_squared_error(true, predicted)
    rmse = np.sqrt(metrics.mean_squared_error(true, predicted))
    r2_square = metrics.r2_score(true, predicted)
    return mae, mse, rmse, r2_square

La regresión lineal se ha estudiado en profundidad y existe mucha literatura sobre cómo se deben estructurar los datos para aprovechar al máximo el modelo.

Por ello, hay mucha sofisticación al hablar de estos requisitos y expectativas, lo que puede resultar intimidante. En la práctica, puede utilizar estas reglas más como reglas generales al utilizar la regresión de mínimos cuadrados ordinarios, la implementación más común de la regresión lineal.

Pruebe diferentes preparaciones de sus datos utilizando estas heurísticas y vea qué funciona mejor para su problema.

  • Suposición lineal. La regresión lineal supone que la relación entre su entrada y salida es lineal. No admite nada más. Esto puede ser obvio, pero es bueno recordarlo cuando tiene muchos atributos. Es posible que deba transformar los datos para que la relación sea lineal (por ejemplo, transformación logarítmica para una relación exponencial).
  • Eliminar ruido. La regresión lineal supone que sus variables de entrada y salida no son ruidosas. Considere utilizar operaciones de limpieza de datos que le permitan exponer y aclarar mejor la señal en sus datos. Esto es más importante para la variable de salida y desea eliminar los valores atípicos en la variable de salida (y) si es posible.
  • Eliminar colinealidad. La regresión lineal sobreajustará sus datos cuando tenga variables de entrada altamente correlacionadas. Considere calcular correlaciones por pares para sus datos de entrada y eliminar los más correlacionados.
  • Distribuciones gaussianas. La regresión lineal hará predicciones más confiables si sus variables de entrada y salida tienen una distribución gaussiana. Puede obtener algún beneficio al usar transformaciones (por ejemplo, log o BoxCox) en sus variables para hacer que su distribución se vea más gaussiana.
  • Reescalar entradas: La regresión lineal a menudo hará predicciones más confiables si reescalar las variables de entrada usando estandarización o normalización.
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

pipeline = Pipeline([
    ('std_scalar', StandardScaler())
])

X_train = pipeline.fit_transform(X_train)
X_test = pipeline.transform(X_test)

12.3 Modelos de regresión

12.3.1 Linear Regression

from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(X_train,y_train)
print(lin_reg.intercept_)
coeff_df = pd.DataFrame(lin_reg.coef_, X.columns, columns=['Coefficient'])
coeff_df
pred = lin_reg.predict(X_test)
sns.scatterplot(x=y_test, y=pred)
plt.xlabel('Valores reales')
plt.ylabel('Valores predichos')
plt.show()
sns.kdeplot(x=y_test-pred)
plt.show()

A continuación se presentan tres métricas de evaluación comunes para problemas de regresión:

  • Mean Absolute Error (MAE) es la media del valor absoluto de los errores:

\[\frac 1n\sum_{i=1}^n|y_i-\hat{y}_i|\]

  • Mean Squared Error (MSE) es la media de los errores al cuadrado:

\[\frac 1n\sum_{i=1}^n(y_i-\hat{y}_i)^2\]

  • Root Mean Squared Error (RMSE) es la raíz cuadrada de la media de los errores al cuadrado:

\[\sqrt{\frac 1n\sum_{i=1}^n(y_i-\hat{y}_i)^2}\]

  • MAE es la más fácil de entender, porque es el error promedio.
  • MSE es más popular que MAE, porque MSE “castiga” los errores mayores, lo que tiende a ser útil en el mundo real.
  • RMSE es incluso más popular que MSE, porque RMSE es interpretable en las unidades de \(y\).

Todas estas son funciones de pérdida, porque queremos minimizarlas.

test_pred = lin_reg.predict(X_test)
train_pred = lin_reg.predict(X_train)

print('Test set evaluation:\n_____________________________________')
print_evaluate(y_test, test_pred)
print('Train set evaluation:\n_____________________________________')
print_evaluate(y_train, train_pred)

results_df = pd.DataFrame(data=[["Linear Regression", *evaluate(y_test, test_pred) , cross_val(LinearRegression())]], 
                          columns=['Model', 'MAE', 'MSE', 'RMSE', 'R2 Square', "Cross Validation"])

12.3.2 Robust Regression

La regresión robusta es una forma de análisis de regresión diseñada para superar algunas limitaciones de los métodos paramétricos y no paramétricos tradicionales. Los métodos de regresión robusta están diseñados para no verse excesivamente afectados por las violaciones de los supuestos del proceso subyacente de generación de datos.

Un caso en el que se debe considerar la estimación robusta es cuando existe una fuerte sospecha de “heteroscedasticidad”.

Una situación común en la que se utiliza la estimación robusta se produce cuando los datos contienen valores atípicos. En presencia de valores atípicos que no provienen del mismo proceso de generación de datos que el resto de los datos, la estimación de mínimos cuadrados es ineficiente y puede estar sesgada. Debido a que las predicciones de mínimos cuadrados se arrastran hacia los valores atípicos, y debido a que la varianza de las estimaciones se infla artificialmente, el resultado es que los valores atípicos pueden quedar enmascarados. (En muchas situaciones, incluidas algunas áreas de geoestadística y estadística médica, son precisamente los valores atípicos los que interesan).

from sklearn.linear_model import RANSACRegressor

model = RANSACRegressor(max_trials=100)
model.fit(X_train, y_train)

test_pred = model.predict(X_test)
train_pred = model.predict(X_train)

print('Test set evaluation:\n_____________________________________')
print_evaluate(y_test, test_pred)
print('====================================')
print('Train set evaluation:\n_____________________________________')
print_evaluate(y_train, train_pred)

results_df_2 = pd.DataFrame(data=[["Robust Regression", *evaluate(y_test, test_pred) , cross_val(RANSACRegressor())]], 
                            columns=['Model', 'MAE', 'MSE', 'RMSE', 'R2 Square', "Cross Validation"])
results_df = pd.concat([results_df,results_df_2])

12.3.3 Ridge Regression

La regresión ridge aborda algunos de los problemas de los mínimos cuadrados ordinarios al imponer una penalización en el tamaño de los coeficientes (Regularizacion de Tikhonov). Los coeficientes ridge minimizan una suma residual penalizada de cuadrados,

\[\min_{w}\big|\big|Xw-y\big|\big|^2_2+\alpha\big|\big|w\big|\big|^2_2\]

\(\alpha\geq0\) es un parámetro de complejidad que controla la cantidad de contracción: cuanto mayor sea el valor de \(\alpha\), mayor será la cantidad de contracción y, por lo tanto, los coeficientes se vuelven más robustos a la colinealidad.

La regresión ridge es un modelo penalizado L2.

from sklearn.linear_model import Ridge

model = Ridge(alpha=100, solver='cholesky', tol=0.0001, random_state=42)
model.fit(X_train, y_train)
pred = model.predict(X_test)

test_pred = model.predict(X_test)
train_pred = model.predict(X_train)

print('Test set evaluation:\n_____________________________________')
print_evaluate(y_test, test_pred)
print('====================================')
print('Train set evaluation:\n_____________________________________')
print_evaluate(y_train, train_pred)

results_df_2 = pd.DataFrame(data=[["Ridge Regression", *evaluate(y_test, test_pred) , cross_val(Ridge())]], 
                            columns=['Model', 'MAE', 'MSE', 'RMSE', 'R2 Square', "Cross Validation"])
results_df = pd.concat([results_df,results_df_2])

13.3.4 LASSO Regression

Un modelo lineal que estima coeficientes dispersos.

Matemáticamente, consiste en un modelo lineal entrenado con L1 a priori como regularizador. La función objetivo a minimizar es:

\[\min_{w}\frac{1}{2n_{samples}} \big|\big|Xw - y\big|\big|_2^2 + \alpha \big|\big|w\big|\big|_1\]

La estimación del lazo resuelve así la minimización de la penalización de mínimos cuadrados con \(\alpha \big|\big|w\big|\big|_1\) añadido, donde \(\alpha\) es una constante y \(\big|\big|w\big|\big|_1\) es la normal L1 del vector de parámetros.

from sklearn.linear_model import Lasso

model = Lasso(alpha=0.1, 
              precompute=True, 
#               warm_start=True, 
              positive=True, 
              selection='random',
              random_state=42)
model.fit(X_train, y_train)

test_pred = model.predict(X_test)
train_pred = model.predict(X_train)

print('Test set evaluation:\n_____________________________________')
print_evaluate(y_test, test_pred)
print('====================================')
print('Train set evaluation:\n_____________________________________')
print_evaluate(y_train, train_pred)

results_df_2 = pd.DataFrame(data=[["Lasso Regression", *evaluate(y_test, test_pred) , cross_val(Lasso())]], 
                            columns=['Model', 'MAE', 'MSE', 'RMSE', 'R2 Square', "Cross Validation"])
results_df = pd.concat([results_df,results_df_2])

13.3.5 Elastic Net

Un modelo de regresión lineal entrenado con L1 y L2 a priori como regularizador. Esta combinación permite aprender un modelo disperso donde pocos de los pesos son distintos de cero como Lasso, manteniendo al mismo tiempo las propiedades de regularización de Ridge. Elastic-net es útil cuando hay múltiples características que están correlacionadas entre sí. Es probable que Lasso elija una de ellas al azar, mientras que elastic-net probablemente elija ambas. Una ventaja práctica de la compensación entre Lasso y Ridge es que permite a Elastic-Net heredar parte de la estabilidad de Ridge bajo rotación. La función objetivo a minimizar es en este caso

\[\min_{w}{\frac{1}{2n_{samples}} \big|\big|X w - y\big|\big|_2 ^ 2 + \alpha \rho \big|\big|w\big|\big|_1 + \frac{\alpha(1-\rho)}{2} \big|\big|w\big|\big|_2^2}\]

from sklearn.linear_model import ElasticNet

model = ElasticNet(alpha=0.1, l1_ratio=0.9, selection='random', random_state=42)
model.fit(X_train, y_train)

test_pred = model.predict(X_test)
train_pred = model.predict(X_train)

print('Test set evaluation:\n_____________________________________')
print_evaluate(y_test, test_pred)
print('====================================')
print('Train set evaluation:\n_____________________________________')
print_evaluate(y_train, train_pred)

results_df_2 = pd.DataFrame(data=[["Elastic Net Regression", *evaluate(y_test, test_pred) , cross_val(ElasticNet())]], 
                            columns=['Model', 'MAE', 'MSE', 'RMSE', 'R2 Square', "Cross Validation"])
results_df = pd.concat([results_df,results_df_2])

12.3.6 Polynomial Regression

Un patrón común en el aprendizaje automático es el uso de modelos lineales entrenados con funciones no lineales de los datos. Este enfoque mantiene el rendimiento generalmente rápido de los métodos lineales, al tiempo que les permite adaptarse a una gama mucho más amplia de datos.

Por ejemplo, una regresión lineal simple se puede ampliar mediante la construcción de características polinómicas a partir de los coeficientes. En el caso de la regresión lineal estándar, es posible que tenga un modelo que se parezca a esto para datos bidimensionales:

\[\hat{y}(w, x) = w_0 + w_1 x_1 + w_2 x_2\]

Si queremos ajustar un paraboloide a los datos en lugar de un plano, podemos combinar las características en polinomios de segundo orden, de modo que el modelo se vea así:

\[\hat{y}(w, x) = w_0 + w_1 x_1 + w_2 x_2 + w_3 x_1 x_2 + w_4 x_1^2 + w_5 x_2^2\]

La observación (a veces sorprendente) es que este sigue siendo un modelo lineal: para ver esto, imagine crear una nueva variable

\[z = [x_1, x_2, x_1 x_2, x_1^2, x_2^2]\]

Con este reetiquetado de los datos, nuestro problema se puede escribir

\[\hat{y}(w, z) = w_0 + w_1 z_1 + w_2 z_2 + w_3 z_3 + w_4 z_4 + w_5 z_5\]

Vemos que la regresión polinómica resultante pertenece a la misma clase de modelos lineales que hemos considerado anteriormente (es decir, el modelo es lineal en w) y se puede resolver con las mismas técnicas. Al considerar ajustes lineales dentro de un espacio de mayor dimensión construido con estas funciones base, el modelo tiene la flexibilidad de ajustarse a un rango mucho más amplio de datos.

from sklearn.preprocessing import PolynomialFeatures

poly_reg = PolynomialFeatures(degree=2)

X_train_2_d = poly_reg.fit_transform(X_train)
X_test_2_d = poly_reg.transform(X_test)

lin_reg = LinearRegression()
lin_reg.fit(X_train_2_d,y_train)

test_pred = lin_reg.predict(X_test_2_d)
train_pred = lin_reg.predict(X_train_2_d)

print('Test set evaluation:\n_____________________________________')
print_evaluate(y_test, test_pred)
print('====================================')
print('Train set evaluation:\n_____________________________________')
print_evaluate(y_train, train_pred)

results_df_2 = pd.DataFrame(data=[["Polynomail Regression", *evaluate(y_test, test_pred), 0]], 
                            columns=['Model', 'MAE', 'MSE', 'RMSE', 'R2 Square', 'Cross Validation'])
results_df = pd.concat([results_df,results_df_2])

12.3.7 Stochastic Gradient Descent

El descenso de gradiente es un algoritmo de optimización muy genérico capaz de encontrar soluciones óptimas para una amplia gama de problemas. La idea general del descenso de gradiente es ajustar los parámetros de forma iterativa para minimizar una función de costo. El descenso de gradiente mide el gradiente local de la función de error con respecto al vector de parámetros y va en la dirección del gradiente descendente. Una vez que el gradiente es cero, se ha alcanzado un mínimo.

from sklearn.linear_model import SGDRegressor

sgd_reg = SGDRegressor(n_iter_no_change=250, penalty=None, eta0=0.0001, max_iter=100000)
sgd_reg.fit(X_train, y_train)

test_pred = sgd_reg.predict(X_test)
train_pred = sgd_reg.predict(X_train)

print('Test set evaluation:\n_____________________________________')
print_evaluate(y_test, test_pred)
print('====================================')
print('Train set evaluation:\n_____________________________________')
print_evaluate(y_train, train_pred)

results_df_2 = pd.DataFrame(data=[["Stochastic Gradient Descent", *evaluate(y_test, test_pred), 0]], 
                            columns=['Model', 'MAE', 'MSE', 'RMSE', 'R2 Square', 'Cross Validation'])
results_df = pd.concat([results_df,results_df_2])

12.3.8 Artficial Neural Network

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Activation, Dropout
from tensorflow.keras.optimizers import Adam

X_train = np.array(X_train)
X_test = np.array(X_test)
y_train = np.array(y_train)
y_test = np.array(y_test)

model = Sequential()

model.add(Dense(X_train.shape[1], activation='relu'))
model.add(Dense(32, activation='relu'))
# model.add(Dropout(0.2))

model.add(Dense(64, activation='relu'))
# model.add(Dropout(0.2))

model.add(Dense(128, activation='relu'))
# model.add(Dropout(0.2))

model.add(Dense(512, activation='relu'))
model.add(Dropout(0.1))
model.add(Dense(1))

model.compile(optimizer=Adam(0.00001), loss='mse')

r = model.fit(X_train, y_train,
              validation_data=(X_test,y_test),
              batch_size=1,
              epochs=100)
test_pred = model.predict(X_test)
train_pred = model.predict(X_train)

print('Test set evaluation:\n_____________________________________')
print_evaluate(y_test, test_pred)

print('Train set evaluation:\n_____________________________________')
print_evaluate(y_train, train_pred)

results_df_2 = pd.DataFrame(data=[["Artficial Neural Network", *evaluate(y_test, test_pred), 0]], 
                            columns=['Model', 'MAE', 'MSE', 'RMSE', 'R2 Square', 'Cross Validation'])
results_df = pd.concat([results_df,results_df_2])

12.3.9 Random Forest Regressor

from sklearn.ensemble import RandomForestRegressor

rf_reg = RandomForestRegressor(n_estimators=1000)
rf_reg.fit(X_train, y_train)

test_pred = rf_reg.predict(X_test)
train_pred = rf_reg.predict(X_train)

print('Test set evaluation:\n_____________________________________')
print_evaluate(y_test, test_pred)

print('Train set evaluation:\n_____________________________________')
print_evaluate(y_train, train_pred)

results_df_2 = pd.DataFrame(data=[["Random Forest Regressor", *evaluate(y_test, test_pred), 0]], 
                            columns=['Model', 'MAE', 'MSE', 'RMSE', 'R2 Square', 'Cross Validation'])
results_df = pd.concat([results_df,results_df_2])

12.3.10 Support Vector Machine

from sklearn.svm import SVR

svm_reg = SVR(kernel='rbf', C=1000000, epsilon=0.001)
svm_reg.fit(X_train, y_train)

test_pred = svm_reg.predict(X_test)
train_pred = svm_reg.predict(X_train)

print('Test set evaluation:\n_____________________________________')
print_evaluate(y_test, test_pred)

print('Train set evaluation:\n_____________________________________')
print_evaluate(y_train, train_pred)

results_df_2 = pd.DataFrame(data=[["SVM Regressor", *evaluate(y_test, test_pred), 0]], 
                            columns=['Model', 'MAE', 'MSE', 'RMSE', 'R2 Square', 'Cross Validation'])
results_df = pd.concat([results_df,results_df_2])
results_df

12.4 Comparación de los modelos

results_df.set_index('Model', inplace=True)
results_df['R2 Square'].plot(kind='barh', figsize=(12, 8))
plt.show()

12.5 Ejercicios prácticos

  1. Cree un nuevo Notebook.
  2. Guarde el archivo como Ejercicios_practicos_clase_11.ipynb.
  3. Asigne un título H1 con su nombre.

12.5.1 Ejercicio práctico 1

Implemente dos modelos de regresión usando la base de datos Air Quality.