Salta al contenuto
3/30Capitolo 3 di 30

In discesa: Gradient Descent e i due passaggi che tutti saltano

Calcola il limite esatto del learning rate, poi osserva una ricerca brute-force su 3.600 direzioni riscoprire il gradiente.

In questa pagina

Il capitolo precedente si era chiuso con una valle.

Non una valle metaforica: una curva vera, la loss tracciata rispetto a un singolo parametro, che scende e poi risale. E la loss sotto di essa non era stata scelta perché ordinata: era stata derivata da un'affermazione sul rumore nelle misurazioni, e l'errore quadratico era uscito dall'altra parte come conseguenza, non come convenzione.

Quindi abbiamo un paesaggio con un fondo, e un motivo per credere che il fondo sia il posto giusto in cui stare. Quello che non abbiamo è un modo per arrivarci.

Questo capitolo ne costruisce uno, ed è l'algoritmo che allena ogni modello nel resto di questo corso — tutti, senza eccezioni, fino a quelli con centinaia di miliardi di parametri inclusi. Sta in circa venti righe. Le due parti difficili non sono in quelle venti righe, e sono le due cose che quasi ogni spiegazione salta:

  • Perché il segno meno. L'aggiornamento sottrae il gradiente. Ogni tutorial lo scrive; pochissimi spiegano perché il gradiente è la direzione che va su, l'unico fatto che rende il segno meno qualcosa di diverso da un atto di fede.
  • Quanto grande un passo. «Troppo grande diverge, troppo piccolo è lento» è vero e inutile. Esiste un numero esatto, è calcolabile dalla loss, e questo capitolo lo calcola due volte: una per una parabola giocattolo e una per i dati reali.

L'impostazione, e perché non puoi semplicemente cercare

Link alla sezione: L'impostazione, e perché non puoi semplicemente cercare

Riformulato perché questo capitolo stia in piedi da solo: gli otto pezzi dal nastro trasportatore del Capitolo 1, ma con una domanda diversa. Non accetta o rifiuta — quello tornerà più avanti — ma predire il peso di un pezzo dalla sua larghezza.

belt.pyPYTHON
import numpy as np

WIDTH  = np.array([18.0, 19.5, 20.2, 21.0, 24.0, 25.5, 23.0, 26.0])
WEIGHT = np.array([47.0, 52.0, 49.0, 55.0, 61.0, 66.0, 70.0, 58.0])

x = WIDTH  - WIDTH.mean()      # 22.15 mm
y = WEIGHT - WEIGHT.mean()     # 57.25 g

Le misurazioni sono centrate, esattamente come nel Capitolo 1 e per un motivo che tornerà con gli interessi prima che questo capitolo finisca. Il modello è una retta, y^=ax+b\hat{y} = a x + b, e la loss è l'errore quadratico medio derivato nel capitolo precedente:

L(a,b)=1ni=1n(axi+byi)2L(a, b) = \frac{1}{n} \sum_{i=1}^{n} \left(a x_i + b - y_i\right)^2

Due parametri. Perché non provare semplicemente tanti valori? Facciamolo davvero: una griglia da a=0a = 0 a 55 e da b=5b = -5 a 55, con passi di 0.010.01:

TEXT
grid 501 x 1001 = 501,501 evaluations in 3.67 s
  best found: a = 2.1000, b = -0.0000, L = 24.592450

Mezzo milione di valutazioni per fissare due numeri a due cifre decimali — e quel secondo è tempo di parete su una macchina, quindi una nuova esecuzione può finire ovunque tra tre e sei; il conteggio delle valutazioni e il minimo sono la parte che si riproduce. Gradient descent, alla fine di questo capitolo, ottiene quattro cifre decimali in otto passi e la risposta float64 completa in trentasei.

Ma la velocità non è l'argomento, e questo è il punto che decide tutto il corso. La ricerca su griglia costa kPk^P valutazioni per PP parametri a kk valori ciascuno. Con mille valori per asse:

modelloparametrivalutazioni della griglia
questa retta210610^{6}
la rete XOR del Capitolo 59102710^{27}
una piccola rete multistrato20.0001060,00010^{60{,}000}

La terza riga non è un numero grande, è un numero privo di significato: nell'universo osservabile ci sono circa 108010^{80} atomi. La ricerca non diventa più lenta man mano che i modelli crescono; smette di esistere. Tutto ciò che segue esiste per via di quella tabella.

Una derivata è una misurazione che puoi fare

Link alla sezione: Una derivata è una misurazione che puoi fare

Fissa b=0b = 0 per un momento, così c'è un parametro e una curva, cioè l'immagine con cui ti ha lasciato il capitolo scorso. Prendi un punto su di essa, a=1a = 1, e chiedi: se sposto aa di una piccola quantità hh, di quanto si muove la loss per unità di spostamento?

L(a+h)L(a)h\frac{L(a + h) - L(a)}{h}

Quel rapporto è un rise over run: la pendenza della retta che passa per due punti della curva. Man mano che hh si riduce, i due punti scivolano insieme e la retta diventa la tangente. La sua pendenza è la derivata L(a)L'(a): il tasso a cui la loss cambia per unità di variazione in aa. Non un'approssimazione di qualcosa, e non una quantità infinitamente piccola. Un limite di rapporti ordinari.

