Séance 8: TP3 - Régression & Optimisation

NoteInformations de la séance
  • Type: Travaux Pratiques
  • Durée: 2h
  • Objectifs: Obj6, Obj7

Introduction

Dans ce TP, nous allons mettre en pratique les concepts de régression vus en cours. Nous travaillerons sur un dataset réel pour prédire les prix de maisons, en comparant différents modèles de régression et en optimisant leurs hyperparamètres.

Objectifs du TP:

  1. Prétraiter des données pour la régression
  2. Implémenter et comparer Linear, Ridge, Lasso, ElasticNet, SVR
  3. Optimiser les hyperparamètres avec CV
  4. Évaluer avec métriques multiples (MAE, RMSE, R²)
  5. Interpréter et visualiser les résultats

1. Chargement et Exploration du Dataset

Nous utilisons le Boston Housing Dataset (ou California Housing si Boston non disponible).

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# Chargement
california = fetch_california_housing()
X, y = california.data, california.target

# DataFrame pour exploration
df = pd.DataFrame(X, columns=california.feature_names)
df['MedHouseVal'] = y

print("=" * 70)
print("DATASET: CALIFORNIA HOUSING")
print("=" * 70)
print(f"\nShape: {X.shape}")
print(f"Features: {california.feature_names}")
print(f"\nDescription du target (prix médian en 100k$):")
print(df['MedHouseVal'].describe())

# Statistiques descriptives
print("\n" + "=" * 70)
print("STATISTIQUES DESCRIPTIVES")
print("=" * 70)
print(df.describe().T)

# Vérifier les valeurs manquantes
print(f"\nValeurs manquantes par feature:")
print(df.isnull().sum())

# Visualisations
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

# Distribution du target
axes[0, 0].hist(y, bins=50, edgecolor='black', alpha=0.7)
axes[0, 0].set_xlabel('Prix Médian (100k$)')
axes[0, 0].set_ylabel('Fréquence')
axes[0, 0].set_title('Distribution des Prix')
axes[0, 0].axvline(y.mean(), color='red', linestyle='--', label=f'Moyenne: {y.mean():.2f}')
axes[0, 0].legend()

# Boxplot des features (normalisées pour comparaison)
df_normalized = (df - df.mean()) / df.std()
axes[0, 1].boxplot([df_normalized[col] for col in california.feature_names],
                    labels=california.feature_names, vert=True)
axes[0, 1].set_xticklabels(california.feature_names, rotation=45, ha='right')
axes[0, 1].set_ylabel('Valeurs normalisées')
axes[0, 1].set_title('Distribution des Features (normalisées)')
axes[0, 1].grid(alpha=0.3)

# Matrice de corrélation
corr_matrix = df.corr()
mask = np.triu(np.ones_like(corr_matrix, dtype=bool))
sns.heatmap(corr_matrix, mask=mask, annot=True, fmt='.2f', cmap='coolwarm',
            center=0, ax=axes[1, 0], cbar_kws={'label': 'Corrélation'})
axes[1, 0].set_title('Matrice de Corrélation')

# Scatter: Feature la plus corrélée avec target
corr_with_target = corr_matrix['MedHouseVal'].drop('MedHouseVal').abs().sort_values(ascending=False)
best_feature = corr_with_target.index[0]
axes[1, 1].scatter(df[best_feature], df['MedHouseVal'], alpha=0.3)
axes[1, 1].set_xlabel(best_feature)
axes[1, 1].set_ylabel('Prix Médian')
axes[1, 1].set_title(f'Prix vs {best_feature} (corr={corr_matrix.loc[best_feature, "MedHouseVal"]:.2f})')
axes[1, 1].grid(alpha=0.3)

plt.tight_layout()
plt.show()

print("\n" + "=" * 70)
print("CORRÉLATIONS AVEC LE TARGET")
print("=" * 70)
print(corr_with_target)
======================================================================
DATASET: CALIFORNIA HOUSING
======================================================================

Shape: (20640, 8)
Features: ['MedInc', 'HouseAge', 'AveRooms', 'AveBedrms', 'Population', 'AveOccup', 'Latitude', 'Longitude']

Description du target (prix médian en 100k$):
count    20640.000000
mean         2.068558
std          1.153956
min          0.149990
25%          1.196000
50%          1.797000
75%          2.647250
max          5.000010
Name: MedHouseVal, dtype: float64

======================================================================
STATISTIQUES DESCRIPTIVES
======================================================================
               count         mean          std         min         25%  \
MedInc       20640.0     3.870671     1.899822    0.499900    2.563400   
HouseAge     20640.0    28.639486    12.585558    1.000000   18.000000   
AveRooms     20640.0     5.429000     2.474173    0.846154    4.440716   
AveBedrms    20640.0     1.096675     0.473911    0.333333    1.006079   
Population   20640.0  1425.476744  1132.462122    3.000000  787.000000   
AveOccup     20640.0     3.070655    10.386050    0.692308    2.429741   
Latitude     20640.0    35.631861     2.135952   32.540000   33.930000   
Longitude    20640.0  -119.569704     2.003532 -124.350000 -121.800000   
MedHouseVal  20640.0     2.068558     1.153956    0.149990    1.196000   

                     50%          75%           max  
MedInc          3.534800     4.743250     15.000100  
HouseAge       29.000000    37.000000     52.000000  
AveRooms        5.229129     6.052381    141.909091  
AveBedrms       1.048780     1.099526     34.066667  
Population   1166.000000  1725.000000  35682.000000  
AveOccup        2.818116     3.282261   1243.333333  
Latitude       34.260000    37.710000     41.950000  
Longitude    -118.490000  -118.010000   -114.310000  
MedHouseVal     1.797000     2.647250      5.000010  

Valeurs manquantes par feature:
MedInc         0
HouseAge       0
AveRooms       0
AveBedrms      0
Population     0
AveOccup       0
Latitude       0
Longitude      0
MedHouseVal    0
dtype: int64


