最后更新于
最后更新于
论文中提出了Batch Normalization这种结构. 从名字中可以看出, Batch Normalization是设计用来消除Internal Covariate Shift问题的, 这种问题出现的原因, 表现和影响在已经详细说明过了.
对于Mini-Batch SGD优化算法, 一次迭代中包含若干个样本. 不同迭代轮次使用的不同batch之间, 样本之间的分布差别可能会很大, 因此无论输入层还是隐层的分布都是不稳定的, 出现ICS问题.
Batch Normalization的主要思想, 是对于每个神经元, 数据在进入激活函数之前, 计算出每个神经元在这个batch中的均值和方差, 然后通过偏移, 缩放, 以及再偏移, 再缩放, 使得这个神经元保持相对稳定的输入分布.
Batch Normalization针对单个神经元进行. 假设一个batch中有个样本, 对于FNN网络, 在一个batch中进行如下步骤:
经过这几部处理后, 得到了最终输入到激活函数中的数据, 具有分布的稳定性.
有一个需要特别注意的细节, 均值和方差使用滑动平均的方式更新. 上面计算得到的均值和方差是当前batch中个样本得到的统计值, 在真实的训练过程中, 需要先将当前batch的统计量与之前累积的统计量求滑动平均:
在推理阶段, 一般输入只有一个实例, 而不是与输入相同大小的batch size. 这时就不能再使用输入数据计算均值和方差了.
这就需要使用训练阶段的统计数据来作用了. 具体来说, 在推理阶段, 直接使用训练阶段所有的样本实例, 计算全局统计量, 来代替一个batch中的统计量. 这样更为准确. 如何获得这个全局统计量呢?
实际使用的统计量是滑动平均得到的, 计算的具体方法见上.
全连接层或卷积操作之后, 激活函数之前.
在CNN中使用Batch Normalization, 秉承了CNN的核心思想, 权值共享, 对于每个通道, 使用同样的参数, 类似于将一个通道作为一个神经元来处理.
单个通道的均值和方差计算公式如下, 都是标量:
使用BN之后:
前后分别对应着水平方向的BN和垂直方向上的BN.
但无论是水平方向还是垂直方向, 使用BN都需要考虑使用方法, 即均值, 方差是如何被计算出来的.
后面两种是如同在CNN中使用BN一样, 从参数共享的角度考虑.
另外还有一个原因, RNN时间片之间是串行的, 对于同一个神经元, 在前后不同时间片进行统计时使用到的数据量也是不同的, 靠后明显有更多的数据, 这样也就产生了偏差.
因此在RNN中如何使用BN就成了一个问题, 实际上不同论文对RNN+BN不同方法的效果验证之间也是有矛盾的.
实践上, 如果RNN需要使用Normalization, 更多的是使用Layer Normalizaiton.
这一部分可以参考:
保持隐藏层中数值的均值, 方差不变, 让数值更稳定, 为后面网络提供坚实的基础, 加快了收敛速度
允许较大的学习率
有轻微的正则化作用, 相当于给隐藏层加入噪声
减弱对初始化的强依赖性
Batch size的限制
每次是在一个batch上计算均值, 方差, 如果batch size太小, 则计算的均值, 方差不足以代表整个数据分布, 引入了太多的噪声, 导致模型的表达能力下降.
但如果使用太大的batch size, 除了占用太大的显存, 有时环境不允许之外, 过大的batch size还会带来过大的梯度, 对模型造成伤害.
增加算力消耗
相比于不使用BN, 在训练过程中, 多出了一步计算Batch统计信息, 一步归一化输出; 在推理阶段也多出了归一化输出.
且在训练阶段, 由于需要统计Batch内的信息然后滑动更新, 这步的耗时还随着batch size的增加进一步增加, 导致收敛所需的总步数减少了, 但每个迭代步的耗时增加了.
放大训练集和测试集的差异
训练集的统计值与测试集存在较大差异时, 在对测试集进行推理时, 会引入过多的噪声, 造成最终效果变差. 在训练接和测试集方差较大的时候, 应谨慎使用BN
对循环网络不友好
参考上面的分析.
这里的, 即为滑动平均系数, tensorflow中默认取0.99. 在得到新的滑动平均值之后, 再使用这个新值对当前的输入进行平移和缩放.
对于训练阶段的每次迭代, 都会得到这次迭代batch中个样本的均值和方差, 使用这里的均值和方差的期望, 即计算所有batch均值的平均值得到全局均值, 计算所有batch方差的无偏估计得到全局方差:
然后每个神经元都有训练好的再偏移参数和再缩放参数, 所以推理阶段的值由下式得到:
上式和式是等价的, 之所以把拆开是因为和对于每个神经元都是可以提前计算好, 存储起来的, 在推理时直接使用, 加速了推理的效率.
假设卷积后, 进入激活函数之前, 此时的feature map的大小为, 其中是batch size, 为通道数量, 和分别为高和宽. 对于每个通道, 我们计算得到一个均值和方差标量, 并设置一个可训练的再偏移参数和再缩放参数标量, 因此卷积层最后得到的均值, 方差等向量的长度为该层通道的数量.
可以看到, 是在上进行的计算.
RNN使用BN结构, 有水平方向和垂直方向两个方向. 水平方向就是在时间维度上展开, 垂直方向是多层RNN的叠加形成Stacked RNN. 直观上在水平/时间方向上引入BN是更自然的一种选择, 但多篇论文对两个方向上使用BN进行了探讨和实验, 在文章中有简单的探讨.
用公式来表示两个方向使用BN, RNN中时间的隐层输出为:
为详细说明, 以上图为例, 输入是的维度, 为batch size, 为隐层的神经元数量, 为时间长度. 上图中的这个batch中共有5个样本, 且这5个样本的长度参差不齐, 隐层的长度也为5. 对于Batch Normalization, 计算均值和方差时自然要在整个batch上进行, 所以计算均值和方差有以下几个方案.
: 这种方案代表着对每个时间片中每个隐层神经元都计算并统计均值, 方差, 并设置再偏移和再缩放的可训练参数. 这种方案带来的参数是最多的
: 即对每个时间片计算一个均值, 方差. 一个时间片上的所有神经元共享统计值
: 神经元角度, 计算单个神经元在所有时间片上的均值和方差
因为RNN网络是按时间维度进行推进的, 相对于可以展开成一个隐藏层共享参数的MLP, 而且最终层数由输入数据的时间片的数量决定, RNN是一个动态的网络. 一个自然的想法就是每个时间片进行独立的统计, 即对应和两种情况. 但这种情况存在一个问题:
每个样本的有效长度是不同的, 当统计到比较靠后的时间片时, 此时batch中的样本就很少了. 例如上图当时, 这个batch size为5的batch就只有一个样本了, 也就无从统计均值和方差, BN也无法推进. 所以每个时间片分别统计这种策略存在一定的问题.
再看这种策略, 即对于每个神经元进行统计. 这个角度与FNN中的类似, 也是考虑到RNN中不同时间片的权值共享的情况. 但权值共享不等于输入相似, 实际中, 不同时间片的输入分布有差别才是正常的现象, 而将这种差别强行堆叠在一个量中, 显然是不太合适的. 就如同FNN, CNN中, 所有layer共享BN参数.