本文部分过程和图片来源于以下参考资料
参考资料:
知乎专栏 - 张俊林 - 深度学习中的Normalization模型 - https://zhuanlan.zhihu.com/p/43200897
知乎专栏 - Juliuszh - 详解深度学习中的Normalization,BN/LN/WN - https://zhuanlan.zhihu.com/p/33173246
CSDN 博客 - 夏洛的网 - https://blog.csdn.net/liuxiao214/article/details/81037416
Normalization的中文翻译一般叫做“规范化”,是一种对数值的特殊函数变换方法,也就是说假设原始的某个数值是x,套上一个起到规范化作用的函数,对规范化之前的数值x进行转换,形成一个规范化后的数值,即
所谓规范化,是希望转换后的数值 x_bar 满足一定的特性,至于对数值具体如何变换,跟规范化目标有关,
也就是说f()函数的具体形式,不同的规范化目标导致具体方法中函数所采用的形式不同。
总结性描述:
将越来越偏的分布拉回到标准化的分布,使得激活函数的输入值落在激活函数对输入比较敏感的区域,
从而使梯度变大,加快学习收敛速度,避免梯度消失的问题。
下图是神经元工作的示意图:
在介绍深度学习Normalization前,我们先普及下神经元的活动过程。深度学习是由神经网络来体现对输入数据的函数变换的,而神经网络的基础单元就是网络神经元,一个典型的神经元对数据进行处理时包含两个步骤的操作
步骤一:对输入数据进行线性变换,产生净激活值
x是输入,w是权重参数,b是偏置,w和b是需要进过训练学习的网络参数。
步骤二:套上非线性激活函数,神经网络的非线性能力来自于此,
目前深度学习最常用的激活函数是ReLu函数: x' = ReLu(y)
如此一个神经元就完成了对输入数据的非线性函数变换。
这里需要强调下,步骤一的输出一般称为净激活(Net Activation),第二步骤经过激活函数后得到的值为激活值。
至于深度学习中的Normalization,因为神经网络里主要有两类实体:神经元或者连接神经元的边,
所以按照规范化操作涉及对象的不同可以分为两大类:
① 一类是对第L层每个神经元的 净激活值 进行Normalization操作,
比如BatchNorm/ LayerNorm/ InstanceNorm/ GroupNorm等方法都属于这一类;
② 另外一类是对神经网络中连接相邻隐层神经元之间的边上的权重进行规范化操作,比如Weight Norm就属于这一类。
广义上讲,一般机器学习里看到的损失函数里面加入的对参数的的L1/L2等正则项,本质上也属于这第二类规范化操作。
L1正则的规范化目标是造成参数的稀疏化,就是争取达到让大量参数值取得0值的效果,
而L2正则的规范化目标是有效减小原始参数值的大小。
有了这些规范目标,通过具体的规范化手段来改变参数值,以达到避免模型过拟合的目的。
下面先介绍第一类针对神经元的规范化操作方法,这是目前DNN做Normalization最主流的做法。
绝大多数网络目前都是将norm层放在激活函数之前。后续有研究提出的,放在激活函数之后效果更好,这里还是看最原始的情况。
对于神经元的激活值来说,不论哪种Normalization方法,其规范化目标都是一样的,
就是将其净激活值规整为均值为0,方差为1的正态分布。即规范化函数统一都是如下形式:
第一步是将神经元的输出 规整到均值为0,方差为1的正态分布范围内;
其中 μ是均值,δ是标准差,它们都是通过 对特定范围S内的神经元的输出值 进行统计所得,
根据选取范围S的不同,可以分为不同的normalize 方式;
第二步的主要目标是,让每个神经元在训练过程中学习到对应的两个调节因子 γ 和 β,对规范到0均值,1方差的值进行微调。
因为经过第一步操作后,Normalization有可能降低神经网络的非线性表达能力,
添加这两个可学习参数是为了保证模型的表达能力不因为规范化而下降
如果写成一体的形式,则是如下形式:
将输入的图像shape记为[N, C, H, W],根据选定的集合S的不同,即进行归整的神经元的选取方式的不同,有4种常见的Normalization方式如上,分别为 ,Batch Normalization(2015年)、Layer Normalization(2016年)、Instance Normalization(2017年)、Group Normalization(2018年)
在batch方向上,对N H W做归一化
Batch Normalization 于2015年由 Google 提出,开 Normalization 之先河。
其规范化针对单个神经元进行,利用网络训练时一个 mini-batch 的数据来计算该神经元 x_i 的均值和方差,
因而称为 Batch Normalization。
这个图把四维[N, C, H, W]输入数据以三维的方式画出来了,即把空间维度 H,W融合成一个维度,看起来可能稍微有点不好理解,没关系,我们可以通过深色格子的数量来分析。
我们可以看到,N那个方向上,深色格子是取满了的,即对mini-batch的N个实例都考虑到了,而空间维度H,W(竖着的方向)也是全部取满了的,即对输入图像的H * W的所有像素点都考虑到了,唯独通道方向C只取了1个格子,
这就说明通道方向上,是分开的,每次只考虑1个通道。
举个例子,如果如果我们有8张大小为256*256的RGB彩色图片,组合一个batch,得到[8, 3, 256, 256 ] 进行Batch Norm操作, 那么具体来说就是:
统计8张图片的 R 通道上的像素点做归一化,即考虑范围为8*1*256*256个像素点;
统计8张图片的 G 通道上的像素点做归一化,即考虑范围为8*1*256*256个像素点;
统计8张图片的 B 通道上的像素点做归一化,即考虑范围为8*1*256*256个像素点;
这个图片可以看作正在对 8张图片(实例)的 R 通道上的 所有像素点做归一化
这就是所谓的对NHW做归一化,即分开通道而统计图像(Batch方向上)来统计像素点。
BN 比较适用的场景是:每个 mini-batch 比较大,数据分布比较接近。
在进行训练之前,要做好充分的 shuffle. 否则效果会差很多。
局限1:如果Batch Size太小,则BN效果明显下降。
实验表明当BatchSize小于8的时候开始对分类效果有明显负面影响。
局限2:对于有些像素级图片生成任务来说,BN效果不佳;
对于图片分类等任务,只要能够找出关键特征,就能正确分类,这算是一种粗粒度的任务,在这种情形下通常BN是有积极效果的。但是对于有些输入输出都是图片的像素级别图片生成任务,比如图片风格转换等应用场景,使用BN会带来负面效果,
这很可能是因为在Mini-Batch内多张无关的图片之间计算统计量,弱化了单张图片本身特有的一些细节信息。
局限3:RNN等动态网络使用BN效果不佳且使用起来不方便
对于RNN来说,尽管其结构看上去是个静态网络,但在实际运行展开时是个动态网络结构,因为输入的Sequence序列是不定长的,这源自同一个Mini-Batch中的训练实例有长有短。对于类似RNN这种动态网络结构,BN使用起来不方便,因为要应用BN,那么RNN的每个时间步需要维护各自的统计量,而Mini-Batch中的训练实例长短不一,这意味着RNN不同时间步的隐层会看到不同数量的输入数据,而这会给BN的正确使用带来问题。假设Mini-Batch中只有个别特别长的例子,那么对较深时间步深度的RNN网络隐层来说,其统计量不方便统计而且其统计有效性也非常值得怀疑。另外,如果在推理阶段遇到长度特别长的例子,也许根本在训练阶段都无法获得深层网络的统计量。综上,在RNN这种动态网络中使用BN很不方便,而且很多改进版本的BN应用在RNN效果也一般。
局限4:训练时和验证/测试时统计量不一致
对于BN来说,采用Mini-Batch内实例来计算统计量,这在训练时没有问题,但是在模型训练好之后,在线推理(online inference)的时候会有麻烦。因为在线推理或预测的时候,是单实例的,不存在Mini-Batch,所以就无法获得BN计算所需的均值和方差,一般解决方法是采用训练时刻记录的各个Mini-Batch的统计量的数学期望,以此来推算全局的均值和方差,在线推理时采用这样推导出的统计量。虽说实际使用并没大问题,但是确实存在训练和推理时刻统计量计算方法不一致的问题。
上面所列BN的四大罪状,表面看是四个问题,其实深入思考,都指向了幕后同一个黑手,这个隐藏在暗处的黑手是谁呢?就是BN要求计算统计量的时候必须在同一个Mini-Batch内的实例之间进行统计,因此形成了Batch内实例之间的相互依赖和影响的关系。如何从根本上解决这些问题?一个自然的想法是:把对Batch的依赖去掉,转换统计集合范围。在统计均值方差的时候,不依赖Batch内数据,只用当前处理的单个训练数据来获得均值方差的统计量,这样因为不再依赖Batch内其它训练数据,那么就不存在因为Batch约束导致的问题。在BN后的几乎所有改进模型都是在这个指导思想下进行的。
Pytorch 实现(2d情况):https://pytorch.org/docs/stable/nn.html#batchnorm2d
torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
其中的参数 affine=True, 就是设置 对应的两个调节因子 γ 和 β,
track_running_stats=True, 就是在训练时 追踪 并统计各个batch的统计量(pytorch称之为:running_mean和running_var),如上面局限4谈到的
momentum=0.1 也是用于统计 running_mean 和 running_var 的一个指数衰减量
在通道方向上,对CHW归一化
我们可以看到,N那个方向上,深色格子是只取了1个,表示现在只考虑一个输入图像(实例),而空间维度H,W(竖着的方向)也是全部取满了的,即对输入图像的H*W的所有像素点都考虑到了,通道方向C现在是取满了的,这就说明通道方向上,是考虑了所有通道的。
仍然以我们的8张大小为256*256的RGB彩色图片,组合一个batch举例,得到[8, 3, 256, 256 ] 进行Layer Norm操作, 那么具体来说就是:
统计第1张图片的,R、G、B 3个通道上的像素点做归一化,即考虑范围为1*3*256*256个像素点;
统计第 i 张 图片······
统计第8张图片的,R、G、B 3个通道上的像素点做归一化,即考虑范围为1*3*256*256个像素点;
这张图片可以看作是正在对第一张图片的 R、G、B三个通道上的像素点做归一化
这就是所谓的对CHW做归一化,即分开图像(实例)而统计通道(Layer方向上)来统计像素点。
前文有述,BN在RNN中用起来很不方便,而Layer Normalization这种在同隐层内计算统计量,而不依赖batch size大小 的模式就比较符合RNN这种动态网络,目前在RNN中貌似也只有LayerNorm相对有效,但Layer Normalization目前看好像也只适合应用在RNN场景下,在CNN等环境下效果是不如BatchNorm或者GroupNorm等模型的。
Pytorch实现: https://pytorch.org/docs/stable/nn.html#layernorm,
torch.nn.LayerNorm(normalized_shape, eps=1e-05, elementwise_affine=True)
仅对HW做归一化,
我们可以看到,N那个方向上,深色格子是只取了1个,表示现在只考虑一个输入图像(实例),而空间维度H,W(竖着的方向)也是全部取满了的,即对输入图像的H*W的所有像素点都考虑到了,通道方向C现在是只取了1个的,这就说明通道方向上只是考虑了1个通道的。
仍然以我们的8张大小为256*256的RGB彩色图片,组合一个batch举例,得到[8, 3, 256, 256 ] 进行Layer Norm操作, 那么具体来说就是:
统计第1张图片的,R 通道上的像素点做归一化,即考虑范围为1*1*256*256个像素点;
统计第1张图片的,G 通道上的像素点做归一化,即考虑范围为1*1*256*256个像素点;
统计第1张图片的,B 通道上的像素点做归一化,即考虑范围为1*1*256*256个像素点;
后面依次统计第 i 张 图片······
这张图片可以看作是 在对第 i 张图片(实例)的 B 通道上的 所有像素点做归一化
这就是所谓的仅对HW做归一化
也就是既不像BatchNorm那样考虑一个batch内的多个实例,也不像Layer Norm那样考虑layer方向上的多个通道,它现在只限制在单个实例 单个通道上操作!
那么实际上,我们想一下卷积操作就会发现,这其实就是在对单个神经元的输出做归一化,因为单个神经元的卷积结果,就是 1个实例中的1个通道上的内容
图像风格化中,生成结果主要依赖于某个图像实例,所以对整个batch归一化不适合图像风格化中,因而对HW做归一化。可以加速模型收敛,并且保持每个图像实例之间的独立。所以很多GAN网络中其实比较喜欢用InstanceNorm来进行操作。
而且往往这些网络 BatchSize的大小是设置成1的,也就是每次都只有1个实例进行前传。那么这样可能会感觉到,如果把BatchNormalization中的BatchSize改为1,那岂不是就是InstanceNormlization了?
从数学运算上来看,二者应该是一样的,因为BatchSize等于1时,此时考虑的集合S范围内的神经元是一样的。
但是可能在深度学习框架中的具体实现会有差别。
比如在测试的时候,我们通常都是输入的单个样例,也就是BatchSize在测试阶段设置为1。
①如果训练阶段用的是BatchNorm,往往BatchSize都大于1,考虑的是一个batch内的实例的统计特性。所以在测试的时候,往往不计算测试集/验证集的单一输入实例的归一化参数,而是使用训练阶段保存下来的归一化参数:均值μ,标准差δ。
更进一步,我们在训练阶段使用BatchNorm的时候,每一个mini-Batch计算完归一化参数之后,会使用指数加权平均 来进行累加,通常会有一个momentum参数,默认值为0.1,即当前mini-batch的统计值占比0.1,加上历史积累的mini-batch的统计值占比0.9。所以最后训练完成后保存下来的模型中的 均值μ和 标准差δ 是对所有mini-batch的一个加权平均统计值,可以看作是照顾到了所有的训练集数据。
②如果训练阶段用的就是InstanceNorm,本身的BatchSize在训练时就是1,即只考虑当前输入实例的影响。那么在测试时,也就需要即时计算 测试集/验证集 的单一输入实例的归一化参数。并不使用训练阶段保存的归一化参数,而且训练阶段也不需要保存归一化参数,因为每个batch都是独立的。
Pytorch实现(2d情况):https://pytorch.org/docs/stable/nn.html#instancenorm2d
torch.nn.InstanceNorm2d(num_features, eps=1e-05, momentum=0.1, affine=False, rack_running_stats=False)
可以看到,pytorch实现时,affine=False,即不设置 两个调节因子 γ 和 β,
rack_running_stats=False, 也不追踪和统计 各个批次的统计量
也就是说,并不是单纯的把batchnorm2d的batchsize改成1就完全等价
将channel分组,然后再做归一化
是Facebook何凯明研究组2017年提出的。这是一种介于前面的Layer Norm 和Instance Norm 之间的Norm操作,我们可以看到,batch方向上仍然只取1个,H*W方向上取满,通道方向上,C现在既不是取满(Layer Norm的操作),也不是只取1个通道(Instance Norm的操作),而是取了介于二者之间的几个通道。相当于把这几个通道分成了一组!
上面举彩色图片的例子这里稍微修改一下,假设我们在彩色图片后面再加两个通道,表示每个像素点的坐标,即现在每个像素点的值为(r, g, b, x, y),那么这时图像就是5个通道的图像了,所谓group norm, 可以理解为:
对某一张图片,我对其R,G,B 3个通道上的像素点做归一化,共有1*3*256*256个像素点;
然后我再对剩下的 X,Y 2个通道上的像素点做归一化,共1*2*256*256个像素点;
这就是所谓通道分组的概念,我这个例子中,我分组的依据是,R、G、B这3个通道是颜色信息,它们之间关系更大一些; 而后两个通道X,Y是表示位置信息的,它们关系要大一些,所以我这么分组。 这只是我这里举了个例子而已,为了说明通道分组的概念。
这张图片也阐述了通道分组的意思。
Pytorch实现:https://pytorch.org/docs/stable/nn.html#groupnorm
torch.nn.GroupNorm(num_groups, num_channels, eps=1e-05, affine=True)
上面讲的第一类,其实都是对神经元的输出进行规范化,只是选取的神经元的范围S不同,或者说选的经过卷积之后的特征图的数据的区域不同,其实这不同的区域就是对应着前面不同的神经元的输出。
这里讲另一种方式:
Weight Normalization —— 参数规范化, 则另辟蹊径,将规范化应用于线性变换函数的权重 w ,这就是 WN 名称的来源。
我浏览过很多写WN的文章,知乎上这篇文章我觉得是写的比较好的:https://zhuanlan.zhihu.com/p/55102378,里面的推导过程是比较详细的。
这里我就不重复了,而且我也觉得让我重新推导写出来也不会超过这篇文章,所以我这里就简单的说几个结论性内容:
具体而言,WN 提出的方案是,将权重向量 w 分解为 向量方向 v 和向量模 g 两部分。后使用SGD分别优化这两个参数。
WN也是和样本量无关的,所以可以应用在batchsize较小以及RNN等动态网络中;另外BN使用的基于mini-batch的归一化统计量代替全局统计量,相当于在梯度计算中引入了噪声。而WN则没有这个问题,所以在生成模型,强化学习等噪声敏感的环境中WN的效果也要优于BN。
WN没有一如额外参数,这样更节约显存。同时WN的计算效率也要优于要计算归一化统计量的BN。
Weight Normalization的优点:
更快的收敛速度;
更强的学习率鲁棒性;
可以应用在RNN等动态网络中;
对噪声更不敏感,更适用在GAN,RL等场景中