======================================================================
CORRÉLATIONS AVEC LE TARGET
======================================================================
MedInc        0.688075
AveRooms      0.151948
Latitude      0.144160
HouseAge      0.105623
AveBedrms     0.046701
Longitude     0.045967
Population    0.024650
AveOccup      0.023737
Name: MedHouseVal, dtype: float64

Exercice 1.1: Analyse Exploratoire

Questions:

  1. Quelle feature est la plus corrélée avec le prix?
  2. Y a-t-il des features fortement corrélées entre elles? (Multicolinéarité potentielle?)
  3. La distribution du target est-elle gaussienne? Quel traitement pourrait être appliqué si non?
  4. Identifiez des outliers potentiels
print("=" * 70)
print("ANALYSE EXPLORATOIRE - RÉPONSES")
print("=" * 70)

# 1. Feature la plus corrélée
print(f"\n1. Feature la plus corrélée avec le prix:")
print(f"   → {best_feature} (corrélation = {corr_matrix.loc[best_feature, 'MedHouseVal']:.3f})")

# 2. Multicolinéarité
print(f"\n2. Paires de features fortement corrélées (|corr| > 0.7):")
corr_pairs = []
for i in range(len(corr_matrix.columns)):
    for j in range(i+1, len(corr_matrix.columns)):
        if i != j:
            corr_val = corr_matrix.iloc[i, j]
            if abs(corr_val) > 0.7:
                corr_pairs.append((corr_matrix.columns[i], corr_matrix.columns[j], corr_val))

if corr_pairs:
    for feat1, feat2, corr_val in corr_pairs:
        print(f"   • {feat1} <-> {feat2}: {corr_val:.3f}")
else:
    print("   → Aucune multicolinéarité forte détectée")

# 3. Distribution du target
from scipy import stats
shapiro_stat, shapiro_p = stats.shapiro(y[:1000])  # Test sur échantillon
print(f"\n3. Test de normalité (Shapiro-Wilk):")
print(f"   Statistique: {shapiro_stat:.4f}, p-value: {shapiro_p:.4e}")
if shapiro_p < 0.05:
    print("   → Distribution NON gaussienne (p < 0.05)")
    print("   Traitements possibles:")
    print("   • Transformation log(y)")
    print("   • Transformation Box-Cox")
    print("   • Utiliser des modèles robustes aux distributions non-gaussiennes")
else:
    print("   → Distribution gaussienne (p >= 0.05)")

# Skewness et Kurtosis
skewness = stats.skew(y)
kurtosis = stats.kurtosis(y)
print(f"   Asymétrie (Skewness): {skewness:.3f}")
print(f"   Aplatissement (Kurtosis): {kurtosis:.3f}")

