Пакетная нормализация
Batch normalization is one of the most exciting recentinnovations in optimizing deep neural networks.— от Яна Гудфеллоу и др. «Глубокое обучение».
Примечание. Хотя этот блог также можно использовать в качестве заметки для чтения «Пакетная нормализация: ускорение глубокого обучения сети за счет уменьшения внутреннего ковариатного сдвига», в нем основное внимание уделяется получению и реализации BN, а инженерная практика сильна.
Пакетная нормализация предложена Google в статье 2015 года «Пакетная нормализация: ускорение обучения глубокой сети за счет уменьшения внутреннего ковариатного сдвига». Я не буду много говорить о BN. Ходят слухи, что «BN хорош», на изображении Эффект классификации действительно хорошо. Мы по-прежнему фокусируемся на самом BN. Чтобы получить более простое и подробное описание BN, этот блог в основном представляет BN со следующих аспектов:
- Цель и идея БН
- формула BN
- Преимущества БН
- bp вывод BN
- Кодовая реализация БН
1. Цель и идея БН
Основная цель BN — решить проблему сложности обучения глубоких сетей. Причина, по которой глубокие нейронные сети трудно обучать, заключается в том, что распределение входных данных каждого слоя изменяется с параметрами предыдущего слоя во время обучения, что требует от нас использования небольшой скорости обучения и хорошей инициализации параметров, но это путь Это сделает процесс обучения очень медленным.
«Распределение входных данных для каждого слоя изменяется во время обучения по мере изменения параметров предыдущего слоя». Это явление упоминается в статье какInternal Covariate Shift,иCovariate ShiftВ статистическом машинном обучении это относится к непоследовательному распределению обучающих и целевых выборок, что приводит к плохой способности обучаемой модели к обобщению. Поэтому идея BN состоит в том, чтобы нормализовать вход каждого скрытого слоя (чтобы вход (Z) функции активации имел среднее значение 0 и дисперсию 1). По сути, это в основном то же самое, что и нормализация признаков в предобработке, Мы нормализуем признаки, чтобы функция стоимости сходилась быстрее и увеличивала скорость обучения, что согласуется с идеей БН. Однако лично я считаю, что магия BN заключается в том, чтобы ввести линейную функцию после выполнения нормализации.
Z ˜ ( i) =γ ⋅Z ( i) нет rm +β Z~(i)=γ⋅Znorm(i)+β \widetilde{Z}^{(i)} = \gamma\cdot Z_{norm} ^{(i)} + \beta Z (i) = γ⋅ Z nor m ( i) + β , где параметр
γ γ \gamma γ играет роль масштаба,
β β \beta β играет роль сдвига, и эти два параметра получаются путем обучения. потому что если только
Z Z Z Z Если нормализация выполнена, это изменит способность представления каждого слоя сети.Например, если функция активации использует сигмоидальную функцию, правильный
Z Z Z Z После нормализации ввод ограничивается линейной частью сигмоиды. Чтобы конкретнее объяснить этот отрывок, давайте рассмотрим пример: левая часть рисунка ниже — это распределение исходного набора данных, а правая часть рисунка — набор данных после нормализации.
Примечание. Для удобства отображения извлекаются только 2-мерные функции, абсцисса представляет функцию, а ордината представляет функцию..
Как видно из приведенного выше рисунка, после нормализации данных каждое измерение признака соответствует стандартному нормальному распределению (обратите внимание на горизонтальные и вертикальные координаты двух изображений), что приводит к тому, что если функция активации является сигмовидной, тогда вход ограничен сигмовидной линейной частью , как показано на следующем рисунке. Все мы знаем, что нейронные сети очень хорошо справляются с нелинейными задачами, и это равносильно саморазрушению.
Поэтому, чтобы справиться с вышеуказанной проблемой, исследователи Google представили Z ˜ ( i) =γ ⋅Z ( i) нет rm +β Z~(i)=γ⋅Znorm(i)+β \widetilde{Z}^{(i)} = \gamma\cdot Z_{norm} ^ {(i)} + \beta Z (i) = γ⋅ Z nor m (i) + β . Таким образом, возможность представления каждого уровня исходной сети может быть восстановлена.
2. Формула БН
Здесь я все еще следую обозначениям моего предыдущего блога, а именно
Z =W ⋅X +b Z=W⋅X+b Z = W \cdot X + b Z= W⋅X+ b , принимая
мини мини мини мини ми ни -
партия партия партия партия
s iz e=m size=m size=m s ze= m , то для
Для определенного скрытого юнита (блока активации) есть:
Формула настолько проста, что и говорить не о чем.
3. Преимущества БН
Давайте посмотрим на преимущества БН, прямо из статьи БН, и добавим немного своего мнения:
- Скорость обучения может быть установлена относительно большой. потому что это можетlearning rateЕсли параметр относительно велик, алгоритм будет быстро сходиться, поэтому скорость обучения определенно будет высокой. Даже если вы используете ту же скорость обучения, что и исходная, скорость обучения будет увеличена из-за наличия нормализации.
- В документе BN говорится, что BN имеет определенную способность избегать переобучения и не может использовать методы регуляризации, такие как отсев и регуляризация L2, но нг сказал в классе, что BN имеет только определенную способность и не может полностью заменить отсев и т. д., Фактический тест также подтвердил заявление ng.
- Не обязательно использовать локальную нормализацию, я не очень хорошо знаком с локальной нормализацией, поэтому много о ней говорить не буду.
Подводя итог, БН действительно прост в использовании, но это не панацея, то есть «БН Дафа хорош, но его нельзя использовать без разбора».
В-четвертых, bp вывод BN
Здесь начинается изюминка, то есть вывод bp BN, который, конечно же, является ядром всего процесса, потому что нам нужны параметры bp. Поскольку БН вводит еще два параметра
γ γ \gamma γ и
β β \beta β , поэтому при обучении требуются 4 параметра, а именно:
γ γ \ гамма γ ,
β β \ бета β ,
В В В В ,
б б б б . На самом деле, поскольку в BN введена линейная формула,
b b b b бесполезен, так что не просите об этом. сначала спроси
γ γ \gamma γ и
Градиент β β \beta β:
Объясните, что здесь есть суммирование, потому что для определенной единицы активации есть образцы mm mm.
Далее спрашивает
W W W W градиент, мы запрашиваем только функцию стоимости
J J J J пара
Производная Z (i) Z(i) Z^{(i)} Z ( i) просто прекрасна, потому что
Z ( i ) = W ⋅ X + b Z ( i ) = W ⋅ X + b Z ^ { ( i ) } = W \ cdot X + b Z ( i ) = W ⋅ X + b , с
Z (i) Z(i) Z^{(i)} Z (i) градиент легко найти
Градиент W W W W. Давайте посмотрим на конкретный процесс вывода.Чтобы сделать вывод ясным с первого взгляда, мы можем сначала нарисовать форму дерева, как показано на следующем рисунке:
Поэтому по цепному правилу:
Вот запрос:
Итак, есть:
По поводу этого вывода почти вся информация в инете только до позиции красной рамочки и заканчивается.Догадка должна основываться на кратком результате статьи Б.Н. Z ˜ ( i) =γ ⋅Z ( i) норма +β Z~(i)=γ⋅Znorm(i)+β \widetilde{Z}^{(i)} = \gamma\cdot Z_{norm}^ {(i)} + \beta Z (i) = γ⋅ Z norm ( i) + β , поэтому мы должны написать J J J J пара Z ˜ ( i) Z~(i) \widetilde{Z}^{(i)} Производная от Z (i), поскольку при выполнении bp передается производная функции активации. ∂ J ∂ Z ˜ ( i) ∂J∂Z ~ (i) \ frac {\ partial J} {\ partial \ widetilde {Z} ^ {(i)}} ∂ Z (i) ∂ J .
Пять, реализация кода BN
Далее мы будем реализовывать этот алгоритм сами.BN можно сказать очень простой при прямом распространении.Достаточно добавить слой BN на основе исходной сети, в основном реализация BP. При реализации очень важно одно:
когда Б.Н. находится в фазе обучения μ μ \mu μ и δ 2 δ2 \delta^2 δ 2 очень легко найти по образцам каждой партии, но на этапе испытаний часто бывает несколько или даже один образец, тогда μ μ \mu μ и δ 2 δ2 \delta^2 δ 2 не может быть получено Решение здесь состоит в том, чтобы использовать экспоненциальное взвешенное среднее (также называемое экспоненциальным скользящим средним) на этапе обучения (подробное введение см. В моем блоге:Методы оптимизации в глубоком обучении - импульс, Нестеров Моментум, АдаГрад, Ададельта, RMSprop, Адам), чтобы вычислить и обновить среднее значение и дисперсию. После завершения обучения среднее значение и дисперсия (moving_average, moving_variance) непосредственно применяются к фазе тестирования как среднее значение и дисперсия тестовой выборки (обратите внимание, что moving_average и moving_variance являются только используется на этапе тестирования), moving_average и Формула расчета moving_variance выглядит следующим образом:
M oving _mean= 0,9∗moving _mean+ 0,1∗current_mean Moving_varianc e= 0,9∗ Moving g_varianc e+ 0,1∗ current t_ v ri ance moving_mean=0,9∗moving_mean +0,1∗current_meanmoving_variance=0,9∗moving_variance+0,1∗current_variance moving\_mean = 0,9*moving\_mean + 0,1*current\_mean \\moving\_variance = 0,9*moving\_variance + 0,1*current\_variance moving_mean = 0,9 * скользящая _ средняя + 0,1 * текущая _ средняя скользящая _ дисперсия = 0,9 * скользящая _ дисперсия + 0,1 * текущая _ дисперсия
Глядя на полученную нами производную, мы видим, что в процессе fp сохраняются следующие переменные:
1 δ 2 +ϵ √ 1δ2+ϵ \frac{1}{\sqrt{\delta^2 + \epsilon}} δ
2 + ε 1 ,
Z ( i) nor m Znorm(i) Z_{norm}^{(i)} Z nor
м ( я ) ,
γ γ \ гамма γ ,
Z ˜ ( i) Z~(i) \widetilde{Z}^{(i)} Z (i) .
Ниже приведена реализация кода, в основном для инициализации нескольких параметров нижнего слоя bn, а затем отображения прямого и обратного распространения слоя bn и обновления параметров при обновлении параметров.
γ γ \gamma γ и
β β \ бета β . Полный код выложен на github:batch_normalization.py
# implement the batch normalization
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
#initialize parameters(w,b)
def initialize_parameters(layer_dims):
"""
:param layer_dims: list,每一层单元的个数(维度)
gamma -- scale vector of shape (size of current layer ,1)
beta -- offset vector of shape (size of current layer ,1)
:return: parameter: directory store w1,w2,...,wL,b1,...,bL
bn_param: directory store moving_mean, moving_var
"""
np.random.seed(3)
L = len(layer_dims)#the number of layers in the network
parameters = {}
# initialize the exponential weight average
bn_param = {}
for l in range(1,L):
parameters["W" + str(l)] = np.random.randn(layer_dims[l],layer_dims[l-1])*0.01
parameters["b" + str(l)] = np.zeros((layer_dims[l],1))
parameters["gamma" + str(l)] = np.ones((layer_dims[l],1))
parameters["beta" + str(l)] = np.zeros((layer_dims[l],1))
bn_param["moving_mean" + str(l)] = np.zeros((layer_dims[l], 1))
bn_param["moving_var" + str(l)] = np.zeros((layer_dims[l], 1))
return parameters, bn_param
def relu_forward(Z):
"""
:param Z: Output of the linear layer
:return:
A: output of activation
"""
A = np.maximum(0,Z)
return A
#implement the activation function(ReLU and sigmoid)
def sigmoid_forward(Z):
"""
:param Z: Output of the linear layer
:return:
"""
A = 1 / (1 + np.exp(-Z))
return A
def linear_forward(X, W, b):
z = np.dot(W, X) + b
return z
def batchnorm_forward(z, gamma, beta, epsilon = 1e-12):
"""
:param z: the input of activation (z = np.dot(W,A_pre) + b)
:param epsilon: is a constant for denominator is 0
:return: z_out, mean, variance
"""
mu = np.mean(z, axis=1, keepdims=True)#axis=1按行求均值
var = np.var(z, axis=1, keepdims=True)
sqrt_var = np.sqrt(var + epsilon)
z_norm = (z - mu) / sqrt_var
z_out = np.multiply(gamma,z_norm) + beta #对应元素点乘
return z_out, mu, var, z_norm, sqrt_var
def forward_propagation(X, parameters, bn_param, decay = 0.9):
"""
X -- input dataset, of shape (input size, number of examples)
parameters -- python dictionary containing your parameters "W1", "b1", "gamma1","beta1",W2", "b2","gamma2","beta2",...,"WL", "bL","gammaL","betaL"
W -- weight matrix of shape (size of current layer, size of previous layer)
b -- bias vector of shape (size of current layer,1)
gamma -- scale vector of shape (size of current layer ,1)
beta -- offset vector of shape (size of current layer ,1)
decay -- the parameter of exponential weight average
moving_mean = decay * moving_mean + (1 - decay) * current_mean
moving_var = decay * moving_var + (1 - decay) * moving_var
the moving_mean and moving_var are used for test
:return:
AL: the output of the last Layer(y_predict)
caches: list, every element is a tuple:(A, W,b,gamma,sqrt_var,z_out,Z_norm)
"""
L = len(parameters) // 4 # number of layer
A = X
caches = []
# calculate from 1 to L-1 layer
for l in range(1,L):
W = parameters["W" + str(l)]
b = parameters["b" + str(l)]
gamma = parameters["gamma" + str(l)]
beta = parameters["beta" + str(l)]
z = linear_forward(A, W, b)
z_out, mu, var, z_norm, sqrt_var = batchnorm_forward(z, gamma, beta) #batch normalization
caches.append((A, W, b, gamma, sqrt_var, z_out, z_norm)) #以激活单元为分界线,把做激活前的变量放在一起,激活后可以认为是下一层的x了
A = relu_forward(z_out) #relu activation function
#exponential weight average for test
bn_param["moving_mean" + str(l)] = decay * bn_param["moving_mean" + str(l)] + (1 - decay) * mu
bn_param["moving_var" + str(l)] = decay * bn_param["moving_var" + str(l)] + (1 - decay) * var
# calculate Lth layer(last layer)
WL = parameters["W" + str(L)]
bL = parameters["b" + str(L)]
zL = linear_forward(A, WL, bL)
caches.append((A, WL, bL, None, None, None, None))
AL = sigmoid_forward(zL)
return AL, caches, bn_param
#calculate cost function
def compute_cost(AL,Y):
"""
:param AL: 最后一层的激活值,即预测值,shape:(1,number of examples)
:param Y:真实值,shape:(1, number of examples)
:return:
"""
m = Y.shape[1]
cost = 1. / m * np.nansum(np.multiply(-np.log(AL), Y) +
np.multiply(-np.log(1 - AL), 1 - Y))
#从数组的形状中删除单维条目,即把shape中为1的维度去掉,比如把[[[2]]]变成2
cost = np.squeeze(cost)
return cost
#derivation of relu
def relu_backward(dA, Z):
"""
:param Z: the input of activation function
:return:
"""
dout = np.multiply(dA, np.int64(Z > 0))
return dout
def batchnorm_backward(dout, cache):
"""
:param dout: Upstream derivatives
:param cache:
:return:
"""
_, _, _, gamma, sqrt_var, _, Z_norm = cache
m = dout.shape[1]
dgamma = np.sum(dout*Z_norm, axis=1, keepdims=True) #*作用于矩阵时为点乘
dbeta = np.sum(dout, axis=1, keepdims=True)
dy = 1./m * gamma * sqrt_var * (m * dout - np.sum(dout, axis=1, keepdims=True) - Z_norm*np.sum(dout*Z_norm, axis=1, keepdims=True))
return dgamma, dbeta, dy
def linear_backward(dZ, cache):
"""
:param dZ: Upstream derivative, the shape (n^[l+1],m)
:param A: input of this layer
:return:
"""
A, W, _, _, _, _, _ = cache
dW = np.dot(dZ, A.T)
db = np.sum(dZ, axis=1, keepdims=True)
da = np.dot(W.T, dZ)
return da, dW, db
def backward_propagation(AL, Y, caches):
"""
Implement the backward propagation presented in figure 2.
Arguments:
Y -- true "label" vector (containing 0 if cat, 1 if non-cat)
caches -- caches output from forward_propagation(),(w,b,gamma,sqrt_var,z_out,Z_norm,A)
Returns:
gradients -- A dictionary with the gradients with respect to dW,db
"""
m = Y.shape[1]
L = len(caches)-1
# print("L: " + str(L))
#calculate the Lth layer gradients
dz = 1./m * (AL - Y)
da, dWL, dbL = linear_backward(dz, caches[L])
gradients = {"dW"+str(L+1): dWL, "db"+str(L+1): dbL}
#calculate from L-1 to 1 layer gradients
for l in reversed(range(0,L)): # L-1,L-3,....,1
#relu_backward->batchnorm_backward->linear backward
A, w, b, gamma, sqrt_var, z_out, z_norm = caches[l]
#relu backward
dout = relu_backward(da,z_out)
#batch normalization
dgamma, dbeta, dz = batchnorm_backward(dout,caches[l])
# print("===============dz" + str(l+1) + "===================")
# print(dz.shape)
#linear backward
da, dW, db = linear_backward(dz,caches[l])
# print("===============dw"+ str(l+1) +"=============")
# print(dW.shape)
#gradient
gradients["dW" + str(l+1)] = dW
gradients["db" + str(l+1)] = db
gradients["dgamma" + str(l+1)] = dgamma
gradients["dbeta" + str(l+1)] = dbeta
return gradients
def update_parameters(parameters, grads, learning_rate):
"""
:param parameters: dictionary, W, b
:param grads: dW,db,dgamma,dbeta
:param learning_rate: alpha
:return:
"""
L = len(parameters) // 4
for l in range(L):
parameters["W" + str(l + 1)] = parameters["W" + str(l + 1)] - learning_rate * grads["dW" + str(l+1)]
parameters["b" + str(l + 1)] = parameters["b" + str(l + 1)] - learning_rate * grads["db" + str(l+1)]
if l < L-1:
parameters["gamma" + str(l + 1)] = parameters["gamma" + str(l + 1)] - learning_rate * grads["dgamma" + str(l + 1)]
parameters["beta" + str(l + 1)] = parameters["beta" + str(l + 1)] - learning_rate * grads["dbeta" + str(l + 1)]
return parameters
def L_layer_model(X, Y, layer_dims, learning_rate, num_iterations):
"""
:param X:
:param Y:
:param layer_dims: list containing the input size and each layer size
:param learning_rate:
:param num_iterations:
:return:
parameters:final parameters:(W,b,gamma,beta)
bn_param: moving_mean, moving_var
"""
costs = []
# initialize parameters
parameters, bn_param = initialize_parameters(layer_dims)
for i in range(0, num_iterations):
#foward propagation
AL,caches,bn_param = forward_propagation(X, parameters,bn_param)
# calculate the cost
cost = compute_cost(AL, Y)
if i % 1000 == 0:
print("Cost after iteration {}: {}".format(i, cost))
costs.append(cost)
#backward propagation
grads = backward_propagation(AL, Y, caches)
#update parameters
parameters = update_parameters(parameters, grads, learning_rate)
print('length of cost')
print(len(costs))
plt.clf()
plt.plot(costs) # o-:圆形
plt.xlabel("iterations(thousand)") # 横坐标名字
plt.ylabel("cost") # 纵坐标名字
plt.show()
return parameters,bn_param
#fp for test
def forward_propagation_for_test(X, parameters, bn_param, epsilon = 1e-12):
"""
X -- input dataset, of shape (input size, number of examples)
parameters -- python dictionary containing your parameters "W1", "b1", "gamma1","beta1",W2", "b2","gamma2","beta2",...,"WL", "bL","gammaL","betaL"
W -- weight matrix of shape (size of current layer, size of previous layer)
b -- bias vector of shape (size of current layer,1)
gamma -- scale vector of shape (size of current layer ,1)
beta -- offset vector of shape (size of current layer ,1)
decay -- the parameter of exponential weight average
moving_mean = decay * moving_mean + (1 - decay) * current_mean
moving_var = decay * moving_var + (1 - decay) * moving_var
the moving_mean and moving_var are used for test
:return:
AL: the output of the last Layer(y_predict)
caches: list, every element is a tuple:(A, W,b,gamma,sqrt_var,z,Z_norm)
"""
L = len(parameters) // 4 # number of layer
A = X
# calculate from 1 to L-1 layer
for l in range(1,L):
W = parameters["W" + str(l)]
b = parameters["b" + str(l)]
gamma = parameters["gamma" + str(l)]
beta = parameters["beta" + str(l)]
z = linear_forward(A, W, b)
#batch normalization
# exponential weight average
moving_mean = bn_param["moving_mean" + str(l)]
moving_var = bn_param["moving_var" + str(l)]
sqrt_var = np.sqrt(moving_var + epsilon)
z_norm = (z - moving_mean) / sqrt_var
z_out = np.multiply(gamma, z_norm) + beta # 对应元素点乘
#relu forward
A = relu_forward(z_out) #relu activation function
# calculate Lth layer(last layer)
WL = parameters["W" + str(L)]
bL = parameters["b" + str(L)]
zL = linear_forward(A, WL, bL)
AL = sigmoid_forward(zL)
return AL
#predict function
def predict(X_test, y_test, parameters, bn_param):
"""
:param X:
:param y:
:param parameters:
:return:
"""
m = y_test.shape[1]
Y_prediction = np.zeros((1, m))
prob = forward_propagation_for_test(X_test, parameters, bn_param)
for i in range(prob.shape[1]):
# Convert probabilities A[0,i] to actual predictions p[0,i]
if prob[0, i] > 0.5:
Y_prediction[0, i] = 1
else:
Y_prediction[0, i] = 0
accuracy = 1- np.mean(np.abs(Y_prediction - y_test))
return accuracy
#DNN model
def DNN(X_train, y_train, X_test, y_test, layer_dims, learning_rate= 0.01, num_iterations=30000):
parameters, bn_param = L_layer_model(X_train, y_train, layer_dims, learning_rate, num_iterations)
accuracy = predict(X_test,y_test,parameters,bn_param)
return accuracy
if __name__ == "__main__":
X_data, y_data = load_breast_cancer(return_X_y=True)
X_train, X_test,y_train,y_test = train_test_split(X_data, y_data, train_size=0.8,test_size=0.2,random_state=28)
X_train = X_train.T
y_train = y_train.reshape(y_train.shape[0], -1).T
X_test = X_test.T
y_test = y_test.reshape(y_test.shape[0], -1).T
accuracy = DNN(X_train,y_train,X_test,y_test,[X_train.shape[0],10,5,1])
print(accuracy)
скопировать код
Используя набор данных breast_cancer в scikit-learn to test, точность теста составляет 96,49%, а схема изменения функции стоимости выглядит следующим образом: