La normalisation des lots a été attribuée à des améliorations substantielles des performances dans les réseaux neuronaux profonds. De nombreux documents sur Internet montrent comment l'implémenter sur une base d'activation par activation. J'ai déjà implémenté backprop en utilisant l'algèbre matricielle, et étant donné que je travaille dans des langages de haut niveau (tout en comptant sur Rcpp
(et éventuellement sur les GPU) pour une multiplication matricielle dense), tout arracher et recourir à for
-loops ralentirait probablement mon code considérablement, en plus d'être une énorme douleur.
La fonction de normalisation par lots est
- est le e nœud, avant son activation
- et sont des paramètres scalaires
- et sont la moyenne et l'écart-type de . (Notez que la racine carrée de la variance plus un facteur de fudge est normalement utilisée - supposons des éléments non nuls pour la compacité)
Sous forme matricielle, la normalisation par lots pour une couche entière serait
- est
- est un vecteur colonne de uns
- et sont maintenant desvecteurs derang des paramètres de normalisation par couche
- et σ X sont desmatrices N × p , où chaque colonne est unvecteur N de moyennes sur colonne et d'écarts-types
- est le produit Kronecker et ⊙ est le produit élémentaire (Hadamard)
Un réseau neuronal monocouche très simple sans normalisation par lots et un résultat continu est
où
- est p 1 × p 2
- est p 2 × 1
- est la fonction d'activation
Si la perte est , alors les gradients sont ∂ R
où
En normalisation par lots, le filet devient ou y = a ( ( γ ⊗ 1 N ) ⊙ ( X Γ 1 - μ X Γ 1 ) ⊙ σ - 1 X Γ 1 + ( β ⊗ 1 N ) ) Γ 2
Existe-t-il un moyen pratique de calculer , ∂ R / ∂ β et ∂ R / ∂ Γ 1 dans le cadre matriciel? Une expression simple, sans recourir au calcul nœud par nœud?
Mise à jour 1:
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
Calculez ensuite les dérivées:
# 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
# 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
Update 2
vec()
et de cela , que
Mise à jour 3
Progresser ici. Je me suis réveillé à 2 heures du matin hier avec cette idée. Les mathématiques ne sont pas bonnes pour dormir.
Et, en fait, c'est:
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
Mise à jour 4
Il correspond en quelque sorte:
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
Il semble que j'ai répondu à ma propre question, mais je ne sais pas si j'ai raison. À ce stade, j'accepterai une réponse qui prouve (ou réfute) rigoureusement ce que j'ai en quelque sorte piraté ensemble.
while(not_answered){
print("Bueller?")
Sys.sleep(1)
}
Rcpp
pour le mettre en œuvre efficacement est utile.