# 4. Outliers
print(f"\n4. Détection d'outliers:")
Q1 = df['MedHouseVal'].quantile(0.25)
Q3 = df['MedHouseVal'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

outliers = df[(df['MedHouseVal'] < lower_bound) | (df['MedHouseVal'] > upper_bound)]
print(f"   Intervalle IQR: [{Q1:.2f}, {Q3:.2f}]")
print(f"   Bornes: [{lower_bound:.2f}, {upper_bound:.2f}]")
print(f"   Nombre d'outliers: {len(outliers)} ({len(outliers)/len(df)*100:.1f}%)")

# Visualisation des outliers
fig, ax = plt.subplots(figsize=(10, 6))
ax.scatter(range(len(y)), np.sort(y), alpha=0.5, s=10)
ax.axhline(lower_bound, color='red', linestyle='--', label='Lower bound')
ax.axhline(upper_bound, color='red', linestyle='--', label='Upper bound')
ax.set_xlabel('Index (trié)')
ax.set_ylabel('Prix')
ax.set_title('Détection d\'Outliers par IQR')
ax.legend()
ax.grid(alpha=0.3)
plt.show()
======================================================================
ANALYSE EXPLORATOIRE - RÉPONSES
======================================================================

1. Feature la plus corrélée avec le prix:
   → MedInc (corrélation = 0.688)

2. Paires de features fortement corrélées (|corr| > 0.7):
   • AveRooms <-> AveBedrms: 0.848
   • Latitude <-> Longitude: -0.925

3. Test de normalité (Shapiro-Wilk):
   Statistique: 0.9460, p-value: 1.2095e-18
   → Distribution NON gaussienne (p < 0.05)
   Traitements possibles:
   • Transformation log(y)
   • Transformation Box-Cox
   • Utiliser des modèles robustes aux distributions non-gaussiennes
   Asymétrie (Skewness): 0.978
   Aplatissement (Kurtosis): 0.328

4. Détection d'outliers:
   Intervalle IQR: [1.20, 2.65]
   Bornes: [-0.98, 4.82]
   Nombre d'outliers: 1071 (5.2%)

Conclusions typiques:

  1. MedInc (revenu médian) généralement le plus corrélé (~0.68)
  2. Multicolinéarité possible → considérer Ridge/ElasticNet
  3. Distribution souvent légèrement asymétrique → transformation log possible
  4. Quelques outliers mais acceptable (<5%)

2. Prétraitement et Split

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# Split train/test (80/20)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

print("=" * 70)
print("SPLIT DES DONNÉES")
print("=" * 70)
print(f"Train set: {X_train.shape}")
print(f"Test set: {X_test.shape}")

# Standardisation (importante pour Ridge, Lasso, SVR)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

print("\nStandardisation:")
print(f"Mean (train): {X_train_scaled.mean(axis=0)}")  # Devrait être ~0
print(f"Std (train): {X_train_scaled.std(axis=0)}")    # Devrait être ~1

# Vérification
print(f"\nVérification après scaling:")
print(f"  Mean proche de 0: {np.allclose(X_train_scaled.mean(axis=0), 0, atol=1e-10)}")
print(f"  Std proche de 1: {np.allclose(X_train_scaled.std(axis=0), 1, atol=1e-2)}")
======================================================================
SPLIT DES DONNÉES
======================================================================
Train set: (16512, 8)
Test set: (4128, 8)

Standardisation:
Mean (train): [-6.59266865e-15 -6.68608149e-17  8.01559239e-15 -1.17273358e-15
 -2.60880895e-18 -1.13675656e-16  7.99652724e-14 -3.87910056e-13]
Std (train): [1. 1. 1. 1. 1. 1. 1. 1.]

Vérification après scaling:
  Mean proche de 0: True
  Std proche de 1: True
WarningImportance de la Standardisation

Pourquoi standardiser? - Ridge/Lasso: Pénalisation équitable des coefficients - SVR: Sensible à l’échelle des features - Convergence plus rapide des algorithmes itératifs

Quand ne PAS standardiser? - Arbres de décision / Random Forest (invariants aux transformations monotones) - Régression linéaire simple (si pas de régularisation)

Règle: Toujours fit sur train, transform sur test (éviter data leakage)

3. Modèles de Base

Exercice 3.1: Régression Linéaire Simple

Entraînez une régression linéaire et évaluez-la.

Instructions: 1. Créez et entraînez le modèle 2. Prédisez sur train et test 3. Calculez MAE, RMSE, R² pour les deux ensembles 4. Affichez les 5 coefficients les plus importants 5. Visualisez prédictions vs vraies valeurs

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

print("=" * 70)
print("RÉGRESSION LINÉAIRE SIMPLE")
print("=" * 70)

# 1. Entraînement
model_lr = LinearRegression()
model_lr.fit(X_train_scaled, y_train)

# 2. Prédictions
y_train_pred = model_lr.predict(X_train_scaled)
y_test_pred = model_lr.predict(X_test_scaled)

# 3. Métriques
def evaluate_model(y_true, y_pred, dataset_name=""):
    mae = mean_absolute_error(y_true, y_pred)
    mse = mean_squared_error(y_true, y_pred)
    rmse = np.sqrt(mse)
    r2 = r2_score(y_true, y_pred)
    
    print(f"\n{dataset_name}:")
    print(f"  MAE:  {mae:.4f}")
    print(f"  RMSE: {rmse:.4f}")
    print(f"  R²:   {r2:.4f}")
    
    return {'MAE': mae, 'RMSE': rmse, 'R2': r2}

train_metrics = evaluate_model(y_train, y_train_pred, "Train Set")
test_metrics = evaluate_model(y_test, y_test_pred, "Test Set")

# 4. Coefficients importants
coef_df = pd.DataFrame({
    'Feature': california.feature_names,
    'Coefficient': model_lr.coef_
})
coef_df['Abs_Coef'] = np.abs(coef_df['Coefficient'])
coef_df = coef_df.sort_values('Abs_Coef', ascending=False)

print("\n" + "=" * 70)
print("COEFFICIENTS (TOP 5)")
print("=" * 70)
print(coef_df[['Feature', 'Coefficient']].head().to_string(index=False))

# 5. Visualisation
fig, axes = plt.subplots(1, 3, figsize=(12, 5))

# Prédictions vs Vraies valeurs (Test)
axes[0].scatter(y_test, y_test_pred, alpha=0.3, s=10)
axes[0].plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 
             'r--', lw=2, label='Prédiction parfaite')
axes[0].set_xlabel('Vraie valeur')
axes[0].set_ylabel('Prédiction')
axes[0].set_title(f'Régression Linéaire (Test R²={test_metrics["R2"]:.3f})')
axes[0].legend()
axes[0].grid(alpha=0.3)

# Résidus
residuals = y_test - y_test_pred
axes[1].scatter(y_test_pred, residuals, alpha=0.3, s=10)
axes[1].axhline(0, color='red', linestyle='--', lw=2)
axes[1].set_xlabel('Prédictions')
axes[1].set_ylabel('Résidus')
axes[1].set_title('Graphique des Résidus')
axes[1].grid(alpha=0.3)

# Distribution des résidus
axes[2].hist(residuals, bins=50, edgecolor='black', alpha=0.7)
axes[2].axvline(residuals.mean(), color='red', linestyle='--', 
               label=f'Moyenne: {residuals.mean():.4f}')
axes[2].set_xlabel('Résidus')
axes[2].set_ylabel('Fréquence')
axes[2].set_title('Distribution des Résidus')
axes[2].legend()
axes[2].grid(alpha=0.3)

plt.tight_layout()
plt.show()

# Analyse des résidus
print("\n" + "=" * 70)
print("ANALYSE DES RÉSIDUS")
print("=" * 70)
print(f"Moyenne: {residuals.mean():.6f} (devrait être $\approx$ 0)")
print(f"Std: {residuals.std():.4f}")
print(f"Min: {residuals.min():.4f}")
print(f"Max: {residuals.max():.4f}")
======================================================================
RÉGRESSION LINÉAIRE SIMPLE
======================================================================

Train Set:
  MAE:  0.5286
  RMSE: 0.7197
  R²:   0.6126

Test Set:
  MAE:  0.5332
  RMSE: 0.7456
  R²:   0.5758

======================================================================
COEFFICIENTS (TOP 5)
======================================================================
  Feature  Coefficient
 Latitude    -0.896929
Longitude    -0.869842
   MedInc     0.854383
AveBedrms     0.339259
 AveRooms    -0.294410


======================================================================
ANALYSE DES RÉSIDUS
======================================================================
Moyenne: 0.003479 (devrait être $pprox$ 0)
Std: 0.7456
Min: -9.8753
Max: 4.1484

Interprétation: - R² train > R² test → léger surapprentissage (normal) - Résidus centrés sur 0 → modèle non biaisé - Distribution des résidus approximativement gaussienne → hypothèses vérifiées

4. Modèles Régularisés

Exercice 4.1: Comparaison Ridge, Lasso, ElasticNet