Vale la pena eseguirla, perché i numeri dicono qualcosa che la definizione non dice:

slope.pyPYTHON
def loss1(a):
    return np.mean((a * x - y) ** 2)

for h in [1.0, 1e-2, 1e-4, 1e-6, 1e-8, 1e-10, 1e-12, 1e-14]:
    q = (loss1(1.0 + h) - loss1(1.0)) / h
    print(f"h = {h:<8.0e}   slope estimate = {q:.10f}   error = {abs(q + 16.385):.3e}")
TEXT
h = 1e+00      slope estimate = -8.9400000000    error = 7.445e+00
h = 1e-02      slope estimate = -16.3105500000   error = 7.445e-02
h = 1e-04      slope estimate = -16.3842555001   error = 7.445e-04
h = 1e-06      slope estimate = -16.3849925556   error = 7.444e-06
h = 1e-08      slope estimate = -16.3850003787   error = 3.787e-07
h = 1e-10      slope estimate = -16.3850444324   error = 4.443e-05
h = 1e-12      slope estimate = -16.3851154866   error = 1.155e-04
h = 1e-14      slope estimate = -17.0530256582   error = 6.680e-01

Qui succedono due cose, ed entrambe reggono il carico.

L'errore non è vagamente proporzionale a hh: è esattamente 7.445h7.445\,h. Dividi hh per cento, l'errore si divide per cento, ogni volta a quattro cifre significative. Quella costante non è decorazione: è metà della seconda derivata della loss, ed è la prima apparizione di un'idea tra due sezioni: vicino a un punto, una curva assomiglia a una retta più una correzione proporzionale a h2h^2.

E poi lo schema si rompe. Sotto h=108h = 10^{-8} la stima peggiora, e a 101410^{-14} è sbagliata già alla seconda cifra. Non è successo nulla di matematico; è successo il contenitore floating-point del capitolo scorso. L(a+h)L(a+h) e L(a)L(a) coincidono nelle prime dieci cifre, sottrarli distrugge quelle cifre, e dividere il relitto per un numero minuscolo amplifica ciò che resta. Esiste un hh migliore: qui attorno a 10810^{-8}, circa la radice quadrata dell'epsilon di macchina. Andare più piccolo non è più prudente, è meno. Ricordatelo; una funzione alla fine di questo capitolo dipende da questo.

La pendenza esatta, dal calcolo infinitesimale invece che dalla misurazione, è 16.385-16.385. Quindi possiamo smettere di misurare e iniziare a derivare.

Ecco l'idea su cui è costruito il resto del corso, detta una volta, chiaramente.

Comporre due funzioni significa passare una dentro l'altra: (fg)(x)=f(g(x))(f \circ g)(x) = f(g(x)). Nient'altro.

Una rete profonda non è come una composizione. È una composizione. Un layer è una funzione; impilare layer significa comporli; la «profondità» è il numero di funzioni nella catena. Quando il Capitolo 5 costruisce una rete, sta costruendo f4f3f2f1f_4 \circ f_3 \circ f_2 \circ f_1 e nient'altro. Il che significa che la regola più importante del calcolo infinitesimale, per i nostri scopi, è quella che differenzia una composizione:

ddxf(g(x))=f(g(x))g(x)\frac{d}{dx} f(g(x)) = f'(g(x)) \cdot g'(x)

I tassi si moltiplicano. Se gg cambia tre volte più velocemente di xx, e ff cambia due volte più velocemente di gg, allora ff cambia sei volte più velocemente di xx. Questo è tutto il contenuto, ed è il motivo per cui un segnale che passa all'indietro attraverso dieci layer viene moltiplicato per dieci numeri: per questo il Capitolo 6 dedica una sezione a cosa succede quando quei numeri sono tutti leggermente minori di uno.

Usala sulla nostra loss. Scrivi il residuo ri=axi+byir_i = a x_i + b - y_i, così che L=1nri2L = \frac{1}{n}\sum r_i^2. Ogni rir_i dipende da aa tramite la funzione interna axia x_i, la cui derivata è xix_i. Chain rule, termine per termine:

La=1ni2rixi,Lb=1ni2ri1\frac{\partial L}{\partial a} = \frac{1}{n}\sum_i 2 r_i \cdot x_i, \qquad \frac{\partial L}{\partial b} = \frac{1}{n}\sum_i 2 r_i \cdot 1

Quei simboli ricci \partial indicano una derivata parziale: differenzi rispetto a una variabile e tratti ogni altra come una costante. Non succede nulla di nuovo: è lo stesso limite di prima, preso lungo un asse. Raccogli le derivate parziali in un vettore e ottieni il gradiente:

L=(La, Lb)\nabla L = \left( \frac{\partial L}{\partial a},\ \frac{\partial L}{\partial b} \right)

Nel punto (a,b)=(1,4)(a, b) = (1, 4) quel vettore è (16.385, 8.0)(-16.385,\ 8.0). Due numeri. La domanda è cosa significhino, ed è il primo passaggio che tutti saltano.

Il gradiente è un vettore di pendenze lungo gli assi. Questo è tutto ciò che abbiamo dimostrato. Non è ovvio — non dovrebbe essere ovvio — che assemblarle in un vettore produca qualcosa che punta in una direzione particolare.

