Faza 01 · lecția 08

Optimizare

Scopul lecției: Antrenarea unei rețele neuronale nu înseamnă altceva decât găsirea punctului cel mai de jos al unei văi.

Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.

Curs
AI Engineering from Scratch
Fază
Fundamente matematice
Lectură
17 min.
Verificat
Cuprinsul lecției
  1. Obiective de învățare
  2. Problema
  3. Conceptul
  4. Ce înseamnă optimizarea
  5. Coborârea pe gradient în forma de bază
  6. Rata de învățare: cel mai important hiperparametru
  7. SGD, lot complet și mini-lot
  8. Momentul: bila care se rostogolește la vale
  9. Adam: rate de învățare adaptive
  10. Scheme de variație a ratei de învățare
  11. Funcții convexe și neconvexe
  12. Vizualizarea peisajului funcției de pierdere
  13. Construirea soluției
  14. Pasul 1: Definiți o funcție de test
  15. Pasul 2: Coborârea pe gradient în forma de bază
  16. Pasul 3: SGD cu moment
  17. Pasul 4: Adam
  18. Pasul 5: Rulați și comparați
  19. Utilizarea soluției
  20. Livrarea rezultatului
  21. Exerciții
  22. Termeni-cheie
  23. Lecturi suplimentare

Antrenarea unei rețele neuronale nu înseamnă altceva decât găsirea punctului cel mai de jos al unei văi.

Tip: Construire Limbaj: Python Cerințe preliminare: Faza 1, lecțiile 04–05 (derivate, gradienți) Durată: ~75 de minute

Obiective de învățare

  • Implementarea de la zero a coborârii pe gradient în forma de bază, a SGD cu moment și a optimizatorului Adam
  • Compararea convergenței optimizatorilor pe funcția Rosenbrock și explicarea modului în care Adam adaptează rata de învățare pentru fiecare pondere
  • Deosebirea peisajelor convexe ale funcției de pierdere de cele neconvexe și explicarea rolului punctelor de șa în spații cu multe dimensiuni
  • Configurarea schemelor de variație a ratei de învățare, precum scăderea în trepte, atenuarea cosinusoidală și încălzirea (warmup), pentru stabilitatea antrenării

Problema

Aveți o funcție de pierdere. Ea vă arată cât de mult greșește modelul. Aveți gradienți. Aceștia vă indică direcția în care pierderea se agravează. Acum aveți nevoie de o strategie pentru a coborî panta.

Abordarea naivă este simplă: deplasați-vă în direcția opusă gradientului. Ajustați dimensiunea pasului printr-o valoare numită rată de învățare. Repetați. Aceasta este coborârea pe gradient și funcționează. Însă afirmația „funcționează” necesită câteva precizări. Dacă rata de învățare este prea mare, depășiți complet valea și ricoșați între versanți. Dacă este prea mică, vă târâți către rezultat în mii de pași inutili. Dacă ajungeți într-un punct de șa, vă opriți, deși nu ați găsit un minim.

Fiecare optimizator din învățarea profundă răspunde la aceeași întrebare: cum ajungeți în punctul cel mai de jos al văii mai rapid și mai sigur?

Conceptul

Ce înseamnă optimizarea

Optimizarea constă în găsirea valorilor de intrare care minimizează sau maximizează o funcție. În învățarea automată, funcția este pierderea, iar intrările sunt ponderile modelului. Antrenarea este optimizare.

minimizați L(w), unde:
  L = funcția de pierdere
  w = ponderile modelului (pot exista milioane de parametri)

Coborârea pe gradient în forma de bază

Acesta este cel mai simplu optimizator. Calculați gradientul pierderii în raport cu fiecare pondere. Deplasați fiecare pondere în direcția opusă gradientului său. Scalați pasul cu rata de învățare.

w = w - lr * gradient

Acesta este întregul algoritm. O singură linie.

Диаграмма к уроку «Optimizare»

Rata de învățare: cel mai important hiperparametru

Rata de învățare controlează dimensiunea pasului. Ea determină toate aspectele convergenței.

