Bucla de antrenare și validare în PyTorch & Deep Learning Practic

Antrenare, Optimizare & Stare Nivel: intermediar

Structura buclei de antrenare din PyTorch: epoci, loturi din DataLoader, pașii forward, loss, backward și step, plus faza de validare cu model.eval() și gradientul oprit.

Sintaxă & invocare Python
for epoca in range(numar_epoci):
    model.train()
    for x, y in train_loader:
        x, y = x.to(device), y.to(device)
        pierdere = criteriu(model(x), y)
        optimizator.zero_grad()
        pierdere.backward()
        optimizator.step()

    model.eval()
    with torch.no_grad():
        ...   # validare

PyTorch nu are o metodă fit() în nucleu: bucla de antrenare o scrii în Python. Tutorialul oficial o împarte în două funcții, una de antrenare și una de test, apelate la fiecare epocă. O epocă este o trecere completă prin setul de date; în interiorul ei iterezi loturile din DataLoader.

Pasul de antrenare are mereu aceeași ordine: datele pe dispozitivul modelului, forward (model(x)), pierderea, zero_grad(), backward(), step(). Dacă folosești un LR scheduler, îl apelezi după optimizer.step(); documentația optim avertizează că ordinea inversă sare peste prima valoare din program.

La validare comuți modelul cu model.eval() (Dropout oprit, BatchNorm cu statisticile acumulate) și oprești gradientul cu torch.no_grad() sau torch.inference_mode(). După validare revii cu model.train(). Oprirea timpurie (early stopping) și salvarea celui mai bun model nu sunt funcții PyTorch: le scrii în buclă, pe baza pierderii de validare.

Concepte cheie și caracteristici

Concept / Parametru Descriere practică
model.train() Mod de antrenare: Dropout activ, BatchNorm actualizează statisticile.
model.eval() Mod de evaluare: Dropout oprit, BatchNorm folosește statisticile acumulate.
optimizator.zero_grad() / backward() / step() Pașii fiecărui lot, în această ordine (zero_grad poate sta și la începutul pasului).
scheduler.step() După optimizer.step(): pe epocă sau pe lot, după tipul planificatorului.
loss.item() Valoarea pierderii ca float; nu păstra tensorul cu graful în liste.

Exemple practice și scenarii reale

Exemplul 1: Funcții separate de antrenare și validare

def antreneaza(loader, model, criteriu, optimizator, device):
    model.train()
    total, n = 0.0, 0
    for x, y in loader:
        x, y = x.to(device), y.to(device)
        pierdere = criteriu(model(x), y)
        optimizator.zero_grad()
        pierdere.backward()
        optimizator.step()
        total += pierdere.item() * x.size(0)
        n += x.size(0)
    return total / n

def valideaza(loader, model, criteriu, device):
    model.eval()
    total, corecte, n = 0.0, 0, 0
    with torch.no_grad():
        for x, y in loader:
            x, y = x.to(device), y.to(device)
            logits = model(x)
            total += criteriu(logits, y).item() * x.size(0)
            corecte += (logits.argmax(dim=1) == y).sum().item()
            n += x.size(0)
    return total / n, corecte / n

Exemplul 2: Epoci cu salvarea celui mai bun model

cel_mai_bun = float("inf")
for epoca in range(20):
    pierdere_train = antreneaza(train_loader, model, criteriu, optimizator, device)
    pierdere_val, acuratete = valideaza(val_loader, model, criteriu, device)
    print(f"epoca {epoca}: train {pierdere_train:.4f}, val {pierdere_val:.4f}, acc {acuratete:.3f}")
    if pierdere_val < cel_mai_bun:
        cel_mai_bun = pierdere_val
        torch.save(model.state_dict(), "cel_mai_bun.pt")

Greșeli frecvente și capcane

  • Să uiți model.train() după validare. Modelul rămâne în modul de evaluare și Dropout nu mai lucrează la epocile următoare.
  • Să aduni pierderile ca tensori (total += pierdere). Fiecare tensor ține referință la graf și memoria crește; folosește pierdere.item().
  • Să lași datele pe CPU când modelul e pe GPU. Fiecare lot se mută cu .to(device) în buclă.

Noțiuni și module înrudite