为了保证深度神经网络训练过程的稳定性,经常需要细心的选择初始化方式,并且选择较小的学习率数值,这无疑增加了任务的复杂性。为此,Google团队提出了Batch Normalization【1】方法(简称BN)用于帮助网络更好的训练。
1、理论分析
BN计算的第一步是对每一层进行独立的归一化:
其中k表示第k维特征,E表示求期望,Var表示求方差。这种归一化操作可能会改变这层的表示,所以作者提出了“identity transform”如下:
其中r(k)和B(k)是两个需要通过网络训练学习的参数,这两个参数随着迭代进行动态的更新,当下述情况时:
此时输出和输入就是完全相同的,都是x(k)。注释:identity transform,个人理解其目的是使得输出和输入相同,即不改变层的表示
综上,实现BN需要求的:均值、方差、参数beta、参数gamma。对应的算法流程如下,需要注意的是,Normalization的计算是对“每个特征”分别进行的:
2、python实现
2.1 数据和各个变量的含义
在使用类似pytorch的框架时发现,由于每个mini-batch中的数据是不同的,所以需要统计整个数据集中的均值和方差需要动态的追踪各个mini-batch(进行实现的时候参考了pytorch的BN文档【7】,其源码很多部分已经迁移到了C++所以分析起来比较困难),动态统计方式如下:
这里的“x”不是论文中的“x”,这里表示running_mean和runnning_var的更新权重。首先定义将要使用的数据:
data = np.array([[1, 2],
[1, 3],
[1, 4]]).astype(np.float32)
然后采用pytorch提供的BatchNorm1d模块进行测试,确保自己代码获得的结果能够和pytorch一致:
bn_torch = nn.BatchNorm1d(num_features=2)
data_torch = torch.from_numpy(data)
bn_output_torch = bn_torch(data_torch)
print(bn_output_torch)
得到的输出如下:
tensor([[ 0.0000, -1.1526],
[ 0.0000, 0.0000],
[ 0.0000, 1.1526]], grad_fn=)
2.2 前向传播实现
因为BN计算过程中需要保存running_mean和running_var,以及更新动量momentum和防止数值计算错误的eps,所以需要设计为类,并用实例属性来保存这些值,下面是初始化方法:
class MyBN:
def __init__(self, momentum, eps, num_features):
"""初始化参数值:param momentum: 追踪样本整体均值和方差的动量:param eps: 防止数值计算错误:param num_features: 特征数量"""
# 对每个batch的mean和var进行追踪统计
self._running_mean = 0
self._running_var = 1
# 更新self._running_xxx时的动量
self._momentum = momentum
# 防止分母计算为0
self._eps = eps
# 对应论文中需要更新的beta和gamma,采用pytorch文档中的初始化值
self._beta = np.zeros(shape=(num_features, ))
self._gamma = np.ones(shape=(num_features, ))
初始化中_beta和_gamma对应于BN中需要学习的参数,分别初始化为0和1,接下来就是前向传播的实现:
def batch_norm(self, x):
"""BN向传播:param x: 数据:return: BN输出"""
x_mean = x.mean(axis=0)
x_var = x.var(axis=0)
# 对应running_mean的更新公式
self._running_mean = (1-self._momentum)*x_mean + self._momentum*self._running_mean
self._running_var = (1-self._momentum)*x_var + self._momentum*self._running_var
# 对应论文中计算BN的公式
x_hat = (x-x_mean)/np.sqrt(x_var+self._eps)
y = self._gamma*x_hat + self._beta
return y
由于pytorch中的BatchNorm中beta和gamma初始化并不是0和1,为了保证初始化值一样,将自己定义的类的beta和gamm替换为torch初始化的值,进行如下测试:
my_bn = MyBN(momentum=0.01, eps=0.001, num_features=2)
my_bn._beta = bn_torch.bias.detach().numpy()
my_bn._gamma = bn_torch.weight.detach().numpy()
bn_output = my_bn.batch_norm(data, )
print(bn_output)
得到的结果和torch的结果一致:
[[ 0. -1.1517622]
[ 0. 0. ]
[ 0. 1.1517622]]
参考: