Forma matricial de retropropagação com normalização em lote


12

A normalização de lotes foi creditada com melhorias substanciais de desempenho em redes neurais profundas. Muito material na internet mostra como implementá-lo, ativação por ativação. Eu já implementei backprop usando álgebra matricial e, como estou trabalhando em linguagens de alto nível (enquanto confio em Rcpp(e eventualmente GPUs) para multiplicação densa de matrizes), rasgar tudo e recorrer a forloops provavelmente atrasaria meu código substancialmente, além de ser uma dor enorme.

A função de normalização do lote é

b(xp)=γ(xpμxp)σxp1+β
em que
  • xp é op nó th, antes que ele é ativado
  • γ eβ são parâmetros escalares
  • μxp eσxp são a média e o DP dexp . (Observe que a raiz quadrada da variação mais um fator de correção é normalmente usado - vamos assumir elementos diferentes de zero para compactação)

Na forma de matriz, a normalização de lote para uma camada inteira seria

b(X)=(γ1p)(XμX)σX1+(β1p)
em que
  • X éN×p
  • 1N é um vetor de coluna de unidades
  • γ eβ agora sãovetores delinhap dos parâmetros de normalização por camada
  • μX eσX sãomatrizesN×p , em que cada coluna é umvetorN de médias decolunae desvios padrão
  • é o produto Kronecker e é o produto elementwise (Hadamard)

Uma rede neural de uma camada muito simples, sem normalização por lotes e um resultado contínuo é

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

Onde

  • Γ1 ép1×p2
  • Γ2 ép2×1
  • a(.) é a função de ativação

Se a perda é R=N1(yy^)2 , em seguida, os gradientes são

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

Onde

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

Sob normalização do lote, a rede se torna

y=a(b(XΓ1))Γ2
ou
y=a((γ1N)(XΓ1μXΓ1)σXΓ11+(β1N))Γ2
Não tenho ideia de como calcular os derivados dos produtos Hadamard e Kronecker. Quanto aos produtos Kronecker, a literatura fica bastante misteriosa.

Existe uma maneira prática de computação , R /beta , e R /y- 1 no âmbito da matriz? Uma expressão simples, sem recorrer à computação nó por nó?R/γR/βR/Γ1

Atualização 1:

Eu descobri - mais ou menos. É: 1 T N ( um ' ( X Γ 1 ) - 2 £ Γ T 2 ) Algumas R código demonstra que este é equivalente ao modo looping para fazê-lo. Primeiro, configure os dados falsos: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

Em seguida, calcule derivativos:

# 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

Eles combinam. Mas ainda estou confuso, porque realmente não sei por que isso funciona. As notas do MatCalc referenciadas por @ Mark L. Stone dizem que a derivada de deve serβ1N

, onde os subscritosm,n, ep,qsão as dimensões deumeB. Té a matriz de comutação, que é apenas 1 aqui porque ambas as entradas são vetores. Eu tento isso e obtenho um resultado que não parece útil:

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

Isso não é conformável. Claramente, não estou entendendo essas regras derivadas da Kronecker. Ajudar com isso seria ótimo. Ainda estou totalmente preso aos outros derivativos, para e Γ 1 - esses são mais difíceis porque não entram de maneira aditiva como o β 1 .γΓ1β1

Atualização 2

Lendo livros didáticos, tenho certeza de que e R /γ exigirão o uso do operador. Mas, aparentemente, sou incapaz de seguir suficientemente as derivações para poder traduzi-las em código. Por exemplo, R /Γ 1 envolverá a derivada de w X Γ 1 em relação a Γ 1 , onde w ( γ 1 ) σ - 1R/Γ1R/γvec()R/Γ1wXΓ1Γ1 (que podemos tratar como uma matriz constante no momento). w(γ1)σXΓ11

Meu instinto é simplesmente dizer "a resposta é ", mas que, obviamente, não funciona porque w não está de acordo com X .wXwX

Eu sei que

(AB)=AB+AB

e a partir disso , que

vec(wXΓ1)vec(Γ1)T=vec(XΓ1)Ivec(w)vec(Γ1)T+vec(w)Ivec(XΓ1)vec(Γ1)T

Atualização 3

Fazendo progresso aqui. Eu acordei às 2 da manhã na noite passada com essa ideia. A matemática não é boa para 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)
tl;dr you're basically pre-multiplying the stub by the batchnorm scale factors. This should be equivalent to:
RΓ=XT("stub"w)

