Uno degli aspetti più delicati nella computazione legata a modelli di deep learning è relativa al numero enorme di parametri, dati e calcoli coinvolti, organizzati in modo pratico in vettori e matrici, e al relativo calcolo del gradiente della funzione di costo rispetto a tali parametri.
E' quindi opportuno capire come impostare in modo efficiente i calcoli nel grafo computazionale del modello, applicando correttamente la chain rule e ricavando in modo elegante i gradienti anche rispetto a grandezze di tipo vettoriale e matriciale - passaggio essenziale nello sviluppo della backpropagation e della vettorizzazione del training set per processare batch di dati senza essere costretti ad iterare per ogni singolo campione.
Utilizziamo in seguito una notazione semplificata, indicando con dx il gradiente di una nostra funzione costo J rispetto alla grandezza x della quale conserverà ovviamente le stesse dimensioni per essere utilizzata direttamente negli algoritmi della discesa del gradiente.
Proponiamo subito una pratica tabella riassuntiva di sviluppo di gradienti utili nella backprogation per modelli di reti neurali densamente connesse (Fully Connected), calcolati rispetto a un gradiente noto dz
Per ogni voce è sviluppato in calce il calcolo di dettaglio, sebbene quasi tutti i casi possano essere derivati dal caso generale del calcolo di dW per Z(n×p)=W(n×m)X(m×p)
z(n×1)=W(n×m)x(m×1) | dx(m×1)=W⊤(m×n)dz(n×1) |
z(1×n)=x(1×m)W(m×n) | dx(1×m)=dz(1×n)W⊤(n×m) |
Z=X | dX=dZ |
Zij=f(Zij) | dX=dZ∗f′(X) |
z(n×1)=W(n×m)x(m×1) | dW(n×m)=dz(n×1) x⊤(1×m) |
z(1×n)=x(1×m)W(m×n) | dW(m×n)=x⊤(m×1)dz(1×n) |
Z(n×p)=W(n×m)X(m×p) | dW(n×m)=dZ(n×p) X⊤(p×m) |
Z(n×p)=X(n×m)W(m×p) | dW(m×p)=X⊤(m×n)dZ(n×p) |
Z=softmax(X)J=CE(Z,Y) | dX=(Z−Y) |
Partiamo dalla definizione di jacobiano, che generalizza il concetto di derivata a una funzione Rn→Rm e al quale è possibile applicare la chain rule in modo assolutamente trasparente:
Sia f:Rn→Rm , f(x)=[f1(x1⋯xn),⋯,fm(x1⋯xn)]
Definiamo quindi il jacobiano ∂f∂x=[∂f1∂x1⋯∂f1∂xn⋮⋱⋮∂fm∂x1⋯∂fm∂xn]=[∂f∂x1⋯∂f∂xn]=[∇f⊤1⋮∇f⊤m]
Come detto il jacobiano supporta pienamente la chain rule, per cui è facile verificare che se f:Rn→Rm e g:Rm→Rl allora ∂g(f(x))∂x=∂g∂f∂f∂x
Notiamo che il jacobiano è la trasposta del gradiente, per cui, ogni qualvolta avremo un jacobiano esplicitamente direttamente coinvolto nel calcolo del gradiente, dovremo fare attenzione a verificare che la dimensione del gradiente sia quella effettivamente attesa e nel caso applicare la trasposizione - in particolare quando operiamo con vettori colonna.
In questo caso abbiamo che zi=m∑i=1Wijxj e ∂zi∂xj=Wij , pertanto avremo un jacobiano ∂z∂x=W .
Attraverso la chain rule tra jacobiani ∂J∂x=∂J∂z∂z∂x=∂J∂zW
Vediamo ora per riprova il calcolo del gradiente, che ci attendiamo essere ricavabile dalla trasposta dello jacobiano.
dx=[∂J∂x1⋮∂J∂xm] e dz=[∂J∂z1⋮∂J∂zn] e per la regola della chain rule ∂J∂xj=∑ni=1∂J∂zi∂zi∂xj=(W:j)⊤dz , e quindi concludiamo esplicitando le dimensioni che:
dx(m×1)=[(W:1)⊤dz⋮(W:m)⊤dz]=W⊤(m×n)dz(n×1)
a conferma della relazione di trasposizione tra jacobiano e gradiente.
In questo caso abbiamo che zi=m∑i=1Wjixj e ∂zi∂xj=Wji , pertanto avremo un jacobiano ∂z∂x=W⊤ .
Attraverso la chain rule tra jacobiani ∂J∂x=∂J∂z∂z∂x=∂J∂zW⊤
Vediamo ora per riprova il calcolo del gradiente. Abbiamo:
dx=[∂J∂x1 ⋯ ∂J∂xm] e dz=[∂J∂z1 ⋯ ∂J∂zn] e per la regola della chain rule ∂J∂xj=n∑i=1∂J∂zi∂zi∂xj=dz (Wj:)⊤ , e quindi concludiamo esplicitando le dimensioni che:
dx(1×m)=[dz (W1:)⊤ ⋯ dz (Wm:)⊤]=dz(1×n)W⊤(n×m)
In questo caso ∂zi∂xj={0 se i≠j1 se i=j pertanto ∂z∂x=I
Per quanto riguarda il gradiente è immediato che dx=dz
Ipotizziamo di avere una funzione f:R→R applicata ad ogni componente del vettore x
In questo caso ∂zi∂xj={0 se i≠jf′(xi) se i=j pertanto ∂z∂x=diag(f′(x))
Per quanto riguarda il gradiente, ipotizzando due vettori colonna, abbiamo ∂J∂xj=n∑i=1∂J∂zi∂zi∂xj=dzj f′(xj)
e quindi dx=dz∗f′(x) , dove l'operatore ∗ effettua una moltiplicazione componente per componente delle due grandezze.
Il risultato relativo al gradiente è valido anche se consideriamo Z,X∈Rn×m infatti ∂Zij∂Xkl={0 se i,j≠k,lf′(Xij) se i,j=k,l e quindi
∂J∂Xkl=m∑i=1n∑j=1∂J∂Zij∂Zij∂Xkl=dZklf′(Xkl) che infatti porta ad un gradiente dX=dZ∗f′(X)
In questo caso e nei successivi dove andremo a calcolare il gradiente rispetto a grandezze strutturate in matrici, andremo convenientemente a definire il ns.gradiente con la stessa dimensione della struttra originaria, cosa che ne permetterà anche l'utilizzo in modo diretto negli algoritimi di discesa del gradiente:
dW=[dW11⋯dW1n⋮⋱⋮dWm1⋯dWmn]. Questo tipo di organizzazione tuttavia rende problematica la definizione diretta dello jacobiano ∂z∂Wche si attende invece un vettore di dimensione mn e non una matrice, che darebbe teoricamente origine a un jacobiano in forma di tensore.
Per tale motivo ci focalizziamo direttamente sul calcolo del gradiente dW e delle singole componenti ∂zk∂Wijsenza entrare nella esplicitazione della struttura dello jacobiano.
Abbiamo innanzitutto che zk=m∑l=1Wklxl e quindi ∂zk∂Wij={0 se k≠ixj se k=i che possiamo anche scrivere come
∂z∂Wij=[0xj0]←i-ma riga e applicando la chain rule ∂J∂Wij=∂J∂z∂z∂Wij=n∑k=1∂J∂zk∂zk∂Wij=dzi xj
e quindi abbiamo che il gradiente dW(n×m)=dz(n×1) x⊤(1×m)
In questo caso zk=m∑l=1Wlkxl e quindi ∂zk∂Wij={0 se k≠jxi se k=j che possiamo anche scrivere come
∂z∂Wij=[0xi0]←j-ma riga e applicando la chain rule ∂J∂Wij=∂J∂z∂z∂Wij=n∑k=1∂J∂zk∂zk∂Wij=dzj xi
e otteniamo il gradiente dW(m×n)=x⊤(m×1)dz(1×n)
Questo caso e il successivo generalizzano i 2 casi precedenti con l'utilizzo di matrici al posto di vettori.
Abbiamo Zij=m∑h=1WihXhj e quindi ∂Zij∂Wkl={ 0 se k≠iXlj se k=i
Applicando la chain rule ∂J∂Wkl=n∑i=1p∑j=1∂J∂Zij∂Zij∂Wkl=p∑j=1dZkjXlj=dZk:(Xl:)⊤
E pertanto dW(n×m)=dZ(n×p) X⊤(p×m)
Abbiamo Zij=m∑h=1XihWhj e quindi ∂Zij∂Wkl={ 0 se j≠lXik se j=l
Applicando la chain rule ∂J∂Wkl=n∑i=1p∑j=1∂J∂Zij∂Zij∂Wkl=n∑i=1dZilXik=(X:k)⊤dZ:l
E pertanto dW(m×p)=X⊤(m×n)dZ(n×p)
Un caso di funzione che troviamo frequentemente nelle reti neurali e merita di essere trattata specificamente è la funzione softmax(x(n×1))=[ex1∑nk=1exk⋮exn∑nk=1exk] . Iniziamo a calcolare il jacobiano:
∂zi∂xj={se i≠j⇒ −exiexj(∑nk=1exk)2=−zizjse i=j⇒ exj∑nk=1exk−exjexj(∑nk=1exk)2=zj(1−zj) e quindi ∂z∂x=[z1(1−z1)⋯−z1zn⋮⋱⋮−znz1⋯zn(1−zn)]
La funzione softmax è quasi sempre presente nel livello di uscita, abbinata ad una funzione di costo entropia incrociata (Cross Entropy) J=CE(z,y)=−y⊤logz , nella quale la somma delle componenti del vettore dato y è uguale a 1. Procederemo in questo caso a prendere a esplicitare il jacobiano ∂J∂z=[−y1z1⋯−ynzn]
Applicando la chain rule agli jacobiani abbiamo ∂J∂x=∂J∂z∂z∂x=[⋯−yjzjzj(1−zj)+∑k≠jykzkzjzk⋯]=[⋯−yj+zjyj+zj∑k≠jyk⋯]=[⋯zj−yj⋯]=(z−x)⊤
Per cui trasponendo otteremo il gradiente dx=z−x
Il risultato può essere esteso anche nel caso di matrici, dove con Z(n×m)=softmax(X(n×m)) applichiamo la funzione softmax colonna per colonna, ovvero [z:1⋯z:m]=[softmax(x:1)⋯softmax(x:m)] . La funzione di costo che utilizzeremo in questo caso è estesa alla matrice come J=m∑i=1n∑j=1−Yij logZij e ∂J∂Zij=−YijZij
In questo caso abbiamo:
∂Zij∂Xkl={se j≠l⇒ 0se j=l , i≠k⇒ −eXil eXkl(∑nt=1eXtl)2=−ZilZklse j=l , i=k⇒ eXkl∑nt=1eXtl−eXkl eXkl(∑nt=1eXtl)2=Zkl(1−Zkl)
∂J∂Xkl=m∑i=1n∑j=1∂J∂Zij∂Zij∂Xkl=m∑i=1−YilZil∂Zil∂Xkl=−YklZklZkl(1−Zkl)+∑i≠kYilZilZilZkl=−Ykl+YklZkl+Zkl∑i≠kYil=Zkl−Ykl
Per cui otteniamo dX=(Z−Y)
Nella pratica la funzione di costo viene moltiplicata per un coefficiente 1m per ottenere valori non legati alla numerica delle colonne/campioni in matrice.