Quindi definiamo ciò che vogliamo davvero. Scegli un vettore unitario u\mathbf{u}, una direzione. La derivata direzionale è il tasso a cui la loss cambia mentre cammini in quella direzione:

DuL=limh0L(θ+hu)L(θ)hD_{\mathbf{u}} L = \lim_{h \to 0} \frac{L(\boldsymbol{\theta} + h\mathbf{u}) - L(\boldsymbol{\theta})}{h}

La chain rule lo trasforma in qualcosa di calcolabile. Camminare lungo u\mathbf{u} cambia aa al tasso u1u_1 e bb al tasso u2u_2, e i contributi si sommano:

DuL=Lau1+Lbu2=LuD_{\mathbf{u}} L = \frac{\partial L}{\partial a} u_1 + \frac{\partial L}{\partial b} u_2 = \nabla L \cdot \mathbf{u}

Il tasso di variazione in qualunque direzione è il prodotto scalare del gradiente con quella direzione. E ora la battuta finale, che è una riga di geometria. Scrivendo il prodotto scalare con l'angolo ϕ\phi tra i vettori,

Lu=Lucosϕ=Lcosϕ\nabla L \cdot \mathbf{u} = \lVert \nabla L \rVert \, \lVert \mathbf{u} \rVert \cos\phi = \lVert \nabla L \rVert \cos\phi

poiché u\mathbf{u} ha lunghezza 1. L'unica cosa che controlli è cosϕ\cos\phi, che è massimo a ϕ=0\phi = 0 e minimo a mezzo giro, ϕ=180\phi = 180 gradi. Quindi:

  • La salita più ripida è lungo L\nabla L stesso, e lì la pendenza è esattamente L\lVert \nabla L \rVert.
  • La discesa più ripida è lungo L-\nabla L, e lì la pendenza è L-\lVert \nabla L \rVert.
  • Perpendicolarmente al gradiente, la loss non cambia affatto. Ecco perché le linee di una mappa a curve di livello incrociano il gradiente ad angolo retto.

Questo è il segno meno. Non una convenzione, non un cambio di segno scelto da qualcuno: la direzione di diminuzione più rapida è il gradiente negativo perché cosϕ\cos\phi è minimizzato a mezzo giro, e per nessun altro motivo.

Dato che questa è un'affermazione su tutte le direzioni, testiamola contro tutte le direzioni. Campiona 3.600 direzioni, una ogni decimo di grado, e misura ciascuna con un piccolo spostamento:

directions.pyPYTHON
theta = np.array([1.0, 4.0])
g = grad(theta)
print("gradient      ", g)
print("its length    ", np.linalg.norm(g))
print("its angle     ", np.degrees(np.arctan2(g[1], g[0])) % 360, "degrees")

best = max(
    ((loss(theta + 1e-6 * u) - loss(theta - 1e-6 * u)) / 2e-6, np.degrees(ang))
    for ang, u in (
        (a, np.array([np.cos(a), np.sin(a)])) for a in np.arange(3600) * 2 * np.pi / 3600
    )
)
print("steepest slope", best[0], "at", best[1], "degrees")
TEXT
gradient       [-16.385   8.   ]
its length     18.23371122399386
its angle      153.97598928042032 degrees
steepest slope 18.233709624837502 at 154.0 degrees

Una ricerca che non sa nulla dei gradienti, su 3.600 direzioni, trova la salita più ripida a 154,0 gradi: la direzione stessa del gradiente, entro la risoluzione di 0,1 gradi della ricerca. E la pendenza che trova lì, 18,2337, è la lunghezza del gradiente a sei cifre. Il teorema non è una storia su cosa significano i gradienti; è un fatto misurabile, e questa è la misurazione.

Perché un piccolo passo in discesa aiuta davvero

Link alla sezione: Perché un piccolo passo in discesa aiuta davvero

Ora il secondo passaggio saltato. Sappiamo qual è la direzione verso il basso. Non ne segue che camminare in quella direzione abbassi la loss, perché «verso il basso» è un'affermazione su uno spostamento infinitesimale e un passo non è infinitesimale.

Il ponte è la linearizzazione. Vicino a un punto, una funzione liscia è la sua tangente più una correzione:

L(θ+δ)=L(θ)+Lδ+O(δ2)L(\boldsymbol{\theta} + \boldsymbol{\delta}) = L(\boldsymbol{\theta}) + \nabla L \cdot \boldsymbol{\delta} + O(\lVert\boldsymbol{\delta}\rVert^2)

Questa è l'espansione di Taylor del primo ordine. Il termine scartato O(δ2)O(\lVert\boldsymbol{\delta}\rVert^2) è la curvatura: lo stesso termine che rendeva la stima della tabella delle pendenze sbagliata di esattamente 7.445h7.445\,h. Inserisci il passo che intendiamo fare, δ=ηL\boldsymbol{\delta} = -\eta \nabla L:

L(θηL)L(θ)ηL2L(\boldsymbol{\theta} - \eta \nabla L) \approx L(\boldsymbol{\theta}) - \eta \lVert \nabla L \rVert^2

