深度学习入门(六):权重的初始值、Batch Normalization、Group Normalization

本文为《深度学习入门 基于Python的理论与实现》的部分读书笔记
代码以及图片均参考此书

权重的初始值

权重初始值可以设为0吗(随机生成初始值的重要性)

在神经网络的学习中,权重的初始值特别重要。实际上,设定什么样的权重初始值,经常关系到神经网络的学习能否成功
权值衰减(weight decay)可以抑制过拟合。如果想减小权重的值,一开始就将初始值设为较小的值才是正途。实际上,在这之前的权重初始值都是像0.01 * np.random.randn(10, 100)这样,使用标准差为0.01 的高斯分布。
然而,如果将权重初始值设为0的话,将无法正确进行学习,或者说,将权重初始值设成一样的值,也无法正确进行学习
这是因为在误差反向传播法中,所有的权重值都会进行相同的更新。比如,在2 层神经网络中,假设第1 层和第2 层的权重为0。这样一来,正向传播时,因为输入层的权重为0,所以第2 层的神经元全部会被传递相同的值。第2 层的神经元中全部输入相同的值,这意味着反向传播时第2 层的权重全部都会进行相同的更新。因此,权重被更新为相同的值,并拥有了对称的值(重复的值)。这使得神经网络拥有许多不同的权重的意义丧失了。为了防止“权重均一化”(严格地讲,是为了瓦解权重的对称结构),必须随机生成初始值。

观察权重初始值对隐藏层激活值分布的影响

下面做一个实验,,向一个5层神经网络(激活函数使用sigmoid 函数)传入随机生成的输入数据,用直方图绘制各层激活值的数据分布

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def relu(x):
    return np.maximum(x, 0)

def tanh(x):
    return np.tanh(x)

# settings
activator = sigmoid # 选择激活函数
std = 1 # 权重初始化的标准差 改变该值来观察权重初始值对隐藏层激活值分布的影响

input_data = np.random.randn(1000, 100)  # 1000个数据
node_num = 100  # 各隐藏层的节点(神经元)数
hidden_layer_size = 5  # 隐藏层有5层
activations = {}  # 激活值的结果保存在这里

x = input_data

for i in range(hidden_layer_size):
    if i != 0:
        x = activations[i - 1]

    w = np.random.randn(node_num, node_num) * std

    y = np.dot(x, w)
    z = activator(y)

    activations[i] = z

# 绘制直方图
fig, axes = plt.subplots(1, len(activations))
for i, z in activations.items():
    axes[i].set_title('layer ' + str(i))
    if i != 0:
        axes[i].set_yticks([], [])
    axes[i].hist(z.flatten(), 30, range=(0, 1))
plt.show()
  • Internal covariate shift: the change in the distribution of network activations due to the change in network parameters during training
  • 使用标准差为1的高斯分布作为权重初始值时的各层激活值的分布:
    在这里插入图片描述
    从上图可知,各层的激活值呈偏向0和1的分布。这里使用的sigmoid函数是S型函数,随着输出不断地靠近0(或者靠近1),它的导数的值逐渐接近0。因此,偏向0和1的数据分布会造成反向传播中梯度的值不断变小,最后消失。这个问题称为梯度消失(gradient vanishing)。层次加深的深度学习中,梯度消失的问题可能会更加严重。

  • 使用标准差为0.01的高斯分布作为权重初始值时的各层激活值的分布:
    在这里插入图片描述
    这次呈集中在0.5附近的分布。因为不像刚才的例子那样偏向0和1,所以不会发生梯度消失的问题。但是,激活值的分布有所偏向,说明在表现力上会有很大问题。为什么这么说呢?因为如果有多个神经元都输出几乎相同的值,那它们就没有存在的意义了。比如,如果100个神经元都输出几乎相同的值,那么也可以由1个神经元来表达基本相同的事情。因此,激活值在分布上有所偏向会出现“表现力受限”的问题。

  • 由上述实验可以看出,各层的激活值的分布都要求有适当的广度。如果传递的是有所偏向的数据,就会出现梯度消失或者“表现力受限”的问题,导致学习可能无法顺利进行。

Xavier 初始值

