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)σxp1+β
  • 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)σX1+(β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=N1(yy^)2

RΓ1=2VTϵ^RΓ2=XT(a(XΓ1)2ϵ^Γ2T)

dove

  • V=a(XΓ1)
  • ϵ^=yy^

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Γ11+(β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 /Γ 1R/γ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

ABA=(InqTmp)(Invec(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/Γ1R/γvec()R/Γ1wXΓ1Γ1w(γ1)σXΓ11

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

So che

(AB)=AB+AB

e da questo , quello

vec(wXΓ1)vec(Γ1)T=vec(XΓ1)Ivec(w)vec(Γ1)T+vec(w)Ivec(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Γ11
  • "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=wXΓ1Γ1("stub")
ijI
RΓij=(wiXi)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, è . PrimoR/γ

  • 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)=(XeNμXT)ΓΣX1/2+eNβT
Γ=diag(γ)ΣX1/2=diag(σX11,σX21,)eN
βR=[2ϵ^(Γ2TI)JX(a)(IeN)]T
2ϵ^(Γ2TI)=vec(2ϵ^Γ2T)TJX(a)=diag(vec(a(b(XΓ1))))
βR=(IeNT)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)=(BTA)vec(X)
γR=[2ϵ^(Γ2TI)JX(a)(ΣXΓ11/2(XΓ1eNμXΓ1T))K]T=KTvec((XΓ1eNμXΓ1T)TWΣXΓ11/2)=diag((XΓ1eNμXΓ1T)TWΣXΓ11/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Γij=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.