La loss scende di ηL2\eta \lVert \nabla L \rVert^2. Ogni parte di ciò è non negativa, quindi la promessa è reale — per un η\eta abbastanza piccolo, perché il termine trascurato cresce come η2\eta^2 e alla fine se la mangia. Questa è tutta la teoria. Ecco la promessa mantenuta, e poi infranta:

TEXT
eta = 0.2       promised    66.49364500   delivered   -16.01619240   ratio -0.240868
eta = 0.1       promised    33.24682250   delivered    12.61936315   ratio  0.379566
eta = 0.01      promised     3.32468225   delivered     3.11840766   ratio  0.937957
eta = 0.001     promised     0.33246822   delivered     0.33040548   ratio  0.993796
eta = 0.0001    promised     0.03324682   delivered     0.03322620   ratio  0.999380
eta = 1e-05     promised     0.00332468   delivered     0.00332448   ratio  0.999938

Leggila dal basso. Man mano che η\eta si riduce, il calo ottenuto converge verso quello promesso — rapporto 0,99938, poi 0,99994 — cioè il teorema di Taylor che fa il suo dovere. Leggila dall'alto e a η=0.2\eta = 0.2 il «calo» ottenuto è meno sedici. Il passo è andato in discesa e la loss è salita.

Quindi la regola di aggiornamento è

θθηL(θ)\boldsymbol{\theta} \leftarrow \boldsymbol{\theta} - \eta \nabla L(\boldsymbol{\theta})

e porta con sé una condizione che nessuno enuncia: η\eta deve essere abbastanza piccolo. Abbastanza piccolo rispetto a cosa, esattamente, è la prossima sezione.

Il learning rate ha un limite, ed è calcolabile

Link alla sezione: Il learning rate ha un limite, ed è calcolabile

Parti dalla valle più semplice possibile, f(x)=x2f(x) = x^2, dove f(x)=2xf'(x) = 2x. Un passo di gradient descent è

xxη2x=x(12η)x \leftarrow x - \eta \cdot 2x = x\,(1 - 2\eta)

La posizione viene moltiplicata per (12η)(1 - 2\eta) a ogni passo. È una successione geometrica, e le successioni geometriche hanno una sola regola: si riducono quando il moltiplicatore è minore di 1 in valore assoluto e crescono altrimenti. Quindi 12η<1\lvert 1 - 2\eta \rvert < 1, cioè 0<η<10 < \eta < 1.

Il confine è esattamente a η=1\eta = 1. Non «attorno a 1», non «1 di solito è troppo grande». A η=1\eta = 1 il moltiplicatore è 1-1 e il punto rimbalza per sempre tra xx e x-x, senza avvicinarsi né scappare. Sotto, converge; sopra, diverge. L'intervallo si divide di nuovo a η=0.5\eta = 0.5, dove il moltiplicatore cambia segno: sotto, l'avvicinamento è monotono; sopra, il punto supera il minimo e alterna i lati; ed esattamente a 0.50.5 il moltiplicatore è 0 e un singolo passo atterra sul minimo.

Quattro regimi, da quattro righe di algebra. Vai e attraversa tu stesso i confini:

14 passaggi, con arrivo a x = -0.0836.

Vedi i dati in tabella
Passoxf(x)
0⁨-1.9000⁩⁨3.6100⁩
1⁨-1.5200⁩⁨2.3104⁩
2⁨-1.2160⁩⁨1.4787⁩
3⁨-0.9728⁩⁨0.9463⁩
4⁨-0.7782⁩⁨0.6057⁩
5⁨-0.6226⁩⁨0.3876⁩
6⁨-0.4981⁩⁨0.2481⁩
7⁨-0.3985⁩⁨0.1588⁩
8⁨-0.3188⁩⁨0.1016⁩
9⁨-0.2550⁩⁨0.0650⁩
10⁨-0.2040⁩⁨0.0416⁩
11⁨-0.1632⁩⁨0.0266⁩
12⁨-0.1306⁩⁨0.0170⁩
13⁨-0.1045⁩⁨0.0109⁩
14⁨-0.0836⁩⁨0.0070⁩
Discesa del gradiente interattiva

Quattordici passi a un tasso di 0,1, da x=1.9x = -1.9, con arrivo a 0.0836-0.0836. Porta il tasso a 0,5 e il primissimo passo atterra sul fondo. Portalo a 0,9 e finisce allo stesso 0.0836-0.0836 di 0,1 — stessa distanza, stile opposto, perché 12η\lvert 1 - 2\eta \rvert è 0,8 per entrambi — ma ci arriva zigzagando attraverso la valle invece di scendere lungo un lato.

E ora quello interessante:

14 passaggi, con arrivo a x = -1.9000.

Vedi i dati in tabella
Passoxf(x)
0⁨-1.9000⁩⁨3.6100⁩
1⁨1.9000⁩⁨3.6100⁩
2⁨-1.9000⁩⁨3.6100⁩
3⁨1.9000⁩⁨3.6100⁩
4⁨-1.9000⁩⁨3.6100⁩
5⁨1.9000⁩⁨3.6100⁩
6⁨-1.9000⁩⁨3.6100⁩
7⁨1.9000⁩⁨3.6100⁩
8⁨-1.9000⁩⁨3.6100⁩
9⁨1.9000⁩⁨3.6100⁩
10⁨-1.9000⁩⁨3.6100⁩
11⁨1.9000⁩⁨3.6100⁩
12⁨-1.9000⁩⁨3.6100⁩
13⁨1.9000⁩⁨3.6100⁩
14⁨-1.9000⁩⁨3.6100⁩
Discesa del gradiente interattiva

