バッチの正規化は、ディープニューラルネットのパフォーマンスが大幅に向上したとされています。インターネット上の多くの資料は、アクティベーションごとにそれを実装する方法を示しています。私はすでに行列代数を使用してバックプロップを実装しましたが、高レベル言語で作業していることを考えてRcpp
(そして、最終的にはGPUの高密度行列乗算に依存しています)、すべてをリッピングして- for
ループに頼るとおそらくコードが遅くなります実質的に、大きな痛みに加えて。
バッチ正規化関数である
- は、アクティブ化される前の番目のノードです。
- とはスカラーパラメーターです
- と平均値とのSDいる。(分散の平方根とファッジファクターが通常使用されることに注意してください-コンパクト化のために非ゼロ要素を仮定しましょう)
行列形式では、層全体のバッチの正規化は次のようになり
- は
- は1の列ベクトルです
- とは、レイヤーごとの正規化パラメーターの行ベクトルです。
- 及びであり各列は行列、 -ベクトル列方向手段と標準偏差の
- はクロネッカー積であり、は要素単位(アダマール)積です。
無バッチの正規化及び連続結果に非常に単純な1層ニューラルネットであり
どこ
- ある
- あり
- はアクティベーション関数です
損失がある場合は、次いで勾配は、 ∂ R
どこ
バッチの正規化の下で、ネットになり 又は Y = (( γ ⊗ 1 N ) ⊙ (X Γ 1 - μ X Γ 1) ⊙ σ - 1 X Γ 1 + ( β ⊗ 1個の N )) Γ 2
実用的な計算方法がある、∂ R / ∂ β、および∂ R / ∂ Γ 1は、マトリクスフレームワーク内?ノードごとの計算に頼らない単純な式?
更新1:
私は考え出した -ソートの。それは: 1 T N('(X Γ 1)⊙ - 2 ε Γ T 2) いくつかのRコードは、これはそれを行うためのループ方法と等価であることを実証しています。最初に偽のデータを設定します:
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
次に、導関数を計算します。
# 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
彼らは一致します。しかし、私はまだ混乱しています。なぜなら、これがなぜ機能するのか本当にわからないからです。@マークL.ストーンによって参照MatCalcノートはの派生と言うあるべき
の添え字はmは、N、及びP、qはの寸法であり、AおよびB。 Tは転流行列で、両方の入力がベクトルであるため、ここでは1だけです。私はこれを試して、役に立たないと思われる結果を得ます:
# 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
更新2
vec()
そして、これから、
アップデート3
ここで進歩しています。昨夜の午前2時にこのアイデアで目が覚めました。数学は睡眠に適していない。
そして、実際には:
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
更新4
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.