Диаграмма к уроку «Optimizare»

Nu există o formulă pentru rata de învățare potrivită. O găsiți prin experimentare. Valori inițiale uzuale: 0.001 pentru Adam și 0.01 pentru SGD cu moment.

SGD, lot complet și mini-lot

Coborârea pe gradient în forma de bază calculează gradientul pe întregul set de date înainte de a efectua un pas. Aceasta se numește coborâre pe gradient pe lot complet. Este stabilă, dar lentă.

Coborârea stocastică pe gradient (stochastic gradient descent, SGD) calculează gradientul pe un singur exemplu ales aleatoriu și efectuează imediat un pas. Este zgomotoasă, dar rapidă.

Coborârea pe gradient cu mini-lot (mini-batch) reprezintă un compromis între cele două. Calculați gradientul pe un lot mic, de 32, 64, 128 sau 256 de exemple, apoi efectuați un pas. Aceasta este varianta utilizată în practică.

Variantă Dimensiunea lotului Calitatea gradientului Viteza per pas Zgomot
GD pe lot complet Întregul set de date Exactă Lentă Fără zgomot
SGD 1 exemplu Foarte zgomotoasă Rapidă Ridicat
Mini-lot 32–256 Estimare bună Echilibrată Moderat

Zgomotul din SGD și mini-loturi nu este o eroare. El ajută la ieșirea din minime locale puțin adânci și din puncte de șa.

Momentul: bila care se rostogolește la vale

Coborârea pe gradient în forma de bază ia în considerare numai gradientul curent. Dacă gradientul urmează o traiectorie în zigzag, fenomen frecvent în văile înguste, progresul este lent. Momentul (momentum) corectează această problemă prin acumularea gradienților anteriori într-un termen de viteză.

v = beta * v + gradient
w = w - lr * v

Analogia este cea a unei bile care se rostogolește la vale. Ea nu se oprește și nu pornește din nou la fiecare denivelare. Acumulează viteză în direcțiile persistente și amortizează oscilațiile.

Диаграмма к уроку «Optimizare»

beta, de regulă 0.9, controlează cât de mult istoric se păstrează. O valoare beta mai mare înseamnă mai mult moment și traiectorii mai line, dar și un răspuns mai lent la schimbările de direcție.

Adam: rate de învățare adaptive

Ponderile diferite au nevoie de rate de învățare diferite. O pondere care primește rareori gradienți mari ar trebui să efectueze pași mai mari atunci când îi primește. O pondere care primește constant gradienți foarte mari ar trebui să efectueze pași mai mici.

Adam (Adaptive Moment Estimation, estimarea adaptivă a momentelor) urmărește două mărimi pentru fiecare pondere:

  1. Primul moment (m): media mobilă a gradienților, asemenea momentului
  2. Al doilea moment (v): media mobilă a pătratelor gradienților; sqrt(v_hat) estimează scara RMS a gradientului
m = beta1 * m + (1 - beta1) * gradient
v = beta2 * v + (1 - beta2) * gradient^2

m_hat = m / (1 - beta1^t)    corecția biasului statistic
v_hat = v / (1 - beta2^t)    corecția biasului statistic

w = w - lr * m_hat / (sqrt(v_hat) + epsilon)

Împărțirea la sqrt(v_hat) este ideea esențială. Ponderile cu gradienți mari sunt împărțite la un număr mare, rezultând un pas efectiv mic. Ponderile cu gradienți mici sunt împărțite la un număr mic, rezultând un pas efectiv mare. Fiecare pondere primește propria rată de învățare adaptivă.

Hiperparametrii impliciți sunt lr=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8. Aceste valori funcționează bine pentru majoritatea problemelor.

Scheme de variație a ratei de învățare

O rată de învățare fixă este un compromis. La începutul antrenării sunt de dorit pași mari pentru un progres rapid. Spre sfârșitul antrenării sunt de dorit pași mici, pentru ajustări fine în apropierea minimului.

Scheme uzuale:

