torch.nn ------ 标准化归一化层

torch.nn ------ 标准化归一化层

作者:elfin   参考资料来源:torch.nn.BatchNorm


标准化结构图

  • BN是所有样本关于某个信道的标准化
  • LN是关于某个样本的所有特征标准化
  • IN是关于某个样本在某个信道上标准化
  • GN是某个样本关于信道分组的标准化

Top---Bottom

一、BatchNorm1d

对 2D 或 3D 输入(具有可选附加通道维度的小批量 1D 输入)应用批量归一化。归一化的公式为:

\[y = \frac{x - \mathrm{E}[x]}{\sqrt{\mathrm{Var}[x] + \epsilon}} * \gamma + \beta \]

​ 这里的\(x\)是张量的一个元素,\(\gamma\)\(\beta\)分别是标准化处理后的数据放缩、偏置(平移)参数,这两个参数需要学习,为什么不直接使用\(\gamma=1,\beta=0\)呢?因为这里数据的分布本身是有差异性的,我们通过标准化强行将数据放缩到0均值,使其接近标准正态分布,同一个伪逆过程让模型自学习其均值方差,以获取更好的性能,如果强制给定处理后的均值方差,理论上也是可以的,但是这里的人为信息偏置引入是很强的,但又没有一个好的科学解释,实验表明让模型学习是一个很好的选择!

详情参考链接:https://zhuanlan.zhihu.com/p/441573901

​ BN层虽然原理是一样的,但是有1d、2d、3d的区别,这里我们先看1d的标准化(归一化)处理。

注:我习惯称标准化,但大部分人都称归一化,虽然名称有本质区别,但是在这里我们不做区分

参数:

  • num_features: 输入大小\(C\),处理的数据维度为\(\left(N,C,L\right)\)或为\(\left(N,C\right)\)
  • eps: 给分母添加的数,防止分母为0,默认1e-5
  • momentum: 移动平均算法(EMA)的动量参数,默认0.1,可以设置为None
  • affine: 布尔值,设置是否可学习的仿射参数
  • track_runing_stats: 是否跟踪数据的均值方差信息,默认为True。如果不记录,推理时我们减均值除标准差就不能处理,只能基于数据的批量均值与方差进行运算,但是此时数据往往样本量只有1

标准化层BN是不改变输入数据的shape的!

  • 输入\(\left(N,C,L\right)\)或为\(\left(N,C\right)\)
  • 输出\(\left(N,C,L\right)\)或为\(\left(N,C\right)\)

案例:

In[1]: elfin = nn.BatchNorm1d(2, affine=False)
In[2]: data = torch.tensor([[1,2], [3,4], [5,6], [7,8], [9,10]], dtype=torch.float32)
In[3]: elfin(data)
Out[3]: 
tensor([[-1.4142, -1.4142],
        [-0.7071, -0.7071],
        [ 0.0000,  0.0000],
        [ 0.7071,  0.7071],
        [ 1.4142,  1.4142]])
In[4]: list(elfin.parameters())
Out[4]: []
In[5]: elfin = nn.BatchNorm1d(2)
In[6]: list(elfin.parameters())
Out[6]: 
[Parameter containing:
 tensor([1., 1.], requires_grad=True),
 Parameter containing:
 tensor([0., 0.], requires_grad=True)]
In[7]: data = torch.tensor([[[1,2], [3,4]], [[7,8], [9,10]]], dtype=torch.float32)
In[8]: data
Out[8]: 
tensor([[[ 1.,  2.],
         [ 3.,  4.]],
        [[ 7.,  8.],
         [ 9., 10.]]])
In[9]: elfin = nn.BatchNorm1d(2, affine=False)
In[10]: elfin(data)
Out[10]: 
tensor([[[-1.1508, -0.8220],
         [-1.1508, -0.8220]],
        [[ 0.8220,  1.1508],
         [ 0.8220,  1.1508]]])

对于\(\left(N,C\right)\)我们很容易从上面的案例看出来它是在\(N\)个元素中进行标准化的。对于\(\left(N,C,L\right)\)是不是在\(N \times L\)个元素上进行标准化呢?

In[11]: data[:,0,:]
Out[11]:
tensor([[1., 2.],
        [7., 8.]])
