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ă.

Curs
AI Engineering from Scratch
Fază
Fundamentele învățării profunde
Lectură
26 min.
Verificat
Cuprinsul lecției
  1. Obiective de învățare
  2. Problema
  3. Conceptul
  4. De ce s-a impus PyTorch
  5. Tensori
  6. Autograd
  7. nn.Module
  8. Funcții de pierdere și optimizatori
  9. Bucla de antrenare
  10. Dataset și DataLoader
  11. Antrenare pe GPU
  12. Comparație: mini-framework, PyTorch și JAX
  13. Construiți
  14. Pasul 1: încărcarea MNIST din fișiere brute
  15. Pasul 2: definirea modelului
  16. Pasul 3: bucla de antrenare
  17. Pasul 4: conectarea tuturor componentelor
  18. Folosiți
  19. Comparație rapidă: mini-framework și PyTorch
  20. Salvarea și încărcarea modelelor
  21. Planificarea ratei de învățare
  22. Livrați
  23. Exerciții
  24. Termeni-cheie
  25. 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ă, iar reshape() î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.

Диаграмма к уроку «Introducere în PyTorch»

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:

  1. În mod implicit, numai tensorii-frunză cu requires_grad=True își acumulează gradienții în atributul .grad.
  2. Gradienții se acumulează implicit — apelați optimizer.zero_grad() înainte de fiecare propagare înapoi.
  3. 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-41e-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.

Диаграмма к уроку «Introducere în PyTorch»

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 și num, 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 de reshape.

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 exemplu if __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_loader după 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=True restrâ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 exemplu torch.load("model.pt", map_location="cpu", weights_only=True). Scriptul însoțitor folosește map_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

  1. Adăugați normalizarea pe loturi. Introduceți nn.BatchNorm1d după 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 CrossEntropyLoss modifică distribuția logits și nu este configurația obișnuită pentru acest MLP; normalizarea se aplică de regulă numai straturilor ascunse.

  1. 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-7 la 1.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.

  2. Portați pe GPU cu precizie mixtă. Adăugați torch.amp.autocast și GradScaler î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.

  3. 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.

  4. Î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 planificator CosineAnnealingLR ș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

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 →