Xavier 的论文中,为了使各层的激活值呈现出具有相同广度的分布,推导了合适的权重尺度。推导出的结论是:

  • 如果前一层的节点数为n,则初始值使用标准差为1n\frac {1}{\sqrt n}的分布
  • 注:当本层为卷积层时,n=n= 上层通道数 ×\times 本层滤波器 W×HW \times H
    在这里插入图片描述
    使用Xavier 初始值后,前一层的节点数越多,要设定为目标节点的初始值的权重尺度就越小。
  • 使用Xavier初始值作为权重初始值时的各层激活值的分布:
    在这里插入图片描述
    可以看出,越是后面的层,图像变得越歪斜,但是呈现了比之前更有广度的分布。因为各层间传递的数据有适当的广度,所以sigmoid 函数的表现力不受限制,有望进行高效的学习。

如果用tanh函数代替sigmoid函数,这个稍微歪斜的问题就能得到改善。实际上,使用tanh函数后,会呈漂亮的吊钟型分布。tanh函数和sigmoid函数同是S型曲线函数,但tanh函数是关于原点(0, 0)对称的S型曲线,而sigmoid函数是关于(x, y)=(0, 0.5) 对称的S型曲线。众所周知,用作激活函数的函数最好具有关于原点对称的性质。

书上是这么写的,但是实际运行了代码之后激活值分布确实这样的,没有说好的漂亮吊钟型?:
在这里插入图片描述

He初始值

Xavier 初始值是以激活函数是线性函数为前提而推导出来的。因为sigmoid函数和tanh函数左右对称,且中央附近可以视作线性函数,所以适合使用Xavier 初始值。但当激活函数使用ReLU时,一般推荐使用ReLU专用的初始值,即“He初始值”

  • 当前一层的节点数为n时,He初始值使用标准差为2n\sqrt {\frac {2}{n}}的高斯分布

(直观上)可以解释为,因为ReLU的负值区域的值为0,为了使它更有广度,所以需要2倍的系数。

下面将激活函数改为Relu,并改变权重初始值的标准差,比较隐藏层激活值的分布:

  • 权重初始值为标准差是0.01 的高斯分布时
    在这里插入图片描述
    当“std = 0.01”时,各层的激活值非常小。神经网络上传递的是非常小的值,说明逆向传播时权重的梯度也同样很小。这是很严重的问题,实际上学习基本上没有进展。

  • 权重初始值为Xavier初始值时
    在这里插入图片描述
    初始值为Xavier 初始值时,层加深后,激活值的偏向变大,学习时会出现梯度消失的问题

  • 权重初始值为He初始值时
    在这里插入图片描述
    初始值为He初始值时,各层中分布的广度相同。由于即便层加深,数据的广度也能保持不变,因此逆向传播时,也会传递合适的值

  • 在Mnist数据集上观察不同的权重初始值的赋值方法会在多大程度上影响神经网络的学习:
    在这里插入图片描述
    这个实验中,神经网络有5层,每层有100个神经元,激活函数使用的是ReLU。从上图的结果可知,std = 0.01 时完全无法进行学习。这和刚才观察到的激活值的分布一样,是因为正向传播中传递的值很小(集中在0附近的数据)。因此,逆向传播时求到的梯度也很小,权重几乎不进行更新。相反,当权重初始值为Xavier初始值和He初始值时,学习进行得很顺利。并且,我们发现He初始值时的学习进度更快一些。

归一化输入(Normalizing inputs)

本节参考吴恩达的深度学习视频

归一化过程:
μ1mi=1mxi\mu \leftarrow \frac {1}{m}\sum_{i=1}^m x_iσ21mi=1m(xiμ)2\sigma^2 \leftarrow \frac {1}{m}\sum_{i=1}^m {(x_i - \mu)^2}xixiμσ2+ϵx_i \leftarrow \frac {x_i - \mu}{\sqrt {\sigma^2 + \epsilon}}

  • 如果归一化了训练数据,那么就需要保存参数μ\muσ\sigma,之后用保存的训练集上的μ\muσ\sigma来归一化测试集,而非分别在训练集和测试集上计算μ\muσ\sigma。这样可以让测试集和训练集都经过相同的μ\muσ\sigma定义的数据转换

归一化图示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • Why normalize inputs?
    在这里插入图片描述
    当输入都在相近范围内时,损失函数优化起来更简单更快速,从而加速网络学习

Batch Normalization

参考:https://arxiv.org/pdf/1502.03167.pdf

如果设定了合适的权重初始值,则各层的激活值分布会有适当的广度,从而可以顺利地进行学习。那么,为了使各层拥有适当的广度,“强制性”地调整激活值的分布会怎样呢?实际上,Batch Normalization方法就是基于这个想法而产生的。