Schemă Formulă Caz de utilizare
Scădere în trepte lr = lr * factor la fiecare N epoci Control simplu, manual
Scădere exponențială lr = lr_0 * decay^t Reducere lină
Atenuare cosinusoidală (cosine annealing) lr = lr_min + 0.5 * (lr_max - lr_min) * (1 + cos(pi * t / T)) Arhitecturi Transformer, antrenare modernă
Încălzire (warmup) + scădere Creștere liniară, apoi scădere Modele mari, prevenirea instabilității inițiale

Funcții convexe și neconvexe

Pentru o funcție convexă, orice minim local este global, dar pot exista mai multe minime globale. Convergența coborârii pe gradient necesită condiții suplimentare, precum netezimea funcției, existența unui minim și o rată de învățare adecvată. O funcție pătratică precum f(x) = x^2 este convexă și are un minim global unic.

Notă tehnică a traducerii: Originalul afirmă că orice funcție convexă are un singur minim și că algoritmul îl găsește întotdeauna. Formularea a fost corectată pentru a include condițiile de convergență și posibilitatea minimelor globale multiple.

Funcțiile de pierdere ale rețelelor neuronale sunt neconvexe. Acestea au numeroase minime locale, puncte de șa și regiuni plate.

Диаграмма к уроку «Optimizare»

În unele peisaje de pierdere cu multe dimensiuni, punctele de șa și regiunile plate pot încetini optimizarea, iar momentul și zgomotul mini-loturilor pot ajuta la părăsirea lor. Nu există însă o garanție generală că toate minimele locale ale rețelelor neuronale sunt apropiate de minimul global sau că punctele de șa reprezintă întotdeauna obstacolul dominant.

Vizualizarea peisajului funcției de pierdere

Pierderea este o funcție a tuturor ponderilor. Pentru un model cu un milion de ponderi, peisajul funcției de pierdere se află într-un spațiu cu 1.000.001 dimensiuni. Îl vizualizăm alegând două direcții aleatorii în spațiul ponderilor și reprezentând pierderea de-a lungul acestora, pentru a obține o suprafață 2D.

Диаграмма к уроку «Optimizare»

Geometria locală a minimului poate fi asociată cu generalizarea, dar noțiunile de minim „ascuțit” și „plat” depind de parametrizare. În anumite configurații, zgomotul SGD favorizează regiuni mai plate și poate oferi o acuratețe finală mai bună decât Adam; aceasta este o observație empirică, nu o regulă universală.

Notă tehnică a traducerii: Originalul prezintă legătura dintre planeitatea minimului, generalizare și avantajul SGD asupra Adam drept universală. Formularea a fost restrânsă deoarece proprietatea nu este invariantă la reparametrizare și depinde de problemă.

gradient-descent

Construirea soluției

Pasul 1: Definiți o funcție de test

Funcția Rosenbrock este un reper clasic pentru optimizare. Minimul său se află în (1, 1), în interiorul unei văi înguste și curbate, care este ușor de găsit, dar greu de urmat.

f(x, y) = (1 - x)^2 + 100 * (y - x^2)^2
def rosenbrock(params):
    x, y = params
    return (1 - x) ** 2 + 100 * (y - x ** 2) ** 2

def rosenbrock_gradient(params):
    x, y = params
    df_dx = -2 * (1 - x) + 200 * (y - x ** 2) * (-2 * x)
    df_dy = 200 * (y - x ** 2)
    return [df_dx, df_dy]

Pasul 2: Coborârea pe gradient în forma de bază

class GradientDescent:
    def __init__(self, lr=0.001):
        self.lr = lr

    def step(self, params, grads):
        return [p - self.lr * g for p, g in zip(params, grads)]

Pasul 3: SGD cu moment

class SGDMomentum:
    def __init__(self, lr=0.001, momentum=0.9):
        self.lr = lr
        self.momentum = momentum
        self.velocity = None

    def step(self, params, grads):
        if self.velocity is None:
            self.velocity = [0.0] * len(params)
        self.velocity = [
            self.momentum * v + g
            for v, g in zip(self.velocity, grads)
        ]
        return [p - self.lr * v for p, v in zip(params, self.velocity)]