# 明显标准化处理后的结果为:-1.1508,-0.8220, 0.8220, 1.1508
# 注:均值4.5,标准差3.0413812651491097

经过证明猜想是对的!


Top---Bottom

二、BatchNorm2d

​ 对4D数据进行标准化(归一化),输入数据形式为\((N,C,H,W)\),输出也是\((N,C,H,W)\)。计算公式见BatchNorm1d

参数说明:

  • num_features: 输入大小\(C\),处理的数据维度为\(\left(N,C,H,W\right)\)
  • eps: 给分母添加的数,防止分母为0,默认1e-5
  • momentum: 移动平均算法(EMA)的动量参数,默认0.1,可以设置为None
  • affine: 布尔值,设置是否可学习的仿射参数
  • track_runing_stats: 是否跟踪数据的均值方差信息,默认为True。如果不记录,推理时我们减均值除标准差就不能处理,只能基于数据的批量均值与方差进行运算,但是此时数据往往样本量只有1
  • device: 设备
  • dtype: 数据类型

动量参数说明

\[\hat{x}_\text{new} = (1 - \text{momentum}) \times \hat{x} + \text{momentum} \times x_t \]

这里实际上是进行移动平均(加权求期望)。

案例:

>>> m = nn.BatchNorm2d(2)
>>> data = torch.randn(2,2,3,2)
>>> data[:,0,:,:]
tensor([[[ 0.2732,  0.7230],
         [ 0.1640, -0.7623],
         [-1.5510, -2.1041]],
        [[-0.3899,  0.7928],
         [-1.2680,  0.1955],
         [ 1.4165, -1.0685]]])
>>> m(data)[:,0,:,:]
tensor([[[ 0.5589,  0.9988],
         [ 0.4520, -0.4539],
         [-1.2252, -1.7661]],
        [[-0.0897,  1.0670],
         [-0.9484,  0.4829],
         [ 1.6770, -0.7533]]], grad_fn=<SliceBackward>)
>>> list(m.parameters())
[Parameter containing:
 tensor([1., 1.], requires_grad=True),
 Parameter containing:
 tensor([0., 0.], requires_grad=True)]

均值:torch.std_mean(data[:,0,:,:])=(tensor(1.0680), tensor(-0.2982));如果根据计算的均值方差计算BN后的数据会有较大差异,这里的原因主要是方差、标准差的计算方式,统计学上我们是除以\(n-1\)(std_mean是使用torch.sqrt(torch.sum((data[:,0,:,:]+0.2982)**2) / 11)=1.0680),BN是除以\(n\),它是使用(torch.sqrt(torch.sum((data[:,0,:,:]+0.2982)**2) / 12)=1.0225),使用1.0225去计算就能得到m(data)的计算结果!

经过实验和1d的接口类似,我们只在通道维度上选择某一个时刻,取出的数据进行标准化操作!


Top---Bottom

三、BatchNorm3d

和BatchNorm2d类似,参考BatchNorm2d。


Top---Bottom

四、LazyBatchNorm1d~3d

延迟初始化weight、bias、running_mean、running_var参数。不需要指定通道维度,可以自己根据通道维数进行设定num_features参数!


Top---Bottom

五、GroupNorm

分组批量标准化,对每个在通道维度上分组进行标准化,BatchNorm默认是在所有通道上,相当于每个通道都是一组!

参数说明:

  • num_group: 通道维度分组数量
  • num_channels: 通道的维度
  • eps: 给分母添加的数,防止分母为0,默认1e-5
  • affine: 布尔值,设置是否可学习的仿射参数

案例:

>>> m = nn.GroupNorm(2,4)
>>> data = torch.randn(2,4,2,2)
# 验证是否在所有样本上分组
>>> torch.mean(data[:, :2, :, :])
tensor(-0.3137)
>>> torch.sqrt(torch.sum((data[:, :2,:,:]+0.3137)**2) / 16)
tensor(0.8521)
>>> data[0, 0, 0, 0]
tensor(-0.6422)
>>> m(data)[0,0,0,0]
tensor(-0.0309, grad_fn=<SelectBackward>)
>>> (-0.6422+0.3137) / 0.8521
-0.3855181316746861
# 验证是否BN模式,在每个信道上进行标准化(因为参数对数量与信道数量一样)
>>> torch.mean(data[:, 0, :, :])
tensor(-0.2070)
>>> torch.sqrt(torch.sum((data[:, 0,:,:]+0.2070)**2) / 8)
tensor(0.6600)
>>> (data[0,0,0,0]+0.207) / 0.66
tensor(-0.6595)
# 验证是在一个样本某组信道上进行标准化
>>> torch.mean(data[0, :2, :, :])
tensor(-0.6166)
>>> torch.sqrt(torch.sum((data[0, :2,:,:]+0.6166)**2) / 8)
tensor(0.8293)
>>> (data[0,0,0,0]+0.6166) / 0.8293
tensor(-0.0309)