Batch Norm的优点:

  • 可以使学习快速进行(可以增大学习率)(main purpose)
  • 不那么依赖初始值(对于初始值不用那么神经质)(main purpose)
  • 抑制过拟合(降低Dropout等的必要性)(a slight regularization effect)

直观地理解Batch Norm的优点:

  1. BN层强制性地调整各层的激活值分布使其拥有适当的广度,使激活值落在非饱和区从而缓解梯度消失问题,加速网络收敛,同时也避免了表现力不足的问题。
  2. 通过BN层可使后面的层对前面层的输出不那么敏感(没有BN层的话,当前面层参数改变时,输出值范围变化,导致后面层的参数也不得不调整),抑制了参数微小变化随网络加深而被放大的问题,减小耦合,是每一层都能独立学习,提升学习速率。(reduce the internal covariate shift)
  3. 如上节所述,归一化所有输入特征可以加速网络学习(enables higher learning rates)
  4. μ\muσ\sigma只是由一个mini-batch计算得到的,而非是整个数据集上的μ\muσ\sigma,因此相当于加入了一些噪声(add some noise to each hidden layer’s activations),因此有轻微的正则化效果(regularization effect)。(如果用更大的mini-batch 就会减小噪声,进而减弱正则化效果)

Batch Norm的缺点:

  • 因为是在batch维度归一化,BN层要有较大的batch才能有效工作,而例如物体检测等任务由于占用内存较多,限制了batch的大小,进而也限制了BN层的归一化功能
  • 测试时直接拿训练集的均值与方差对测试集进行归一化,可能会使测试集依赖于训练集

实际上,在不使用Batch Norm的情况下,如果不赋予一个尺度好的初始值,学习将完全无法进行

在这里插入图片描述

BN层的正向传播

  • Batch Norm步骤1:按mini-batch进行归一化
    μB1mi=1mxi\mu_B \leftarrow \frac {1}{m}\sum_{i=1}^m x_iσB21mi=1m(xiμB)2\sigma_B^2 \leftarrow \frac {1}{m}\sum_{i=1}^m {(x_i - \mu_B)^2}xixiμBσB2+ϵx_i \leftarrow \frac {x_i - \mu_B}{\sqrt {\sigma_B^2 + \epsilon}}
    训练时,用指数加权平均来记录μ\muσ\sigma,从而在推理时用指数加权平均后的μ\muσ\sigma对测试数据进行归一化

但是,如果只进行这样的归一化会产生一个问题:这样归一化使BN层的下一层网络的输入值方差为1,但是通过前面权重初始值的实验可以看出,方差为1不一定会使激活值具有广度,因此还需要进行下面的步骤对归一化后的数据进行缩放及平移:

  • Batch Norm步骤2:为了保证后面的层还能接受到前面层学习到的特征,需要对归一化后的数据进行缩放和平移来改变正规化后数据的均值与方差
    y=γx+βy = \gamma x + \beta
    可以看出,当γ=σB2+ϵ,β=μB\gamma = \sqrt{\sigma_B^2 + \epsilon}, \beta = \mu_B时,该式就变成了归一化的逆变换,这样也允许网络通过学习,将BN层变为恒等变换来保持原来网络的能力(This is what makes BN really powerful)
    一开始设置γ=1,β=0\gamma = 1, \beta = 0,然后再通过学习调整到合适的值

注意到BN层前面Affine层的偏置bb实际上可以被忽略(它会在正则化时被减去),它的偏置作用被步骤2中的β\beta所替代

BN层被用在卷积层之后时,为了使同一张特征图上的各个元素都能被以相同的方式归一化,需要进行如下操作:

N, C, H, W = x.shape
x = x.reshape(N, -1)

这样改变输入数据的形状后,统一地对一幅图像上的数据进行归一化

书上是这么实现的,即对所有样本中同一个通道同一个位置的NN个元素为一组进行归一化。但是我看了Pytorch的函数之后发现它对BN的标准实现是所有样本上的相同通道的所有N×H×WN \times H \times W个元素为一组进行归一化,即以通道为单位进行归一化
即应该作如下形状变换:

N, C, H, W = x.shape
x = x.transpose(1, 0, 2, 3).reshape(C, -1)

虽然之后给出的代码还是基于书上的实现模式,但是如果要更改的话也不难

