在神经网络搭建时,通常在卷积或者RNN后都会添加一层标准化层以及激活层。今天介绍下常用标准化层--batchNorm,LayerNorm,InstanceNorm,GroupNorm的实现原理和代码。
先理解一些基本概念,归一化和标准化。
归一化(Normalization)
对原始数据进行线性变换把数据映射到0,1之间。常用的图像数据在输入网络前先除以255,将像素值归一化到 0,1,就是归一化的一种方式:min-max normalization
\frac {x-min(x)}{max(x)-min(x)}
标准化(Standardization)
对原始数据进行处理,调整输出数据均值为0,方差为1,服从标准正态分布。常用的网络层中的BN就是标准化的一种方式:z-score
\frac {x-\mu}{\sigma}
不过BN还会增加一个尺度变换和偏移。
在数据处理中增加归一化和标准化的原因是将数据被限定在一定的范围内,从而消除奇异样本数据导致的不良影响,以及在深度学习中加速收敛和缓解梯度弥散问题。
本文主要介绍神经网络中常用的几种标准化方式
Batch Normalization:https://arxiv.org/pdf/1502.03167.pdf
Layer Normalizaiton:https://arxiv.org/pdf/1607.06450v1.pdf
Instance Normalization:https://arxiv.org/pdf/1607.08022.pdf
Group Normalization:https://arxiv.org/pdf/1803.08494.pdf
借鉴GN论文中的示意图
BN(https://arxiv.org/pdf/1502.03167.pdf)是2015年由Google提出,目前BN几乎成为CNN网络的标配。BN的提出主要是要解决内部协变量偏移(internal covariate shift)的问题:网络训练过程中,参数的变化会让下一层的输入数据分布发生变化,随着网络层数变深,分布变化会越来越大,偏移越严重,让模型训练变得难收敛。BN通过标准化操作,强行将网络输出均值和方差拉回到0和1的正态分布,再通过缩放和偏移操作保留原有特征特性以及防止数据集中在0附近,丢失了后续激活层中非线性特性。
BN层的优势:加快了模型训练收敛速度,缓解了深层网络中“梯度弥散”的问题。
https://zhuanlan.zhihu.com/p/38176412 这篇文章对BN层有详细的解释。
我们假设BN层的输入表示为B,C,H,W 。
B:batchsize
C: 特征图通道数
H:特征图高
W:特征图宽
如图一所示,BN是针对batch_size维度进行标准化,在B,H,W上进行归一化,也就是与通道数无关,执行完有C个均值,C个方差。每个样本的通道Cn公用同样均值和方差。
BN实现代码:
import numpy as np
import torch
from torch import nn
def torch_bn_offical(x):
""" 调用官方API
"""
# BN统计B,H,W均值,剩C通道
c = x.shape[1]
# affine=False, 方便结果对比只做减均值除方差,不乘gamma加 beta
bn = nn.BatchNorm2d(num_features=c, eps=0, affine=False, track_running_stats=False)
official_bn = bn(x)
return official_bn
def torch_bn_our(x):
c = x.shape[1]
mu = x.mean(dim=[0, 2, 3]).view(1, c, 1, 1)
std = x.std(dim=[0, 2, 3], unbiased=False).view(1, c, 1, 1)
my_bn = (x - mu) / std
return my_bn
BN的缺点:
通过BN的实现可以看出,均值和方差是在一个batch上计算的,同一个batch的同一通道拥有一样均值和方差。如果bs太小,计算得到的均值方差不能很好代码数据分布。
而且如果对于RNN这种任务,序列长度不固定的情况下,BN就无法计算;而且对于RNN任务,不同文本输入,在batch上做归一化也是不合理的,因为词和词的特征差异是很大的。
如图一所示,LN是针对layer维度进行标准化,在C,H,W上进行归一化,也就是与batch无关,执行完有B个均值,B个方差。每个样本公用同样均值和方差。通常在NLP领域的任务,都会使用LN作为标准化层。
LN代码实现:
def torch_ln_offical(x):
""" 调用官方API
"""
# afflne=False, 只做减均值除方差,不乘gamma加 beta
layer_norm = nn.LayerNorm(normalized_shape=x.shape[1:], eps=0, elementwise_affine=False)
official_ln = layer_norm(x)
return official_ln
def torch_ln_our(x):
b = x.shape[0]
mu = x.mean(dim=[1, 2, 3]).view(b, 1, 1, 1)
std = x.std(dim=[1, 2, 3], unbiased=False).view(b, 1, 1, 1)
my_ln = (x - mu) / std
return my_ln
LN的优点:
和batchsize大小和输入序列长度无关。可应用到NLP任务以及小bs的训练任务。
如图一所示,IN是针对单个样本进行标准化,在H,W上进行归一化,也就是与batch和layer都无关,执行完有B,C个均值,B,C个方差。每个样本实例的通道有自己的均值和方差。
IN代码实现:
def torch_in_offical(x):
""" 调用官方API
"""
c = x.shape[1]
instance_norm = nn.InstanceNorm2d(num_features=c, eps=0, affine=False, track_running_stats=False)
official_in = instance_norm(x)
return official_in
def torch_in(x):
b = x.shape[0]
c = x.shape[1]
mu = x.mean(dim=[2, 3]).view(b, c, 1, 1)
std = x.std(dim=[2, 3], unbiased=False).view(b, c, 1, 1)
my_in = (x - mu) / std
return my_in
LN的优缺点:
IN通常在生成式任务中使用,如果BN的batchsize=1,可以认为和IN结果一致,但是在infer阶段,IN是需要统计输入样本的均值方差的,所以IN的性能会下降。
介于LN和IN之间,其首先将channel分为许多组(group),对每一组做归一化,及先将feature的维度由N, C, H, Wreshape为N, G,C//G , H, W,归一化的维度为C//G , H, W
如图一所示,GN是介于LN和IN之间,将C分为多个group,B,C,H,W转换为B*G,C/G,H,W然后对每个组进行归一化,也就是与batch和layer都无关,执行完有B,G个均值,B,G个方差。每个group有自己的均值和方差。
GN代码实现:
def torch_gn_offical(x, num_groups):
""" 调用官方API
"""
gn = nn.GroupNorm(num_groups=num_groups, num_channels=x.shape[1], eps=0, affine=False)
official_bn = gn(x)
return official_bn
def torch_gn(x, num_groups):
b = x.shape[0]
x1 = x.view(b, num_groups, -1)
mu = x1.mean(dim=-1).reshape(b, num_groups, -1)
std = x1.std(dim=-1).reshape(b, num_groups, -1)
x1_norm = (x1 - mu) / std
my_gn = x1_norm.reshape(x.shape)
return my_gn
GN的优缺点:
GN和BN对比,避开了batchsize对训练的影响,训练开销小;GN的num_group=1就是LN,num_group=C就是IN。
BN是对BHW维度进行标注化,适合CV任务,batchsize设置需要偏大
LN是对CHW维度进行标准化,适合NLP任务
IN是对H*W维度进行标注化,适合CV中生成式任务
GN是对HW(C/group_num)进行标注化,适合CV任务
目前也有一些论文提出标准化是采用以上方式进行结合对方式~
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。