Chargement des librairies et des données

In [ ]:
# Import necessary modules and set options
import pandas as pd
import numpy as np
import itertools
import matplotlib.pyplot as plt
import statsmodels.api as sm
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet, RidgeCV, LassoCV, ElasticNetCV, LarsCV, lars_path
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import mean_squared_error
import warnings
warnings.filterwarnings("ignore")
In [ ]:
prostate = pd.read_csv('../data/Prostate_Cancer.txt', delimiter=',')
In [ ]:
prostate.head(10)
In [ ]:
prostate = prostate.drop(['id'], axis=1)
prostate.shape

Jeux de données apprentissage et test

In [ ]:
# Train-test split
y_train = np.array(prostate[prostate.train == "T"]['lpsa'])
y_test = np.array(prostate[prostate.train == "F"]['lpsa'])
X_train = np.array(prostate[prostate.train == "T"].drop(['lpsa', 'train'], axis=1))
X_test = np.array(prostate[prostate.train == "F"].drop(['lpsa', 'train'], axis=1))

Baseline : régression linéaire classique

In [ ]:
linreg_model = LinearRegression(normalize=True).fit(X_train, y_train)
linreg_prediction = linreg_model.predict(X_test)
linreg_mse = np.mean((y_test - linreg_prediction)**2)
linreg_coefs = dict(
    zip(['Intercept'] + prostate.columns.tolist()[:-1], 
        np.round(np.concatenate((linreg_model.intercept_, linreg_model.coef_), 
        axis=None), 3))
)

print('Linear Regression MSE: {}'.format(np.round(linreg_mse, 3)))
print('Linear Regression coefficients:')
linreg_coefs

On est habitué à ça

In [ ]:
X1_train = sm.add_constant(X_train) # adding a constant
mod = sm.OLS(y_train, X1_train)
res = mod.fit()
print(res.summary())

Sous-ensemble optimal

Une approche simple pour choisir un sous-ensemble de variables pour la régression linéaire consiste à essayer toutes les combinaisons possibles et à en choisir une qui minimise un critère certain. C'est ce que vise la recherche du meilleur sous-ensemble de variables pour la régression. Pour chaque $k \in \{1, 2, \ldots, p\}$, où $p$ est le nombre total de variables disponibles, il sélectionne le sous-ensemble de taille $k$ qui donne la plus petite somme résiduelle de carrés. Cependant, la somme des carrés ne peut pas être utilisée comme critère pour déterminer $k$ elle-même, car elle diminue nécessairement avec $k$: plus le nombre de variables incluses dans le modèle est grand, plus ses résidus sont petits. Cela ne garantit toutefois pas de meilleures performances prédictives. C’est pourquoi un autre critère devrait être utilisé pour sélectionner le modèle final. Pour les modèles axés sur la prédiction, une erreur (éventuellement croisée) sur les données de test est un choix courant. Comme la recherche du meilleur sous-ensemble de régression n'est implémentée dans aucun package Python, nous devons effectuer une boucle sur $k$ et tous les sous-ensembles de taille $k$ manuellement. Le bout de code suivant implémente la méthode :

In [ ]:
results = pd.DataFrame(columns=['num_features', 'features', 'MSE'])

# Loop over all possible numbers of features to be included
for k in range(1, X_train.shape[1] + 1):
    # Loop over all possible subsets of size k
    for subset in itertools.combinations(range(X_train.shape[1]), k):
        subset = list(subset)
        linreg_model = LinearRegression(normalize=True).fit(X_train[:, subset], y_train)
        linreg_prediction = linreg_model.predict(X_test[:, subset])
        linreg_mse = np.mean((y_test - linreg_prediction)**2)
        results = results.append(pd.DataFrame([{'num_features': k,
                                                'features': subset,
                                                'MSE': linreg_mse}]))

# Inspect best combinations
results = results.sort_values('MSE').reset_index()
print(results.head())

# Fit best model
best_subset_model = LinearRegression(normalize=True).fit(X_train[:, results['features'][0]], y_train)
best_subset_coefs = dict(
    zip(['Intercept'] + prostate.columns.tolist()[:-1], 
        np.round(np.concatenate((best_subset_model.intercept_, best_subset_model.coef_), axis=None), 3))
)

print('Best Subset Regression MSE: {}'.format(np.round(results['MSE'][0], 4)))
print('Best Subset Regression coefficients:')
best_subset_coefs

Régression Ridge

In [ ]:
n_alphas = 300
alphas = np.logspace(-5, 4, n_alphas)
coefs = []
for a in alphas:
    ridge =  Ridge(alpha=a, fit_intercept=False)
    ridge.fit(X_train, y_train)
    coefs.append(ridge.coef_)

# #############################################################################
# Display results
ax = plt.gca()
ax.plot(alphas, coefs)
ax.set_xscale('log')
ax.set_xlim(ax.get_xlim()[::-1])  # reverse axis
plt.xlabel('alpha')
plt.ylabel('weights')
plt.title('Ridge coefficients as a function of the regularization')
plt.axis('tight')
plt.show()