Esattamente sul confine. Quattordici passi a un tasso di 1, e finisce a 1.9-1.9: precisamente dove era partito, senza aver fatto altro che rimbalzare. Un piccolo spostamento più in alto e il rimbalzo cresce invece di restare uguale; a 1,2 è fuori scala in quattro passi. Un tasso troppo grande non converge lentamente. Non converge.

Ora la regola generale, che cade fuori dallo stesso argomento. Il moltiplicatore 12η1 - 2\eta era in realtà 1ηf1 - \eta f'', e vicino a un minimo una loss con più parametri ha un numero del genere per ogni direzione: gli autovalori della matrice delle seconde derivate. Ogni direzione deve essere stabile insieme alle altre, quindi il limite è fissato dal più grande:

η<2λmax\eta < \frac{2}{\lambda_{\max}}

Per f(x)=x2f(x) = x^2, f=2f'' = 2, limite 1, che è ciò che abbiamo appena derivato. Per il nostro nastro, la matrice delle seconde derivate è 2nAA\frac{2}{n} A^{\top} A con AA la matrice a due colonne degli input, e i suoi autovalori sono 2 e 14,89, quindi il limite è 2/14.89=0.134322 / 14.89 = 0.13432. Questa è una previsione con cinque cifre significative. Testiamola:

TEXT
lr=0.1343    -> L =      24.5924
lr=0.13431   -> L =      24.5924
lr=0.13432   -> L =       4707.8       BLEW UP
lr=0.13433   -> L =  4.00452e+16       BLEW UP
lr=0.1344    -> L = 1.18229e+107       BLEW UP

Cinque cifre decimali di accordo tra una riga di algebra lineare e centomila iterazioni di un loop for.

Ed ecco dove torna il Capitolo 1. Tutto quanto sopra ha usato misurazioni centrate. Esegui lo stesso identico codice su millimetri e grammi grezzi e gli autovalori sono 0,0298 e 998,1 invece di 2 e 14,89. Il limite crolla da 0,134 a 0,002004 — altrettanto esattamente, convergendo a lr=0.002003 ed esplodendo a lr=0.002004.

Peggio del limite è il rapporto tra gli autovalori. Il numero di condizionamento misura quanto la valle sia lontana dall'essere rotonda: una trincea lunga e sottile impone un tasso abbastanza piccolo per le pareti ripide, e poi il fondo della trincea viene percorso con lo stesso passo da lumaca. Il nostro passa da 7,44 centrato a 33.452 grezzo. Con il miglior tasso che ciascuna versione può prendere:

featurenumero di condizionamentomiglior tassopassi per arrivare entro l'1% dell'ottimo
centrate7,440,118410
millimetri e grammi grezzi33.4520,002003779.513

Stessi dati, stesso codice, stessa risposta alla fine — e ottomila volte il lavoro, perché nessuno ha sottratto una media. Nel Capitolo 1 la stessa omissione è costata al perceptron un fattore di seimila in epoche, e la diagnosi lì era geometrica: i dati galleggiavano lontano dall'origine. Qui è la stessa geometria con un costume da ottimizzazione, ed è per questo che la normalizzazione degli input non è un consiglio di igiene ma aritmetica.1

Nulla di quanto sopra aveva bisogno di una libreria. Ecco l'intero ottimizzatore.

descent.pyPYTHON
def loss(theta):
    a, b = theta
    return np.mean((a * x + b - y) ** 2)


def grad(theta):
    a, b = theta
    residual = a * x + b - y
    return np.array([np.mean(2 * residual * x), np.mean(2 * residual)])


def descend(theta, lr, steps):
    theta = np.array(theta, dtype=float)
    for _ in range(steps):
        theta = theta - lr * grad(theta)   
    return theta


theta = descend([0.0, 0.0], lr=0.05, steps=60)
print(theta, loss(theta))
TEXT
[ 2.10040296e+00 -2.76445533e-15] 24.592448791134984

La soluzione dei minimi quadrati in forma chiusa per questi otto punti è a=2.100403a = 2.100403, b=0b = 0, con una loss di 24.59244924.592449. Il loop l'ha trovata a otto cifre significative senza sapere che esiste una forma chiusa — il che conta, perché dal Capitolo 5 in poi non ce ne sarà una.

La traiettoria, perché guardarla è il punto:

TEXT
   0 a=0.000000 b=0.000000 L=57.437500
   1 a=1.563750 b=0.000000 L=26.736582
   2 a=1.963288 b=-0.000000 L=24.732418
   5 a=2.098116 b=-0.000000 L=24.592488
  10 a=2.100400 b=-0.000000 L=24.592449
  60 a=2.100403 b=-0.000000 L=24.592449

La maggior parte della distanza viene coperta nei primi due passi, perché il gradiente è massimo quando sei più lontano dal fondo e si riduce man mano che ti avvicini. Gradient descent rallenta automaticamente vicino a un minimo. È una feature ed è anche, nel Capitolo 6, un problema.

