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)σxp1+β
  • 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)σX1+(β1p)
  • 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+ϵ

  • 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=N1(yy^)2

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

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

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Γ11+(β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

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

wXwX

(AB)=AB+AB

et de cela , que

vec(wXΓ1)vec(Γ1)T=vec(XΓ1)Ivec(w)vec(Γ1)T+vec(w)Ivec(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Γ11
  • "stub"a(b(XΓ1))2ϵ^Γ2T

RΓ1=wXΓ1Γ1("stub")
ijI
RΓij=(wiXi)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)=(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)
vec(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×pdΓij=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.