事实上,根据每一组归一化选取元素标准的不同,BN又衍生出了其他的算法:

  • LN(Layer Normalization):以同一个样本上的所有C×H×WC \times H \times W个元素为一组进行归一化,这样计算的均值与方差与batch size无关
  • IN(Instance Normalization):以同一个样本且同一个通道上的所有H×WH \times W个元素为一组进行归一化,这样计算的均值与方差与batch size无关
  • GN(Group Normalization):之后介绍

在这里插入图片描述

  • 通过将BN层插入到激活函数前面或者后面,可以减小数据分布的偏向

原论文中将BN层放在了激活函数之前:We add the BN transform immediately before the nonlinearity such as sigmoid or ReLU.
但是我也看到有说BN层放在激活函数之后效果更好的

BN层的反向传播

基于计算图进行推导

本节参考:
Understanding the backward pass through Batch Normalization Layer

  • 计算图:
    在这里插入图片描述
    标注好正向传播对应的步骤,之后按照相反的顺序进行反向传播:
    在这里插入图片描述
    在这里插入图片描述

Lβ=i=1NLyiLy0\frac {\partial L}{\partial \beta} = \sum_{i=1}^N \frac {\partial L}{\partial y_i},即\frac {\partial L}{\partial y}在第0轴上的累加值Lγx^=Ly\frac {\partial L}{\partial \gamma \hat {x}} = \frac {\partial L}{\partial y}
注:
out=y设out = yLβi=k=1N(Lykiykiβi)=k=1N(Lyki(γxki^+βi)βi)=k=1NLykiLβ=i=1NLyi\begin{aligned} \frac {\partial L}{\partial \beta_i} &= \sum_{k=1}^N {(\frac {\partial L}{\partial y_{ki}} \frac {\partial y_{ki}}{\partial \beta_i})} \\&= \sum_{k=1}^N {(\frac {\partial L}{\partial y_{ki}} \frac {\partial {(\gamma \hat {x_{ki}} + \beta_i)}}{\partial \beta_i})} \\&= \sum_{k=1}^N {\frac {\partial L}{\partial y_{ki}}} \\\frac {\partial L}{\partial \beta} &= \sum_{i=1}^N \frac {\partial L}{\partial y_i} \end{aligned}

在这里插入图片描述
Lγ=i=1NLγxi^xi^=i=1NLyixi^Lyxi^0\frac {\partial L}{\partial \gamma} = \sum_{i=1}^N \frac {\partial L}{\partial \gamma \hat {x_i}} * \hat {x_i} = \sum_{i=1}^N \frac {\partial L}{\partial y_i} * \hat {x_i},即\frac {\partial L}{\partial y} * \hat {x_i} 在第0轴上的累加值Lx^=Lγxi^γ=Lyγ\frac {\partial L}{\partial \hat {x}} = \frac {\partial L}{\partial \gamma \hat {x_i}} * \gamma = \frac {\partial L}{\partial y} * \gamma
在这里插入图片描述
Livar=i=1NLxi^xμ=i=1NLyγxμLyγxμ0\frac {\partial L}{\partial ivar} = \sum_{i=1}^N \frac {\partial L}{\partial \hat {x_i}} * x\mu = \sum_{i=1}^N \frac {\partial L}{\partial y} * \gamma * x\mu,即 \frac {\partial L}{\partial y} * \gamma * x\mu在第0轴上的累加值Lxμ1=Lx^ivar=Lyγivar\frac {\partial L}{\partial x\mu_1} = \frac {\partial L}{\partial \hat {x}} * ivar = \frac {\partial L}{\partial y} * \gamma * ivar

注:

  • xμxμivar1σ2+ϵx\mu 为 x - \mu,ivar为\frac {1}{\sqrt {\sigma^2 + \epsilon}}
  • step7的反向传播过程与上一步一模一样
  • 之前的博客里有说过,根据链式法则,计算图中,正向传播分叉出去的路径,在反向传播会合时需要把梯度相加,因此这里计算的是Lxμ\frac {\partial L}{\partial x\mu}的一部分Lxμ1\frac {\partial L}{\partial x\mu_1}

This step during the forward pass was the final step of the normalization combining the two branches (nominator and denominator) of the computational graph. During the backward pass we will calculate the gradients that will flow separately through these two branches backwards.

在这里插入图片描述
Lsqrtvar=1sqrtvar2Livar\frac {\partial L}{\partial sqrtvar} = \frac {-1}{sqrtvar^2} * \frac {\partial L}{\partial ivar}
注:

  • sqrtvarσ2+ϵsqrtvar为\sqrt {\sigma^2 + \epsilon}
  • 因为反向传播得到的梯度表达式越来越复杂,从这里开始就不再把式子全部展开了