L'argomento finora ha un buco. Il passo si ferma quando L=0\nabla L = \mathbf{0}, e noi lo abbiamo chiamato «il minimo». Un punto con gradiente zero è un punto critico, ed essere un minimo è solo uno dei modi per esserlo:

  • un minimo locale: in salita in ogni direzione, ma forse non il punto più basso tra tutti;
  • un massimo locale: in discesa in ogni direzione;
  • un punto di sella: in salita in alcune direzioni e in discesa in altre. La superficie f(x,y)=x2y2f(x,y) = x^2 - y^2 ha f=(2x,2y)\nabla f = (2x, -2y), che è zero all'origine, dove la funzione è un minimo lungo l'asse xx e un massimo lungo l'asse yy allo stesso tempo.

Gradient descent non può distinguerli, perché guarda sempre e solo il gradiente, e il gradiente è zero in tutti e tre.

La nostra retta ha un solo punto critico ed è la risposta: una loss di errore quadratico su un modello lineare è convessa, una singola ciotola, e la discesa su di essa non può mancare il minimo globale. Questa proprietà non sopravvive al contatto con questo corso. La loss di una rete neurale non è convessa, e dal Capitolo 5 in poi «il minimo» non è una cosa che esiste: ce ne sono molti, di profondità diverse, e quale ottieni dipende da dove sei partito. Questa è una frase e resta una frase, perché la teoria è grande e la conseguenza pratica è piccola.

Puoi vedere l'intera conseguenza su una curva. Prendi f(x)=x44x22+x10f(x) = \tfrac{x^4}{4} - \tfrac{x^2}{2} + \tfrac{x}{10}, che ha due valli di profondità diverse:

TEXT
   x =  -1.046681   f(x) =  -0.352386   minimum
   x =   0.101031   f(x) =   0.005026   maximum
   x =   0.945649   f(x) =  -0.152639   minimum

40 passaggi, con arrivo a x = 0.9456.

Vedi i dati in tabella
Passoxf(x)
0⁨0.1100⁩⁨0.0050⁩
1⁨0.1122⁩⁨0.0050⁩
2⁨0.1149⁩⁨0.0049⁩
3⁨0.1182⁩⁨0.0049⁩
4⁨0.1223⁩⁨0.0048⁩
5⁨0.1275⁩⁨0.0047⁩
6⁨0.1338⁩⁨0.0045⁩
7⁨0.1416⁩⁨0.0042⁩
8⁨0.1513⁩⁨0.0038⁩
9⁨0.1633⁩⁨0.0032⁩
10⁨0.1781⁩⁨0.0022⁩
11⁨0.1962⁩⁨0.0007⁩
12⁨0.2183⁩⁨-0.0014⁩
13⁨0.2453⁩⁨-0.0046⁩
14⁨0.2779⁩⁨-0.0093⁩
15⁨0.3170⁩⁨-0.0160⁩
16⁨0.3633⁩⁨-0.0253⁩
17⁨0.4172⁩⁨-0.0377⁩
18⁨0.4783⁩⁨-0.0535⁩
19⁨0.5455⁩⁨-0.0721⁩
20⁨0.6163⁩⁨-0.0922⁩
21⁨0.6869⁩⁨-0.1116⁩
22⁨0.7526⁩⁨-0.1277⁩
23⁨0.8092⁩⁨-0.1393⁩
24⁨0.8540⁩⁨-0.1463⁩
25⁨0.8868⁩⁨-0.1499⁩
26⁨0.9091⁩⁨-0.1516⁩
27⁨0.9236⁩⁨-0.1522⁩
28⁨0.9325⁩⁨-0.1525⁩
29⁨0.9379⁩⁨-0.1526⁩
30⁨0.9411⁩⁨-0.1526⁩
31⁨0.9430⁩⁨-0.1526⁩
32⁨0.9441⁩⁨-0.1526⁩
33⁨0.9448⁩⁨-0.1526⁩
34⁨0.9451⁩⁨-0.1526⁩
35⁨0.9454⁩⁨-0.1526⁩
36⁨0.9455⁩⁨-0.1526⁩
37⁨0.9455⁩⁨-0.1526⁩
38⁨0.9456⁩⁨-0.1526⁩
39⁨0.9456⁩⁨-0.1526⁩
40⁨0.9456⁩⁨-0.1526⁩
Discesa del gradiente interattiva

Quaranta passi da x=0.11x = 0.11, assestandosi a 0.94560.9456: la più bassa delle due valli? No, la meno profonda. Ora sposta il punto di partenza di una tacca a sinistra, a 0.100.10. Stesso tasso, stessi quaranta passi, e si assesta invece a 1.0461-1.0461, dove la loss è 0,199747 più bassa. Lo spartiacque è la gobba a 0.1010310.101031, e tutta la differenza tra le due risposte è da quale lato ti è capitato di iniziare.

Atterrare nella valle meno profonda è peggio del 56,7% in loss, e l'algoritmo non ha modo di saperlo, perché dall'interno di una valle ogni direzione è in salita. Non c'è riparazione per questo in gradient descent e non ne arriverà una. Quello che c'è, in pratica, è la scoperta che conta molto meno di quanto questa immagine suggerisca: nelle dimensioni molto alte di una rete reale, la maggior parte dei punti critici si rivela essere una sella invece che una trappola,2 e il Capitolo 5 misura quanto spesso una piccola rete si blocchi davvero.

