Einstieg in PyTorch
Was wir in den ersten drei Lektionen von Hand geschrieben haben, noch einmal mit PyTorch – Tensoren, automatische Ableitung, nn.Module, Optimierer. Jeder Schritt wird mit der handgeschriebenen Fassung verglichen; berechnete Gradienten und trainierte Parameter sind genau gleich.
- Etwa 45 Minuten
- Niveau: Einsteiger
- Getestet: 2026-09-14 torch 2.14, CPU, fester Zufallsstartwert
Code und Programmausgaben stehen genau so da, wie sie gelaufen sind – Kommentare und Ausgaben sind daher auf Chinesisch.
In den ersten drei Lektionen haben wir lineare Regression, Gradientenabstieg und Backpropagation von Hand geschrieben. Es funktioniert, ist aber langsam, und bei jedem neuen Modell muss man viel Code neu schreiben.
PyTorch ist das derzeit verbreitetste Deep-Learning-Framework. Diese Lektion behandelt nicht alle Funktionen, sondern tut nur eines: das, was wir in den ersten drei Lektionen getan haben, mit PyTorch noch einmal tun und jeden Schritt mit der handgeschriebenen Fassung vergleichen. Du wirst sehen, dass es genau dasselbe tut wie dein eigener Code, nur in einer schnelleren und bequemeren Schreibweise.
Installation (eine Grafikkarte ist nicht nötig; diese Lektion und das nächste Modul laufen komplett auf der CPU):
uv add torch
Tensoren und automatische Ableitung
Das Grundlegendste in PyTorch ist der Tensor (tensor): ein mehrdimensionales Array, sehr ähnlich einem numpy-Array. Eine Zahl ist ein 0-dimensionaler Tensor, eine Zahlenreihe 1-dimensional, eine Tabelle 2-dimensional.
Tensoren können etwas, das numpy-Arrays nicht können: automatisch ableiten. Legt man einen Tensor mit requires_grad=True an, protokolliert PyTorch jede Operation, an der er beteiligt ist, genau wie die Num der vorigen Lektion:
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
Genau dasselbe Ergebnis wie mit der handgeschriebenen Num der vorigen Lektion: -24, 16, 8. f.backward() tut, was Num.backward in der vorigen Lektion tat: von f aus entlang des protokollierten Rechenwegs die Gradienten per Kettenregel zurückreichen und im .grad jedes Tensors ablegen. .item() wandelt einen Tensor mit nur einer Zahl in eine gewöhnliche Python-Zahl um.
Lineare Regression: vier Dinge
Schreibt man die lineare Regression aus Lektion 2 mit PyTorch neu, braucht man die vier grundlegenden Dinge, mit denen PyTorch Modelle trainiert:
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
- Modell:
nn.Linear(1, 1)istw × x + b, 1 Eingabe, 1 Ausgabe. Es legt die beiden Parameterwundbselbst an, verwaltet sie und hatrequires_gradschon gesetzt. - Verlustfunktion:
nn.MSELoss()ist die mittlere quadratische Abweichung aus Lektion 1. - Optimierer:
torch.optim.SGDaktualisiert die Parameter.optimizer.step()istw -= lr × dwaus Lektion 2, nur für jeden Parameter inmodel.parameters(). - Trainingsschleife: Gradienten nullen, Backpropagation, Parameter aktualisieren, drei Zeilen, in genau derselben Reihenfolge wie in der vorigen Lektion von Hand.
unsqueeze(1) ändert die Form von (50,) in (50, 1), also „50 Beispiele mit je 1 Merkmal“. Bei allen Schichten von PyTorch ist die erste Dimension vereinbarungsgemäß die Zahl der Beispiele; dazu unten mehr.
== 2. 线性回归 100 步后:损失 143.05,换算回原单位 w=1.2571 b=14.806
Genau dasselbe Ergebnis wie beim handgeschriebenen Gradientenabstieg in Lektion 2: Verlust 143,05, w = 1.2571, b = 14.806.
Ein eigenes Netz: von nn.Module erben
Für etwas komplexere Modelle schreibt man eine eigene Klasse, die von nn.Module erbt. Damit schreiben wir das XOR-Netz der vorigen Lektion neu:
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))))
In __init__ wird festgelegt, welche Schichten es gibt, in forward, wie die Daten durch diese Schichten fließen. In der vorigen Lektion schrieben wir die zwei Klassen Neuron und Layer; jetzt ist ein einziges nn.Linear(2, 4) „eine Schicht mit 4 Neuronen und je 2 Eingaben“, das die Berechnungen der 4 Neuronen zusammenfasst und mit einer einzigen Matrixmultiplikation erledigt.
nn.Module findet automatisch die Parameter aller Schichten, die du in __init__ definierst, und net.parameters() liefert sie. Gezählt:
== 3. 异或网络,共 17 个参数
Wie beim handgeschriebenen Netz der vorigen Lektion, 17 Parameter. Training:
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()
Beachte, dass net(X) die 4 Beispiele auf einmal berechnet. In der vorigen Lektion rechneten wir sie mit einer for-Schleife einzeln; jetzt kommen die 4 Beispiele in einen Tensor der Form (4, 2), und eine Matrixoperation berechnet alle auf einmal.
第 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']
Auch hier wurde XOR gelernt. Der Verlauf des Verlusts stimmt nicht genau mit den Zahlen der vorigen Lektion überein, weil die Anfangswerte der Parameter zufällig sind und PyTorch andere Zufallszahlen verwendet als unsere handgeschriebene Fassung. Der Trend ist aber derselbe: anfangs langsam, dann ein plötzlicher Abfall, am Ende nahe 0.
Für Vorhersagen nach dem Training steht torch.no_grad() darum:
with torch.no_grad(): # 只是预测,不需要记录求导信息
print(" 训练后的预测:", [f"{v:+.3f}" for v in net(X).squeeze(1).tolist()])
Standardmäßig protokolliert PyTorch bei jeder Operation die Informationen für die Ableitung. Wird nur vorhergesagt und nicht trainiert, spart das Abschalten Speicher und ist schneller.
Formen: die häufigste Fehlerquelle
Beim Programmieren mit PyTorch haben die meisten Fehlermeldungen mit der Form von Tensoren zu tun. Eine Vereinbarung sollte man sich merken: Die erste Dimension ist die Zahl der Beispiele in einem Stapel.
batch = torch.randn(32, 2) # 32 个样本,每个 2 个特征
== 4. 一次喂 32 个样本:输入形状 (32, 2) → 隐藏层 (32, 4) → 输出 (32, 1)
Die Eingabe sind 32 Beispiele mit je 2 Merkmalen; nach der verborgenen Schicht 32 Beispiele mit je 4 Zahlen; am Ende 32 Beispiele mit je 1 Ausgabe. Die Zahl der Beispiele bleibt über den ganzen Weg gleich; jede Schicht ändert nur die letzte Dimension.
Einen Stapel Beispiele auf einmal zu verarbeiten statt einzeln, ist einer der wichtigsten Gründe, warum Deep Learning schnell ist. Matrixoperationen sind auf der CPU und besonders auf der GPU hoch optimiert; 32 Beispiele auf einmal brauchen kaum länger als 1 Beispiel.
Beim Programmieren an wichtigen Stellen x.shape auszugeben, ist die wirksamste Methode zur Fehlersuche.
Handgeschrieben und PyTorch im Vergleich
| Handgeschriebene Fassung | PyTorch |
|---|---|
Num, hält lokale Ableitungen fest |
Tensor, requires_grad=True |
Num.backward() |
loss.backward() |
p.grad = 0.0 |
optimizer.zero_grad() |
p.value -= lr * p.grad |
optimizer.step() |
Die zwei Klassen Neuron, Layer |
nn.Linear |
| Funktion für mittlere quadratische Abweichung | nn.MSELoss() |
| Schleife über einzelne Beispiele | Ein Stapel Beispiele in einem Tensor, auf einmal berechnet |
PyTorch zaubert nicht. Den wichtigsten Teil hast du schon selbst geschrieben; jetzt wechselst du nur zu einer schnelleren, bequemeren Schreibweise. Stößt du später auf seltsame Trainingsprobleme, etwa ein Verlust, der nicht sinkt, oder Gradienten, die zu NaN werden, weißt du, was darunter passiert, und damit, wo du suchen musst.
Übungen
- Nimm für die lineare Regression die nicht standardisierte Fläche (direkt
area). Wie klein muss die Lernrate sein, damit es nicht divergiert? Stimmt das mit dem Fazit aus Lektion 2 überein? - Ersetze
torch.optim.SGDdurchtorch.optim.Adammit Lernrate 0,01 und trainiere das XOR-Netz neu. Sinkt der Verlust schneller oder langsamer? - Erzeuge absichtlich einen Formfehler: Ändere die XOR-Eingabe
Xin die Form(4, 3)(3 Merkmale pro Beispiel), führ es aus und lies die Fehlermeldung: Wie sagt sie dir, dass die Formen nicht passen?
Selbsttest
1. Was tun optimizer.zero_grad(), loss.backward() und optimizer.step() jeweils?
zero_grad setzt die Gradienten aller Parameter auf null, damit sie sich nicht mit denen des vorigen Schritts addieren; backward führt vom Verlust aus die Backpropagation durch, berechnet den Gradienten jedes Parameters und legt ihn in .grad ab; step aktualisiert jeden Parameter anhand seines Gradienten, beim einfachsten SGD ist das p = p - lr × p.grad.
2. Warum ist bei PyTorch die erste Dimension eines Tensors vereinbarungsgemäß die Zahl der Beispiele?
Deep Learning verarbeitet immer einen Stapel Beispiele auf einmal; sie in einem Tensor zusammen per Matrixoperation zu berechnen ist viel schneller als einzeln. Ist die erste Dimension die Zahl der Beispiele, muss jede Schicht nur die letzte, die Merkmalsdimension, verarbeiten, und die Zahl der Beispiele bleibt im ganzen Netz gleich.
3. Warum verwendet man bei Vorhersagen torch.no_grad()?
Standardmäßig protokolliert PyTorch bei jeder Operation die Informationen für die Backpropagation, was zusätzlichen Speicher und Zeit kostet. Wird nur vorhergesagt und nicht trainiert, schaltet no_grad das ab; das spart Ressourcen und ist schneller.
Fragen und Diskussion
Hängst du in dieser Lektion fest? Frag hier. Und wenn du die Frage von jemandem beantworten kannst, tu es gern.
Eine Frage bringt 3 Punkte, eine Antwort 6. Beiträge erscheinen nach der Prüfung.
Diskussion wird geladen…