李宏毅深度学习笔记:Batch Normalization

李宏毅深度学习笔记:Batch Normalization

本文是学习李宏毅老师讲解batch normalization公开课的笔记。
Batch Normalization的paper:Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift
最后补充pytorch里面的nn.BatchNorm2d使用方法和注意事项。

Feature Scaling

Feature Scaling指特征缩放,是将不同的feature缩放到相同的scale上。这里scale指的是取值范围。

  1. Feature Scaling的对training的影响
    下图是一个简单的例子:
    图一
    在上图中, x 1 x_1 x1 x 2 x_2 x2是模型输入的不同feature, w 1 w_1 w1 w 2 w_2 w2是weight, b b b是bias, a a a是模型的输出,即 a = w 1 ∗ x 1 + w 2 ∗ x 2 + b a = w_1 * x_1 + w_2 * x_2 + b a=w1x1+w2x2+b 。那么 a a a最终会影响到Loss。模型的输入 x 1 x_1 x1 x 2 x_2 x2的scale差别很大, x 2 x_2 x2的各个元素的取值都远大于 x 1 x_1 x1,容易理解,这会导致 w 1 w_1 w1 w 2 w_2 w2有相同变化时, w 2 w_2 w2 a a a的影响远大于 w 1 w_1 w1,并最终导致 w 2 w_2 w2Loss的影响远大于 w 1 w_1 w1,error surface如下图:
    图二
    上图说明了 w 1 w_1 w1 w 2 w_2 w2Loss的影响,可以看出,Loss w 2 w_2 w2方向的梯度gradient即斜率大于在 w 1 w_1 w1方向上的,说明 w 2 w_2 w2Loss的影响大于 w 1 w_1 w1
    如果现在对 x 1 x_1 x1 x 2 x_2 x2Feature Scaling,如下图:
    图三
    现在 x 1 x_1 x1 x 2 x_2 x2有了相同的scale,所以 w 1 w_1 w1 w 2 w_2 w2 a a a的影响也就是对最终Loss的影响就相同了,error surface如下图:
    图四
    可以看出error surface比较接近正圆形。
    那么椭圆形和正圆形有什么不同呢?在做梯度下降的时候,如果是椭圆形,gradient在不同方向上差别比较大,这会让training变得不容易。为了让gradient能到达尽快到达0,也就是Loss局部最优,需要让gradient 尽量“走直线”,如下图红线所示:
    图五
    为了让gradient尽量“走直线”,需要在横向和纵向上设置不同的learning rate,并且learning rate的比例应该和对应feature的比例相同,如果有很多feature(远多于两个)的话,这是一件非常麻烦的事情。而error surface为正圆形的话,在所有方向上设置一个learning rate就可以让gradient“走直线”,会让training容易得多。
  2. 如何做Feature Scaling
    从上面我们知道了Feature Scaling带来的好处,接下来我们介绍经典的Feature Scaling的做法。先给出一组training data,如下图:
    图六
    现在给定 R R R N N N维的training data,分别为 x 1 x^1 x1, x 2 x^2 x2, x 3 x^3 x3, … , x R x^R xR x i j x_i^j xij 表示第 j j j个training data( x j x^j xj)的第 i i i维元素 ( i = 1 , . . . , N , j = 1 , . . . , R ) (i = 1, ..., N , j = 1, ... ,R) (i=1,...,N,j=1,...,R),如下图:
    图七
    对第 i i i ( i = 1 , . . . , N ) ( i = 1, ... , N ) (i=1,...,N),先求所有training data这一维(即绿色框圈住的部分,绿色框从上向下平移求每一维)的平均值 m i m_i mi 和标准差 σ i \sigma_i σi,即 m i = 1 R ∑ j = 1 R x i j m_i =\frac{1}{R}\sum_{j=1}^R x_i^j mi=R1j=1Rxij σ i = ∑ j = 1 N ( x i j − m i ) 2 R \sigma_i = \sqrt{\frac{\sum_{j=1}^N(x_i^j - m_i)^2}{R}} σi=Rj=1N(xijmi)2 ,这样就求出了 N N N个平均值和标准差。
    求出每一维的平均值和标准差后,对所有training data的所有元素按公式 x i j = x i j − m i σ i , ( i = 1 , . . . , N , j = 1 , . . . , R ) x_i^j = \frac{x_i^j - m_i}{\sigma_i}, (i = 1, ..., N , j = 1, ... ,R) xij=σixijmi,(i=1,...,N,j=1,...,R)进行更新,更新之后所有维度的平均值均为 0 0 0,标准差均为 1 1 1
  3. 总结,做了Feature Scaling之后梯度下降会比不做会收敛地更快。