Passi più economici: stocastico, minibatch, momentum

Link alla sezione: Passi più economici: stocastico, minibatch, momentum

Una cosa di grad sopra dovrebbe infastidirti: somma sull'intero dataset a ogni passo. Otto pezzi sono nulla. Un milione significa un milione di calcoli di gradiente per muovere i parametri una volta.

La via d'uscita è che il gradiente è una media, e una media può essere stimata da un campione. Calcolalo su una manciata casuale — un minibatch — e fai un passo su quello. La stima è rumorosa; è anche non distorta, e centinaia di passi rumorosi economici battono un passo esatto costoso. Su centomila pezzi sintetici, contando i gradienti per esempio invece dei passi:

metodopassi per arrivare entro lo 0,1% dell'ottimogradienti per esempio
full batch7700.000
minibatch di 321003.200
un esempio alla volta17.58017.580

Duecentodiciannove volte meno aritmetica per raggiungere lo stesso punto. E l'estremo — un esempio alla volta, l'approssimazione stocastica originale di Robbins e Monro3non è il vincitore: è cinque volte peggiore dei batch da 32, perché 32 esempi costano quasi nulla più di uno su hardware che moltiplica matrici, mentre il rumore cala con la radice quadrata della dimensione del batch. Questo trade-off è il motivo per cui ogni script di training che leggerai mai contiene un batch_size.

Momentum è l'altra correzione economica, e mira dritto alla trincea. In una valle mal condizionata i passi zigzagano attraverso la direzione stretta mentre strisciano lungo quella lunga. Momentum mantiene una media mobile dei gradienti passati, così le componenti oscillanti si annullano e quella coerente si accumula:4

vβv+L(θ),θθηv\mathbf{v} \leftarrow \beta \mathbf{v} + \nabla L(\boldsymbol{\theta}), \qquad \boldsymbol{\theta} \leftarrow \boldsymbol{\theta} - \eta \mathbf{v}

Due righe in più. Sul nastro grezzo non centrato — numero di condizionamento 33.452, il caso peggiore che abbiamo — al miglior tasso che la discesa semplice può prendere:

TEXT
momentum beta=0.0   ->    79,513 steps to 1%
momentum beta=0.9   ->     1,609 steps to 1%
momentum beta=0.99  ->       461 steps to 1%

Un fattore di 172 per due righe di codice. Il Capitolo 6 lo trasforma in Adam; il meccanismo è già qui.

Il controllo che ti servirà nel Capitolo 5

Link alla sezione: Il controllo che ti servirà nel Capitolo 5

Ogni gradiente in questo capitolo è stato derivato a mano e quindi potrebbe essere sbagliato. La correzione è la tabella delle pendenze dall'inizio: misura numericamente la derivata e confronta. Usa la differenza centrale, L(θ+h)L(θh)2h\frac{L(\theta+h) - L(\theta-h)}{2h}, che annulla il termine di errore principale ed è molto più accurata per lo stesso hh.

gradcheck.pyPYTHON
def numeric_grad(f, theta, h=1e-5):
    theta = np.asarray(theta, dtype=float)
    out = np.zeros_like(theta)
    for i in range(theta.size):
        bump = np.zeros_like(theta)
        bump[i] = h
        out[i] = (f(theta + bump) - f(theta - bump)) / (2 * h)     
    return out


def gradcheck(f, df, theta, h=1e-5):
    analytic = np.asarray(df(theta), dtype=float)
    numeric = numeric_grad(f, theta, h)
    return np.max(np.abs(analytic - numeric) / np.maximum(1e-8, np.abs(analytic) + np.abs(numeric)))

La forma relativa del confronto conta: una differenza assoluta di 10410^{-4} è un disastro su un gradiente di dimensione 10310^{-3} e irrilevante su uno di dimensione 10610^{6}.

TEXT
relative error: 1.8929136036763527e-11
with 2 dropped: 0.33333333331650744

La prima riga è il gradiente derivato a mano qui sopra. La seconda è la stessa funzione con il fattore 2 lasciato fuori da una componente — un refuso di un solo carattere — e il controllo lo intercetta immediatamente. Qualunque cosa sotto circa 10710^{-7} è accordo; qualunque cosa sopra 10410^{-4} è un bug. Tieni questa funzione: il Capitolo 5 la usa per fare debug di un motore di differenziazione automatica, ed è l'unico motivo per cui un gradiente sbagliato sia trovabile.

Tutto in questo capitolo si è retto su un'assunzione mai dichiarata: che tu possa scrivere L/θ\partial L / \partial \theta.

Per una retta con due parametri, era una riga di algebra. Smette quasi subito di esserlo. Chiedi a un sistema di algebra simbolica la derivata della loss di una rete rispetto a un singolo peso del primo layer, per un singolo esempio, e conta l'aritmetica nella risposta:

reteoperazioni in una derivata parziale
quattro unità nascoste, un layer40
quattro unità nascoste, due layer301
quattro unità nascoste, tre layer1.717