Pasul 4: Adam

class Adam:
    def __init__(self, lr=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8):
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.epsilon = epsilon
        self.m = None
        self.v = None
        self.t = 0

    def step(self, params, grads):
        if self.m is None:
            self.m = [0.0] * len(params)
            self.v = [0.0] * len(params)

        self.t += 1

        self.m = [
            self.beta1 * m + (1 - self.beta1) * g
            for m, g in zip(self.m, grads)
        ]
        self.v = [
            self.beta2 * v + (1 - self.beta2) * g ** 2
            for v, g in zip(self.v, grads)
        ]

        m_hat = [m / (1 - self.beta1 ** self.t) for m in self.m]
        v_hat = [v / (1 - self.beta2 ** self.t) for v in self.v]

        return [
            p - self.lr * mh / (vh ** 0.5 + self.epsilon)
            for p, mh, vh in zip(params, m_hat, v_hat)
        ]

Pasul 5: Rulați și comparați

def optimize(optimizer, func, grad_func, start, steps=5000):
    params = list(start)
    history = [params[:]]
    for _ in range(steps):
        grads = grad_func(params)
        params = optimizer.step(params, grads)
        history.append(params[:])
    return history

start = [-1.0, 1.0]

gd_history = optimize(GradientDescent(lr=0.0005), rosenbrock, rosenbrock_gradient, start)
sgd_history = optimize(SGDMomentum(lr=0.0001, momentum=0.9), rosenbrock, rosenbrock_gradient, start)
adam_history = optimize(Adam(lr=0.01), rosenbrock, rosenbrock_gradient, start)

for name, history in [("GD", gd_history), ("SGD+M", sgd_history), ("Adam", adam_history)]:
    final = history[-1]
    loss = rosenbrock(final)
    print(f"{name:6s} -> x={final[0]:.6f}, y={final[1]:.6f}, loss={loss:.8f}")

Rezultatul așteptat: Adam converge cel mai repede. SGD cu moment urmează o traiectorie mai lină. GD în forma de bază avansează lent de-a lungul văii înguste.

Utilizarea soluției

În practică, utilizați optimizatoarele din PyTorch sau JAX. Acestea gestionează grupurile de parametri, reducerea ponderilor (weight decay), tăierea gradienților (gradient clipping) și accelerarea pe GPU.

import torch

model = torch.nn.Linear(784, 10)

sgd = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
adam = torch.optim.Adam(model.parameters(), lr=0.001)
adamw = torch.optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01)

scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(adam, T_max=100)

Reguli orientative:

  • Începeți cu Adam (lr=0.001). Funcționează pentru majoritatea problemelor fără reglaje suplimentare.
  • Treceți la SGD cu moment (lr=0.01, momentum=0.9) atunci când aveți nevoie de cea mai bună acuratețe finală și vă puteți permite mai multe reglaje.
  • Utilizați AdamW, adică Adam cu reducerea decuplată a ponderilor (decoupled weight decay), pentru arhitecturile Transformer.
  • Utilizați întotdeauna o schemă de variație a ratei de învățare pentru antrenări care durează mai mult de câteva epoci.
  • Dacă antrenarea este instabilă, reduceți rata de învățare. Dacă antrenarea este prea lentă, măriți-o.

Livrarea rezultatului

Această lecție produce un prompt pentru alegerea optimizatorului potrivit. Consultați outputs/prompt-optimizer-guide.md.

Clasele de optimizatori construite aici reapar în faza 3, când vom antrena de la zero o rețea neuronală.

