Der Batch-Normalisierung wurden erhebliche Leistungsverbesserungen in tiefen neuronalen Netzen zugeschrieben. Zahlreiches Material im Internet zeigt, wie es von Aktivierung zu Aktivierung umgesetzt werden kann. Ich habe Backprop bereits mithilfe der Matrixalgebra implementiert, und da ich in Hochsprachen arbeite (während ich mich auf Rcpp
(und möglicherweise auch auf GPUs) für eine dichte Matrixmultiplikation verlasse), for
würde das Herausreißen aller Elemente und das Zurückgreifen auf -loops wahrscheinlich meinen Code verlangsamen im Wesentlichen zusätzlich zu einem großen Schmerz.
Die Batch - Normierungsfunktion ist
- ist der te Knoten, bevor er aktiviert wird
- und sind skalare Parameter
- und sind der Mittelwert und die SD von . (Beachten Sie, dass normalerweise die Quadratwurzel der Varianz plus eines Fudge-Faktors verwendet wird. Nehmen wir für die Kompaktheit Elemente ungleich Null an.)
In Matrixform wäre die Chargennormalisierung für eine ganze Schicht
- ist
- ist ein Spaltenvektor von Einsen
- und sind jetzt Zeilen- Vektoren der Pro-Schicht-Normalisierungsparameter
- und sind Matrizen, wobei jede Spalte ein Vektor aus spaltenweisen Mitteln und Standardabweichungen ist
- ist das Kronecker-Produkt und ist das elementweise (Hadamard-) Produkt
Ein sehr einfaches einschichtiges neuronales Netz ohne Chargennormalisierung und kontinuierliches Ergebnis ist
wo
- ist
- ist
- ist die Aktivierungsfunktion
Wenn der Verlust ist , dann sind die Gradienten
wo
Unter Chargennormalisierung wird das Netz zu oder y = a ( ( γ ≤ 1 N ) ≤ ( X ≤ 1 - μ X ≤ 1 ) ≤ σ - 1 X ≤ 1 + ( β ≤ 1 N ) ) ≤ 2
Gibt es eine praktische Art und Weise der Berechnung , ∂ R / ∂ & bgr; und ∂ R / ∂ & Ggr; 1 innerhalb der Matrix Rahmen? Ein einfacher Ausdruck, ohne auf die knotenweise Berechnung zurückzugreifen?
Update 1:
Ich habe herausgefunden, - Art von. Es ist: 1 T N ( a ' ( X Γ 1 ) ⊙ - 2 & egr; Γ T 2 ) Einige R Code zeigt , dass dies auf die Looping Art und Weise entspricht , es zu tun. Richten Sie zuerst die gefälschten Daten ein:
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
Berechnen Sie dann Ableitungen:
# 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
Sie passen. Aber ich bin immer noch verwirrt, weil ich nicht wirklich weiß, warum das funktioniert. Die von Mark L. Stone referenzierten MatCalc-Noten besagen, dass die Ableitung von sein sollte
# 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()
und daraus das
Update 3
Hier Fortschritte machen. Ich bin letzte Nacht um 2 Uhr morgens mit dieser Idee aufgewacht. Mathe ist nicht gut zum Schlafen.
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 . First
Similar to before, the chain rule gets you as far as
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)
}
Rcpp
to implement it efficiently is useful.