Comparez les 3 modèles régularisés avec différentes valeurs de alpha.

Instructions:

  1. Testez alpha dans [0.001, 0.01, 0.1, 1, 10, 100]
  2. Pour chaque modèle et chaque alpha:
    • Entraînez sur train
    • Calculez R² sur test
  3. Tracez R² vs alpha pour les 3 modèles
  4. Identifiez le meilleur modèle et le meilleur alpha
from sklearn.linear_model import Ridge, Lasso, ElasticNet

print("=" * 70)
print("COMPARAISON RIDGE, LASSO, ELASTICNET")
print("=" * 70)

# 1. Grille d'alphas
alphas = [0.001, 0.01, 0.1, 1, 10, 100]

# Stocker les résultats
results = {'Ridge': [], 'Lasso': [], 'ElasticNet': []}

# 2. Entraînement
for alpha in alphas:
    # Ridge
    ridge = Ridge(alpha=alpha)
    ridge.fit(X_train_scaled, y_train)
    r2_ridge = r2_score(y_test, ridge.predict(X_test_scaled))
    results['Ridge'].append(r2_ridge)
    
    # Lasso
    lasso = Lasso(alpha=alpha, max_iter=10000)
    lasso.fit(X_train_scaled, y_train)
    r2_lasso = r2_score(y_test, lasso.predict(X_test_scaled))
    results['Lasso'].append(r2_lasso)
    
    # ElasticNet
    elastic = ElasticNet(alpha=alpha, l1_ratio=0.5, max_iter=10000)
    elastic.fit(X_train_scaled, y_train)
    r2_elastic = r2_score(y_test, elastic.predict(X_test_scaled))
    results['ElasticNet'].append(r2_elastic)

# 3. Visualisation
plt.figure(figsize=(12, 6))
for model_name, r2_scores in results.items():
    plt.plot(alphas, r2_scores, marker='o', label=model_name, linewidth=2)

plt.xscale('log')
plt.xlabel('alpha (log scale)')
plt.ylabel('R² Score (Test)')
plt.title('Comparaison Ridge, Lasso, ElasticNet')
plt.legend()
plt.grid(alpha=0.3)
plt.axhline(test_metrics['R2'], color='red', linestyle='--', 
           label=f'Linear (alpha=0): {test_metrics["R2"]:.4f}', alpha=0.5)
plt.tight_layout()
plt.show()

# 4. Meilleur modèle
print("\n" + "=" * 70)
print("MEILLEURS HYPERPARAMÈTRES")
print("=" * 70)

for model_name, r2_scores in results.items():
    best_idx = np.argmax(r2_scores)
    best_alpha = alphas[best_idx]
    best_r2 = r2_scores[best_idx]
    print(f"\n{model_name}:")
    print(f"  Meilleur alpha: {best_alpha}")
    print(f"  R² Test: {best_r2:.4f}")

# Tableau comparatif
df_comparison = pd.DataFrame(results, index=[f'alpha={a}' for a in alphas])
print("\n" + "=" * 70)
print("TABLEAU COMPARATIF (R² Test)")
print("=" * 70)
print(df_comparison.to_string())
======================================================================
COMPARAISON RIDGE, LASSO, ELASTICNET
======================================================================


======================================================================
MEILLEURS HYPERPARAMÈTRES
======================================================================

Ridge:
  Meilleur alpha: 100
  R² Test: 0.5778

Lasso:
  Meilleur alpha: 0.01
  R² Test: 0.5816

ElasticNet:
  Meilleur alpha: 0.01
  R² Test: 0.5803

======================================================================
TABLEAU COMPARATIF (R² Test)
======================================================================
                Ridge     Lasso  ElasticNet
alpha=0.001  0.575788  0.576856    0.576543
alpha=0.01   0.575788  0.581615    0.580319
alpha=0.1    0.575791  0.481361    0.514765
alpha=1      0.575816 -0.000219    0.203126
alpha=10     0.576060 -0.000219   -0.000219
alpha=100    0.577791 -0.000219   -0.000219

Observations attendues: - Ridge: R² stable, peu sensible à alpha - Lasso: R² peut chuter si alpha trop élevé (trop de coefficients à 0) - ElasticNet: Compromis entre Ridge et Lasso

Exercice 4.2: Sélection de Features avec Lasso

Utilisez Lasso pour identifier les features importantes.

Instructions: 1. Entraînez Lasso avec alpha=0.1 2. Affichez le nombre de coefficients non-nuls 3. Identifiez les features sélectionnées 4. Comparez les coefficients Lasso vs Linear

print("=" * 70)
print("SÉLECTION DE FEATURES AVEC LASSO")
print("=" * 70)

# 1. Entraînement
lasso = Lasso(alpha=0.1, max_iter=10000)
lasso.fit(X_train_scaled, y_train)

# 2. Coefficients non-nuls
non_zero_mask = np.abs(lasso.coef_) > 1e-5
n_selected = np.sum(non_zero_mask)
n_total = len(lasso.coef_)

print(f"\nNombre de features sélectionnées: {n_selected}/{n_total}")

# 3. Features sélectionnées
selected_features = pd.DataFrame({
    'Feature': california.feature_names,
    'Lasso_Coef': lasso.coef_,
    'Linear_Coef': model_lr.coef_,
    'Selected': non_zero_mask
})
selected_features['Abs_Lasso'] = np.abs(selected_features['Lasso_Coef'])
selected_features = selected_features.sort_values('Abs_Lasso', ascending=False)

print("\n" + "=" * 70)
print("COMPARAISON COEFFICIENTS")
print("=" * 70)
print(selected_features[['Feature', 'Lasso_Coef', 'Linear_Coef', 'Selected']].to_string(index=False))

# 4. Visualisation
fig, axes = plt.subplots(1, 2, figsize=(12, 6))

# Barplot comparatif
x_pos = np.arange(len(california.feature_names))
width = 0.35