在这里插入图片描述
Lvar=0.51var+ϵLsqrtvar\frac {\partial L}{\partial var} = 0.5 * \frac {1}{\sqrt {var + \epsilon}} * \frac {\partial L}{\partial sqrtvar}
在这里插入图片描述
Lsq=1Nones(N,D)Lvar\frac {\partial L}{\partial sq} = \frac {1} {N} * ones(N, D) * \frac {\partial L}{\partial var}

注:

  • sq(xμ)2sq为(x-\mu)^2
    Lsqi,j=Lvarjvarjsqi,j=Lvarj1Nk=1Nsqk,jsqi,j=1NLvarj\begin{aligned} \frac {\partial L}{\partial sq_{i,j}} &= \frac {\partial L}{\partial var_j} \frac {\partial var_j}{\partial sq_{i,j}} \\&= \frac {\partial L}{\partial var_j} \frac {\partial \frac {1} {N}\sum^N_{k=1} sq_{k,j}}{\partial sq_{i,j}} \\&= \frac {1} {N} \frac {\partial L}{\partial var_j} \end{aligned}

  • ones(N,D)ones(N, D)N×DN \times D 维的全一矩阵

在这里插入图片描述
Lxμ2=2xμLsq\frac {\partial L}{\partial x\mu_2} = 2 * x\mu * \frac {\partial L}{\partial sq}

在这里插入图片描述
Lxμ=Lxμ1+Lxμ2\frac {\partial L}{\partial x\mu} = \frac {\partial L}{\partial x\mu_1} + \frac {\partial L}{\partial x\mu_2}Lx1=Lxμ\frac {\partial L}{\partial x_1} = \frac {\partial L}{\partial x\mu}Lμ=1i=1NLxμi\frac {\partial L}{\partial \mu} = -1 * \sum_{i=1}^N \frac {\partial L}{\partial x\mu_i}

注:

  • 这里进行之前说的路径汇合梯度相加的操作,同时也要注意这里计算的是Lx\frac {\partial L}{\partial x}的一部分Lx1\frac {\partial L}{\partial x_1}

在这里插入图片描述
Lx2=1Nones(N,D)Lμ\frac {\partial L}{\partial x_2} = \frac {1} {N} * ones(N, D) * \frac {\partial L}{\partial \mu}
在这里插入图片描述
Lx=Lx1+Lx2\frac {\partial L}{\partial x} = \frac {\partial L}{\partial x_1} + \frac {\partial L}{\partial x_2}

不借助计算图,直接推导

本节参考:
What does the gradient flowing through batch normalization looks like?

