À la fin de cette section, vous saurez :
torch.nn.torch-summary pour inspecter l’architecture du réseau.Le broadcasting est un mécanisme qui permet à PyTorch de faire des opérations entre tenseurs de dimensions différentes sans avoir à écrire de boucles. C'est comme cela qu'est fait l'opération de centrage des données (soustraction de la moyenne) dans la standardisation des données.
💡 Idée principale :
import torch
# Matrice 3x2
X = torch.tensor([[1., 2.],
[3., 4.],
[5., 6.]])
# Moyenne de chaque colonne
mean = X.mean(dim=0) # dimension (2,)
# On soustrait la moyenne à chaque ligne
X_centered = X - mean # broadcasting
print("X centré :", X_centered)
💡 Conclusion : Même si mean est un vecteur (dimension 2), PyTorch l’applique à toutes les lignes de X. Le tenseur mean est automatiquement “étendu” pour correspondre à X.
✅ Résultat : On peut centrer toutes les lignes d’un coup, sans boucle.
Lorsqu’on entraîne un modèle, il est essentiel de suivre l’évolution de la loss pour savoir si le modèle apprend correctement et converge vers une solution. Dans l’exemple précédent, nous avons comparé l’impact de la standardisation sur les prédictions finales. Nous allons maintenant observer l’évolution de la loss pendant l’entraînement pour mieux comprendre la convergence et déterminer un nombre d’epochs approprié. Nous allons continuer à utiliser les données suivantes pour entraîner le modèle :
# Données d'entraînement
X = torch.tensor([[0.],[10.],[20.],[30.],[40.],[50.]])
y = 2*X + 1
Pour suivre la loss pour le modèle avec et sans standardisation il faut d'abord créer deux listes pour stocker les valeurs de la loss à chaque epoch. Pour cela, il suffit d'ajouter le code suivant avant la classe de création du modèle :
...
# Listes pour stocker l'évolution de la loss
losses_no_std = []
losses_std = []
...
Ensuite, pendant l’entraînement, on ajoute la valeur de la loss dans les listes à chaque epoch. Cela se fait comme suit :
...
# Sans standardisation
pred_no_std = model_no_std(X)
...
optimizer_no_std.step()
losses_no_std.append(loss_no_std.item()) # Ligne à ajouter
# Avec standardisation
pred_std = model_std(X_stdized)
...
optimizer_std.step()
losses_std.append(loss_std.item()) # Ligne à ajouter
...
Enfin on ajoute les lignes de code suivantes pour tracer les loss à la fin du code :
...
# Visualisation de la loss
plt.plot(losses_no_std, label='Sans standardisation')
plt.plot(losses_std, label='Avec standardisation')
plt.xlabel('Epoch')
plt.ylabel('Loss MSE')
plt.title("Évolution de la loss pendant l'entraînement")
plt.legend()
plt.show()
Remarque : Si vous relancer l'entraînement, le graphique de la loss peut varier à cause de l'initialisation aléatoire des poids sauf si vous utilisez un seed fixe.
Pour éviter de trop entraîner le modèle, on peut surveiller la loss et arrêter l’entraînement lorsque la perte ne diminue plus. Cela s’appelle l’early stopping. On peut automatiser le processus avec PyTorch. Tout d'abord, il faut remettre le nombre d'epoch à 5000. Ensuite il faut créer les variables suivantes et les ajouter avant la classe qui construit le modèle :
...
# Paramètres pour l'early stopping
patience = 50 # nombre d'epochs sans amélioration avant arrêt
best_loss_std = float('inf') # meilleure loss observée pour le modèle avec standardisation (initialisée à l'infini pour que la première amélioration soit toujours acceptée)
counter_std = 0 # compteur d'epochs sans amélioration
patience_no_std = 50
best_loss_no_std = float('inf')
counter_no_std = 0
...
Ensuite, il faut ajouter le code suivant à la fin de chaque boucle d'entraînement pour vérifier si la loss s'est améliorée ou non. Si elle ne s'améliore pas pendant un certain nombre d'epochs (défini par patience), l'entraînement s'arrête automatiquement. Voici le code à ajouter :
...
# Sans standardisation
...
losses_no_std.append(loss_no_std.item())
# Early stopping pour le modèle sans standardisation (code à ajouter)
if loss_no_std.item() < best_loss_no_std:
best_loss_no_std = loss_no_std.item()
counter_no_std = 0
else:
counter_no_std += 1
if counter_no_std >= patience_no_std:
print(f"Arrêt anticipé (sans std) à l'epoch {epoch}, loss = {best_loss_no_std:.4f}")
break
# Avec standardisation
...
losses_std.append(loss_std.item())
# Early stopping pour le modèle standardisé (code à ajouter)
if loss_std.item() < best_loss_std:
best_loss_std = loss_std.item()
counter_std = 0
else:
counter_std += 1
if counter_std >= patience:
print(f"Arrêt anticipé (avec std) à l'epoch {epoch}, loss = {best_loss_std:.4f}")
break
...
💡 Remarque :
torch-summary et la performance des gradients avec autograd profilerIl existe plusieurs outils PyTorch qui permettent d'inspecter et de profiler les modèles. Le but étant de parvenir à identifier les goulots d'étranglement et à optimiser les performances. Parmi eux, on trouve :
torchsummary : pour visualiser la structure du modèle et le nombre de paramètres par couche.torch.autograd.profiler : pour profiler le calcul des gradients et identifier les opérations coûteuses.torchsummarytorchsummary permet de visualiser la structure du modèle et le nombre de paramètres par couche avant l'entraînement. Pour l'utiliser, il faut d'abord l'installer :
pip install torch-summary
Ensuite, juste après la définition de votre modèle, vous pouvez faire un résumé du modèle :
from torchsummary import summary
# Modèle standardisé défini précédemment
# Créer une copie sur CPU pour torchsummary
model_std_cpu = MLP().to("cpu")
# Résumé du modèle
# input_size correspond aux dimensions d'un échantillon (hors batch)
# Ici, chaque échantillon a 1 feature (scalaire)
summary(model_std_cpu, input_size=(1,), device="cpu")
Explications :
input_size : dimensions d’un échantillon (hors batch).
Dans notre exemple, chaque échantillon est un scalaire (1 feature), donc input_size=(1,). device : est égal à "cpu" pour éviter tout conflit CUDA si le modèle ou PyTorch envoie certains tenseurs sur GPU.Pour encore plus améliorer la performance de votre modèle, PyTorch fournit torch.autograd.profiler.profile pour profiler le calcul des gradients ce qui permet de :
Pour tester le profiler, il suffit d'ajouter le code suivant juste après le code de torchsummary :
...
# torch.autograd.profiler est utilisé dans ce chapitre pour la simplicité
# Pour des usages avancés (timeline, TensorBoard), on peut utiliser torch.profiler
import torch.autograd.profiler as profiler
# Faire un profiling sur une seule passe avant la boucle d'entraînement
with profiler.profile(use_cuda=True, profile_memory=True) as prof_dummy:
# Forward + backward sur le modèle standardisé
pred_std = model_std(X_stdized)
loss_std = ((pred_std - y)**2).mean()
optimizer_std.zero_grad()
loss_std.backward()
# Afficher le profil CPU (temps d'exécution)
print("Profil CPU pour le modèle standardisé (une seule passe avant entraînement) :")
print(prof_dummy.key_averages().table(sort_by="cpu_time_total"))
# Afficher le profil GPU (mémoire consommée)
print(prof_dummy.key_averages().table(sort_by="self_cuda_memory_usage", row_limit=10))
...
Conclusion :
cpu_time_total pour identifier les opérations coûteuses en calcul,self_cuda_memory_usage pour repérer celles qui consomment le plus de mémoire GPU.Self CPU % : temps passé directement dans l’opération.CPU total % : temps total incluant les sous-opérations.# of Calls : nombre d’appels à l’opération.Self CUDA Memory Usage : mémoire GPU utilisée directement par l’opération.CUDA Memory Usage : mémoire totale incluant les sous-opérations.# of Calls : nombre d’appels à l’opération.aten::linear) prennent la majeure partie du temps : multiplication matricielle + bias.ReLU, Tanh) et les calculs de loss (mean, pow) consomment moins de temps mais sont nécessaires pour propager les gradients.detach ou clone apparaissent lorsqu’on fait des copies ou qu’on détache un tenseur du graphe pour ne pas calculer de gradient dessus.Dans cet exercice, vous allez entraîner un MLP simple sur un jeu de données synthétiques avec deux features ayant des échelles différentes. Vous comparerez les performances lorsque les données sont brutes ou standardisées.
On vous donne les données suivantes :
# Données synthétiques
N = 500
X1 = torch.linspace(0, 1, N).unsqueeze(1) # petite amplitude
X2 = torch.linspace(0, 100, N).unsqueeze(1) # grande amplitude
X = torch.cat([X1, X2], dim=1)
y = 3*X1 + 0.05*X2**2 + torch.randn(N,1) * 0.5
Objectif :
Comprendre l’importance de la standardisation des données pour l’entraînement d’un réseau de neurones et observer l’évolution de la loss.
Consigne : Écrire un programme qui :
1) Définit une classe MLP simple sans couches cachées avec :
ReLU2) Crée deux modèles : un pour les données brutes, un pour les données standardisées.
3) Entraîne les deux modèles avec Adam et une fonction de perte MSE pendant 1000 epochs avec un learning rate de 0.01.
4) Stocke et trace l’évolution de la loss pour les deux modèles.
5) Trace les prédictions finales des deux modèles sur le même graphique que les données réelles.
6) Comparez les performances des deux modèles et notez lequel converge plus vite et donne de meilleures prédictions.
7) A quelle epoch peut-on considérer que le modèle sur données standardisées a convergé et comment on peut faire pour le déterminer ?
Astuce :
torch.randn() pour un démarrage aléatoire et de mettre optimizer.zero_grad() avant loss.backward().(X - X_mean)/X_std.loss.item() à chaque epoch et utilisez matplotlib.pyplot.plot().patience à 20.if loss.item() < best_loss - 5)Résultat attendu :
Le graphique montre les prédictions du MLP sur les données brutes (rouge) et standardisées (bleu) par rapport aux données réelles (noir). Vous devez obtenir un résultat similaire à celui-ci avant de réduire le nombre d'epochs :
Cet exercise permet d'observer l'overfitting avec un MLP sur des données bruitées. L'overfitting se produit lorsque le modèle apprend trop bien les détails des données d'entraînement, au détriment de sa capacité à généraliser sur de nouvelles données.
Objectif :
Consigne : Écrire un programme qui :
1) Génère un jeu de données 1D avec N=100 points :
X uniformément dans \([-3.14,3.14]\).y = sin(X) + bruit avec bruit = 0.2 * torch.randn_like(y).2) Définit trois modèles MLP avec Tanh comme activation :
3) Entraîne chaque modèle avec MSELoss et Adam pendant :
4) Trace sur le même graphique :
sin(X)5) Trace également l’évolution de la loss pour chaque modèle.
6) Teste les modèles sur une nouvelle valeur de X (ex. X=0.5) et affiche les prédictions et la valeur vraie.
Questions :
7) Que remarquez-vous sur la capacité de généralisation du MLP petit vs grand ?
8) Que se passe-t-il si on augmente encore le nombre d’epochs pour le MLP petit ?
9) Quel rôle joue le bruit dans la difficulté de l’apprentissage ?
10) Comment pourrait-on améliorer la généralisation des modèles (pistes) ?
11) Pouvez-vous écrire du code pour éviter de l'overfitting ?
Astuce :
torch.manual_seed(0) pour la reproductibilité.optimizer.zero_grad(), loss.backward(), optimizer.step().with torch.no_grad().Résultats attendus :
Réusltat pour éviter l'overfitting :
![]()
:alt: Comparaison MLP petit vs grand :align: center