axes[0].bar(x_pos - width/2, model_lr.coef_, width, label='Linear', alpha=0.7)
axes[0].bar(x_pos + width/2, lasso.coef_, width, label='Lasso (alpha=0.1)', alpha=0.7)
axes[0].set_xticks(x_pos)
axes[0].set_xticklabels(california.feature_names, rotation=45, ha='right')
axes[0].set_ylabel('Coefficient')
axes[0].set_title('Comparaison Coefficients: Linear vs Lasso')
axes[0].legend()
axes[0].axhline(0, color='black', linewidth=0.8)
axes[0].grid(alpha=0.3, axis='y')

# Scatter: Linear vs Lasso
axes[1].scatter(model_lr.coef_, lasso.coef_, s=100, alpha=0.6)
for i, feature in enumerate(california.feature_names):
    axes[1].annotate(feature, (model_lr.coef_[i], lasso.coef_[i]),
                     fontsize=8, alpha=0.7)
axes[1].plot([model_lr.coef_.min(), model_lr.coef_.max()],
             [model_lr.coef_.min(), model_lr.coef_.max()],
             'r--', label='y=x')
axes[1].set_xlabel('Coefficient Linear')
axes[1].set_ylabel('Coefficient Lasso')
axes[1].set_title('Linear vs Lasso Coefficients')
axes[1].legend()
axes[1].grid(alpha=0.3)

plt.tight_layout()
plt.show()

print("\n" + "=" * 70)
print("FEATURES ÉLIMINÉES PAR LASSO")
print("=" * 70)
eliminated = selected_features[~selected_features['Selected']]['Feature'].tolist()
if eliminated:
    print("Features mises à 0:")
    for feat in eliminated:
        print(f"  • {feat}")
else:
    print("Aucune feature éliminée (alpha peut-être trop faible)")

Interprétation: - Lasso réduit certains coefficients exactement à 0 - Features éliminées = probablement redondantes ou peu informatives - Peut améliorer l’interprétabilité du modèle

5. Optimisation avec Cross-Validation

Exercice 5.1: RidgeCV et LassoCV

Utilisez les versions CV pour trouver automatiquement le meilleur alpha.

from sklearn.linear_model import RidgeCV, LassoCV

print("=" * 70)
print("OPTIMISATION AUTOMATIQUE AVEC CV")
print("=" * 70)

# Grille d'alphas
alphas_cv = np.logspace(-3, 3, 50)

# RidgeCV
print("\n1. RidgeCV...")
ridge_cv = RidgeCV(alphas=alphas_cv, cv=5, scoring='r2')
ridge_cv.fit(X_train_scaled, y_train)
print(f"   Meilleur alpha: {ridge_cv.alpha_:.4f}")

y_pred_ridge = ridge_cv.predict(X_test_scaled)
ridge_metrics = evaluate_model(y_test, y_pred_ridge, "Ridge (CV)")

# LassoCV
print("\n2. LassoCV...")
lasso_cv = LassoCV(alphas=alphas_cv, cv=5, max_iter=10000, random_state=42)
lasso_cv.fit(X_train_scaled, y_train)
print(f"   Meilleur alpha: {lasso_cv.alpha_:.4f}")

y_pred_lasso = lasso_cv.predict(X_test_scaled)
lasso_metrics = evaluate_model(y_test, y_pred_lasso, "Lasso (CV)")

# ElasticNetCV
from sklearn.linear_model import ElasticNetCV

print("\n3. ElasticNetCV...")
elastic_cv = ElasticNetCV(alphas=alphas_cv, l1_ratio=[0.1, 0.5, 0.7, 0.9, 0.95, 0.99],
                          cv=5, max_iter=10000, random_state=42)
elastic_cv.fit(X_train_scaled, y_train)
print(f"   Meilleur alpha: {elastic_cv.alpha_:.4f}")
print(f"   Meilleur l1_ratio: {elastic_cv.l1_ratio_:.2f}")

y_pred_elastic = elastic_cv.predict(X_test_scaled)
elastic_metrics = evaluate_model(y_test, y_pred_elastic, "ElasticNet (CV)")

# Comparaison finale
print("\n" + "=" * 70)
print("COMPARAISON FINALE")
print("=" * 70)

final_comparison = pd.DataFrame({
    'Modèle': ['Linear', 'Ridge (CV)', 'Lasso (CV)', 'ElasticNet (CV)'],
    'MAE': [test_metrics['MAE'], ridge_metrics['MAE'], 
            lasso_metrics['MAE'], elastic_metrics['MAE']],
    'RMSE': [test_metrics['RMSE'], ridge_metrics['RMSE'], 
             lasso_metrics['RMSE'], elastic_metrics['RMSE']],
    'R²': [test_metrics['R2'], ridge_metrics['R2'], 
           lasso_metrics['R2'], elastic_metrics['R2']]
})

print(final_comparison.to_string(index=False))

# Meilleur modèle
best_idx = final_comparison['R²'].idxmax()
best_model_name = final_comparison.loc[best_idx, 'Modèle']
print(f"\n→ Meilleur modèle: {best_model_name}")

6. SVR (Support Vector Regression)

Exercice 6.1: SVR avec différents kernels

Comparez SVR linéaire et RBF.

from sklearn.svm import SVR
from sklearn.model_selection import GridSearchCV

print("=" * 70)
print("SUPPORT VECTOR REGRESSION")
print("=" * 70)

# Note: SVR est lent, on réduit le dataset pour démo
X_train_small = X_train_scaled[:5000]
y_train_small = y_train[:5000]

# 1. SVR Linéaire
print("\n1. SVR Linéaire...")
svr_lin = SVR(kernel='linear', C=1.0)
svr_lin.fit(X_train_small, y_train_small)
y_pred_svr_lin = svr_lin.predict(X_test_scaled)
svr_lin_metrics = evaluate_model(y_test, y_pred_svr_lin, "SVR Linear")

