Deep Learning 1 — Neurones linéaires
:::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. :::
Premier contact avec les réseaux de neurones, à partir du cas le plus simple : un neurone linéaire. Ne vous attendez pas à du deep learning ici — nous bâtissons les fondations qui vont nous servir pour tous les chapitres suivants.
Pourquoi ce chapitre ?
Avant les CNN et les Transformers, il faut comprendre ce qu'est un neurone et comment il apprend. Vous y voyez :
- ce qu'est un neurone linéaire et le lien avec la régression linéaire ;
- la descente de gradient codée à la main ;
- pourquoi il faut normaliser les entrées ;
- l'introduction à PyTorch : tenseurs,
nn.Linear, optimiseurs,DataLoader; - l'importance des fonctions d'activation non linéaires.
Le neurone linéaire
C'est exactement la régression linéaire du chapitre 2 ML, sous une autre forme :
avec les entrées, les poids, le biais, la sortie.
L'apprentissage consiste à trouver les bons et qui minimisent une fonction de coût. Pour la régression :
C'est la MSE, déjà rencontrée.
La descente de gradient
Quand la solution analytique n'existe pas ou est trop coûteuse, nous ajustons les paramètres pas à pas, dans la direction qui fait baisser :
où est le learning rate.
Pour la régression linéaire, le calcul donne :
Le terme , c'est l'erreur de prédiction. Le gradient propage cette erreur vers via .
Trois variantes
- Batch : gradient calculé sur tout le dataset à chaque étape. Lent sur gros datasets.
- SGD (Stochastic) : un seul exemple. Rapide mais bruité.
- Minibatch : un sous-ensemble (32, 64, ...). Le compromis utilisé en pratique.
Pourquoi normaliser ?
Sans normalisation, les variables à grande échelle dominent la dynamique de la descente de gradient et imposent un learning rate minuscule. La convergence devient très lente.
Pour une régression linéaire, la courbure de la fonction de coût en est proportionnelle à . Si a des valeurs grandes, la courbure est grande et il faut petit.
Solution : StandardScaler ramène toutes les variables à moyenne 0 et écart-type 1.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
:::warning fit_transform vs transform
On ajuste uniquement sur le train (fit_transform), puis on applique au test (transform). Sinon : data leakage.
:::
PyTorch : autograd et optimiseurs
PyTorch automatise deux choses qu'on faisait à la main :
- le calcul des gradients (
autograd) ; - la mise à jour des paramètres (optimiseurs : SGD, Adam, etc.).
Les tenseurs PyTorch sont l'équivalent des tableaux NumPy, mais avec en plus la possibilité de stocker un gradient et de tourner sur GPU.
import torch
import torch.nn as nn
# Conversion NumPy → tenseur
X_train_t = torch.tensor(X_train, dtype=torch.float32)
y_train_t = torch.tensor(y_train, dtype=torch.float32).view(-1, 1)
Le .view(-1, 1) met y au format (n, 1) pour qu'il matche la sortie d'une couche nn.Linear(m, 1).
Le neurone linéaire en PyTorch
model = nn.Linear(m, 1)
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
La boucle d'entraînement standard
for _ in range(epochs):
optimizer.zero_grad() # 1. remettre les gradients à zéro
y_hat = model(X_train_t) # 2. forward
loss = criterion(y_hat, y_train_t)
loss.backward() # 3. autograd calcule tous les gradients
optimizer.step() # 4. mise à jour des paramètres
Mémorisez ces 4 étapes — elles reviendront dans tous les chapitres suivants.
DataLoader : minibatch automatique
Pour ne pas écrire à la main la sélection de minibatchs :
from torch.utils.data import TensorDataset, DataLoader
train_dataset = TensorDataset(X_train_t, y_train_t)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
for _ in range(epochs):
for Xb, yb in train_loader:
optimizer.zero_grad()
y_hat = model(Xb)
loss = criterion(y_hat, yb)
loss.backward()
optimizer.step()
shuffle=True remélange les données à chaque epoch — presque toujours souhaité en train.
Empilement et non-linéarité
Empiler deux couches nn.Linear ne crée pas un modèle plus puissant — algébriquement, deux transformations linéaires se composent en une seule transformation linéaire :
Pour aller plus loin, il faut insérer une non-linéarité entre les couches.
Trois activations classiques
- Sigmoid : , sortie dans . Saturation pour grand.
- Tanh : , sortie dans , centrée en 0.
- ReLU : . Pas de saturation pour . Activation par défaut en deep learning moderne.
model = nn.Sequential(
nn.Linear(m, 16),
nn.ReLU(), # non-linéarité
nn.Linear(16, 1),
)
La profondeur n'a de sens qu'avec de la non-linéarité.