Forma matrice di backpropagation con normalizzazione batch


12

La normalizzazione in lotti è stata accreditata con sostanziali miglioramenti delle prestazioni nelle reti neurali profonde. Un sacco di materiale su Internet mostra come implementarlo su una base di attivazione per attivazione. Ho già implementato il backprop usando l'algebra della matrice e dato che sto lavorando in linguaggi di alto livello (facendo affidamento su Rcpp(e infine sulle GPU) per la densa moltiplicazione della matrice), strappando tutto e ricorrendo a for-loops probabilmente rallenterei il mio codice sostanzialmente, oltre ad essere un enorme dolore.

La funzione di normalizzazione batch è dove

b(xp)=γ(xp−μxp)σxp−1+β
  • xp è il nodo , prima che venga attivatop
  • γ e sono parametri scalariβ
  • μxp e sono la media e la SD di . (Si noti che viene normalmente utilizzata la radice quadrata della varianza più un fattore di fudge - supponiamo che elementi non zero per la compattezza)σxpxp

In forma di matrice, la normalizzazione batch per un intero livello sarebbe dove

b(X)=(γ⊗1p)⊙(X−μX)⊙σX−1+(β⊗1p)
  • N × pX èN×p
  • 1N è un vettore colonna di quelli
  • β pγ e sono ora riga vettori dei parametri di normalizzazione per livelloβp
  • σ X N × p NμX e sono matrici , dove ogni colonna è un vettore di medie a colonna e deviazioni standardσXN×pN
  • ⊙⊗ è il prodotto Kronecker e è il prodotto elementwise (Hadamard)⊙

Una rete neurale a uno strato molto semplice senza normalizzazione batch e un risultato continuo è

y=a(XΓ1)Γ2+ϵ

dove

  • p 1 × p 2Γ1 èp1×p2
  • p 2 × 1Γ2 èp2×1
  • a(.) è la funzione di attivazione

Se la perdita è , i gradienti sono ∂ RR=N−1∑(y−y^)2

∂R∂Γ1=−2VTϵ^∂R∂Γ2=XT(a′(XΓ1)⊙−2ϵ^Γ2T)

dove

  • V=a(XΓ1)
  • ϵ^=y−y^

Sotto la normalizzazione batch, la rete diventa o Non ho idea di come calcolare i derivati ​​dei prodotti Hadamard e Kronecker. Per quanto riguarda i prodotti Kronecker, la letteratura diventa piuttosto arcana. y = a ( ( γ ⊗ 1 N ) ⊙ ( X Γ 1 - μ X Γ 1 ) ⊙ σ - 1 X Γ 1 + ( β ⊗ 1 N ) ) Γ 2

y=a(b(XΓ1))Γ2
y=a((γ⊗1N)⊙(XΓ1−μXΓ1)⊙σXΓ1−1+(β⊗1N))Γ2

Esiste un modo pratico di calcolare , e all'interno del framework matriciale? Un'espressione semplice, senza ricorrere al calcolo nodo per nodo?∂ R / ∂ β ∂ R / ∂ Γ 1∂R/∂γ∂R/∂β∂R/∂Γ1

Aggiornamento 1:

Ho capito - sorta di. È: Alcuni codici R dimostrano che ciò equivale al modo di fare il looping. Innanzitutto imposta i dati falsi:∂R/∂β

1NT(a′(XΓ1)⊙−2ϵ^Γ2T)
set.seed(1)
library(dplyr)
library(foreach)

#numbers of obs, variables, and hidden layers
N <- 10
p1 <- 7
p2 <- 4
a <- function (v) {
  v[v < 0] <- 0
  v
}
ap <- function (v) {
  v[v < 0] <- 0
  v[v >= 0] <- 1
  v
}

# parameters
G1 <- matrix(rnorm(p1*p2), nrow = p1)
G2 <- rnorm(p2)
gamma <- 1:p2+1
beta <- (1:p2+1)*-1
# error
u <- rnorm(10)

