Premiers pas avec PyTorch
Refaire avec PyTorch ce que les trois leçons précédentes ont écrit à la main – tenseurs, dérivation automatique, nn.Module, optimiseurs. Chaque étape est comparée à la version écrite à la main : gradients calculés et paramètres entraînés sont exactement les mêmes.
- Environ 45 minutes
- Niveau : Débutant
- Testé : 2026-09-14 torch 2.14, CPU, graine aléatoire fixée
Le code et les sorties des programmes sont reproduits tels qu’ils ont tourné : commentaires et sorties sont donc en chinois.
Dans les trois leçons précédentes, nous avons écrit à la main la régression linéaire, la descente de gradient et la rétropropagation. Cela fonctionne, mais c'est lent, et chaque nouveau modèle demande de réécrire beaucoup de code.
PyTorch est aujourd'hui le framework d'apprentissage profond le plus utilisé. Cette leçon ne présente pas toutes ses fonctions, elle ne fait qu'une chose : refaire avec PyTorch ce que nous avons fait dans les trois leçons précédentes, en comparant chaque étape avec la version écrite à la main. Vous verrez qu'il fait exactement ce que vous avez écrit vous-même, simplement dans une écriture plus rapide et plus pratique.
Installation (pas besoin de carte graphique : cette leçon et le module suivant tournent entièrement sur CPU) :
uv add torch
Tenseurs et dérivation automatique
L'élément de base de PyTorch est le tenseur (tensor) : un tableau multidimensionnel, très semblable à un tableau numpy. Un nombre est un tenseur de dimension 0, une suite de nombres de dimension 1, un tableau de dimension 2.
Un tenseur a une capacité qu'un tableau numpy n'a pas : la dérivation automatique. En créant un tenseur avec requires_grad=True, PyTorch enregistre chaque opération à laquelle il participe, exactement comme le Num de la leçon précédente :
a = torch.tensor(2.0, requires_grad=True) # requires_grad:记下它参与的运算,以便求导
b = torch.tensor(-3.0, requires_grad=True)
c = torch.tensor(10.0, requires_grad=True)
f = (a * b + c) ** 2
f.backward()
print(f" f = {f.item()},df/da={a.grad.item()}, df/db={b.grad.item()}, df/dc={c.grad.item()}")
== 1. 同一个算式 f = (a × b + c)²,a=2, b=-3, c=10
f = 16.0,df/da=-24.0, df/db=16.0, df/dc=8.0
Exactement le même résultat qu'avec le Num écrit à la main à la leçon précédente : -24, 16, 8. f.backward() fait ce que faisait Num.backward : partir de f, remonter le calcul enregistré, faire revenir les gradients par la règle en chaîne et les stocker dans le .grad de chaque tenseur. .item() convertit un tenseur à un seul nombre en nombre Python ordinaire.
Régression linéaire : quatre éléments
Réécrire avec PyTorch la régression linéaire de la leçon 2 fait intervenir les quatre éléments de base de l'entraînement d'un modèle avec PyTorch :
x = torch.tensor((area - area.mean()) / area.std(), dtype=torch.float32).unsqueeze(1) # 标准化,形状 (50, 1)
y = torch.tensor(price, dtype=torch.float32).unsqueeze(1)
model = nn.Linear(1, 1) # 就是 w × x + b
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
loss_fn = nn.MSELoss()
for step in range(100):
loss = loss_fn(model(x), y)
optimizer.zero_grad() # 清空上一步的梯度
loss.backward() # 反向传播,算出每个参数的梯度
optimizer.step() # 按梯度更新参数:p -= lr × p.grad
- Le modèle :
nn.Linear(1, 1)estw × x + b, 1 entrée, 1 sortie. Il crée et gère lui-même les deux paramètreswetb, avecrequires_graddéjà activé. - La fonction de perte :
nn.MSELoss()est l'erreur quadratique moyenne de la leçon 1. - L'optimiseur :
torch.optim.SGDmet à jour les paramètres.optimizer.step()est lew -= lr × dwde la leçon 2, appliqué à chacun des paramètres demodel.parameters(). - La boucle d'entraînement : remettre les gradients à zéro, rétropropager, mettre à jour les paramètres, trois lignes, dans exactement le même ordre que la version écrite à la main.
unsqueeze(1) fait passer la forme de (50,) à (50, 1), c'est-à-dire « 50 exemples, 1 caractéristique chacun ». Toutes les couches de PyTorch supposent que la première dimension est le nombre d'exemples ; nous y reviendrons plus bas.
== 2. 线性回归 100 步后:损失 143.05,换算回原单位 w=1.2571 b=14.806
Exactement le même résultat que la descente de gradient écrite à la main à la leçon 2 : perte 143,05, w = 1.2571, b = 14.806.
Son propre réseau : hériter de nn.Module
Pour un modèle un peu plus complexe, on écrit sa propre classe, qui hérite de nn.Module. Réécrivons ainsi le réseau XOR de la leçon précédente :
class TinyNet(nn.Module):
def __init__(self):
super().__init__()
self.hidden = nn.Linear(2, 4)
self.output = nn.Linear(4, 1)
def forward(self, x):
return torch.tanh(self.output(torch.tanh(self.hidden(x))))
__init__ définit les couches, forward décrit comment les données les traversent. À la leçon précédente, nous avions écrit deux classes, Neuron et Layer ; ici, un seul nn.Linear(2, 4) est « une couche de 4 neurones à 2 entrées chacun », qui regroupe les calculs des 4 neurones en une seule multiplication matricielle.
nn.Module trouve automatiquement les paramètres de toutes les couches définies dans __init__, et net.parameters() les renvoie. Comptons :
== 3. 异或网络,共 17 个参数
17 paramètres, comme le réseau écrit à la main à la leçon précédente. Entraînement :
X = torch.tensor([[0, 0], [0, 1], [1, 0], [1, 1]], dtype=torch.float32)
Y = torch.tensor([[-1], [1], [1], [-1]], dtype=torch.float32)
optimizer = torch.optim.SGD(net.parameters(), lr=0.1)
for epoch in range(1, 301):
loss = ((net(X) - Y) ** 2).sum() # 和第 3 课一样:4 个样本的平方误差之和
optimizer.zero_grad()
loss.backward()
optimizer.step()
Remarquez que net(X) calcule les 4 exemples d'un coup. À la leçon précédente, nous les calculions un par un avec une boucle for ; ici, les 4 exemples sont placés dans un tenseur de forme (4, 2), et une seule opération matricielle calcule tout.
第 1 轮 损失 4.5469
第 10 轮 损失 3.7176
第 50 轮 损失 0.5588
第 100 轮 损失 0.0514
第 200 轮 损失 0.0174
第 300 轮 损失 0.0100
训练后的预测: ['-0.953', '+0.964', '+0.944', '-0.943']
Le XOR est lui aussi appris. L'évolution de la perte ne suit pas exactement les chiffres de la leçon précédente, car les valeurs initiales des paramètres sont aléatoires et PyTorch n'utilise pas les mêmes nombres aléatoires que notre version écrite à la main. Mais la tendance est la même : lente au début, puis une chute soudaine, et enfin proche de 0.
Pour les prédictions après l'entraînement, on enveloppe le code dans torch.no_grad() :
with torch.no_grad(): # 只是预测,不需要记录求导信息
print(" 训练后的预测:", [f"{v:+.3f}" for v in net(X).squeeze(1).tolist()])
Par défaut, PyTorch enregistre pour chaque opération les informations nécessaires à la dérivation. Quand on ne fait que prédire sans entraîner, les désactiver économise de la mémoire et va plus vite.
Les formes : la source d'erreurs la plus fréquente
En programmant avec PyTorch, la plupart des erreurs concernent la forme des tenseurs. Une convention à retenir : la première dimension est le nombre d'exemples d'un lot.
batch = torch.randn(32, 2) # 32 个样本,每个 2 个特征
== 4. 一次喂 32 个样本:输入形状 (32, 2) → 隐藏层 (32, 4) → 输出 (32, 1)
L'entrée compte 32 exemples de 2 caractéristiques chacun ; après la couche cachée, 32 exemples de 4 nombres chacun ; à la fin, 32 exemples d'une sortie chacun. Le nombre d'exemples reste le même tout au long du parcours ; chaque couche ne change que la dernière dimension.
Traiter un lot d'exemples d'un coup plutôt qu'un par un est l'une des principales raisons de la rapidité de l'apprentissage profond. Les opérations matricielles sont très optimisées sur CPU, et surtout sur GPU ; calculer 32 exemples d'un coup ne prend guère plus de temps qu'un seul.
En programmant, afficher x.shape aux endroits clés est la méthode de débogage la plus efficace.
Version à la main et PyTorch comparées
| Version écrite à la main | PyTorch |
|---|---|
Num, qui note les dérivées locales |
Tenseur, requires_grad=True |
Num.backward() |
loss.backward() |
p.grad = 0.0 |
optimizer.zero_grad() |
p.value -= lr * p.grad |
optimizer.step() |
Les deux classes Neuron et Layer |
nn.Linear |
| Fonction d'erreur quadratique moyenne | nn.MSELoss() |
| Boucle sur les exemples un par un | Un lot d'exemples dans un tenseur, calculé d'un coup |
PyTorch ne fait aucune magie. Vous en avez déjà écrit vous-même la partie centrale ; vous passez simplement à une écriture plus rapide et plus commode. Face à un problème d'entraînement étrange, comme une perte qui ne baisse pas ou des gradients qui deviennent NaN, vous saurez ce qui se passe en dessous, et donc par où chercher.
Exercices
- Pour la régression linéaire, utilisez la surface non standardisée (directement
area). Quel taux d'apprentissage faut-il pour ne pas diverger ? Est-ce cohérent avec la conclusion de la leçon 2 ? - Remplacez
torch.optim.SGDpartorch.optim.Adamavec un taux d'apprentissage de 0,01, et réentraînez le réseau XOR. La perte baisse-t-elle plus vite ou plus lentement ? - Provoquez volontairement une erreur de forme : donnez à l'entrée
Xdu XOR la forme(4, 3)(3 caractéristiques par exemple), lancez, et lisez le message d'erreur : comment vous signale-t-il que les formes ne correspondent pas ?
Auto-test
1. Que font respectivement optimizer.zero_grad(), loss.backward() et optimizer.step() ?
zero_grad remet à zéro les gradients de tous les paramètres, pour éviter qu'ils s'ajoutent à ceux de l'étape précédente ; backward effectue la rétropropagation depuis la perte, calcule le gradient de chaque paramètre et le stocke dans .grad ; step met à jour chaque paramètre selon son gradient ; le SGD le plus simple fait p = p - lr × p.grad.
2. Pourquoi PyTorch suppose-t-il que la première dimension d'un tenseur est le nombre d'exemples ?
L'apprentissage profond traite toujours un lot d'exemples à la fois ; les placer dans un tenseur et les calculer ensemble par opérations matricielles est bien plus rapide que un par un. Avec la première dimension réservée au nombre d'exemples, chaque couche n'a qu'à traiter la dernière dimension, celle des caractéristiques, et le nombre d'exemples reste constant dans tout le réseau.
3. Pourquoi utiliser torch.no_grad() pour les prédictions ?
Par défaut, PyTorch enregistre pour chaque opération les informations nécessaires à la rétropropagation, ce qui coûte de la mémoire et du temps supplémentaires. Quand on ne fait que prédire sans entraîner, no_grad les désactive : cela économise des ressources et va plus vite.
Questions et discussion
Bloqué sur cette leçon ? Posez votre question ici. Et si vous pouvez répondre à quelqu'un, n'hésitez pas.
Une question rapporte 3 points, une réponse 6. Les messages paraissent après vérification.
Chargement de la discussion…