为了推理上方便书写,先引入克罗内克符号:
δi,j=1             if i=j\delta_{i,j} = 1 \ \ \ \ \ \ \ \ \ \ \ \ \ if \ i = j δi,j=0             if ij\delta_{i,j} = 0 \ \ \ \ \ \ \ \ \ \ \ \ \ if \ i \neq j
下面正式进行推导:
Lxi,j=k,lLyk,lyk,lxi,j=k,lLyk,lyk,lx^k,lx^k,lxi,j=k,lLyk,lγlx^k,lxi,j              (1)\begin{aligned} \frac {\partial L}{\partial x_{i,j}} &= \sum_{k,l} \frac {\partial L}{\partial y_{k,l}} \frac {\partial y_{k,l}}{\partial x_{i,j}} \\&= \sum_{k,l} \frac {\partial L}{\partial y_{k,l}} \frac {\partial y_{k,l}}{\partial \hat x_{k,l}} \frac {\partial \hat x_{k,l}}{\partial x_{i,j}} \\&= \sum_{k,l} \frac {\partial L}{\partial y_{k,l}} \gamma_l \frac {\partial \hat x_{k,l}}{\partial x_{i,j}} \ \ \ \ \ \ \ \ \ \ \ \ \ \ (1) \end{aligned} x^k,l=xk,lμlσl2+ϵ\begin{aligned} \because \hat x_{k,l} = \frac{x_{k,l} - \mu_l}{\sqrt {\sigma_l^2 + \epsilon}} \end{aligned} x^k,lxi,j=(xk,lμl)12(σl2+ϵ)32σl2xi,j+(σl2+ϵ)12(δi,kδj,l1Nδj,l)\begin{aligned} \therefore \frac {\partial \hat x_{k,l}}{\partial x_{i,j}} = (x_{k,l} - \mu_l) \cdot -\frac{1}{2} \cdot (\sigma_l^2 + \epsilon)^{-\frac{3}{2}} \frac {\partial \sigma_l^2}{\partial x_{i,j}} + (\sigma_l^2 + \epsilon)^{-\frac{1}{2}}(\delta_{i,k} \delta_{j,l } - \frac{1}{N} \delta_{j,l}) \end{aligned} σl2=1Np=1N(xp,lμl)2\begin{aligned} \because \sigma_l^2 = \frac{1}{N}\sum_{p=1}^N(x_{p,l} - \mu_l)^2 \end{aligned} σl2xi,j=2Np=1N(xp,lμl)(δi,pδj,l1Nδj,l)=2N(xi,lμl)δj,l2N2δj,lp=1N(xp,lμl)=2N(xi,lμl)δj,l\begin{aligned} \therefore \frac {\partial \sigma_l^2}{\partial x_{i,j}} &= \frac {2}{N} \sum_{p=1}^N (x_{p,l} - \mu_l)(\delta_{i,p} \delta_{j,l} - \frac{1}{N}\delta_{j,l}) \\&= \frac{2}{N} (x_{i,l} - \mu_l) \delta_{j,l} - \frac{2}{N^2} \delta_{j,l} \sum_{p=1}^N (x_{p,l} - \mu_l) \\&= \frac{2}{N} (x_{i,l} - \mu_l) \delta_{j,l} \end{aligned} x^k,lxi,j=1N(xk,lμl)(σl2+ϵ)32(xi,lμl)δj,l+(σl2+ϵ)12(δi,kδj,l1Nδj,l)\begin{aligned} \therefore \frac {\partial \hat x_{k,l}}{\partial x_{i,j}} = -\frac{1}{N} (x_{k,l} - \mu_l) (\sigma_l^2 + \epsilon)^{-\frac{3}{2}} (x_{i,l} - \mu_l)\delta_{j,l} + (\sigma_l^2 + \epsilon)^{-\frac{1}{2}}(\delta_{i,k} \delta_{j,l } - \frac{1}{N} \delta_{j,l}) \end{aligned} (1)Lxi,j=k,lLyk,lγl(1N(xk,lμl)(σl2+ϵ)32(xi,lμl)δj,l+(σl2+ϵ)12(δi,kδj,l1Nδj,l))=k,lLyk,lγl(σl2+ϵ)12(δi,kδj,l1Nδj,l)k,lLyk,lγl(1N(xk,lμl)(σl2+ϵ)32(xi,lμl)δj,l)=Lyi,jγj(σj2+ϵ)121Nk=1NLyk,jγj(σj2+ϵ)121Nk=1NLyk,jγj(xi,jμj)(xk.jμj)(σj2+ϵ)32=1Nγj(σj2+ϵ)12(NLyi,jk=1NLyk,j(xi,jμj)(σj2+ϵ)1k=1NLyk,j(xk,jμj))=1Nγj(σj2+ϵ)12(NLyi,jk=1NLyk,j(xi,jμj)(σj2+ϵ)12k=1NLyk,j(xk,jμj)(σj2+ϵ)12)=1Nγj(σj2+ϵ)12(NLyi,jk=1NLyk,jx^i,jk=1NLyk,jx^k,j)\begin{aligned} \therefore 代入(1)式,得\\ \frac {\partial L}{\partial x_{i,j}} &= \sum_{k,l} \frac {\partial L}{\partial y_{k,l}} \gamma_l (-\frac{1}{N} (x_{k,l} - \mu_l) (\sigma_l^2 + \epsilon)^{-\frac{3}{2}} (x_{i,l} - \mu_l)\delta_{j,l} + (\sigma_l^2 + \epsilon)^{-\frac{1}{2}}(\delta_{i,k} \delta_{j,l } - \frac{1}{N} \delta_{j,l})) \\&= \sum_{k,l} \frac {\partial L}{\partial y_{k,l}} \gamma_l (\sigma_l^2 + \epsilon)^{-\frac{1}{2}}(\delta_{i,k} \delta_{j,l } - \frac{1}{N} \delta_{j,l}) - \sum_{k,l} \frac {\partial L}{\partial y_{k,l}} \gamma_l(-\frac{1}{N} (x_{k,l} - \mu_l) (\sigma_l^2 + \epsilon)^{-\frac{3}{2}} (x_{i,l} - \mu_l)\delta_{j,l}) \\&= \frac {\partial L}{\partial y_{i,j} } \gamma_j(\sigma_j^2 + \epsilon)^{-\frac{1}{2}} - \frac {1}{N} \sum_{k=1}^N \frac{\partial L}{\partial y_{k,j}} \gamma_j (\sigma_j^2 + \epsilon)^{-\frac{1}{2}} - \frac {1}{N} \sum_{k=1}^N \frac{\partial L}{\partial y_{k,j}} \gamma_j (x_{i,j} - \mu_j)(x_{k.j} - \mu_j) (\sigma_j^2 + \epsilon)^{-\frac{3}{2}} \\&= \frac{1}{N} \gamma_j (\sigma_j^2 + \epsilon)^{-\frac{1}{2}}(N \frac{\partial L}{\partial y_{i,j}} - \sum_{k=1}^N \frac{\partial L}{\partial y_{k,j}} - (x_{i,j} - \mu_j)(\sigma_j^2 + \epsilon)^{-1} \sum_{k=1}^N \frac{\partial L}{\partial y_{k,j}}(x_{k,j} - \mu_j)) \\&= \frac{1}{N} \gamma_j (\sigma_j^2 + \epsilon)^{-\frac{1}{2}}(N \frac{\partial L}{\partial y_{i,j}} - \sum_{k=1}^N \frac{\partial L}{\partial y_{k,j}} - (x_{i,j} - \mu_j)(\sigma_j^2 + \epsilon)^{-\frac{1}{2}} \sum_{k=1}^N \frac{\partial L}{\partial y_{k,j}}(x_{k,j} - \mu_j)(\sigma_j^2 + \epsilon)^{-\frac{1}{2}}) \\&= \frac{1}{N} \gamma_j (\sigma_j^2 + \epsilon)^{-\frac{1}{2}}(N \frac{\partial L}{\partial y_{i,j}} - \sum_{k=1}^N \frac{\partial L}{\partial y_{k,j}} - \hat x_{i,j} \sum_{k=1}^N \frac{\partial L}{\partial y_{k,j}}\hat x_{k,j}) \end{aligned}
类似地,很容易得到Lγi\frac {\partial L}{\partial \gamma_i}Lβi\frac {\partial L}{\partial \beta_i}
Lγi=k,lLyk,lyk,lγi=k,lLyk,l(x^k,lγl+βl)γi=k,lLyk,lx^k,lδi,l=k=1NLyk,ix^k,i\begin{aligned} \frac {\partial L}{\partial \gamma_i} &= \sum_{k,l} \frac {\partial L}{\partial y_{k,l}} \frac {\partial y_{k,l}}{\partial \gamma_i} \\&= \sum_{k,l} \frac {\partial L}{\partial y_{k,l}} \frac {\partial (\hat x_{k,l} * \gamma_l + \beta_l)}{\partial \gamma_i} \\&=\sum_{k,l} \frac {\partial L}{\partial y_{k,l}} \hat x_{k,l}\delta_{i,l} \\&= \sum_{k=1}^N \frac {\partial L}{\partial y_{k,i}} \hat x_{k,i} \end{aligned} Lβi=k,lLyk,lyk,lβi=k,lLyk,l(x^k,lγl+βl)βi=k,lLyk,lδi,l=k=1NLyk,i\begin{aligned} \frac {\partial L}{\partial \beta_i} &= \sum_{k,l} \frac {\partial L}{\partial y_{k,l}} \frac {\partial y_{k,l}}{\partial \beta_i} \\&= \sum_{k,l} \frac {\partial L}{\partial y_{k,l}} \frac {\partial (\hat x_{k,l} * \gamma_l + \beta_l)}{\partial \beta_i} \\&=\sum_{k,l} \frac {\partial L}{\partial y_{k,l}}\delta_{i,l} \\&= \sum_{k=1}^N \frac {\partial L}{\partial y_{k,i}} \end{aligned}

