BN作用
首先,在进行训练之前,一般要对数据做归一化,使其分布一致,但是在深度神经网络训练过程中,通常以送入网络的每一个batch训练,这样每个batch具有不同的分布;此外,为了解决batch normalizaiton论文中提出的internal covarivate shift问题,因为在训练过程中,数据分布会发生变化,对下一层网络的学习带来困难。
Internal Covariate Shift
深度神经网络具有如下特性:
网络中层与层之间有着高度关联性和耦合性。前一层的输出是后一层的输入。随着训练的进行,网络中的参数也随着梯度下降在不停更新。
- 一方面,当底层网络中参数发生微弱变化时,由于每一层中的线性变换与非线性激活映射,这些微弱变化随着网络层数的加深而被放大;
- 另一方面,参数的变化导致每一层的输入分布会发生改变,进而上层的网络需要不停地去适应这些分布变化,使得我们的模型训练变得困难。
随着梯度下降,每一层的参数都在更新,这就导致逐层的分布会不断发生变化,那么每层的参数就要不断适应这种输入分布的变化,这就导致模型收敛的速度非常低。
同时当激活函数选择了sigmoid,tanh等激活函数时,很容易使模型陷入梯度饱和区,这就会导致梯度消失。一种思路是替换激活函数,如ReLU函数,另一种思路是,如果我们可以将输入控制在一个稳定的范围,如:均值为0,方差为1的范围。这样就能避免它们陷入了梯度饱和区。
白化是ML常用的规范化数据的方法,常见的是PCA和ZCA。但是如果每层都做这种操作的话,首先计算成本很高,其次它会改变网络每一次的分布,把网络本身的特性抹去了。
所以batch normalization就是强行将数据拉回到均值为0,方差为1的正太分布上,这样不仅数据分布一致,而且避免发生梯度消失。但是network不同层学习到的数据特征,归一化到正太分布后所有层学习到的特征会趋于相似,相当于没有学习。于是引入了两个可以学习的参数γ、β,加入了这两个参数之后,网络可以更加容易的学习到更多的东西。比如极端情况,当缩放变量γ和平移变量β分别等于batch数据的方差和均值时,最后得到的yi就和原来的xi一模一样了,相当于batch normalization没有起作用了。这样就保证了每一次数据经过归一化后还保留的有学习来的特征,同时又能完成归一化这个操作,加速训练。
1. 使得模型训练收敛速度更快
- 没有BN之前,需要小心的调整学习率和权重初始化,但是有了BN可以放心的使用大学习率,但是使用了BN,就不用小心的调参了,较大的学习率极大的提高了学习速度
2. 使得模型隐藏层输出的特征分布更稳定,更利于模型的学习
- BN层使网络中每层输入数据分布比较稳定,从而可以加速模型的学习速率(后一层网络不必去不断适应前面网络输出的分布变化,可以做到独立训练)
- BN层使模型对网络参数不那么敏感,简化调参过程,使得网络更为稳定。当学习率设置比较高的时候,参数变化的步伐会比较大,容易出现震荡和不收敛,但是使用BN将不会受到参数值大小的影响。
3. 缓解梯度消失的问题
- 通过BN操作可以让激活函数的输入数据落在梯度非饱和区,缓解梯度消失的问题
4. 起到一定正则化作用
- 通过自适应学习 γ γ γ与 β β β数据不仅可以保留更多的原始信息,同时因为每一个batch的 γ γ γ与 β β β都会不同,这就为学习过程增加了随机噪音,一定程度上起到了正则化作用,可以代替其他正则方式如dropout等。
5. 起到去相关的作用
- BN降低了数据之间的绝对差异,有一个去相关的性质,更多的考虑相对差异性,因此在分类任务上具有更好的效果(个人见解)
前向传播
反向传播
∂
ℓ
∂
x
i
=
∂
ℓ
∂
x
^
i
⋅
1
σ
B
2
+
ϵ
+
∂
ℓ
∂
σ
B
2
⋅
2
(
x
i
−
μ
B
)
m
+
∂
ℓ
∂
μ
B
⋅
1
m
\frac{\partial \ell}{\partial x_{i}}=\frac{\partial \ell}{\partial \widehat{x}_{i}} \cdot \frac{1}{\sqrt{\sigma_{\mathcal{B}}^{2}+\epsilon}}+\frac{\partial \ell}{\partial \sigma_{\mathcal{B}}^{2}} \cdot \frac{2\left(x_{i}-\mu_{\mathcal{B}}\right)}{m}+\frac{\partial \ell}{\partial \mu_{\mathcal{B}}} \cdot \frac{1}{m}
∂xi∂ℓ=∂x
i∂ℓ⋅σB2+ϵ1+∂σB2∂ℓ⋅m2(xi−μB)+∂μB∂ℓ⋅m1
∂
ℓ
∂
γ
=
∑
i
=
1
m
∂
ℓ
∂
y
i
⋅
x
^
i
\frac{\partial \ell}{\partial \gamma}=\sum_{i=1}^{m} \frac{\partial \ell}{\partial y_{i}} \cdot \widehat{x}_{i}
∂γ∂ℓ=i=1∑m∂yi∂ℓ⋅x
i
∂
ℓ
∂
β
=
∑
i
=
1
m
∂
ℓ
∂
y
i
\frac{\partial \ell}{\partial \beta}=\sum_{i=1}^{m} \frac{\partial \ell}{\partial y_{i}}
∂β∂ℓ=i=1∑m∂yi∂ℓ
Python实现代码如下:
import numpy as np
def batchnorm_forward(x, gamma, beta, bn_param):
"""Forward pass for batch normalization: y = gamma * x_hat + beta.
Parameters
----------
x: Data of shape (N, D)
gamma: Scale parameter of shape (D,)
beta: Shift paremeter of shape (D,)
bn_param: Dictionary with the following keys:
mode: 'train' or 'test', required
eps: Constant for numeric stability
momentum: Constant for running mean / variance.
running_mean: Array of shape (D,) giving running mean of features
running_var: Array of shape (D,) giving running variance of features
Returns
-------
y: bn result, an array of shape (N, D)
cache: A tuple of values needed in the backward pass
"""
mode = bn_param['mode']
eps = bn_param.get('eps', 1e-5)
momentum = bn_param.get('momentum', 0.9)
x = np.array(x)
N, D = x.shape
running_mean = bn_param.get('running_mean', np.zeros(D, dtype=x.dtype))
running_var = bn_param.get('running_var', np.zeros(D, dtype=x.dtype))
y, cache = None, None
if mode == 'train':
sample_mean = np.mean(x, axis=0)
sample_var = np.var(x, axis=0)
x_norm = (x - sample_mean) / np.sqrt(sample_var + eps)
running_mean = momentum * running_mean + (1 - momentum) * sample_mean
running_var = momentum * running_var + (1 - momentum) * sample_var
y = gamma * x_norm + beta
cache = (x_norm, x, sample_mean, sample_var, eps, gamma, beta)
elif mode == 'test':
scale = gamma / np.sqrt(running_var + eps)
y = scale * x + (beta - running_mean * scale)
else:
raise ValueError('Invalid forward batchnorm mode "%s"' % mode)
# Store the updated running mean and var back into bn_param
bn_param['running_mean'] = running_mean
bn_param['running_var'] = running_var
return y, cache
def batchnorm_backward(dy, cache):
"""Backward pass for batch normalization.
Parameters
----------
dy: Upstream derivatives, of shape (N, D)
cache: Variable of intermediates from batchnorm_forward
Returns
-------
dx: An array of gradient with respect to inputs x, of shape (N, D)
dx = dx_norm * dx_norm_dx + dvar * dvar_dx + dmean * dmean_dx
dgamma: An array of gradient with respect to scale parameter gamma, of shape (D,)
dgamma = dy * x_norm
dbeta: An array of gradient with respect to shift parameter beta, of shape (D,)
dbeta = dy
"""
dx, dgamma, dbeta = None, None, None
x_norm, x, sample_mean, sample_var, eps, gamma, beta = cache
N = x.shape[0]
dx_norm = gamma * dy
dx_norm_dx = 1 / np.sqrt(sample_var + eps)
dvar = np.sum(dx_norm * (x - sample_mean) * -0.5 * (sample_var + eps) ** -1.5, axis=0)
dvar_dx = 2 * (x - sample_mean) / N
dx_norm_dmean = -1 / np.sqrt(sample_var + eps)
dvar_dmean = -2 * np.sum(x - sample_mean, axis=0) / N
dmean = np.sum(dx_norm * dx_norm_dmean + dvar * dvar_dmean , axis=0)
dmean_dx = np.ones_like(x) / N
dx = dx_norm * dx_norm_dx + dvar * dvar_dx + dmean * dmean_dx
dgamma = np.sum(dy * x_norm, axis=0)
dbeta = np.sum(dy, axis=0)
return dx, dgamma, dbeta
def main():
x = ([1.0, 0.2, 1.2, -0.2, 3.1, 2.6, 2.1, 0.9, 5.0, 0.02],
[-1.1, 2.4, 3.1, 5.1, 9.5, -3.2, 0.45, 3.12, 2.9, 1.4],
[5.5, 2.5, 3.1, 0.9, 2.1, 1.1, 0.5, -5.1, 2.9, 1.11])
gamma = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
beta = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
bn_param = {'mode' : 'train'}
y, cache = batchnorm_forward(x, gamma, beta, bn_param)
dy = y
dx, dgamma, dbeta = batchnorm_backward(dy, cache)
x -= dx
gamma -= dgamma
beta -= dbeta
print('x:', x)
print('gamma:', gamma)
print('beta:', beta)
if __name__ == "__main__":
main()
参考
Batch Normalization学习笔记及其实现
batch normalization原理以及反向传播公式
BatchNormalization、LayerNormalization、InstanceNorm、GroupNorm、SwitchableNorm总结