Workflow supervisé et descente de gradient & Compromis biais-variance
Maîtriser le pipeline supervisé — collecte, entraînement, évaluation — et la descente de gradient qui optimise θ, puis comprendre pourquoi un modèle trop expressif généralise mal.
Workflow supervisé et descente de gradient
L’idée en une phrase
L’apprentissage supervisé suit un pipeline en cinq étapes — collecter les données, choisir un modèle F(x, θ), définir une fonction de coût L(θ), optimiser θ par descente de gradient, puis évaluer sur des données jamais vues — et c’est exactement ce pipeline que la boucle variationnelle hybride reproduit : le circuit quantique paramétré remplace F, la mesure fournit L, et un optimiseur classique met à jour θ via les gradients.
Analogie : Imaginer un randonneur perdu dans un brouillard épais au sommet d’une colline, cherchant la vallée la plus basse. Il ne voit pas le paysage entier, mais il peut sentir la pente sous ses pieds. À chaque pas, il avance dans la direction de la descente la plus raide. Le gradient est cette pente locale, le pas est le learning rate η, et la vallée est le minimum de la fonction de coût. Si le pas est trop grand, le randonneur enjambe la vallée ; trop petit, il met des heures à l’atteindre.
Points clés
- Le pipeline supervisé : (1) collecter les paires
(x, y), (2) séparer en jeu d’entraînement et jeu de test, (3) choisir le modèle F(x, θ), (4) minimiser L(θ) sur le jeu d’entraînement, (5) évaluer sur le jeu de test. - La descente de gradient met à jour les paramètres selon la règle
θ ← θ − η · ∇L(θ), où∇L(θ)est le vecteur des dérivées partielles de L par rapport à chaque paramètre, et η est le learning rate. - Le learning rate η est le premier hyperparamètre rencontré : il n’est pas appris par le modèle, il est fixé par le praticien. Un η trop grand provoque des oscillations (divergence), un η trop petit ralentit la convergence.
- En QML, le gradient
∇L(θ)ne peut pas être calculé par rétropropagation classique à travers un circuit quantique. On utilise le Parameter Shift Rule : on évalue le circuit àθ + π/2etθ − π/2, et la différence donne la dérivée. Le coût est de 2 évaluations de circuit par paramètre.
Exemple concret
On dispose de 5 mesures reliant le nombre d’heures d’étude (x) à une note sur 10 (y) : x = [1, 2, 3, 4, 5], y = [2.1, 4.0, 5.8, 8.1, 9.9]. On pose F(x, θ) = θ₀ + θ₁·x et on utilise la descente de gradient au lieu de la solution analytique. On part de θ₀ = 0, θ₁ = 0, η = 0.01. Après 2000 itérations, la MSE passe de 43.5 à 0.012 et les paramètres convergent vers θ₀ ≈ 0.07, θ₁ ≈ 1.97 — les mêmes valeurs que la solution exacte par moindres carrés, atteintes de façon itérative.
Solution analytique vs descente de gradient
| Critère | Solution analytique (moindres carrés) | Descente de gradient |
|---|---|---|
| Applicabilité | Modèles linéaires uniquement | Tout modèle différentiable |
| Coût calcul | O(n·p²) — une seule résolution | O(n·p·T) — T itérations |
| Hyperparamètres | Aucun | η (learning rate), T (itérations) |
| QML compatible | Non (pas de matrice inverse quantique en NISQ) | Oui (via Parameter Shift Rule) |
Code Python — descente de gradient sur modèle linéaire
import numpy as np
# Données : heures d'étude → note sur 10
x = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
y = np.array([2.1, 4.0, 5.8, 8.1, 9.9])
N = len(x)
# Initialisation des paramètres
theta = np.array([0.0, 0.0])
eta = 0.01 # learning rate
n_epochs = 2000
for epoch in range(n_epochs):
# Prédictions : F(x, θ) = θ₀ + θ₁·x
preds = theta[0] + theta[1] * x
# Gradients de la MSE par rapport à θ₀ et θ₁
error = preds - y
grad_theta0 = 2.0 / N * np.sum(error)
grad_theta1 = 2.0 / N * np.sum(error * x)
# Mise à jour : θ ← θ − η·∇L
theta[0] -= eta * grad_theta0
theta[1] -= eta * grad_theta1
print(f"θ₀ = {theta[0]:.4f}, θ₁ = {theta[1]:.4f}")
# θ₀ ≈ 0.0705, θ₁ ≈ 1.9699
loss = np.mean((theta[0] + theta[1] * x - y) ** 2)
print(f"MSE finale = {loss:.6f}")
# MSE finale ≈ 0.0118
Piège courant : « La descente de gradient trouve toujours le minimum global » est inexact. Pour un modèle linéaire avec la MSE, la surface de perte est convexe — il n’y a qu’un seul minimum. Mais pour un réseau de neurones ou un circuit quantique paramétré, la surface de perte est non convexe et contient de nombreux minima locaux. La descente de gradient peut rester piégée dans un minimum local, ce qui explique l’importance de l’initialisation de θ et du choix de η.
Compromis biais-variance et généralisation
L’idée en une phrase
L’erreur de prédiction d’un modèle se décompose en biais (erreur systématique due à un modèle trop simple) et variance (instabilité due à un modèle trop sensible aux données d’entraînement) ; un modèle utile minimise leur somme, pas l’un au détriment de l’autre. En QML, ce compromis prend la forme du dilemme expressivité ↔ entraînabilité : un circuit profond avec beaucoup de portes paramétrées peut représenter plus de fonctions (biais faible), mais il devient plus difficile à entraîner et sujet aux plateaus de Barren (variance du gradient qui s’effondre).
Analogie : Considérons deux cartographes qui doivent dessiner la côte d’une île à partir de 10 points GPS. Le premier trace une ligne droite entre le premier et le dernier point : c’est rapide mais la carte rate toutes les baies et les caps — c’est le biais (sous-ajustement). Le second passe exactement par chaque point GPS en dessinant des zigzags complexes : la carte colle aux 10 mesures mais devient absurde entre les points, inventant des fjords imaginaires — c’est la variance (sur-ajustement). Le bon cartographe trace une courbe lisse qui capture la forme générale sans coller au bruit des mesures GPS.
Points clés
- Le biais mesure l’écart entre la prédiction moyenne du modèle (sur tous les jeux d’entraînement possibles) et la vraie valeur. Un modèle à biais élevé est trop rigide (underfitting).
- La variance mesure la dispersion des prédictions d’un modèle quand on change le jeu d’entraînement. Un modèle à variance élevée est trop flexible (overfitting).
- Erreur totale = biais² + variance + bruit irréductible. Le bruit est inhérent aux données et ne peut pas être réduit.
- Pour détecter le sur-ajustement : comparer la perte d’entraînement et la perte de test. Un écart croissant signale que le modèle mémorise le bruit. Les outils classiques sont la validation croisée (k-fold) et la régularisation (ajouter un terme de pénalité sur la norme de θ).
- En QML NISQ, la capacité du modèle dépend du nombre de paramètres du circuit (profondeur × qubits), mais l’augmenter sans précaution provoque les plateaus de Barren (chapitre 12) — un phénomène où les gradients deviennent exponentiellement petits.
Exemple concret
On génère 20 points bruités suivant y = sin(x) + ε. On ajuste trois modèles polynomiaux — degré 1 (2 paramètres), degré 4 (5 paramètres) et degré 15 (16 paramètres) — sur 15 points d’entraînement, puis on évalue sur 5 points de test.
| Modèle | Paramètres | MSE entraînement | MSE test | Diagnostic |
|---|---|---|---|---|
| Degré 1 | 2 | 0.35 | 0.38 | Biais élevé (underfitting) |
| Degré 4 | 5 | 0.04 | 0.06 | Bon compromis |
| Degré 15 | 16 | 0.001 | 2.50 | Variance élevée (overfitting) |
Le degré 15 colle presque parfaitement aux données d’entraînement (MSE ≈ 0) mais explose sur les données de test : il a mémorisé le bruit.
Underfitting vs bon ajustement vs overfitting
| Critère | Underfitting (biais élevé) | Bon ajustement | Overfitting (variance élevée) |
|---|---|---|---|
| Complexité du modèle | Trop simple | Adaptée | Trop complexe |
| MSE entraînement | Élevée | Faible | Très faible (≈ 0) |
| MSE test | Élevée | Faible (proche du train) | Très élevée |
| Remède | Augmenter la complexité | — | Régulariser, réduire la complexité, plus de données |
| Analogie QML | Circuit trop peu profond | Circuit bien dimensionné | Circuit trop profond (barren plateaus) |
Code Python — visualiser le compromis biais-variance
import numpy as np
# Données bruitées : y = sin(x) + bruit
np.random.seed(42)
x_all = np.linspace(0, 2 * np.pi, 20)
y_all = np.sin(x_all) + 0.3 * np.random.randn(20)
# Séparer entraînement (15 points) / test (5 points)
indices = np.arange(20)
np.random.shuffle(indices)
train_idx, test_idx = indices[:15], indices[15:]
x_train, y_train = x_all[train_idx], y_all[train_idx]
x_test, y_test = x_all[test_idx], y_all[test_idx]
# Ajuster des polynômes de degrés croissants
for degree in [1, 4, 15]:
# Ajustement sur le jeu d'entraînement
coeffs = np.polyfit(x_train, y_train, degree)
poly = np.poly1d(coeffs)
mse_train = np.mean((poly(x_train) - y_train) ** 2)
mse_test = np.mean((poly(x_test) - y_test) ** 2)
print(f"Degré {degree:2d} : MSE train = {mse_train:.4f}, MSE test = {mse_test:.4f}")
# Degré 1 : MSE train faible mais écart → underfitting
# Degré 4 : MSE train et test proches → bon compromis
# Degré 15 : MSE train ≈ 0, MSE test explose → overfitting
Piège courant : « Il suffit d’ajouter des données pour résoudre l’overfitting » est une simplification. Plus de données aide effectivement, car la variance diminue avec N. Mais si le modèle est excessivement complexe par rapport à la dimension du problème, même beaucoup de données ne suffiront pas. En QML NISQ, le nombre de données est souvent limité par le coût de préparation et de mesure des états quantiques — la régularisation et le choix de la profondeur du circuit sont donc les leviers principaux.
Fil rouge — la frontière quantique/classique
Ce chapitre reste classique dans ses outils (NumPy, polynômes), mais il pose les deux mécanismes que la boucle variationnelle hybride utilisera directement. Côté optimisation : la descente de gradient θ ← θ − η·∇L est identique en QML, sauf que le gradient est estimé sur le QPU via le Parameter Shift Rule plutôt que par rétropropagation. Côté compromis : le nombre de portes paramétrées d’un circuit quantique joue le même rôle que le degré d’un polynôme — trop peu et le circuit ne peut rien apprendre (biais), trop et les gradients s’évanouissent dans les plateaus de Barren (un problème de variance spécifiquement quantique, traité au chapitre 12).
Kata Qiskit — descente de gradient manuelle
Objectif : implémenter la descente de gradient sur la MSE pour un modèle linéaire F(x, θ) = θ₀ + θ₁·x, en calculant les gradients analytiquement. Ce kata reproduit exactement la boucle prédire → calculer le gradient → mettre à jour θ que le Parameter Shift Rule transposera au QPU.
Squelette :
# kata_day_3_4.py
import numpy as np
def gradient_mse(x, y, theta):
"""Calculer le gradient de la MSE par rapport à theta pour F(x,θ) = θ₀ + θ₁·x.
Retourne un array [∂L/∂θ₀, ∂L/∂θ₁].
"""
N = len(x)
preds = theta[0] + theta[1] * x
error = preds - y
# TODO 1 : calculer ∂L/∂θ₀ = (2/N) · Σ error
grad_theta0 = 0.0
# TODO 2 : calculer ∂L/∂θ₁ = (2/N) · Σ (error · x)
grad_theta1 = 0.0
return np.array([grad_theta0, grad_theta1])
def train(x, y, theta_init, eta, n_epochs):
"""Entraîner le modèle linéaire par descente de gradient.
Retourne (theta_final, liste_des_pertes).
"""
theta = theta_init.copy()
losses = []
for epoch in range(n_epochs):
# TODO 3 : calculer les prédictions
preds = np.zeros_like(y)
# TODO 4 : calculer la MSE et l'ajouter à losses
loss = 0.0
losses.append(loss)
# TODO 5 : calculer le gradient et mettre à jour theta
pass
return theta, losses
Auto-correction :
# test_kata_day_3_4.py
import numpy as np
from kata_day_3_4 import gradient_mse, train
# Test 1 : gradient_mse avec des valeurs connues
x = np.array([0.0, 1.0, 2.0])
y = np.array([1.0, 2.0, 3.0])
theta = np.array([0.0, 0.0])
grad = gradient_mse(x, y, theta)
assert grad is not None, "gradient_mse() ne doit pas retourner None"
# ∂L/∂θ₀ = (2/3) * (-1 + -2 + -3) = -4.0
# ∂L/∂θ₁ = (2/3) * (0 + -2 + -6) = -16/3 ≈ -5.333
np.testing.assert_allclose(grad[0], -4.0, atol=1e-8,
err_msg=f"∂L/∂θ₀ attendu = -4.0, obtenu = {grad[0]}")
np.testing.assert_allclose(grad[1], -16.0/3, atol=1e-8,
err_msg=f"∂L/∂θ₁ attendu = -5.333, obtenu = {grad[1]}")
# Test 2 : gradient nul au minimum
theta_opt = np.array([1.0, 1.0]) # F = 1 + x → prédit [1, 2, 3] = y exactement
grad_opt = gradient_mse(x, y, theta_opt)
np.testing.assert_allclose(grad_opt, [0.0, 0.0], atol=1e-8,
err_msg="Le gradient doit être nul au minimum exact")
# Test 3 : train converge
x_data = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
y_data = np.array([2.1, 4.0, 5.8, 8.1, 9.9])
theta_init = np.array([0.0, 0.0])
theta_final, losses = train(x_data, y_data, theta_init, eta=0.01, n_epochs=2000)
assert theta_final is not None, "train() ne doit pas retourner None"
assert len(losses) == 2000, f"Il doit y avoir 2000 valeurs de perte, obtenu {len(losses)}"
assert losses[-1] < losses[0], "La perte doit diminuer au cours de l'entraînement"
assert losses[-1] < 0.1, f"La perte finale doit être < 0.1, obtenue {losses[-1]:.4f}"
assert abs(theta_final[0] - 0.07) < 0.5, f"θ₀ attendu ≈ 0.07, obtenu {theta_final[0]:.2f}"
assert abs(theta_final[1] - 1.97) < 0.1, f"θ₁ attendu ≈ 1.97, obtenu {theta_final[1]:.4f}"
print("Kata validé !")
Solution et explication
# kata_day_3_4.py
import numpy as np
def gradient_mse(x, y, theta):
"""Calculer le gradient de la MSE par rapport à theta pour F(x,θ) = θ₀ + θ₁·x."""
N = len(x)
preds = theta[0] + theta[1] * x
error = preds - y
grad_theta0 = 2.0 / N * np.sum(error)
grad_theta1 = 2.0 / N * np.sum(error * x)
return np.array([grad_theta0, grad_theta1])
def train(x, y, theta_init, eta, n_epochs):
"""Entraîner le modèle linéaire par descente de gradient."""
theta = theta_init.copy()
losses = []
for epoch in range(n_epochs):
preds = theta[0] + theta[1] * x
loss = np.mean((preds - y) ** 2)
losses.append(loss)
grad = gradient_mse(x, y, theta)
theta -= eta * grad
return theta, lossesPourquoi : ce kata décompose la boucle d’entraînement en ses éléments atomiques — prédiction, perte, gradient, mise à jour — pour montrer qu’il n’y a aucune magie. En QML, la seule différence sera la façon d’obtenir le gradient : au lieu de la formule analytique (2/N)·Σ(error·x), on utilisera le Parameter Shift Rule sur le circuit quantique. Mais la structure θ ← θ − η·∇L reste identique.