Internal Covariate Shift

机器学习前提假设:独立同分布假设,即假设训练数据和测试数据独立同分布。
我们将训练和测试数据记为 X X X,对应的label记为 Y Y Y

  1. Internal Covariate Shift现象
    我们知道神经网络有很多层,在训练过程中,除了最底层(input layer)的输入数据为training set,输入数据的分布不会变化之外,其他层(hidden layer 和 output layer,下文我们称为高层)的输入数据都是前一层的输出数据,而随着反向传播使得每一层的参数改变,他们的输出数据分布也会发生变化,导致高层的输入数据分布发生改变,且越高层的输入数据分布变化会越剧烈,这使得高层需要不断去重新适应低层的参数更新。Google将这一现象总结为Internal Covariate Shift,简称ICS。
    这个现象导致:为了训练好模型,我们需要非常谨慎地去设定学习率、初始化权重以及尽可能细致的参数更新策略。(例如学习率设定的小一点,那高层输入数据分布的shift也会小一点。)
    用数学语言来描述就是,高层的每一层输入数据的label对于输入数据分布的条件概率是一致的,但输入数据分布每一轮训练的边缘概率不同,即对所有 x ∈ X x \in X xX,有 P t r a i n ( Y ∣ x = X ) = P t e s t ( Y ∣ x = X ) P_{train}(Y|x=X) = P_{test}(Y|x = X) Ptrain(Yx=X)=Ptest(Yx=X),但是, P t r a i n ( X ) ≠ P t e s t ( Y ) P_{train}(X) \ne P_{test}(Y) Ptrain(X)=Ptest(Y)。其中Y是X的标签。也就是对于高层,输入数据分布每一轮训练都会改变,也就是不再独立同分布了,但是对应label不会改变。
    注意:Internal corvariate shift和covariate shift是两回事,前者是网络内部,后者是针对输入数据,比如我们在训练数据前做归一化等预处理操作。
  2. Internal Covariate Shift的影响
    1. 高层网络需要不断适应新的输入数据分布,降低学习速度。
    2. 底层输入的变化可能趋向于变大或变小,导致上层落入饱和区,使得学习过早停止。
    3. 每层的更新都会影响到其它层,因此每层的参数更新策略需要尽可能谨慎。
  3. 前面我们对输入数据做Feature Scaling,使输入层更加容易train。那我们现在对高层也做Feature Scaling,使得高层的输入数据的平均值和标准差一直为0和1,这样虽然输入数据在每一次反向传播后都会改变,但是最起码它的statistic是不变的,从而在一定程度上减轻Internal Covariate Shift的问题。
  4. 对高层的输入做Feature Scaling的问题是,每次做的时候都需要先求出输入的statistics,但是由于上一层的参数变化,每轮训练的statistics都会改变,没有办法用很简单的方法很快求出他们的statistics。解决的方法是使用Batch Normalization

训练过程的Batch Normalization

大部分work都会先做BN,再通过activation function,这样做得好处是:一般我们采用的激活函数,如hyperbolic tangent(双曲正切,tanh),sigmoid,都会有饱和区域(saturation region),如果activation function的输入落在了饱和区域,在反向传播时很可能会造成梯度消失(gradient vanish)的问题。所以我们会希望输入落在微分比较大也就是0的附近。而在激活层之前做normalization使得平均值为0标准差为1,在一定程度上可以使得输入落在0附近的概率更大。