# matrix batch norm function
b <- function(x, bet = beta, gam = gamma){
  xs <- scale(x)
  gk <- t(matrix(gam)) %x% matrix(rep(1, N))
  bk <- t(matrix(bet)) %x% matrix(rep(1, N))
  gk*xs+bk
}
# activation-wise batch norm function
bi <- function(x, i){
  xs <- scale(x)
  gk <- t(matrix(gamma[i]))
  bk <- t(matrix(beta[i]))
  suppressWarnings(gk*xs[,i]+bk)
}

X <- round(runif(N*p1, -5, 5)) %>% matrix(nrow = N)
# the neural net
y <- a(b(X %*% G1)) %*% G2 + u

Quindi calcolare i derivati:

# drdbeta -- the matrix way
drdb <- matrix(rep(1, N*1), nrow = 1) %*% (-2*u %*% t(G2) * ap(b(X%*%G1)))
drdb
           [,1]      [,2]    [,3]        [,4]
[1,] -0.4460901 0.3899186 1.26758 -0.09589582
# the looping way
foreach(i = 1:4, .combine = c) %do%{
  sum(-2*u*matrix(ap(bi(X[,i, drop = FALSE]%*%G1[i,], i)))*G2[i])
}
[1] -0.44609015  0.38991862  1.26758024 -0.09589582

Si abbinano. Ma sono ancora confuso, perché non so davvero perché funzioni. Le note MatCalc a cui fa riferimento @Mark L. Stone affermano che la derivata di dovrebbe essereβ⊗1N

∂A⊗B∂A=(Inq⊗Tmp)(In⊗vec(B)⊗Im)
dove il indici , , e , sono le dimensioni di e . è la matrice di commutazione, che è solo 1 qui perché entrambi gli ingressi sono vettori. Provo questo e ottengo un risultato che non sembra utile:mnpqABT
# playing with the kroneker derivative rule
A <- t(matrix(beta)) 
B <- matrix(rep(1, N))
diag(rep(1, ncol(A) *ncol(B))) %*% diag(rep(1, ncol(A))) %x% (B) %x% diag(nrow(A))
     [,1] [,2] [,3] [,4]
 [1,]    1    0    0    0
 [2,]    1    0    0    0
 snip
