torch.nn ------ 标准化归一化层
torch.nn ------ 标准化归一化层
作者:elfin 参考资料来源:torch.nn.BatchNorm
- BN是所有样本关于某个信道的标准化
- LN是关于某个样本的所有特征标准化
- IN是关于某个样本在某个信道上标准化
- GN是某个样本关于信道分组的标准化
一、BatchNorm1d
对 2D 或 3D 输入(具有可选附加通道维度的小批量 1D 输入)应用批量归一化。归一化的公式为:
这里的\(x\)是张量的一个元素,\(\gamma\)和\(\beta\)分别是标准化处理后的数据放缩、偏置(平移)参数,这两个参数需要学习,为什么不直接使用\(\gamma=1,\beta=0\)呢?因为这里数据的分布本身是有差异性的,我们通过标准化强行将数据放缩到0均值,使其接近标准正态分布,同一个伪逆过程让模型自学习其均值方差,以获取更好的性能,如果强制给定处理后的均值方差,理论上也是可以的,但是这里的人为信息偏置引入是很强的,但又没有一个好的科学解释,实验表明让模型学习是一个很好的选择!
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
经过证明猜想是对的!
二、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: 数据类型
动量参数说明
这里实际上是进行移动平均(加权求期望)。
案例:
>>> 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的接口类似,我们只在通道维度上选择某一个时刻,取出的数据进行标准化操作!
三、BatchNorm3d
和BatchNorm2d类似,参考BatchNorm2d。
四、LazyBatchNorm1d~3d
延迟初始化weight、bias、running_mean、running_var参数。不需要指定通道维度,可以自己根据通道维数进行设定num_features参数!
五、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也能有较好的错误率!
六、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创建进程完美会在后面单独讲解!这里只需要浅显的理解即可!
七、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)
九、局部标准化LRN
LRN局部响应标准化,有两种LRN实现,一种是在通道上进行某个窗口大小为size的邻域内进行标准化,另一种是在某个通道对应的特征图上大小为size的邻域内进行标准化!
具体参考文章:https://blog.csdn.net/weixin_46221946/article/details/122729460
完!