下面我们以 b a t c h _ s i z e = 3 batch\_size = 3 batch_size=3 为例说明如何做Batch Normalization.

  1. 如下图,我们对输入层的输出,也就是第二层的输入,做Batch Normalizaiton
    图八
    首先计算 z 1 z^1 z1, z 2 z^2 z2, z 3 z^3 z3的平均值 μ \mu μ和标准差 σ \sigma σ μ = 1 3 ∑ i = 1 3 z i \mu = \frac{1}{3}\sum_{i=1}^3z^i μ=31i=13zi, σ = 1 3 ∑ i = 1 3 ( z i − μ ) 2 \sigma=\sqrt{\frac{1}{3}\sum_{i=1}^3(z^i-\mu)^2} σ=31i=13(ziμ)2 .
    这里我们可以先注意一下, μ \mu μ σ \sigma σ都是依赖于 z 1 z^1 z1, z 2 z^2 z2, z 3 z^3 z3的,后文需要用到这个点。
    由于每一个bacth都会有一次反向传播改变 W 1 W^1 W1的值,使得对整个训练集得到的第一层的整个输出做Normalization是不切实际的,所以这里只对一个batch的输出做Normalization。我们是希望做 normalization时计算出的 μ \mu μ σ \sigma σ能尽可能的近似于整个输出的平均值和标准差,所以batch_size不能太小。
  2. z 1 z^1 z1, z 2 z^2 z2, z 3 z^3 z3按公式: z ~ i = z i − μ σ ( i = 1 , 2 , 3 ) \tilde z^i = \frac{z^i-\mu}{\sigma} (i=1, 2, 3) z~i=σziμ(i=1,2,3) 进行更新,得到 z ~ 1 , z ~ 2 , z ~ 3 \tilde z^1, \tilde z^2, \tilde z^3 z~1,z~2,z~3,这样得到的 z ~ 1 , z ~ 2 , z ~ 3 \tilde z^1, \tilde z^2, \tilde z^3 z~1,z~2,z~3的平均值为0,标准差为1,。如下图所示:图九
  3. 做了Normalization之后,反向传播也会通过 μ \mu μ σ \sigma σ这两条路径对 W 1 W^1 W1进行更新,这是因为 W 1 W^1 W1的变化会影响到 z 1 , z 2 , z 3 z^1, z^2, z^3 z1,z2,z3的值从而影响到 μ \mu μ σ \sigma σ的值,也就是这两个值是随着训练而不断变化的,所以反向传播时也会通过他们。
  4. 在某些情况下我们可能不希望 z ~ 1 , z ~ 2 , z ~ 3 \tilde z^1, \tilde z^2, \tilde z^3 z~1,z~2,z~3的平均值和标准差为0和1,而是为其他值,那我们将 z ~ 1 , z ~ 2 , z ~ 3 \tilde z^1, \tilde z^2, \tilde z^3 z~1,z~2,z~3按公式: z ^ i = γ ⊙ z ~ i + β ( i = 1 , 2 , 3 ) \hat z^i = \gamma \odot \tilde z^i+\beta (i=1,2,3) z^i=γz~i+β(i=1,2,3)进行更新,这样得到的 z ^ 1 , z ^ 2 , z ^ 3 \hat z^1, \hat z^2, \hat z^3 z^1,z^2,z^3的平均值和标准差分别为 γ \gamma γ β \beta β。并且我们也可以把 γ \gamma γ β \beta β当做网络的参数,让网络自己去学习 γ \gamma γ β \beta β的取值分别为多少才最适用。如下图:
    图十