[13,]    0    1    0    0
[14,]    0    1    0    0
snip
[28,]    0    0    1    0
[29,]    0    0    1    0
[snip
[39,]    0    0    0    1
[40,]    0    0    0    1

Questo non è conforme. Chiaramente non capisco quelle regole derivate di Kronecker. Aiutare con quelli sarebbe fantastico. Sono ancora totalmente bloccato sugli altri derivati, per e - quelli sono più difficili perché non entrano in modo additivo come .γΓ1β⊗1

Aggiornamento 2

Leggendo i libri di testo, sono abbastanza sicuro che e richiederanno l'uso dell'operatore. Ma a quanto pare non sono in grado di seguire sufficientemente le derivazioni da riuscire a tradurle in codice. Ad esempio, implicherà il prendere la derivata di rispetto a , dove (che possiamo considerare come una matrice costante per il momento). ∂R/∂Γ1∂R/∂γvec()∂R/∂Γ1w⊙XΓ1Γ1w≡(γ⊗1)⊙σXΓ1−1

Il mio istinto è semplicemente dire "la risposta è ", ma ovviamente non funziona perché non è conforme a .w⊙XwX

So che

∂(A⊙B)=∂A⊙B+A⊙∂B

e da questo , quello

∂vec(w⊙XΓ1)∂vec(Γ1)T=vec(XΓ1)I∂vec(w)∂vec(Γ1)T+vec(w)I∂vec(XΓ1)∂vec(Γ1)T
Ma non sono sicuro di come valutarlo, figuriamoci nel codificarlo.

Aggiornamento 3

Fare progressi qui. Questa notte mi sono svegliato alle 2 del mattino con questa idea. La matematica non fa bene al sonno.

Ecco , dopo un po 'di zucchero notazionale:∂R/∂Γ1

  • w≡(γ⊗1)⊙σXΓ1−1
  • "stub"≡a′(b(XΓ1))⊙−2ϵ^Γ2T

Ecco cosa hai dopo che sei arrivato alla fine della regola della catena: Inizia in questo modo la strada loop - e sarà pedice colonne e è una matrice identità conforme:

∂R∂Γ1=∂w⊙XΓ1∂Γ1("stub")
ijI
∂R∂Γij=(wi⊙Xi)T("stub"j)
∂R∂Γij=(IwiXi)T("stub"j)
∂R∂Γij=XiTIwi("stub"j)
tl; dr stai praticamente pre-moltiplicando lo stub per i fattori di scala batchnorm. Questo dovrebbe essere equivalente a:
∂R∂Γ=XT("stub"⊙w)

E infatti è:

stub <- (-2*u %*% t(G2) * ap(b(X%*%G1)))
w <- t(matrix(gamma)) %x% matrix(rep(1, N)) * (apply(X%*%G1, 2, sd) %>% t %x% matrix(rep(1, N)))
drdG1 <- t(X) %*% (stub*w)

loop_drdG1 <- drdG1*NA
for (i in 1:7){
  for (j in 1:4){
    loop_drdG1[i,j] <- t(X[,i]) %*% diag(w[,j]) %*% (stub[,j])
  }
}

> loop_drdG1
           [,1]       [,2]       [,3]       [,4]
[1,] -61.531877  122.66157  360.08132 -51.666215
[2,]   7.047767  -14.04947  -41.24316   5.917769
[3,] 124.157678 -247.50384 -726.56422 104.250961
[4,]  44.151682  -88.01478 -258.37333  37.072659
[5,]  22.478082  -44.80924 -131.54056  18.874078
[6,]  22.098857  -44.05327 -129.32135  18.555655
[7,]  79.617345 -158.71430 -465.91653  66.851965
> drdG1
           [,1]       [,2]       [,3]       [,4]
[1,] -61.531877  122.66157  360.08132 -51.666215
[2,]   7.047767  -14.04947  -41.24316   5.917769
[3,] 124.157678 -247.50384 -726.56422 104.250961
[4,]  44.151682  -88.01478 -258.37333  37.072659
[5,]  22.478082  -44.80924 -131.54056  18.874078
[6,]  22.098857  -44.05327 -129.32135  18.555655
[7,]  79.617345 -158.71430 -465.91653  66.851965

Aggiornamento 4

Qui, penso, è . Primo∂R/∂γ

  • XΓ~≡(XΓ−μXΓ)⊙σXΓ−1
  • γ~≡γ⊗1N

Simile a prima, la regola della catena arriva fino a looping ti dà Che, come prima, sta praticamente pre-moltiplicando lo stub. Dovrebbe quindi essere equivalente a:

∂R∂γ~=∂γ~⊙XΓ~∂γ~("stub")
∂R∂γ~i=(XΓ~)iTIγ~i("stub"i)
∂R∂γ~=(XΓ~)T("stub"⊙γ~)

In un certo senso corrisponde:

drdg <- t(scale(X %*% G1)) %*% (stub * t(matrix(gamma)) %x% matrix(rep(1, N)))

loop_drdg <- foreach(i = 1:4, .combine = c) %do% {
  t(scale(X %*% G1)[,i]) %*% (stub[,i, drop = F] * gamma[i])  
}

> drdg
           [,1]      [,2]       [,3]       [,4]
[1,]  0.8580574 -1.125017  -4.876398  0.4611406
[2,] -4.5463304  5.960787  25.837103 -2.4433071
[3,]  2.0706860 -2.714919 -11.767849  1.1128364
[4,] -8.5641868 11.228681  48.670853 -4.6025996
> loop_drdg
[1]   0.8580574   5.9607870 -11.7678486  -4.6025996

La diagonale sul primo è uguale al vettore sul secondo. Ma proprio dal momento che la derivata è rispetto a una matrice - sebbene con una certa struttura, l'output dovrebbe essere una matrice simile con la stessa struttura. Dovrei prendere la diagonale dell'approccio matrix e semplicemente prenderla come ? Non ne sono sicuro.γ

Sembra che io abbia risposto alla mia domanda ma non sono sicuro di aver ragione. A questo punto accetterò una risposta che dimostri rigorosamente (o smentisca) ciò che ho hackerato insieme.

while(not_answered){
  print("Bueller?")
  Sys.sleep(1)
}

2
Il capitolo 9, sezione 14 di "Calcolo differenziale a matrice con applicazioni in statistica ed econometria" di Magnus e Neudecker, terza edizione janmagnus.nl/misc/mdc2007-3rdedition copre i differenziali dei prodotti Kronecker e si conclude con un esercizio sul differenziale del prodotto Hadamard. "Note su Matrix Calculus" di Paul L. Fackler www4.ncsu.edu/~pfackler/MatCalc.pdf ha molto materiale sulla differenziazione dei prodotti Kronceker
— Mark L. Stone,

Grazie per i riferimenti. Ho trovato quelle note MatCalc prima, ma non copre Hadamard, e comunque non sono mai sicuro se una regola del calcolo non matriciale si applica o non si applica al caso matriciale. Regole del prodotto, regole della catena, ecc. Esaminerò il libro. Accetterei una risposta che mi indichi tutti gli ingredienti di cui ho bisogno per disegnarlo da solo ...
— generic_user,

perché stai facendo questo? perché non usare framewrok come Keras / TensorFlow? È uno spreco di tempo produttivo per implementare questi algoritmi di basso livello, che potresti usare per risolvere problemi reali
— Aksakal,

1
Più precisamente, sto adattando reti che sfruttano la struttura parametrica nota, sia in termini di rappresentazioni lineari in parametri dei dati di input, sia in termini di struttura longitudinale / pannello. I framework consolidati sono così fortemente ottimizzati da andare oltre la mia capacità di hackerare / modificare. Inoltre la matematica è utile in generale. Molti codemonkeys non hanno idea di cosa stiano facendo. Allo stesso Rcppmodo è utile imparare abbastanza per implementarlo in modo efficiente.
— generic_user

1
@ MarkL.Stone non è solo teoricamente sano, è praticamente facile! Un processo più o meno meccanico! &% $ #!
— generic_user

Risposte:


1

Non una risposta completa, ma per dimostrare ciò che ho suggerito nel mio commento se dove , ed è un vettore di quelli, quindi secondo la regola della catena Notando che e , vediamo che

b(X)=(X−eNμXT)ΓΣX−1/2+eNβT
Γ=diag⁡(γ)ΣX−1/2=diag⁡(σX1−1,σX2−1,…)eN
∇βR=[−2ϵ^(Γ2T⊗I)JX(a)(I⊗eN)]T
−2ϵ^(Γ2T⊗I)=vec⁡(−2ϵ^Γ2T)TJX(a)=diag⁡(vec⁡(a′(b(XΓ1))))
∇βR=(I⊗eNT)vec⁡(a′(b(XΓ1))⊙−2ϵ^Γ2T)=eNT(a′(b(XΓ1))⊙−2ϵ^Γ2T)
tramite l'identità . Allo stesso modo, dove (lo "stub") e è unvec⁡(AXB)=(BT⊗A)vec⁡(X)
∇γR=[−2ϵ^(Γ2T⊗I)JX(a)(ΣXΓ1−1/2⊗(XΓ1−eNμXΓ1T))K]T=KTvec⁡((XΓ1−eNμXΓ1T)TWΣXΓ1−1/2)=diag⁡((XΓ1−eNμXΓ1T)TWΣXΓ1−1/2)
W=a′(b(XΓ1))⊙−2ϵ^Γ2TKNp×pmatrice binaria che seleziona le colonne del prodotto Kronecker corrispondenti agli elementi diagonali di una matrice quadrata. Ciò deriva dal fatto che . A differenza del primo gradiente, questa espressione non è equivalente all'espressione che hai derivato. Considerando che è una funzione lineare , non dovrebbe esserci un fattore di nel gradiente. Lascio il gradiente di , ma dirò che per derivazione con fisso crea l '"esplosione" che gli scrittori dell'articolo cercano di evitare. In pratica, si dovrà anche trovare le Jacobiani di e WRTb γ i γ i Γ 1 w Σ X μ X XdΓi≠j=0bγiγiΓ1wΣXμXX e usa la regola del prodotto.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.