随着网络第一层和第二层的参数在训练时不断变化,第三层所使用的激活函数的输入值可能由于乘法效应而变得极大或极小,比如第一层所使用的激活函数的输入值不在一个数量级上,这会导致模型识别不出重要的输入数据,造成模型训练的不稳定性。
为应对上述情况,批量归一化方法被提出,批量归一化试图对深度学习模型的某一层所使用的激活函数的输入进行归一化,使得批量数据呈类似与正态分布。
给定一个批量 B = x 1 , 2 , 3 , . . , m B=x_{1,2,3,..,m} B=x1,2,3,..,m我们需要学习拉升参数γ和偏移参数β
有定义:
μ B ← 1 m ∑ i = 1 m x i \mu_{B} ←\frac{1}{m} \sum_{i=1}^mx_{i} \quad μB←m1i=1∑mxi σ B 2 ← 1 m ∑ i = 1 m ( x i − μ B ) 2 \sigma_{B}^2 ←\frac{1}{m} \sum_{i=1}^m(x_{i}-\mu_{B})^2 \quad σB2←m1i=1∑m(xi−μB)2 x i ^ ← x i − μ B σ B 2 + ϵ \hat{x_{i}} ←\frac{x_{i}-\mu_{B}}{\sigma_{B}^2+ \epsilon\\} xi^←σB2+ϵxi−μB y i ← γ x i ^ + β {y_{i}} ←\gamma \hat{x_{i}} +\beta yi←γxi^+β ↑ B N γ , β ( x i ) ↑BN_{\gamma,\beta}(x_{i}) ↑BNγ,β(xi)
让神经网络自己学习gamma,beta【看归一化有无扩展作用,若没有,用gamma,beta抵消normalize操作】
我们现在来手动实现一个简化的批量归一化层。实现时对全连接层和二维卷积层这两种情况做了区分。
对于全连接层,我们对每个批量进行归一化。
对于二维卷积,我们对每个通道做了归一化,并保持其可以正确地广播。
import numpy as np
def pure_batch_norm(X,gamma, beta,eps=1e-5):
assert len(X.shape) in (2,4)
# 对于全连接:batch_size * feature
if len(X.shape) == 2:
# 每个输入维度在样本上的均值和方差
mean = X.mean(axis=0)
variance = ((X-mean)**2).mean()(axis=0)
# 2D卷积:batch_size * channel * height * width
else:
# 对每个通道计算均值和方差,需要保持4D形状使得可以正确广播
mean = X.mean(axis=(0,2,3),keepdims=True)
variance = ((X-mean)**2).mean(axis=(0,2,3),keepdims=True) #keepidms=True:保持其二维或者三维的特性,(结果保持其原来维数)
#均一化
X_hat = (X-mean)/np.sqrt(variance + eps) #+eps:防止variance=0
#拉升和偏移
return gamma.reshape(mean.shape) * X_hat + beta.shape(mean.shape)
我们通常既然训练时使用了批量归一化,那么测试时也该使用批量归一化吗?
如果不用,由于数据的量纲问题,训练出的模型参数很可能在测试时不准确。
如果用,考虑极端情况:测试的数据只有一个数据时的预测也会出现问题(相当于没有用)。所以,在测试时我们还是需要对数据使用批量归一化。在训练时,把原先训练时用到的批量均值和方差替换成整个训练数据的均值和方差。但当训练数据很大时,这个计算开销很大。
因此我们用平均移动的方差来近似计算(也就是BatchNorm层的moving_mean和moving_variance)
详细解释卷积神经网络CNN中卷积层以及BN层的参数
def batch_norm(X, gamma,beta, is_training, moving_mean, moving_variance, eps = 1e-5, moving_momentum=0.9):
assert len(X.shape) in (2,4)
# 全连接: batch_size * feature
if len(X.shape) == 2:
# 每个输入维度在样本上的均值和方差
mean = X.mean(axis=0)
variance = ((X-mean)**2).mean(axis=0)
# 2D卷积:batch_size * channel * height * width
else:
# 对每个通道计算均值和方差,需要保持4D形状使得可以正确广播
mean = X.mean(axis=(0,2,3),keepdims=True)
variance = ((X-mean)**2).mean(axis=(0,2,3),keepdims=True)
# 变形使得可以正确地广播
moving_mean = moving_mean.reshape(mean.shape)
moving_variance = moving_variance.reshape(mean.shape)
#均一化
if is_training:
X_hat = (X - mean)/np.sqrt(variance + eps)
# 更新全局的均值和方差
moving_mean[:] = moving_momentum * moving_mean + (1.0- moving_moentum) * mean
moving_variance[:] = moving_momentum * moving_variance +(1.0- moving_moentum) * variance
else:
#测试阶段使用全局的均值和方差
X_hat = (X - moving_mean)/np.sqrt(moving_variance + eps)
#拉升和偏移
return gamma.reshape(mean.shape) * X_hat +beta.reshape(mean.shape)