测试过程的Batch Normalization

  1. 下图是我们在training过程做Batch Normalization的过程:
    图十一
    但是这个过程在测试阶段会有问题,那就是测试过程没有batch,那么我们怎么去计算用于Batch Normalization μ \mu μ σ \sigma σ呢?
  2. 理想的做法:在训练过程中我们计算每个batch的 μ \mu μ σ \sigma σ是为了代替整个training data的平均值和标准差,所以我们在测试的时候也可以采用整个training data的平均值和标准差。
    但是问题是有可能training data很大,去计算平均值和方差并不方便;也有可能training data是分batch进入的,很可能并没有留下来,那根本没办法计算平均值和方差。
  3. 可实操的做法:将训练过程中所有batch的 μ \mu μ σ \sigma σ都保存下来,然后按权重求和得到用于testing的 μ \mu μ σ \sigma σ,如下图:
    图十二
    由于随着updata,accuracy会不断增大,所以一般我们会给后面的 μ \mu μ σ \sigma σ更大的权重,前面的权重就小一些。

Batch Normalization的好处

  1. 由于BN解决了Internal Covariate Shift的问题,使得网络训练可以设置更大的learning rate,从而可以减少网络训练时间。
  2. 在一定程度上可以防止gradient vanishing(梯度消失)。
  3. 减小参数的初始化对网络学习的影响。例如我们将某层参数的初始化扩大 K K K倍,那对应的这一层的输出也增大 K K K倍,但是这层的输出做了BN之后的输出不会改变,如下图:、
    图十三
  4. BN会减少regularization(正则化)的需求,即在一定程度上对抗overfitting。

PyTorch中的BatchNorm2d

  1. 语法:
    CLASS torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    
    计算公式: y = x − E [ x ] V a r [ x ] + ϵ ∗ γ + β y = \frac{x - \mathrm{E}[x]}{ \sqrt{\mathrm{Var}[x] + \epsilon}} * \gamma + \beta y=Var[x]+ϵ xE[x]γ+β
    其中 ϵ \epsilon ϵ 是参数中的eps, E [ x ] \mathrm{E}[x] E[x] V a r [ x ] \mathrm{Var}[x] Var[x]分别为均值和方差, γ \gamma γ β \beta β是上文中提到过的可学习到的新的均值和标准差。
  2. 参数的意思:
    1. num_features:即输入的channel;
    2. eps:就是计算公式中的 ϵ \epsilon ϵ,为保证数值稳定性(分母不能趋近或取0),给分母加上的值,默认为 1 e − 5 1e-5 1e5
    3. momentum:这个参数很多博客都解释错了,上文我们讲过BN在test时没有batch,无法计算均值和标准差,实际的做法是将训练过程中每轮的均值和标准差保存下来,然后按权相加,得到test时使用的均值和标准差。而momentum表示的就是权值。计算过程如下:
      假设 x ^ \hat x x^是test时的均值或标准差,
      1. 第一轮的的 x ^ \hat x x^就是这一轮训练数据计算出的statistic;
      2. 第二轮到最后一轮的每一轮,假设第 t t t轮的statistic为 x t x_t xt x ^ \hat x x^都按公式 x ^ new = ( 1 − momentum ) × x ^ + momentum × x t , x ^ = x ^ n e w \hat{x}_\text{new} = (1 - \text{momentum}) \times \hat{x} + \text{momentum} \times x_t, \hat x = \hat{x}_{new} x^new=(1momentum)×x^+momentum×xt,x^=x^new 进行更新;
    4. affine:当设为true时,使normalization后的均值和标准差设置为可学习的参数:缩放变量 γ \gamma γ和和平移变量 β \beta β;否则为0和1。缩放加平移就是仿射,affine的意思就是仿射。
  3. 举例说明如何计算 E [ x ] \mathrm{E}[x] E[x] V a r [ x ] \mathrm{Var}[x] Var[x]。假如BN的输入维度是 B ∗ C ∗ H ∗ W = 4 ∗ 3 ∗ 2 ∗ 2 B*C*H*W = 4 * 3 * 2 * 2 BCHW=4322 ,那么最终会求得 C = 3 C = 3 C=3 E [ x ] \mathrm{E}[x] E[x] V a r [ x ] \mathrm{Var}[x] Var[x],下图是一个 E [ x ] \mathrm{E}[x] E[x] V a r [ x ] \mathrm{Var}[x] Var[x]的计算过程:
    图十四

猜你喜欢

转载自blog.csdn.net/qyhaill/article/details/102463870