代码实现

class BatchNormalization:
    def __init__(self, gamma, beta, momentum=0.9, running_mean=None, running_var=None):
        self.gamma = gamma
        self.beta = beta
        self.momentum = momentum
        self.input_shape = None # Conv层的情况下为4维,全连接层的情况下为2维  

        # 测试时使用的平均值和方差
        self.running_mean = running_mean
        self.running_var = running_var  
        
        # backward时使用的中间数据
        self.batch_size = None
        self.xc = None # x - mu
        self.std = None # 标准差
        self.dgamma = None 
        self.dbeta = None

    def forward(self, x, train_flg=True):
        self.input_shape = x.shape
        if x.ndim != 2:
            x = x.reshape(x.shape[0], -1)

        out = self.__forward(x, train_flg)

        return out.reshape(*self.input_shape)

    def __forward(self, x, train_flg):
        if self.running_mean is None:
            n, d = x.shape
            self.running_mean = np.zeros(d)
            self.running_var = np.zeros(d)

        if train_flg == True:
            mu = x.mean(axis=0) # 将数据正规化
            xc = x - mu
            var = np.mean(xc**2, axis=0)
            std = np.sqrt(var + 10e-7)
            xn = xc / std
            
            self.batch_size = x.shape[0]
            self.xc = xc
            self.xn = xn
            self.std = std
            self.running_mean = self.momentum * self.running_mean + (1-self.momentum) * mu
            self.running_var = self.momentum * self.running_var + (1-self.momentum) * var 
        else:
            xc = x - self.running_mean
            xn = xc / np.sqrt(self.running_var + 10e-7)

        return self.gamma * xn + self.beta

    def backward(self, dout):
        if dout.ndim != 2:
            dout = dout.reshape(dout.shape[0], -1)

        dx = self.__backward(dout)

        dx = dx.reshape(*self.input_shape)
        return dx

    def __backward(self, dout):
        dbeta = dout.sum(axis=0)
        dgamma = np.sum(self.xn * dout, axis=0)

        self.dgamma = dgamma
        self.dbeta = dbeta

        # 利用计算图得到的结论进行反向传播
        # dxn = self.gamma * dout
        # dxc = dxn / self.std
        # dstd = -np.sum((dxn * self.xc) / (self.std**2), axis=0)
        # dvar = 0.5 * dstd / self.std
        # dxc += (2.0 / self.batch_size) * self.xc * dvar
        # dmu = np.sum(dxc, axis=0)
        # dx = dxc - dmu / self.batch_size

        # 利用直接推得的梯度公式进行反向传播 
        # 这样的实现更快速,而且更准确
        dx = self.batch_size**(-1) * self.gamma * self.std**(-1) * (self.batch_size * dout - dbeta - self.xn * dgamma)

        return dx