# 2. SVR RBF
print("\n2. SVR RBF...")
svr_rbf = SVR(kernel='rbf', C=1.0, gamma='scale')
svr_rbf.fit(X_train_small, y_train_small)
y_pred_svr_rbf = svr_rbf.predict(X_test_scaled)
svr_rbf_metrics = evaluate_model(y_test, y_pred_svr_rbf, "SVR RBF")

# 3. Comparaison
print("\n" + "=" * 70)
print("COMPARAISON SVR")
print("=" * 70)

svr_comparison = pd.DataFrame({
    'Kernel': ['linear', 'rbf'],
    'MAE': [svr_lin_metrics['MAE'], svr_rbf_metrics['MAE']],
    'RMSE': [svr_lin_metrics['RMSE'], svr_rbf_metrics['RMSE']],
    'R²': [svr_lin_metrics['R2'], svr_rbf_metrics['R2']]
})
print(svr_comparison.to_string(index=False))

# 4. Optimisation SVR RBF
print("\n" + "=" * 70)
print("OPTIMISATION SVR RBF AVEC GRIDSEARCH")
print("=" * 70)

# Réduction supplémentaire pour vitesse
X_val = X_train_scaled[5000:6000]
y_val = y_train[5000:6000]

param_grid = {
    'C': [0.1, 1, 10],
    'gamma': ['scale', 'auto', 0.01, 0.1],
    'epsilon': [0.01, 0.1, 0.5]
}

grid_search = GridSearchCV(
    SVR(kernel='rbf'),
    param_grid,
    cv=3,
    scoring='r2',
    n_jobs=-1,
    verbose=1
)

print("Entraînement GridSearch (peut être long)...")
grid_search.fit(X_train_small, y_train_small)

print(f"\nMeilleurs paramètres: {grid_search.best_params_}")
best_svr = grid_search.best_estimator_
y_pred_best_svr = best_svr.predict(X_val)
best_svr_metrics = evaluate_model(y_val, y_pred_best_svr, "SVR optimisé (val)")

# Visualisation SVR
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# Prédictions vs Vraies valeurs
axes[0].scatter(y_val, y_pred_best_svr, alpha=0.3, s=10)
axes[0].plot([y_val.min(), y_val.max()], [y_val.min(), y_val.max()], 
             'r--', lw=2, label='Prédiction parfaite')
axes[0].set_xlabel('Vraie valeur')
axes[0].set_ylabel('Prédiction')
axes[0].set_title(f'SVR Optimisé (R²={best_svr_metrics["R2"]:.3f})')
axes[0].legend()
axes[0].grid(alpha=0.3)

# Comparaison modèles
models_comparison = pd.DataFrame({
    'Modèle': ['Linear', 'Ridge', 'Lasso', 'SVR Linear', 'SVR RBF'],
    'RMSE': [test_metrics['RMSE'], ridge_metrics['RMSE'], 
             lasso_metrics['RMSE'], svr_lin_metrics['RMSE'], svr_rbf_metrics['RMSE']],
    'R²': [test_metrics['R2'], ridge_metrics['R2'], 
           lasso_metrics['R2'], svr_lin_metrics['R2'], svr_rbf_metrics['R2']]
})

x_pos = np.arange(len(models_comparison))
width = 0.35

bars1 = axes[1].bar(x_pos - width/2, models_comparison['RMSE'], width, label='RMSE')
bars2 = axes[1].bar(x_pos + width/2, models_comparison['R²'], width, label='R²')

axes[1].set_xticks(x_pos)
axes[1].set_xticklabels(models_comparison['Modèle'], rotation=45, ha='right')
axes[1].set_ylabel('Score')
axes[1].set_title('Comparaison Modèles de Régression')
axes[1].legend()

# Annoter les barres
for bars in [bars1, bars2]:
    for bar in bars:
        height = bar.get_height()
        axes[1].annotate(f'{height:.3f}',
                        xy=(bar.get_x() + bar.get_width() / 2, height),
                        xytext=(0, 3),
                        textcoords="offset points",
                        ha='center', va='bottom', fontsize=8)

plt.tight_layout()
plt.show()

print("\n" + "=" * 70)
print("CONSÉILS POUR SVR")
print("=" * 70)
print("""
• SVR est puissant mais COMPUTATIONNELLEMENT COÛTEUX
• Scaling des features OBLIGATOIRE
• GridSearch peut être très long
• Pour grands datasets, considérez:
  - LinearSVR (plus rapide que SVR kernel='linear')
  - Réduction de features (PCA) avant SVR
  - Échantillonnage pour hyperparamètres
""")

7. Comparaison Finale de Tous les Modèles

Exercice 7.1: Tableau de Bord Complet

Créez un tableau de bord comparatif de tous les modèles.

print("=" * 70)
print("TABLEAU DE BORD COMPARATIF")
print("=" * 70)

# Collecte de tous les résultats
all_results = []

# Fonction pour ajouter un modèle
def add_result(name, y_pred, metrics_func=evaluate_model):
    mae = mean_absolute_error(y_test, y_pred)
    rmse = np.sqrt(mean_squared_error(y_test, y_pred))
    r2 = r2_score(y_test, y_pred)
    return {'Modèle': name, 'MAE': mae, 'RMSE': rmse, 'R²': r2}

# Ajout de tous les modèles
all_results.append(add_result('Linear', y_test_pred))
all_results.append(add_result('Ridge (CV)', y_pred_ridge))
all_results.append(add_result('Lasso (CV)', y_pred_lasso))
all_results.append(add_result('ElasticNet (CV)', y_pred_elastic))
all_results.append(add_result('SVR Linear', y_pred_svr_lin))
all_results.append(add_result('SVR RBF', y_pred_svr_rbf))

df_all_results = pd.DataFrame(all_results)

# Tri par R²
df_all_results = df_all_results.sort_values('R²', ascending=False).reset_index(drop=True)