ridge_cv = RidgeCV(normalize=True, alphas=np.logspace(-5, 4, n_alphas), store_cv_values=True)
ridge_model = ridge_cv.fit(X_train, y_train)
ridge_prediction = ridge_model.predict(X_test)
ridge_mse = np.mean((y_test - ridge_prediction)**2)
ridge_coefs = dict(
    zip(['Intercept'] + prostate.columns.tolist()[:-1], 
        np.round(np.concatenate((ridge_model.intercept_, ridge_model.coef_), 
                                axis=None), 3))
)

print('Ridge Regression MSE: {}'.format(np.round(ridge_mse, 3)))
print('Ridge Regression coefficients:')
ridge_coefs

Erreur évaluée par validation croisée en fonction de $\alpha$

In [ ]:
errors = ridge_cv.cv_values_.mean(axis=0)
ax = plt.gca()
ax.plot(alphas, errors)
ax.set_xscale('log')
plt.xlabel('alpha')
plt.ylabel('error')
plt.axis('tight')
plt.show()

Régression Lasso

In [ ]:
print("Computing regularization path using the LARS ...")
_, _, coefs = lars_path(X_train, y_train, method='lasso', verbose=True)

xx = np.sum(np.abs(coefs.T), axis=1)
xx /= xx[-1]

plt.plot(xx, coefs.T)
ymin, ymax = plt.ylim()
plt.vlines(xx, ymin, ymax, linestyle='dashed')
plt.xlabel('|coef| / max|coef|')
plt.ylabel('Coefficients')
plt.title('LASSO Path')
plt.axis('tight')
plt.show()

lasso_cv = LassoCV(normalize=True, alphas=np.logspace(-5, 4, n_alphas))
lasso_model = lasso_cv.fit(X_train, y_train)
lasso_prediction = lasso_model.predict(X_test)
lasso_mse = np.mean((y_test - lasso_prediction)**2)
lasso_coefs = dict(
    zip(['Intercept'] + prostate.columns.tolist()[:-1], 
        np.round(np.concatenate((lasso_model.intercept_, lasso_model.coef_), axis=None), 3))
)

print('LASSO MSE: {}'.format(np.round(lasso_mse, 3)))
print('LASSO coefficients:')
lasso_coefs

Erreur quadratique moyenne calculée par validation croisée en fonction de la valeur de alpha

In [ ]:
errors = lasso_cv.mse_path_.mean(axis=1)
ax = plt.gca()
ax.plot(alphas, errors)
ax.set_xscale('log')
plt.xlabel('alpha')
plt.ylabel('error')
plt.axis('tight')
plt.show()

Régression Elasticnet

In [ ]:
elastic_net_cv = ElasticNetCV(normalize=False, alphas=np.logspace(-5, 4, 400), 
                              l1_ratio=np.linspace(0, 1, 100), cv=3)
elastic_net_model = elastic_net_cv.fit(X_train, y_train)
elastic_net_prediction = elastic_net_model.predict(X_test)
elastic_net_mse = np.mean((y_test - elastic_net_prediction)**2)
elastic_net_coefs = dict(
    zip(['Intercept'] + prostate.columns.tolist()[:-1], 
        np.round(np.concatenate((elastic_net_model.intercept_, 
                                 elastic_net_model.coef_), axis=None), 3))
)

Affichage des résultats

In [ ]:
print('Best alpha : {}'.format(np.round(elastic_net_model.alpha_, 3)))
print('Best l1_ratio : {}'.format(np.round(elastic_net_model.l1_ratio_, 3)))

print('Elastic Net MSE: {}'.format(np.round(elastic_net_mse, 3)))
print('Elastic Net coefficients:')
elastic_net_coefs

Utilisation de la fonction GridSearchCV

In [ ]:
# Create the hyperparameter grid
alphas = np.logspace(-5, 4, n_alphas)
l1_space = np.linspace(0, 1, 100)
param_grid = {'l1_ratio': l1_space, 'alpha':alphas}

# Instantiate the ElasticNet regressor: elastic_net
elastic_net = ElasticNet(normalize=True)

# Setup the GridSearchCV object: gm_cv
gm_cv = GridSearchCV(elastic_net, param_grid, cv=3, n_jobs=3) ## attention n_jobs=-1 va utiliser tous les CPUs 

Maintenant on lance la recherche des hyper-paramètres optimaux par validation-croisée

In [ ]:
# Fit it to the training data
np.random.seed(11)
gm_cv.fit(X_train, y_train)

Affichage des résultats

In [ ]:
# Predict on the test set and compute metrics
y_pred = gm_cv.predict(X_test)
r2 = gm_cv.score(X_test, y_test)
mse = mean_squared_error(y_test, y_pred)
print("Tuned ElasticNet alpha and l1 ratio: {}".format(gm_cv.best_params_))
print("Tuned ElasticNet R squared: {}".format(r2))
print("Tuned ElasticNet MSE: {}".format(mse))

Affichage du meilleur modèle

In [ ]:
best_coefs = dict(zip(['Intercept'] + prostate.columns.tolist()[:-1], 
        np.round(np.concatenate((gm_cv.best_estimator_.intercept_, 
                                 gm_cv.best_estimator_.coef_), axis=None), 3)))
print("Best model:")
best_coefs