Group Normalization

参考:https://blog.csdn.net/u014380165/article/details/79810040

前面提到,Batch Normalization因为是在batch维度归一化,BN层要有较大的batch才能有效工作,而例如物体检测等任务由于占用内存较多,限制了batch的大小,进而也限制了BN层的归一化功能

GN(Group Normalization)从通道方向计算均值与方差,使用更为灵活有效,避开了batch大小对归一化的影响

当BN层被用在卷积层之后时,根据每一组归一化选取元素标准的不同,BN可以衍生出其他的算法:

  • LN(Layer Normalization):以同一个样本(feature map)上的所有C×H×WC \times H \times W个元素为一组进行归一化,这样计算的均值与方差与batch size无关
    Si={kkN=iN}S_i = \{k | k_N = i_N\}
  • IN(Instance Normalization):以同一个样本(feature map)且同一个通道上的所有H×WH \times W个元素为一组进行归一化,这样计算的均值与方差与batch size无关
    Si={kkN=iN,kC=iC}S_i = \{k | k_N = i_N, k_C = i_C\}
  • GN(Group Normalization):Si={kkN=iN,kCC/G=iCC/G}S_i = \{k | k_N = i_N, \lfloor \frac {k_C}{C/G} \rfloor = \lfloor \frac {i_C}{C/G} \rfloor \}
    kN=iNk_N = i_N使计算都是在一个样本(feature map)上进行的。GG表示将CC分成GGgroupsgroups,每个groupgroup中有C//GC // Gchannelchannel。因此GG是一个超参数。可以看出G=1G=1时的GN变为LN,而G=CG=C时GN变为IN

因此GN的思想就是在相同feature map的相同groupgroup中进行归一化操作,而groupgroup只是在channelchannel维度上进行划分,因此归一化操作就和batch size无关。

在这里插入图片描述
GN之所以可以工作,是因为特征图中,不同通道代表形状、边缘和纹理等不同意义,这些不同的通道并不是完全独立地分布,而是可以放到一起进行归一化分析

N, C, H, W = x.shape
x = x.reshape(N, G, C // G, H, W).reshape(N * G, -1)

之后的处理与BN相同

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章