La terza riga è una rete con 57 parametri — una rete così piccola che nel Capitolo 6 sarebbe una nota a piè di pagina — e scrivere il suo gradiente a mano significa circa 97.869 operazioni per un esempio di training. Non esiste una notazione che salvi questo. A salvarlo è l'osservazione che la chain rule applicata a una composizione ha un'enorme struttura, che le stesse quantità intermedie compaiono più e più volte, e che calcolarle nell'ordine giusto dà tutte le derivate più o meno al prezzo di un forward pass. Questo è il Capitolo 5.

Ma prima c'è un problema più piccolo, e ci aspetta subito.

Ora abbiamo una macchina che rotolerà in discesa su qualunque loss differenziabile. Puntala alla domanda originale del nastro — accettare o rifiutare, un target che è 1 o 0 — metti una sigmoide sull'output così che predica una probabilità, e minimizza l'errore quadratico. Funzionerà. Ma si muoverà appena proprio quando è più in errore, e il gradiente spiega perché:

output zzpredizioneveritàgradiente con errore quadraticogradiente con cross-entropy
000,500012.5×1012.5 \times 10^{-1}5.0×1015.0 \times 10^{-1}
2-20,119211.850×1011.850 \times 10^{-1}8.808×1018.808 \times 10^{-1}
6-60,002514.921×1034.921 \times 10^{-3}9.975×1019.975 \times 10^{-1}
10-104.54×1054.54 \times 10^{-5}19.079×1059.079 \times 10^{-5}1.0001.000

Un modello che è sicuro, catastroficamente sbagliato — predice 0,0000454 quando la risposta è 1 — produce un gradiente di errore quadratico pari a 9×1059 \times 10^{-5}. Non ha idea di essere nei guai. L'altra colonna, da una loss che non abbiamo ancora derivato, riporta 1,0: urgenza massima, esattamente dove è meritata.

Il che solleva la domanda con cui si apre il prossimo capitolo. Il capitolo scorso diceva che una loss è un'assunzione sul rumore, e l'errore quadratico assume rumore gaussiano. Quale modello di rumore ha una risposta sì-o-no — e quale loss esce quando applichi la stessa derivazione?


Il metodo è più antico di tutti questi: Cauchy lo descrisse in una nota all'Académie des Sciences nel 1847, come modo per risolvere sistemi di equazioni camminando in discesa sulla somma dei loro residui quadratici. Da leggere accanto a questo capitolo anche: An overview of gradient descent optimization algorithms di Sebastian Ruder (arXiv:1609.04747), che copre momentum fino ad Adam in quattordici pagine leggibili; il capitolo 3 di Numerical Optimization di Nocedal e Wright (2ª ed., Springer, 2006), il cui teorema 3.3 dà il tasso di convergenza della steepest descent su una quadratica in termini del numero di condizionamento — è la teoria dietro al motivo per cui il condizionamento decide il conteggio dei passi, anche se tratta la line search invece del limite a passo fisso 2/λmax2/\lambda_{\max} misurato sopra — oppure §5.8 e §7.1 di Mathematics for Machine Learning di Deisenroth, Faisal e Ong per lo stesso terreno con meno apparato; §6.1 di Understanding Deep Learning di Prince e §4.3 di Deep Learning di Goodfellow, Bengio e Courville; Dive into Deep Learning §12.1–12.3, che contiene l'analisi del minibatch con più misurazioni di quante ce ne stiano qui; e il capitolo 4 di Hands-On Machine Learning di Géron (3ª ed.), il trattamento più pratico del learning rate come qualcosa che si regola invece di derivare. Gli appunti MIT 6.390 mettono gradient descent prima della classificazione, come fa questo corso e per lo stesso motivo.

  1. LeCun, Y., Bottou, L., Orr, G. B. e Müller, K.-R. Efficient BackProp, in Neural Networks: Tricks of the Trade (Springer, 1998), pp. 9–50. La sezione 4.3 dà la raccomandazione e la sezione 5.1 l'argomento usato nel riquadro di dettaglio sopra: centrare e scalare gli input cambia gli autovalori della matrice delle seconde derivate, e quindi il numero di passi, non solo il comfort numerico.

  2. Dauphin, Y. N., Pascanu, R., Gulcehre, C., Cho, K., Ganguli, S. e Bengio, Y. Identifying and attacking the saddle point problem in high-dimensional non-convex optimization, arXiv:1406.2572 (2014). L'argomento secondo cui in alte dimensioni i punti critici sono in stragrande maggioranza selle invece che minimi locali, poiché un minimo richiede che ognuna di migliaia di direzioni curvi verso l'alto contemporaneamente.

  3. Robbins, H. e Monro, S. A Stochastic Approximation Method. Annals of Mathematical Statistics 22(3), pp. 400–407 (1951). L'articolo che stabilì che una stima rumorosa di un gradiente è sufficiente, dato uno step size che si riduce nel modo giusto.

  4. Polyak, B. T. Some methods of speeding up the convergence of iteration methods. USSR Computational Mathematics and Mathematical Physics 4(5), pp. 1–17 (1964). Il metodo heavy-ball, che è l'aggiornamento momentum sopra, ventidue anni prima che backpropagation raggiungesse questo campo.

Pronto a lasciare scegliere LIA?

Crea con ogni modello AI in un unico posto — inizia gratis oggi.