Exerciții

  1. Explorarea ratei de învățare. Rulați coborârea pe gradient în forma de bază pe funcția Rosenbrock cu ratele de învățare [0.0001, 0.0005, 0.001, 0.005, 0.01]. Reprezentați grafic sau afișați pierderea finală după 5000 de pași pentru fiecare valoare. Găsiți cea mai mare rată de învățare pentru care algoritmul încă converge.

  2. Compararea momentului. Rulați SGD cu valorile momentului [0.0, 0.5, 0.9, 0.99] pe funcția Rosenbrock. Urmăriți pierderea la fiecare pas. Pentru ce valoare a momentului convergența este cea mai rapidă? Care valoare duce la depășirea minimului?

  3. Comportamentul lângă un punct de șa. Definiți funcția f(x, y) = x^2 - y^2, care are un punct de șa în origine și nu este mărginită inferior. Pornind exact din (0, 0), observați că metodele deterministe de ordinul întâi rămân pe loc deoarece gradientul este zero. Repetați din (0,01, 0,01) și comparați deplasarea GD, SGD cu moment și Adam de-a lungul direcției de curbură negativă.

Notă tehnică a traducerii: Originalul pornește din (0,01, 0,01), care nu este punctul de șa, și întreabă care algoritm „iese” din acesta. Exercițiul a fost reformulat pentru a separa pornirea exactă de o perturbare mică.

  1. Implementarea scăderii ratei de învățare. Adăugați clasei GradientDescent o schemă de scădere exponențială: lr = lr_0 * 0.999^step. Comparați convergența cu și fără scădere pe funcția Rosenbrock.

Termeni-cheie

Termen Formulare uzuală Ce înseamnă de fapt
Coborâre pe gradient „Coborâți panta” Actualizarea ponderilor prin scăderea gradientului scalat cu rata de învățare. Cel mai simplu optimizator.
Rată de învățare „Dimensiunea pasului” Un scalar care controlează cât de mult deplasează fiecare actualizare ponderile. O valoare prea mare provoacă divergență. O valoare prea mică irosește resurse de calcul.
Moment „Continuați să vă deplasați” Acumularea gradienților anteriori într-un vector de viteză. Amortizează oscilațiile și accelerează deplasarea în direcțiile persistente.
SGD „Eșantionare aleatorie” Coborârea stocastică pe gradient. Gradientul este calculat pe o submulțime aleatorie, nu pe întregul set de date. În practică, denumește aproape întotdeauna SGD cu mini-loturi.
Mini-lot „O porțiune de date” O submulțime mică a datelor de antrenare, între 32 și 256 de exemple, utilizată pentru estimarea gradientului. Echilibrează viteza și precizia gradientului.
Adam „Optimizatorul implicit” Estimarea adaptivă a momentelor. Urmărește pentru fiecare pondere mediile mobile ale gradienților și ale pătratelor acestora, pentru a atribui fiecărei ponderi propria rată de învățare.
Corecția biasului statistic „Remedierea pornirii la rece” Primul și al doilea moment din Adam sunt inițializate cu zero. Corecția împarte la (1 - beta^t) pentru a compensa biasul statistic din pașii inițiali; nu se referă la termenul liber al unui neuron.
Schemă de variație a ratei de învățare „Modificarea lr în timp” O funcție care ajustează rata de învățare în timpul antrenării: pași mari la început și pași mici spre sfârșit.
Funcție convexă „O singură vale” O funcție în care orice minim local este global, fără ca minimul să fie neapărat unic. Convergența coborârii pe gradient depinde de ipotezele funcției și de alegerea pasului. Funcțiile de pierdere ale rețelelor neuronale nu sunt, în general, convexe.
Punct de șa „Plat, dar nu este un minim” Un punct în care gradientul este zero, dar care reprezintă un minim în unele direcții și un maxim în altele. Este frecvent în spații cu multe dimensiuni.
Peisajul funcției de pierdere „Relieful” Funcția de pierdere reprezentată în spațiul ponderilor. Este vizualizată prin secționarea de-a lungul a două direcții aleatorii.
Convergență „Ați ajuns la destinație” Optimizatorul a ajuns într-un punct în care pașii următori nu mai reduc semnificativ pierderea.

Lecturi suplimentare


Sursă: Optimization — originalul Navigare: înapoi: 01.07 — Teorema lui Bayes · Faza 1 — Fundamente matematice · Catalog complet · în continuare: 01.09 — Teoria informației.