Faza 03 · lecția 11
Introducere în PyTorch
Scopul lecției: Ați construit motorul din pistoane și arbori cotiți. Acum învățați să-l folosiți pe cel pe care îl conduce toată lumea.
Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.
Cuprinsul lecției
- Obiective de învățare
- Problema
- Conceptul
- De ce s-a impus PyTorch
- Tensori
- Autograd
- nn.Module
- Funcții de pierdere și optimizatori
- Bucla de antrenare
- Dataset și DataLoader
- Antrenare pe GPU
- Comparație: mini-framework, PyTorch și JAX
- Construiți
- Pasul 1: încărcarea MNIST din fișiere brute
- Pasul 2: definirea modelului
- Pasul 3: bucla de antrenare
- Pasul 4: conectarea tuturor componentelor
- Folosiți
- Comparație rapidă: mini-framework și PyTorch
- Salvarea și încărcarea modelelor
- Planificarea ratei de învățare
- Livrați
- Exerciții
- Termeni-cheie
- Lecturi suplimentare
Ați construit motorul din pistoane și arbori cotiți. Acum învățați să-l folosiți pe cel pe care îl conduce toată lumea.
Tip: Construire Limbaje: Python Cerințe preliminare: Lecția 03.10 — Construiți-vă propriul mini-framework Durată: ~75 de minute
Obiective de învățare
- Să construiți și să antrenați rețele neuronale folosind
nn.Module,nn.Sequentialși autograd din PyTorch. - Să utilizați tensori PyTorch, accelerarea pe GPU și bucla standard de antrenare (
zero_grad, propagare înainte, pierdere, propagare înapoi,step). - Să convertiți componentele mini-frameworkului construit de la zero în echivalentele lor PyTorch.
- Să profilați și să comparați viteza de antrenare a frameworkului Python pur cu cea a PyTorch pe aceeași sarcină.
Problema
Aveți un mini-framework funcțional: straturi liniare, ReLU, dropout, normalizare pe loturi, Adam, un DataLoader și o buclă de antrenare. Acesta antrenează în Python pur o rețea cu patru straturi pe o problemă de clasificare circulară.
Este și de 500 de ori mai lent decât PyTorch pe aceeași problemă.
Mini-frameworkul procesează câte un exemplu prin bucle Python imbricate. PyTorch trimite aceleași operații către nuclee C++/CUDA optimizate care rulează pe GPU. Pe un singur NVIDIA A100, PyTorch ar antrena un ResNet-50 cu 25,6 milioane de parametri pe ImageNet, cu 1,28 milioane de imagini, în aproximativ șase ore. Frameworkul construit aici ar avea nevoie de circa 3.000 de ore pentru aceeași sarcină — dacă nu ar epuiza mai întâi memoria.
Notă tehnică a traducerii: Multiplicatorul
500xși timpii de șase, respectiv 3.000 de ore nu provin dintr-un benchmark reproductibil furnizat de lecție. Viteza depinde de hardware, versiuni, dimensiunea lotului, precizie, încărcarea datelor și implementare; valorile trebuie tratate ca ilustrații, nu ca rezultate generalizabile.
Viteza nu este singura diferență. Frameworkul nu oferă suport GPU. Nu are diferențiere automată — ați scris manual backward() pentru fiecare modul. Nu are serializare, antrenare distribuită, precizie mixtă sau o metodă de depanare a fluxului gradienților în afara instrucțiunilor de afișare.
PyTorch acoperă toate aceste lipsuri, păstrând același model mental pe care l-ați construit deja: Module, forward(), parameters(), backward(), optimizer.step(). Conceptele se transferă aproape direct. Sintaxa este foarte asemănătoare. Diferența este că PyTorch ascunde un deceniu de inginerie a sistemelor în spatele unei interfețe apropiate de cea proiectată de la zero.
Conceptul
De ce s-a impus PyTorch
În 2015, TensorFlow cerea definirea unui graf static de calcul înaintea execuției. Construiți graful, îl compilați, apoi introduceți datele în el. Depanarea însemna examinarea vizualizărilor grafului. Modificarea arhitecturii putea necesita reconstruirea grafului.
PyTorch a fost lansat în 2017 cu o filozofie diferită: execuția imediată (eager execution). Scrieți Python, iar codul rulează imediat. y = model(x) chiar calculează acum y; nu înseamnă „adăugați un nod într-un graf care va calcula mai târziu y”. Astfel au devenit utilizabile instrumentele Python obișnuite de depanare: print(), pdb și ramurile if/else din propagarea înainte.
Până în 2020, ecosistemul de cercetare adoptase pe scară largă PyTorch. Sursa afirmă că ponderea PyTorch în lucrările de cercetare ML a crescut de la 7% în 2017 la peste 75% în 2022 și că Meta, Google DeepMind, OpenAI, Anthropic și Hugging Face îl folosesc drept framework principal. TensorFlow 2.x a introdus la rândul său execuția imediată.
Notă tehnică a traducerii: Procentele de utilizare și atribuirea unui „framework principal” companiilor nu sunt însoțite de o metodologie sau de surse primare și se pot schimba în timp. Faptul că TensorFlow 2.x oferă execuție imediată nu demonstrează singur motivația cauzală formulată de original drept o „recunoaștere tacită”.
Lecția de reținut: experiența dezvoltatorului produce efecte cumulative. Un framework ușor de depanat poate câștiga adopție chiar dacă anumite operații au costuri de execuție diferite.
Tensori
Un tensor este un tablou multidimensional cu trei proprietăți esențiale: formă, tip de date (dtype) și dispozitiv.
import torch
x = torch.zeros(3, 4) # shape: (3, 4), dtype: float32, device: cpu
x = torch.randn(2, 3, 224, 224) # batch of 2 RGB images, 224x224
x = torch.tensor([1, 2, 3]) # from a Python list
Forma descrie dimensiunile. Un scalar are forma (), un vector (n,), o matrice (m, n), iar un lot de imagini (batch, channels, height, width).
Tipul de date controlează precizia și memoria.
dtype |
Biți | Domeniu sau precizie | Caz de utilizare |
|---|---|---|---|
float32 |
32 | ~7 cifre zecimale | Antrenare implicită |
float16 |
16 | ~3,3 cifre zecimale | Precizie mixtă |
bfloat16 |
16 | Domeniu de exponenți ca float32, precizie mai mică |
Antrenarea LLM-urilor |
int8 |
8 | -128–127 | Inferență cuantizată |
Dispozitivul determină unde are loc calculul.
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
x = torch.randn(3, 4, device=device)
x = x.to("cuda")
x = x.cpu()
În general, tensorii care participă la aceeași operație trebuie să se afle pe același dispozitiv. Una dintre cele mai frecvente erori pentru începători este RuntimeError: Expected all tensors to be on the same device. Remediați-o mutând tensorii implicați pe același dispozitiv înaintea calculului; operațiile explicite de copiere, precum to(), sunt excepții firești.
Schimbarea formei poate fi o operație cu timp constant când întoarce un view și modifică numai metadatele, nu datele.
x = torch.randn(2, 3, 4)
x.view(2, 12) # reshape to (2, 12) -- must be contiguous
x.reshape(6, 4) # reshape to (6, 4) -- works always
x.permute(2, 0, 1) # reorder dimensions
x.unsqueeze(0) # add dimension: (1, 2, 3, 4)
x.squeeze() # remove size-1 dimensions
Notă tehnică a traducerii: Afirmația originală că schimbarea formei este mereu o operație cu timp constant și că
reshape„funcționează întotdeauna” este prea largă.view()cere o dispunere compatibilă, iarreshape()întoarce un view când poate, dar poate copia datele în caz contrar; forma cerută trebuie oricum să păstreze numărul de elemente.
Autograd
Mini-frameworkul v-a cerut să implementați backward() pentru fiecare modul. PyTorch nu vă cere aceasta. El înregistrează operațiile efectuate asupra tensorilor într-un graf aciclic orientat, adică graful de calcul, apoi îl parcurge în sens invers pentru a calcula automat gradienții.
Diferența esențială față de frameworkul propriu este că PyTorch folosește diferențiere automată bazată pe bandă (tape-based autodiff). Fiecare operație se adaugă pe „bandă” în propagarea înainte. Apelarea .backward() o parcurge în sens invers.
x = torch.randn(3, requires_grad=True)
y = x ** 2 + 3 * x
z = y.sum()
z.backward()
print(x.grad) # dz/dx = 2x + 3
Trei reguli pentru autograd:
- În mod implicit, numai tensorii-frunză cu
requires_grad=Trueîși acumulează gradienții în atributul.grad. - Gradienții se acumulează implicit — apelați
optimizer.zero_grad()înainte de fiecare propagare înapoi. torch.no_grad()dezactivează urmărirea gradienților și se folosește la evaluare.
Notă tehnică a traducerii: Tensorii care nu sunt frunze își pot păstra și ei gradientul dacă apelați explicit
retain_grad(). Regula de mai sus descrie comportamentul implicit, nu o interdicție absolută.
nn.Module
nn.Module este clasa de bază pentru componentele rețelelor neuronale din PyTorch. Ați construit deja această abstracție în lecția 10. Versiunea PyTorch adaugă înregistrarea automată a parametrilor, descoperirea recursivă a modulelor, gestionarea dispozitivelor și serializarea prin dicționarul de stare.
import torch.nn as nn
class MLP(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super().__init__()
self.layer1 = nn.Linear(input_dim, hidden_dim)
self.relu = nn.ReLU()
self.layer2 = nn.Linear(hidden_dim, output_dim)
def forward(self, x):
x = self.layer1(x)
x = self.relu(x)
x = self.layer2(x)
return x
Când atribuiți un nn.Module sau un nn.Parameter unui atribut în __init__, PyTorch îl înregistrează automat. model.parameters() colectează recursiv fiecare parametru înregistrat. De aceea nu mai trebuie să adunați manual ponderile ca în mini-framework.
Componente de bază:
| Modul | Ce face | Parametri |
|---|---|---|
nn.Linear(in, out) |
Wx + b |
in*out + out |
nn.Conv2d(in_ch, out_ch, k) |
Convoluție 2D | in_ch*out_ch*k*k + out_ch |
nn.BatchNorm1d(features) |
Normalizează activările | 2 * features |
nn.Dropout(p) |
Anulare aleatoare | 0 |
nn.ReLU() |
max(0, x) |
0 |
nn.GELU() |
Unitate liniară cu eroare gaussiană | 0 |
nn.Embedding(vocab, dim) |
Tabel de căutare | vocab * dim |
nn.LayerNorm(dim) |
Normalizare pe exemplu | 2 * dim |
Funcții de pierdere și optimizatori
PyTorch oferă versiuni pregătite pentru producție ale componentelor construite anterior.
Funcții de pierdere din torch.nn:
| Pierdere | Sarcină | Intrare |
|---|---|---|
nn.MSELoss() |
Regresie | Orice formă |
nn.CrossEntropyLoss() |
Clasificare multiclasa | Logits, nu softmax |
nn.BCEWithLogitsLoss() |
Clasificare binară | Logits, nu sigmoidă |
nn.L1Loss() |
Regresie robustă | Orice formă |
nn.CTCLoss() |
Alinierea secvențelor | Probabilități logaritmice |
Notă: CrossEntropyLoss combină intern LogSoftmax și NLLLoss. Furnizați logits neprelucrați, nu ieșiri softmax. Aplicarea prealabilă a softmaxului modifică obiectivul și gradientul fără ca API-ul să emită neapărat o eroare.
Optimizatori din torch.optim:
| Optimizator | Când se folosește | LR orientativă |
|---|---|---|
SGD(params, lr, momentum) |
CNN-uri și pipeline-uri bine reglate | 0,01–0,1 |
Adam(params, lr) |
Punct de pornire implicit | 1e-3 |
AdamW(params, lr, weight_decay) |
Transformatoare și ajustare fină | 1e-4–1e-3 |
LBFGS(params) |
Scară mică, ordinul al doilea | 1,0 |
Valorile ratei de învățare sunt numai puncte de pornire și trebuie reglate pentru model, date, dimensiunea lotului și schemă.
Bucla de antrenare
Fiecare buclă PyTorch de antrenare urmează același tipar în cinci pași. Îl cunoașteți deja din lecția 10.
Tiparul canonic:
for epoch in range(num_epochs):
model.train()
for inputs, targets in train_loader:
inputs, targets = inputs.to(device), targets.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
Cinci linii în interiorul buclei pe loturi. Arhitectura se schimbă. Datele se schimbă. Acest tipar de bază rămâne recognoscibil, chiar dacă antrenările mari adaugă acumularea gradienților, precizie mixtă, paralelism, planificatoare și alte mecanisme.
Notă tehnică a traducerii: Atribuirea originală că exact aceste cinci linii „au antrenat GPT-4, Stable Diffusion și LLaMA” este retorică și nu descrie integral sistemele distribuite folosite la scară mare; detaliile publice despre antrenarea GPT-4 sunt, de asemenea, insuficiente pentru a verifica această formulare literală.
Dataset și DataLoader
Dataset din PyTorch este o clasă abstractă cu două metode: __len__ și __getitem__. DataLoader o învelește cu grupare în loturi, amestecare și încărcare multiproces.
from torch.utils.data import Dataset, DataLoader
class MNISTDataset(Dataset):
def __init__(self, images, labels):
self.images = images
self.labels = labels
def __len__(self):
return len(self.labels)
def __getitem__(self, idx):
return self.images[idx], self.labels[idx]
loader = DataLoader(dataset, batch_size=64, shuffle=True, num_workers=4)
num_workers=4 creează patru procese de lucru pentru a încărca date în paralel, în timp ce acceleratorul procesează lotul curent. În sarcini limitate de accesul la disc, precum imagini mari sau audio, aceasta poate accelera antrenarea.
Notă tehnică a traducerii: Accelerarea nu este garantată și poate fi negativă pentru seturi mici sau transformări ieftine. Pe Windows, încărcarea multiproces cere în mod obișnuit ca punctul de intrare al scriptului să fie protejat prin
if __name__ == "__main__":, iar procesele de lucru pot mări consumul de memorie.
Antrenare pe GPU
Mutarea unui model pe GPU:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
Aceasta mută recursiv pe GPU fiecare parametru și buffer. Apoi mutați fiecare lot în timpul antrenării:
inputs, targets = inputs.to(device), targets.to(device)
Precizia mixtă poate reduce memoria și mări debitul pe GPU-uri moderne precum A100, H100 sau RTX 4090, executând automat operațiile potrivite la precizie redusă și folosind scalarea gradienților pentru a limita underflow-ul cu float16:
from torch.amp import autocast, GradScaler
scaler = GradScaler()
for inputs, targets in loader:
with autocast(device_type="cuda"):
outputs = model(inputs)
loss = criterion(outputs, targets)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
optimizer.zero_grad()
Notă tehnică a traducerii: Reducerea la jumătate a memoriei și dublarea debitului nu sunt garanții. Câștigul depinde de hardware, dimensiunile operațiilor, tipul numeric, lățimea de bandă, operațiile eligibile pentru autocast și costul transferurilor. Autocast alege precizia pe operație; nu rulează necondiționat întreaga propagare înainte și înapoi în
float16.
Comparație: mini-framework, PyTorch și JAX
| Caracteristică | Mini-framework (L10) | PyTorch | JAX |
|---|---|---|---|
| Autodiff | backward() manual |
Autograd bazat pe bandă | Transformări funcționale |
| Execuție | Imediată, bucle Python | Imediată, nuclee C++ | Urmărire și compilare JIT |
| Suport GPU | Nu | Da: CUDA, ROCm, MPS | Da: CUDA, TPU |
| Viteză pentru MLP pe MNIST | ~300 s/epocă | ~0,5 s/epocă | ~0,3 s/epocă |
| Sistem de module | Clasă Module proprie |
nn.Module |
Funcții fără stare: Flax/Equinox |
| Depanare | print() |
print(), pdb, breakpoint() |
Mai dificilă în cod urmărit sau compilat JIT |
| Ecosistem | Niciunul | Hugging Face, Lightning, timm | Flax, Optax, Orbax |
| Curbă de învățare | L-ați construit | Moderată | Abruptă, paradigmă funcțională |
| Utilizare în producție | Probleme demonstrative | Meta, OpenAI, Anthropic, HF | Google DeepMind, Midjourney |
Notă tehnică a traducerii: Timpii per epocă nu sunt însoțiți de hardware, versiuni, dimensiunea lotului sau o metodologie comună și nu constituie un benchmark comparabil. Afirmațiile despre dificultatea depanării și utilizarea organizațională sunt simplificări dependente de instrumente, versiuni și proiecte.
dropout-mask
Construiți
Un MLP cu trei straturi, antrenat pe MNIST numai cu primitive PyTorch. Fără învelișuri de nivel înalt și fără torchvision.datasets. Descărcăm și interpretăm singuri datele brute.
Pasul 1: încărcarea MNIST din fișiere brute
MNIST este distribuit în patru fișiere comprimate cu gzip: imaginile de antrenare, 60.000 x 28 x 28, etichetele de antrenare, imaginile de testare, 10.000 x 28 x 28, și etichetele de testare. Le descărcăm și interpretăm formatul binar.
import torch
import torch.nn as nn
import struct
import gzip
import urllib.request
import os
def download_mnist(path="./mnist_data"):
base_url = "https://storage.googleapis.com/cvdf-datasets/mnist/"
files = [
"train-images-idx3-ubyte.gz",
"train-labels-idx1-ubyte.gz",
"t10k-images-idx3-ubyte.gz",
"t10k-labels-idx1-ubyte.gz",
]
os.makedirs(path, exist_ok=True)
for f in files:
filepath = os.path.join(path, f)
if not os.path.exists(filepath):
urllib.request.urlretrieve(base_url + f, filepath)
def load_images(filepath):
with gzip.open(filepath, "rb") as f:
magic, num, rows, cols = struct.unpack(">IIII", f.read(16))
data = f.read()
images = torch.frombuffer(bytearray(data), dtype=torch.uint8)
images = images.reshape(num, rows * cols).float() / 255.0
return images
def load_labels(filepath):
with gzip.open(filepath, "rb") as f:
magic, num = struct.unpack(">II", f.read(8))
data = f.read()
labels = torch.frombuffer(bytearray(data), dtype=torch.uint8).long()
return labels
Notă tehnică a traducerii: Funcțiile citesc câmpurile
magicșinum, dar nu verifică valorile magice, lungimea datelor sau concordanța dintre numărul declarat de înregistrări și conținut. Pentru date neîncrezătoare sau descărcări incomplete, validați formatul înainte dereshape.
Pasul 2: definirea modelului
Un MLP cu trei straturi: 784 -> 256 -> 128 -> 10. Activări ReLU și dropout pentru regularizare. Fără normalizare pe loturi, pentru simplitate.
class MNISTModel(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(256, 128),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(128, 10),
)
def forward(self, x):
return self.net(x)
Stratul de ieșire produce zece logits neprelucrați, câte unul pentru fiecare cifră. Nu aplicați softmax — CrossEntropyLoss îl tratează intern.
Numărul parametrilor este 784*256 + 256 + 256*128 + 128 + 128*10 + 10 = 235.146. Este foarte mic după standardele actuale. GPT-2 small are aproximativ 124 de milioane de parametri. Modelul MNIST se antrenează rapid pe hardware obișnuit.
Pasul 3: bucla de antrenare
Tiparul canonic propagare înainte–pierdere–propagare înapoi–actualizare.
def train_one_epoch(model, loader, criterion, optimizer, device):
model.train()
total_loss = 0
correct = 0
total = 0
for images, labels in loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
total_loss += loss.item() * images.size(0)
_, predicted = outputs.max(1)
correct += predicted.eq(labels).sum().item()
total += labels.size(0)
return total_loss / total, correct / total
def evaluate(model, loader, criterion, device):
model.eval()
total_loss = 0
correct = 0
total = 0
with torch.no_grad():
for images, labels in loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
total_loss += loss.item() * images.size(0)
_, predicted = outputs.max(1)
correct += predicted.eq(labels).sum().item()
total += labels.size(0)
return total_loss / total, correct / total
Observați torch.no_grad() în timpul evaluării. Acesta dezactivează autograd, reducând memoria necesară și eliminând costul construirii unui graf de calcul nefolosit.
Pasul 4: conectarea tuturor componentelor
def main():
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
download_mnist()
train_images = load_images("./mnist_data/train-images-idx3-ubyte.gz")
train_labels = load_labels("./mnist_data/train-labels-idx1-ubyte.gz")
test_images = load_images("./mnist_data/t10k-images-idx3-ubyte.gz")
test_labels = load_labels("./mnist_data/t10k-labels-idx1-ubyte.gz")
train_dataset = torch.utils.data.TensorDataset(train_images, train_labels)
test_dataset = torch.utils.data.TensorDataset(test_images, test_labels)
train_loader = torch.utils.data.DataLoader(
train_dataset, batch_size=64, shuffle=True
)
test_loader = torch.utils.data.DataLoader(
test_dataset, batch_size=256, shuffle=False
)
model = MNISTModel().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
num_params = sum(p.numel() for p in model.parameters())
print(f"Device: {device}")
print(f"Parameters: {num_params:,}")
print(f"Train samples: {len(train_dataset):,}")
print(f"Test samples: {len(test_dataset):,}")
print()
for epoch in range(10):
train_loss, train_acc = train_one_epoch(
model, train_loader, criterion, optimizer, device
)
test_loss, test_acc = evaluate(
model, test_loader, criterion, device
)
print(
f"Epoch {epoch+1:2d} | "
f"Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f} | "
f"Test Loss: {test_loss:.4f} | Test Acc: {test_acc:.4f}"
)
torch.save(model.state_dict(), "mnist_mlp.pt")
print(f"\nModel saved to mnist_mlp.pt")
print(f"Final test accuracy: {test_acc:.4f}")
Notă tehnică a traducerii: Fragmentul definește
main(), dar nu îl apelează. Pentru a rula singur, are nevoie de un gard de intrare, de exempluif __name__ == "__main__": main(); scriptul însoțitor al lecției conține acest gard.
Rezultatul așteptat de sursă după zece epoci este o acuratețe de testare de aproximativ 97,8%. Ea estimează un timp de antrenare de circa 30 de secunde pe CPU, cinci secunde pe GPU și aproximativ 45 de minute în mini-frameworkul cu aceeași arhitectură.
Notă tehnică a traducerii: Acuratețea și timpii nu sunt garantați și nu sunt însoțiți de versiuni, procesor, GPU, sistem de operare sau metodologie. Rezultatele pot varia între rulări și platforme; comparați-le numai prin măsurători realizate în același mediu.
Notă tehnică a traducerii: Codul evaluează
test_loaderdupă fiecare epocă. Pentru monitorizare și alegerea hiperparametrilor, separați un set de validare din datele de antrenare și rezervați setul de test pentru evaluarea finală; altfel, selecția repetată poate adapta deciziile la test.
Folosiți
Comparație rapidă: mini-framework și PyTorch
| Mini-framework (lecția 10) | PyTorch |
|---|---|
model = Sequential(Linear(784, 256), ReLU(), ...) |
model = nn.Sequential(nn.Linear(784, 256), nn.ReLU(), ...) |
pred = model.forward(x) |
pred = model(x) |
optimizer.zero_grad() |
optimizer.zero_grad() |
grad = criterion.backward(), apoi model.backward(grad) |
loss.backward() |
optimizer.step() |
optimizer.step() |
| Fără GPU | model.to("cuda") |
| Propagare înapoi manuală pentru fiecare modul | Autograd gestionează totul |
Interfața este foarte asemănătoare. Diferența se află în mecanismele interne.
Salvarea și încărcarea modelelor
torch.save(model.state_dict(), "model.pt")
model = MNISTModel()
model.load_state_dict(torch.load("model.pt", weights_only=True))
model.eval()
Salvați state_dict(), adică dicționarul parametrilor și bufferelor persistente, nu obiectul modelului. Salvarea obiectului complet se bazează pe pickle și cuplează artefactul de definiția clasei și de structura codului. Dicționarele de stare sunt metoda recomandată pentru compatibilitate, dar arhitectura care le încarcă trebuie să aibă chei și forme compatibile.
Notă tehnică a traducerii:
weights_only=Truerestrânge obiectele pe care le poate construi unpicklerul și reduce suprafața de executare a codului arbitrar, dar documentația PyTorch precizează că nu elimină toate riscurile, precum refuzul serviciului sau anumite forme de corupere a memoriei. Încărcați numai artefacte din surse de încredere.
Notă tehnică a traducerii: Pentru a încărca pe un calculator fără CUDA un punct de control salvat pe GPU, transmiteți și
map_location, de exemplutorch.load("model.pt", map_location="cpu", weights_only=True). Scriptul însoțitor foloseștemap_location=device.
Planificarea ratei de învățare
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
optimizer, T_max=10
)
for epoch in range(10):
train_one_epoch(model, train_loader, criterion, optimizer, device)
scheduler.step()
PyTorch oferă numeroase planificatoare, între care StepLR, ExponentialLR, CosineAnnealingLR, OneCycleLR și ReduceLROnPlateau. Toate se conectează la aceeași interfață a optimizatorului, deși momentul și argumentele apelului step() pot diferi între planificatoare.
Livrați
Această lecție produce două artefacte:
outputs/prompt-pytorch-debugger.md— un prompt pentru diagnosticarea eșecurilor frecvente de antrenare în PyTorch.outputs/skill-pytorch-patterns.md— o referință de skill pentru tipare de antrenare PyTorch.
Exerciții
- Adăugați normalizarea pe loturi. Introduceți
nn.BatchNorm1ddupă straturile liniare ascunse, înaintea activării ReLU, nu după stratul final de logits. Comparați acuratețea de testare și viteza de antrenare cu versiunea care folosește numai dropout. Sursa estimează că normalizarea pe loturi ar trebui să atingă peste 98% în mai puține epoci; verificați experimental în mediul dumneavoastră.
Notă tehnică a traducerii: Aplicarea BatchNorm pe ultimul strat înainte de
CrossEntropyLossmodifică distribuția logits și nu este configurația obișnuită pentru acest MLP; normalizarea se aplică de regulă numai straturilor ascunse.
-
Implementați un instrument de găsire a ratei de învățare. Antrenați o epocă folosind o rată de învățare care crește exponențial de la
1e-7la1.0. Reprezentați pierderea în funcție de LR. O valoare maximă candidată se află înainte ca pierderea să înceapă să crească. Folosiți testul pentru a alege o LR mai bună pentru modelul MNIST. -
Portați pe GPU cu precizie mixtă. Adăugați
torch.amp.autocastșiGradScalerîn bucla de antrenare. Măsurați debitul în exemple pe secundă cu și fără precizie mixtă pe GPU. Sursa sugerează o accelerare de aproximativ două ori pe A100; măsurați rezultatul real, deoarece nu este garantat. -
Construiți un Dataset personalizat. Descărcați Fashion-MNIST, care are același format ca MNIST, dar conține articole vestimentare. Implementați clasa
FashionMNISTDataset(Dataset)cu__getitem__și__len__. Antrenați același MLP și comparați acuratețea. Fashion-MNIST este mai dificil; sursa estimează aproximativ 88% față de aproximativ 98%, valori care depind de configurație. -
Înlocuiți Adam cu SGD și momentum. Antrenați cu
SGD(params, lr=0.01, momentum=0.9). Comparați curbele de convergență. Apoi adăugați un planificatorCosineAnnealingLRși verificați dacă SGD ajunge din urmă Adam până la epoca 10.
Termeni-cheie
| Termen | Ce spun oamenii | Ce înseamnă de fapt |
|---|---|---|
| Tensor | „Un tablou multidimensional” | Un tablou tipizat și conștient de dispozitiv, ale cărui operații se integrează cu diferențierea automată |
| Autograd | „Retropropagare automată” | Sistem bazat pe bandă care înregistrează operațiile în propagarea înainte, apoi le parcurge invers pentru a calcula gradienții |
nn.Module |
„Un strat” | Clasa de bază pentru un bloc de calcul diferențiabil; înregistrează parametrii, permite imbricarea și gestionează modurile de antrenare și evaluare |
state_dict |
„Ponderile modelului” | Un OrderedDict care asociază numele parametrilor și bufferelor persistente cu tensori; reprezentarea serializabilă recomandată a stării modelului |
.backward() |
„Calculați gradienții” | Parcurge graful de calcul în sens invers, calculând și acumulând gradienții pentru tensorii-frunză cu requires_grad=True și pentru tensorii nefrunză reținuți explicit |
.to(device) |
„Mutați pe GPU” | Transferă recursiv parametrii și bufferele unui modul pe dispozitivul specificat, precum CPU, CUDA sau MPS |
DataLoader |
„Pipeline-ul de date” | Iterator care grupează, amestecă și, opțional, paralelizează încărcarea datelor dintr-un Dataset |
| Precizie mixtă | „Folosiți float16” | Folosește precizia redusă pentru operațiile eligibile și păstrează precizie mai mare acolo unde este necesar, adesea împreună cu scalarea gradienților |
| Execuție imediată | „Rulați acum” | Operațiile se execută când sunt apelate, nu sunt amânate implicit până la o etapă ulterioară de compilare; o alegere centrală în proiectarea PyTorch |
zero_grad |
„Resetați gradienții” | Resetează sau elimină gradienții parametrilor înaintea următoarei propagări înapoi, deoarece PyTorch îi acumulează implicit |
Lecturi suplimentare
- Paszke și colaboratorii, „PyTorch: An Imperative Style, High-Performance Deep Learning Library” (2019) — lucrarea originală despre compromisurile de proiectare PyTorch.
- PyTorch Tutorials, „Learning PyTorch with Examples” (https://pytorch.org/tutorials/beginner/pytorch_with_examples.html) — traseul oficial de la tensori la
nn.Module. - PyTorch Performance Tuning Guide (https://pytorch.org/tutorials/recipes/recipes/tuning_guide.html) — precizie mixtă, procese
DataLoader, memorie fixată și alte optimizări pentru producție. - Horace He, „Making Deep Learning Go Brrrr” (https://horace.io/brrr_intro.html) — explicații despre viteza antrenării pe GPU și strategii de optimizare specifice PyTorch.
Sursă: Originalul în limba engleză
Navigare: ← Lecția 03.10 — Construiți-vă propriul mini-framework · Faza 3 — Fundamentele învățării profunde · Catalog complet · Lecția 03.12 — Introducere în JAX →