根据案例我们知道GN是在某个样本的一组信道上进行所有特征的标准化!

GN标准化是何凯明团队(2018)提出的BN替代品,它解决了在小批量时的BN不稳定问题,所以在小批量时,LN也能有较好的错误率!


Top---Bottom

六、SyncBatchNorm并行标准化

SyncBatchNorm只会发生在训练时,推理时我们并不会使用SyncBatchNorm!所以我们在些模型的时候可以直接写BN,并行运算时,可以进行转换torch.nn.SyncBatchNorm.convert_sync_batchnorm(),经过这个接口的转换后再交给DDP进行封装!

# With Learnable Parameters
m = nn.SyncBatchNorm(100)
# creating process group (optional)
# ranks is a list of int identifying rank ids.
ranks = list(range(8))
r1, r2 = ranks[:4], ranks[4:]
# Note: every rank calls into new_group for every
# process group created, even if that rank is not
# part of the group.
process_groups = [torch.distributed.new_group(pids) for pids in [r1, r2]]
process_group = process_groups[0 if dist.get_rank() <= 3 else 1]
# Without Learnable Parameters
m = nn.BatchNorm3d(100, affine=False, process_group=process_group)
input = torch.randn(20, 100, 35, 45, 10)
output = m(input)

# network is nn.BatchNorm layer
sync_bn_network = nn.SyncBatchNorm.convert_sync_batchnorm(network, process_group)
# only single gpu per process is currently supported
ddp_sync_bn_network = torch.nn.parallel.DistributedDataParallel(
                        sync_bn_network,
                        device_ids=[args.local_rank],
                        output_device=args.local_rank)

参数说明

  • process_group: 进程分组,默认是将所有进程处理的数据聚合进行BN操作,设置这个参数之后就会变成在组内进行操作BN
  • 其他参数见BatchNorm2d

关于distributed设置进程间通信和torch.multiprocessing创建进程完美会在后面单独讲解!这里只需要浅显的理解即可!


Top---Bottom

七、InstanceNorm?d与LazyInstanceNorm?d

InstanceNorm是在某个样本的某个信道上进行标准化,而LazyInstanceNorm与LazyBN是一样的道理!

八、LayerNorm

NLP最常用的标准化层LayerNorm是将一个样本所有的特征进行标准化
normalized_shape参数指定的是要进行标准化的维度,和BN的参数刚好相反,如数据维度为\((B,C,N)\),normalized_shape=[C,N],则是每一个样本所有特征进行标准化,如果是normalized_shape=N,则只在最后一个维度上进行标准化,这里normalized_shape必须指定最后几个维度不能是中间维度(不包含最后一个维度的情况)!对于图像数据可以参考案例。

官方案例

# NLP Example
batch, sentence_length, embedding_dim = 20, 5, 10
embedding = torch.randn(batch, sentence_length, embedding_dim)
layer_norm = nn.LayerNorm(embedding_dim)
# Activate module
layer_norm(embedding)
# Image Example
N, C, H, W = 20, 5, 10, 10
input = torch.randn(N, C, H, W)
# Normalize over the last three dimensions (i.e. the channel and spatial dimensions)
# as shown in the image below
layer_norm = nn.LayerNorm([C, H, W])
output = layer_norm(input)

Top---Bottom

九、局部标准化LRN

LRN局部响应标准化,有两种LRN实现,一种是在通道上进行某个窗口大小为size的邻域内进行标准化,另一种是在某个通道对应的特征图上大小为size的邻域内进行标准化!

具体参考文章:https://blog.csdn.net/weixin_46221946/article/details/122729460


Top---Bottom

完!

posted @ 2022-04-13 16:00  巴蜀秀才  阅读(1634)  评论(0编辑  收藏  举报