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
- è il nodo , prima che venga attivato
- e sono parametri scalari
- 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)
In forma di matrice, la normalizzazione batch per un intero livello sarebbe dove
- N × p è
- è un vettore colonna di quelli
- β p e sono ora riga vettori dei parametri di normalizzazione per livello
- σ X N × p N e sono matrici , dove ogni colonna è un vettore di medie a colonna e deviazioni standard
- ⊙ è il prodotto Kronecker e è il prodotto elementwise (Hadamard)
Una rete neurale a uno strato molto semplice senza normalizzazione batch e un risultato continuo è
dove
- p 1 × p 2 è
- p 2 × 1 è
- è la funzione di attivazione
Se la perdita è , i gradienti sono ∂ R
dove
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
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
Aggiornamento 1:
Ho capito - sorta di. È: Alcuni codici R dimostrano che ciò equivale al modo di fare il looping. Innanzitutto imposta i dati falsi:
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
# 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 .
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). vec()
Il mio istinto è semplicemente dire "la risposta è ", ma ovviamente non funziona perché non è conforme a .
So che
e da questo , quello
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:
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:
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
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:
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)
}
Rcpp
modo è utile imparare abbastanza per implementarlo in modo efficiente.