Forme matricielle de rétropropagation avec normalisation par lots


12

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

b(xp)=γ(xp−μxp)σxp−1+β
où
  • xp est lep e nœud, avant son activation
  • γ etβ sont des paramètres scalaires
  • μxp etσxp sont la moyenne et l'écart-type dexp . (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

b(X)=(γ⊗1p)⊙(X−μX)⊙σX−1+(β⊗1p)
où
  • X estN×p
  • 1N est un vecteur colonne de uns
  • γ etβ sont maintenant desvecteurs derangp 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μXσXN×pN
  • 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

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

où

  • est p 1 × p 2Γ1p1×p2
  • est p 2 × 1Γ2p2×1
  • est la fonction d'activationa(.)

Si la perte est , alors les gradients sont ∂ RR=N−1∑(y−y^)2

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

où

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

En normalisation par lots, le filet devient ou 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
Je ne sais pas comment calculer les dérivés des produits Hadamard et Kronecker. Au sujet des produits Kronecker, la littérature devient assez mystérieuse.

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?∂R/∂γ∂R/∂β∂R/∂Γ1

Mise à jour 1:

∂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

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

β⊗1N

∂A⊗B∂A=(Inq⊗Tmp)(In⊗vec(B)⊗Im)
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

γΓ1β⊗1

Update 2

∂R/∂Γ1∂R/∂γvec()∂R/∂Γ1w⊙XΓ1Γ1w≡(γ⊗1)⊙σXΓ1−1

w⊙XwX

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

et de cela , que

∂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

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.

∂R/∂Γ1

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

∂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)
∂R∂Γ=XT("stub"⊙w)

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

∂R/∂γ

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

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

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)
}

2
Le chapitre 9, section 14 de «Matrix Differential Calculus with Applications in Statistics and Econometrics» de Magnus et Neudecker, 3e édition janmagnus.nl/misc/mdc2007-3rdedition couvre les différentiels des produits Kronecker et se termine par un exercice sur le différentiel du produit Hadamard. "Notes on Matrix Calculus" par Paul L. Fackler www4.ncsu.edu/~pfackler/MatCalc.pdf a beaucoup de matériel sur la différenciation des produits Kronceker
— Mark L. Stone

Merci pour les références. J'ai déjà trouvé ces notes MatCalc, mais cela ne couvre pas Hadamard, et de toute façon je ne suis jamais certain si une règle du calcul non matriciel s'applique ou ne s'applique pas au cas matriciel. Règles de produit, règles de chaîne, etc. J'examinerai le livre. J'accepterais une réponse qui me pointe vers tous les ingrédients dont j'ai besoin pour le
— dessiner

pourquoi fais-tu ça? pourquoi ne pas utiliser des framewroks tels que Keras / TensorFlow? C'est une perte de temps productif pour implémenter ces algorithmes de bas niveau, que vous pourriez utiliser pour résoudre des problèmes réels
— Aksakal

1
Plus précisément, j'adapte des réseaux qui exploitent une structure paramétrique connue - à la fois en termes de représentations linéaires en paramètres des données d'entrée, ainsi que de structure longitudinale / panneau. Les frameworks établis sont tellement fortement optimisés qu'ils dépassent ma capacité de pirater / modifier. De plus, les mathématiques sont généralement utiles. Beaucoup de codemonkeys n'ont aucune idée de ce qu'ils font. De même, apprendre suffisamment Rcpppour le mettre en œuvre efficacement est utile.
— generic_user

1
@ MarkL.Stone non seulement est-il théoriquement sain, c'est pratiquement facile! Un processus plus ou moins mécanique! &% # $!
— generic_user

Réponses:


1

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)
vec⁡(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×pdΓi≠j=0bγiγiΓ1wΣXμXX
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.