And, in fact it is:

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

Update 4

Here, I think, is R/γ. First

  • XΓ~(XΓμXΓ)σXΓ1
  • γ~γ1N

Similar to before, the chain rule gets you as far as

Rγ~=γ~XΓ~γ~("stub")
Looping gives you
Rγ~i=(XΓ~)iTIγ~i("stub"i)
Which, like before, is basically pre-multiplying the stub. It should therefore be equivalent to:
Rγ~=(XΓ~)T("stub"γ~)

It sort of matches:

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

The diagonal on the first is the same as the vector on the second. But really since the derivative is with respect to a matrix -- albeit one with a certain structure, the output should be a similar matrix with the same structure. Should I take the diagonal of the matrix approach and simply take it to be γ? I'm not sure.

It seems that I have answered my own question but I am unsure whether I am correct. At this point I will accept an answer that rigorously proves (or disproves) what I've sort of hacked together.

while(not_answered){
  print("Bueller?")
  Sys.sleep(1)
}

2
Chapter 9 section 14 of "Matrix Differential Calculus with Applications in Statistics and Econometrics" by Magnus and Neudecker, 3rd edition janmagnus.nl/misc/mdc2007-3rdedition covers differentials of Kronecker products and concludes with an exercise on differential of Hadamard product. "Notes on Matrix Calculus" by Paul L. Fackler www4.ncsu.edu/~pfackler/MatCalc.pdf has a lot of material on differentiating Kronceker products
Mark L. Stone

Thanks for the references. I've found those MatCalc notes before, but it doesn't cover Hadamard, and anyway I'm never certain whether a rule from non-matrix calculus applies or doesn't apply to to matrix case. Product rules, chain rules, etc. I'll look into the book. I'd accept an answer that points me to all of the ingredients I need to pencil it out myself...
generic_user

why are you doing this? why not use framewroks such as Keras/TensorFlow? It's a waste of productive time to implement these low level algorithms, that you could use on solving actual problems
Aksakal

1
More precisely, I'm fitting networks that exploit known parametric structure -- both in terms of linear-in-parameters representations of input data, as well as longitudinal/panel structure. Established frameworks are so heavily optimized as to be beyond my ability to hack/modify. Plus math is helpful generally. Plenty of codemonkeys have no idea what they're doing. Likewise learning enough Rcpp to implement it efficiently is useful.
generic_user

1
@MarkL.Stone not only is it theoretically sound, it's practically easy! A more or less mechanical process! &%#$!
generic_user

Respostas:


1

Not a complete answer, but to demonstrate what I suggested in my comment if

b(X)=(XeNμXT)ΓΣX1/2+eNβT
where Γ=diag(γ), ΣX1/2=diag(σX11,σX21,) and eN is a vector of ones, then by the chain rule
βR=[2ϵ^(Γ2TI)JX(a)(IeN)]T
Noting that 2ϵ^(Γ2TI)=vec(2ϵ^Γ2T)T and JX(a)=diag(vec(a(b(XΓ1)))), we see that
βR=(IeNT)vec(a(b(XΓ1))2ϵ^Γ2T)=eNT(a(b(XΓ1))2ϵ^Γ2T)
via the identity vec(AXB)=(BTA)vec(X). Similarly,
γ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)
where W=a(b(XΓ1))2ϵ^Γ2T (the "stub") and K is an Np×p binary matrix that selects the columns of the Kronecker product corresponding to the diagonal elements of a square matrix. This follows from the fact that dΓij=0. Unlike the first gradient, this expression is not equivalent to the expression you derived. Considering that b is a linear function w.r.t γi, there should not be a factor of γi in the gradient. I leave the gradient of Γ1 to the OP, but I will say for derivation with fixed w creates the "explosion" the writers of the article seek to avoid. In practice, you will also need to find the Jacobians of ΣX and μX w.r.t X and use product rule.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.