Нормализация партии была приписана существенным улучшениям производительности в глубоких нейронных сетях. Много материала в интернете показывает, как реализовать его на основе активации за активацию. Я уже реализовал backprop, используя матричную алгебру, и учитывая, что я работаю на языках высокого уровня (полагаясь Rcpp
(и, в конечном итоге, на GPU) на плотное матричное умножение), вырывая все и прибегая к for
-loops, возможно, замедлю мой код по существу, в дополнение к огромной боли.
Функция нормализации партии имеет вид где
- - это й узел, прежде чем он активируется
- и - скалярные параметры
- и - среднее значение и SD для . (Обратите внимание, что обычно используется квадратный корень из дисперсии плюс коэффициент выдумки - давайте предположим ненулевые элементы для компактности)
В матричной форме пакетная нормализация для всего слоя будет где
- N × p равно
- является вектором столбцов
- β p и теперь являются строчными векторами параметров нормализации для каждого слоя
- σ X N × p N и - это матриц, где каждый столбец является вектором по столбцам средних значений и стандартных отклонений
- ⊙ - произведение Кронекера, а - поэлементное произведение (Адамар).
Очень простая однослойная нейронная сеть без пакетной нормализации и непрерывного результата:
где
- p 1 × p 2 - это
- p 2 × 1 - это
- является функцией активации
Если потеря равна , то градиенты будут ∂ R
где
При нормализации партии сеть становится или Я не знаю, как вычислить производные произведений Адамара и Кронекера. Что касается продуктов Kronecker, литература становится довольно загадочной. y = a ( ( γ ⊗ 1 N ) ⊙ ( X Γ 1 - μ X Γ 1 ) ⊙ σ - 1 X Γ 1 + ( β ⊗ 1 N ) ) Γ 2
Существуют ли практические способы вычисления , и в рамках матрицы? Простое выражение, не прибегая к вычислениям по узлам?∂ R / ∂ β ∂ R / ∂ Γ 1
Обновление 1:
Я разобрался с . Это: Некоторый код 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
Они совпадают. Но я все еще смущен, потому что я действительно не знаю, почему это работает. В заметках MatCalc, на которые ссылается @Mark L. Stone, говорится, что производная от должна быть
# 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
Здесь, я думаю, есть . Первый
Как и раньше, правило цепочки приводит вас к Зацикливание дает вам Что, как и прежде, в основном является предварительным умножением заглушки. Следовательно, он должен быть эквивалентен:
Это своего рода совпадения:
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
Диагональ на первом совпадает с вектором на втором. Но на самом деле, поскольку производная относится к матрице, хотя и с определенной структурой, на выходе должна быть похожая матрица с такой же структурой. Должен ли я взять диагональ матричного подхода и просто принять его за ? Я не уверен.
Кажется, я ответил на свой вопрос, но я не уверен, что я прав. На этом этапе я приму ответ, который строго доказывает (или опровергает) то, что я вроде как взломал вместе.
while(not_answered){
print("Bueller?")
Sys.sleep(1)
}
Rcpp
для эффективной реализации, полезно.Ответы:
Не полный ответ, но чтобы продемонстрировать то, что я предложил в своем комментарии, если где , и является вектором единиц, тогда по правилу цепочки Отметив, что и , мы видим, что
источник