print("\nClassement par R²:")
print(df_all_results.to_string(index=False))

# Visualisation
fig, axes = plt.subplots(2, 2, figsize=(12, 12))

# 1. Barplot R²
x_pos = np.arange(len(df_all_results))
axes[0, 0].barh(x_pos, df_all_results['R²'], color='skyblue')
axes[0, 0].set_yticks(x_pos)
axes[0, 0].set_yticklabels(df_all_results['Modèle'])
axes[0, 0].set_xlabel('R² Score')
axes[0, 0].set_title('Comparaison R² des Modèles')
axes[0, 0].invert_yaxis()  # Meilleur en haut

# Annoter les valeurs
for i, v in enumerate(df_all_results['R²']):
    axes[0, 0].text(v + 0.001, i, f'{v:.4f}', va='center')

# 2. Comparaison MAE vs RMSE
scatter = axes[0, 1].scatter(df_all_results['MAE'], df_all_results['RMSE'], 
                             s=200, alpha=0.6, c=df_all_results['R²'], cmap='viridis')
for i, row in df_all_results.iterrows():
    axes[0, 1].annotate(row['Modèle'], (row['MAE'], row['RMSE']), fontsize=8)
axes[0, 1].set_xlabel('MAE')
axes[0, 1].set_ylabel('RMSE')
axes[0, 1].set_title('MAE vs RMSE (couleur = R²)')
plt.colorbar(scatter, ax=axes[0, 1], label='R² Score')

# 3. Temps d'entraînement (exemple)
# Dans un cas réel, on mesurerait le temps
training_times = [0.1, 0.2, 0.3, 0.4, 2.0, 5.0]  # valeurs d'exemple
df_all_results['Temps(s)'] = training_times

axes[1, 0].scatter(df_all_results['Temps(s)'], df_all_results['R²'], s=100)
for i, row in df_all_results.iterrows():
    axes[1, 0].annotate(row['Modèle'], (row['Temps(s)'], row['R²']), fontsize=8)
axes[1, 0].set_xlabel('Temps d\'entraînement (s)')
axes[1, 0].set_ylabel('R² Score')
axes[1, 0].set_title('Performance vs Temps d\'entraînement')
axes[1, 0].grid(alpha=0.3)

# 4. Heatmap des métriques
metrics_df = df_all_results.set_index('Modèle')[['MAE', 'RMSE', 'R²']]
sns.heatmap(metrics_df, annot=True, fmt='.4f', cmap='YlOrRd', 
            center=0, ax=axes[1, 1], cbar_kws={'label': 'Valeur'})
axes[1, 1].set_title('Heatmap des Métriques par Modèle')
axes[1, 1].tick_params(axis='x', rotation=45)
axes[1, 1].tick_params(axis='y', rotation=0)

plt.tight_layout()
plt.show()

print("\n" + "=" * 70)
print("RECOMMANDATIONS FINALES")
print("=" * 70)

# Recommandation basée sur différents critères
print("""
1. Pour PERFORMANCE MAX (R²):
{} (R²={:.4f})
   
2. Pour INTERPRÉTABILITÉ (coefficients):
   → Lasso (CV) (sélection de features)
   
3. Pour RAPIDITÉ:
   → Linear ou Ridge (CV)
   
4. Pour COMPLEXITÉ NON-LINÉAIRE:
   → SVR RBF (mais plus lent)
   
5. COMPROMIS PERFORMANCE/TEMPS:
   → ElasticNet (CV)
""".format(df_all_results.iloc[0]['Modèle'], df_all_results.iloc[0]['R²']))

# Sauvegarde des résultats
df_all_results.to_csv('resultats_regression_comparaison.csv', index=False)
print("\nRésultats sauvegardés dans 'resultats_regression_comparaison.csv'")

8. Projet Bonus: Regression Avancée

Exercice 8.1: Ensemble Methods

Testez Random Forest et Gradient Boosting pour la régression.

from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.model_selection import RandomizedSearchCV

# TODO: Implémentez Random Forest et Gradient Boosting
# Optimisez avec RandomizedSearchCV
# Comparez avec les modèles linéaires

Indices: - Pas besoin de standardisation pour les arbres - Optimisez: n_estimators, max_depth, min_samples_split - Métrique: RMSE ou MAE - Visualisez l’importance des features

print("=" * 70)
print("ENSEMBLE METHODS: RANDOM FOREST & GRADIENT BOOSTING")
print("=" * 70)

# Pas besoin de scaling pour les arbres
X_train_trees = X_train
X_test_trees = X_test

# 1. Random Forest
print("\n1. Random Forest Regressor...")
rf = RandomForestRegressor(random_state=42, n_jobs=-1)

# Hyperparamètres
param_dist_rf = {
    'n_estimators': [100, 200, 300],
    'max_depth': [None, 10, 20, 30],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4]
}

rf_random = RandomizedSearchCV(
    rf, param_dist_rf, n_iter=20, cv=3, 
    scoring='neg_mean_squared_error', n_jobs=-1, random_state=42
)

rf_random.fit(X_train_trees, y_train)
best_rf = rf_random.best_estimator_
y_pred_rf = best_rf.predict(X_test_trees)

print(f"Meilleurs paramètres RF: {rf_random.best_params_}")
rf_metrics = evaluate_model(y_test, y_pred_rf, "Random Forest")

# 2. Gradient Boosting
print("\n2. Gradient Boosting Regressor...")
gbr = GradientBoostingRegressor(random_state=42)

param_dist_gbr = {
    'n_estimators': [100, 200],
    'learning_rate': [0.01, 0.05, 0.1],
    'max_depth': [3, 4, 5],
    'subsample': [0.8, 0.9, 1.0]
}

gbr_random = RandomizedSearchCV(
    gbr, param_dist_gbr, n_iter=15, cv=3,
    scoring='neg_mean_squared_error', n_jobs=-1, random_state=42
)

