Aller au contenu principal

Machine Learning 2 — Régression

:::tip Notebook Kaggle Le code complet et exécutable de ce chapitre est sur Kaggle : Ouvrir →

Versions anglaise et chinoise disponibles depuis la page d'accueil. :::

Le cœur du cours commence ici. Nous attaquons la régression : prédire une valeur numérique à partir d'autres valeurs. Nous codons d'abord la régression linéaire à la main pour comprendre les rouages, puis nous passons à scikit-learn pour aller plus vite.

Pourquoi ce chapitre ?

La régression est la première grande famille de problèmes en ML. Vous y apprenez :

  • la régression linéaire par moindres carrés et la descente de gradient ;
  • les métriques pour évaluer une prédiction numérique ;
  • le découpage train/test et la validation croisée ;
  • la régression polynomiale et la régularisation Ridge ;
  • les k-plus-proches-voisins et l'importance de la normalisation.

La régression linéaire

L'idée la plus simple : trouver une droite qui passe au plus près d'un nuage de points.

y^=ax+b\hat{y} = a x + b

Nous voulons que la droite minimise les résidus ei=yi(axi+b)e_i = y_i - (a x_i + b) — l'écart entre la valeur observée et la valeur prédite.

La méthode des moindres carrés

Nous choisissons aa et bb pour minimiser la somme des carrés des résidus :

J(a,b)=i=1n(yiaxib)2J(a, b) = \sum_{i=1}^{n} (y_i - a x_i - b)^2

Pourquoi le carré ? Deux raisons : ça pénalise plus fort les grosses erreurs, et ça donne une solution analytique propre.

La solution

En annulant les dérivées partielles, on tombe sur :

a=Cov(x,y)Var(x),b=yˉaxˉa = \frac{\mathrm{Cov}(x, y)}{\mathrm{Var}(x)}, \quad b = \bar{y} - a\,\bar{x}

La droite passe toujours par le point moyen (xˉ,yˉ)(\bar{x}, \bar{y}).

La descente de gradient

Quand on a beaucoup de variables (ou pour les modèles plus complexes que vous verrez plus tard), la solution analytique n'existe plus. On passe à la descente de gradient : ajuster progressivement les paramètres dans la direction qui fait baisser la loss.

wwηJww \leftarrow w - \eta \, \frac{\partial J}{\partial w}

η\eta est le learning rate (taux d'apprentissage). Trop petit, l'apprentissage rampe ; trop grand, la loss diverge. Le bon choix se fait souvent par essai-erreur.

Trois variantes selon le nombre d'exemples utilisés à chaque mise à jour :

  • Batch : tous les exemples à chaque étape. Précis mais lent sur gros datasets.
  • SGD (Stochastic) : un seul exemple. Rapide mais bruité.
  • Minibatch : un sous-ensemble (typiquement 32 ou 64). Le compromis utilisé en pratique.

Métriques d'évaluation

Une fois le modèle entraîné, comment savoir s'il prédit bien ? Plusieurs métriques :

MétriqueFormuleInterprétation
MAE$\frac{1}{n}\sumy_i - \hat{y}_i
MSE1n(yiy^i)2\frac{1}{n}\sum (y_i - \hat{y}_i)^2pénalise plus les grosses erreurs
RMSEMSE\sqrt{\mathrm{MSE}}comme MSE mais dans l'unité de yy
MAPE$\frac{1}{n}\sum(y_i - \hat{y}_i)/y_i
1(yiy^i)2(yiyˉ)21 - \frac{\sum (y_i - \hat{y}_i)^2}{\sum (y_i - \bar{y})^2}part de variance expliquée

Le vaut 1 pour un modèle parfait, 0 pour un modèle qui ne fait pas mieux que prédire la moyenne, et peut être négatif pour un modèle pire que la moyenne.

:::warning MAE vs RMSE Si MAE et RMSE divergent fortement, c'est qu'il y a des outliers. RMSE explose à cause du carré, MAE encaisse mieux. :::

Train/test et validation croisée

Évaluer un modèle sur les données qui ont servi à l'entraîner, c'est comme noter un étudiant sur les annales qu'il a apprises par cœur. Toujours réserver une partie des données pour le test.

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)

Pour ne pas dépendre d'un seul tirage, la validation croisée en k plis entraîne kk fois sur des découpages différents et moyenne les scores :

from sklearn.model_selection import cross_val_score

scores = cross_val_score(model, X, y, cv=5, scoring='r2')
print(scores.mean(), scores.std())

Régression polynomiale et régularisation

Quand la relation y(x)y(x) n'est pas une droite, on ajoute des puissances de xx comme nouvelles variables :

y^=β0+β1x+β2x2+β3x3+\hat{y} = \beta_0 + \beta_1 x + \beta_2 x^2 + \beta_3 x^3 + \dots

Le modèle reste linéaire en ses coefficients, mais devient courbe. Plus le degré est élevé, plus le modèle peut s'ajuster aux données — y compris au bruit. C'est le surapprentissage (overfitting).

Ridge : pénaliser les gros coefficients

Pour empêcher les coefficients de partir dans tous les sens, on ajoute un terme de pénalité à la fonction de coût :

J(w)=i(yiy^i)2+αjwj2J(w) = \sum_i (y_i - \hat{y}_i)^2 + \alpha \sum_j w_j^2

C'est la régression Ridge. Le paramètre α\alpha contrôle la force de la régularisation : plus α\alpha est grand, plus les coefficients sont contraints à rester petits.

from sklearn.linear_model import Ridge
model = Ridge(alpha=1.0)

Variantes proches : Lasso (pénalité en valeur absolue, fait de la sélection de variables), ElasticNet (combinaison Ridge + Lasso).

k plus proches voisins (k-NN)

Le k-NN regressor est une méthode très différente : pas de formule globale, on regarde les kk voisins les plus proches dans le jeu d'entraînement et on moyenne leurs valeurs cibles.

y^(x)=1kiNk(x)yi\hat{y}(x) = \frac{1}{k} \sum_{i \in \mathcal{N}_k(x)} y_i

from sklearn.neighbors import KNeighborsRegressor
model = KNeighborsRegressor(n_neighbors=5)

Le choix de kk est un arbitrage : petit kk → modèle flexible mais sensible au bruit ; grand kk → prédictions plus lisses mais qui peuvent rater les détails.

Normalisation : indispensable pour le k-NN

Le k-NN repose entièrement sur des distances. Si une variable a une échelle 1000 fois plus grande qu'une autre, elle écrase tout dans le calcul de distance.

Solution : mettre toutes les variables sur la même échelle.

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

:::warning Le piège du data leakage On ajuste le scaler uniquement sur le train (fit_transform), puis on applique au test (transform). Sinon, on fait fuiter de l'information du test dans la procédure d'entraînement. :::

Pipeline scikit-learn

Pour éviter les erreurs et chaîner proprement plusieurs étapes :

from sklearn.pipeline import Pipeline

pipe = Pipeline([
('scaler', StandardScaler()),
('model', KNeighborsRegressor(n_neighbors=5)),
])

pipe.fit(X_train, y_train)
y_hat = pipe.predict(X_test)

Le Pipeline garantit que le scaler est ajusté uniquement sur le train, même en validation croisée.


Notebook complet sur Kaggle (forkable) →