gbr_random.fit(X_train_trees, y_train)
best_gbr = gbr_random.best_estimator_
y_pred_gbr = best_gbr.predict(X_test_trees)

print(f"Meilleurs paramètres GBR: {gbr_random.best_params_}")
gbr_metrics = evaluate_model(y_test, y_pred_gbr, "Gradient Boosting")

# 3. Comparaison
print("\n" + "=" * 70)
print("COMPARAISON MODÈLES AVANCÉS")
print("=" * 70)

ensemble_results = pd.DataFrame([
    {'Modèle': 'Random Forest', 'MAE': rf_metrics['MAE'], 
     'RMSE': rf_metrics['RMSE'], 'R²': rf_metrics['R2']},
    {'Modèle': 'Gradient Boosting', 'MAE': gbr_metrics['MAE'], 
     'RMSE': gbr_metrics['RMSE'], 'R²': gbr_metrics['R2']},
    {'Modèle': 'Meilleur Linéaire', 'MAE': df_all_results.iloc[0]['MAE'],
     'RMSE': df_all_results.iloc[0]['RMSE'], 'R²': df_all_results.iloc[0]['R²']}
])

print(ensemble_results.to_string(index=False))

# 4. Importance des features
fig, axes = plt.subplots(1, 2, figsize=(12, 6))

# Random Forest
rf_importances = pd.DataFrame({
    'Feature': california.feature_names,
    'Importance': best_rf.feature_importances_
}).sort_values('Importance', ascending=True)

axes[0].barh(rf_importances['Feature'], rf_importances['Importance'])
axes[0].set_xlabel('Importance')
axes[0].set_title('Importance des Features - Random Forest')

# Gradient Boosting
gbr_importances = pd.DataFrame({
    'Feature': california.feature_names,
    'Importance': best_gbr.feature_importances_
}).sort_values('Importance', ascending=True)

axes[1].barh(gbr_importances['Feature'], gbr_importances['Importance'])
axes[1].set_xlabel('Importance')
axes[1].set_title('Importance des Features - Gradient Boosting')

plt.tight_layout()
plt.show()

# 5. Visualisation prédictions
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

axes[0].scatter(y_test, y_pred_rf, alpha=0.3, s=10)
axes[0].plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--')
axes[0].set_xlabel('Vraie valeur')
axes[0].set_ylabel('Prédiction')
axes[0].set_title(f'Random Forest (R²={rf_metrics["R2"]:.3f})')
axes[0].grid(alpha=0.3)

axes[1].scatter(y_test, y_pred_gbr, alpha=0.3, s=10)
axes[1].plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--')
axes[1].set_xlabel('Vraie valeur')
axes[1].set_ylabel('Prédiction')
axes[1].set_title(f'Gradient Boosting (R²={gbr_metrics["R2"]:.3f})')
axes[1].grid(alpha=0.3)

plt.tight_layout()
plt.show()

print("\n" + "=" * 70)
print("CONCLUSION ENSEMBLE METHODS")
print("=" * 70)
print("""
• Random Forest et Gradient Boosting PERFORMENT TRÈS BIEN
• Pas besoin de feature scaling
• Capturent relations non-linéaires complexes
• MOINS INTERPRÉTABLES que les modèles linéaires
• PLUS LENTS à entraîner
• Risque de surapprentissage si pas bien régularisés

→ Recommandation: Utiliser pour compétitions Kaggle
→ Pour production: Privilégier modèles plus simples si performance similaire
""")

Conclusion

Résumé des Points Clés

  1. Prétraitement:
    • Standardisation cruciale pour modèles régularisés et SVR
    • Split stratifié (si target stratifiable)
    • Pas de data leakage
  2. Modèles Linéaires:
    • Linear: Simple, rapide, interprétable
    • Ridge: Régularisation L2, réduit overfitting
    • Lasso: Régularisation L1, sélection features
    • ElasticNet: Compromis L1+L2
  3. Modèles Non-Linéaires:
    • SVR: Puissant mais lent, sensible aux hyperparamètres
    • Random Forest: Robuste, capture non-linéarités
    • Gradient Boosting: Souvent meilleure performance
  4. Optimisation:
    • Utiliser *CV (RidgeCV, LassoCV) pour alpha automatique
    • GridSearch pour petit espace
    • RandomizedSearch pour grand espace
  5. Évaluation:
    • Multiples métriques: MAE, RMSE, R²
    • Visualisations: résidus, prédictions vs vraies valeurs
    • Importance des features pour interprétation

Checklist de Validation

Avant de soumettre votre travail:

Pour Aller Plus Loin

Extensions possibles:

  1. Feature Engineering:

    • Créer interactions entre features
    • Transformations polynomiales
    • Variables dummy pour catégorielles
  2. Pipeline Scikit-learn:

    from sklearn.pipeline import Pipeline
    from sklearn.compose import ColumnTransformer
    
    pipeline = Pipeline([
        ('scaler', StandardScaler()),
        ('regressor', RidgeCV())
    ])
  3. Validation Croisée Temporelle:

    • Pour données chronologiques
    • TimeSeriesSplit au lieu de KFold
  4. Prédiction d’Intervalles:

    • Quantile Regression
    • Bootstrap pour incertitude
  5. Déploiement:

    • Sauvegarde modèle (joblib)
    • API avec FastAPI/Flask
    • Monitoring des performances

Exercices supplémentaires:

  1. Testez PolynomialFeatures + Regression
  2. Implémentez une validation croisée imbriquée
  3. Ajoutez XGBoost ou LightGBM à la comparaison
  4. Créez un dashboard interactif avec Plotly

Prochain TP: Séries Temporelles ou Deep Learning

TipAstuce Finale

La meilleure pratique: Commencez toujours par un modèle simple (régression linéaire), puis complexifiez si nécessaire. Souvent, les modèles simples suffisent et sont plus faciles à maintenir en production!