我愿称之为全网最通透的layernorm讲解(往下翻)

   在我们平常面试和工程中会用到BN和LN,但或许没有去了解过BN和LN到底在那个维度上进行的正则化(减均值除以标准差)。下面将会采用各种例子来为大家介绍BN层和LN层各个参数以及差别。

 一、BatchNorm(批标准化):

  BatchNorm一共有三个函数分别是BatchNorm1d,BatchNorm2d,BatchNorm3d,她们的输入的tensor的维度是不一样的,以及参数的定义也是不一样的,我们一个一个的说。

 BatchNorm1d:

torch.nn.BatchNorm1d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)

参数含义:

  num_features:如果你输出的tensor是(N,C,L)维度的,那么这里定义为C;如果你输入的tensor是(N,L)维度的,则此处设定为L。这里N表示batch_size,C是数据的channel(通道),L是特征维度(数据长度)。

  eps:对输入数据进行归一化时加在分母上,防止除零。

  momentum :计算整个样本全局均值running_mean和方差running_var时是采用动量的模式进行,这个设定的是这个动量的大小,后文会提到。

  affine:一个布尔值,当设置为True时,此模块具有可学习的仿射参数weight和bias,一般我们做正则化是使得数据服从N(0,1),但是经过仿射变换可以到到N(bias,weight^2)的正态分布。这两个参数可以学习的,初始化时weigt为1,bias为0

  track_running_stats: 设为True时,BatchNorm层会统计全局均值running_mean和方差running_var

  从参数的含义我们可以知道,针对不同的tensor输出,我们提前设定的num_features时不一样的,当做BN的tensor维度是(N,C,L)时,我们定义的num_features是C,意味着我们会根据每个通道的不同样本的L长度特征进行相加再除以N*L得到均值,因此会得到C个均值。再具体点的实例就是输入为(5,3,10)的tensor,我们会取[0,0,:],[1,0,:],....[4,0,:]这些向量的数值(一共有5*10个数字)加起来除以5*10得到第一个通道的均值,并对以上数字进行正则化。当做BN的tensor维度是(N,L),我们定义的num_features是L,因此我们会计算出L个均值和方差,可以看成(N,L,1)的形式,每一个特征长度为1,只有L个通道,具体点的实例:输入维度为(4,5)的tensor,会取[0,0],[1,0],[2,0],[3,0]这4个数进行正则化,可以知道我们最终会得到L个均值和方差

  momentum参数的应用是为了计算全局样本的均值和方差的,因为当训练完样本数据后,我们可以得到一个整个样本的均值和方差,但是这个均值和方差的得到不是把所有样本都计算遍历一遍计算得到的,而是在每一个betch经过BatchNorm1d的时候,内部会储存下该次batch的均值和方差,并通过以下等式来计算得到全局的均值和方差。

xnew=(1momentum)×xcur+momentum×xbatchxnew=(1−momentum)×xcur+momentum×xbatch

  如果track_running_stats=False,则在内部不会进行计算全局均值running_mean和方差running_var

  下面直接上例子来看看:

1
2
3
4
5
6
7
m = nn.BatchNorm1d(5, affine=False, momentum=0.1)
tensor = torch.FloatTensor([i for i in range(20)]).reshape(4,5)
print(tensor)
output = m(tensor)
print(output)
print(m.running_mean)
print(m.running_var)

  结果如下:

复制代码
### tensor的取值
tensor([[ 0., 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.], [10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.]])
### BN之后的结果 tensor([[
-1.3416, -1.3416, -1.3416, -1.3416, -1.3416], [-0.4472, -0.4472, -0.4472, -0.4472, -0.4472], [ 0.4472, 0.4472, 0.4472, 0.4472, 0.4472], [ 1.3416, 1.3416, 1.3416, 1.3416, 1.3416]])
### 全局均值(由于momentum=0.1,running_mean初始值为0,所以这是根据公式计算过后的结果) tensor([
0.7500, 0.8500, 0.9500, 1.0500, 1.1500])
### 全局方差 tensor([
5.0667, 5.0667, 5.0667, 5.0667, 5.0667])
复制代码

  我们再来看看,输入的tensor是三维的情况:

1
2
3
4
5
6
7
m = nn.BatchNorm1d(2, affine=False, momentum=0.1)
tensor = torch.FloatTensor([i for i in range(18)]).reshape(3,2,3)
print(tensor)
output = m(tensor)
print(output)
print(m.running_mean)
print(m.running_var)

  结果如下:

复制代码
### tensor的取值
tensor([[[ 0., 1., 2.], [ 3., 4., 5.]], [[ 6., 7., 8.], [ 9., 10., 11.]], [[12., 13., 14.], [15., 16., 17.]]])
### BN之后的取值 tensor([[[
-1.4094e+00, -1.2081e+00, -1.0067e+00], [-1.4094e+00, -1.2081e+00, -1.0067e+00]], [[-2.0135e-01, -2.9802e-08, 2.0135e-01], [-2.0135e-01, 5.9605e-08, 2.0135e-01]], [[ 1.0067e+00, 1.2081e+00, 1.4094e+00], [ 1.0067e+00, 1.2081e+00, 1.4094e+00]]])
### 全局均值和方差 tensor([
0.7000, 1.0000]) tensor([3.6750, 3.6750])
复制代码

  大概检验一下,根据计算公式,第一个均值应该是每个样本通道1的所有特征求和得到的均值也就是(0+1+2+6+7+8+12+13+14)/ 9 = 7 (全局均值是乘以了momentum=0.1的结果),方差应该是:[(07)2+.....+(147)2]/9=24.66[(0−7)2+.....+(14−7)2]/9=24.66(注意这里是有偏样本方差分母是N)。[0,0,0]位置这个数BN之后为:(07)/24.66=1.4094(0−7)/24.66=−1.4094,有些同学可能或说为什么全局方差不对呢?因为全局方差中计算的是无偏的样本方差(分母是N-1),并且初始值running_var=1.

 BatchNorm2d:

torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)

 参数:

  num_features:输入的tensor是(N,C,H,W)维度,num_features定义为C。其中N表示batch_size

  为什么会出现BatchNorm2d呢?那是因为1d只能处理(N,L)和(N,C,L)两种输入,但是在CV领域特征图常常是(N,C,H,W),这时候1d就没法处理了,就需要2d了,其实有了1d的了解,2d就是把剩下两个维度的数全部搞在一起进行计算均值和方差。具体点的例子就是输入tensor为(5,3,100,120),那我们定义的num_featurs=3那么我们将提取[0,0,:,:],[1,0,:,:],[2,0,:,:]...[4,0,:,:]这几个矩阵的所有数(一共有5*100*120个数字)计算均值和方差,因此我们可以知道我们最后得到的依然是3组均值和方差。

  其他参数和1d是一样的,作用也是一样的。我们接下来看下例子。

1
2
3
4
5
6
7
m = nn.BatchNorm2d(3, affine=False, momentum=0.1)
tensor = torch.FloatTensor([i for i in range(36)]).reshape(3,3,2,2)
print(tensor)
output = m(tensor)
print(output)
print(m.running_mean)
print(m.running_var)

  结果如下:

复制代码
tensor([[[[ 0.,  1.],
          [ 2.,  3.]],

         [[ 4.,  5.],
          [ 6.,  7.]],

         [[ 8.,  9.],
          [10., 11.]]],


        [[[12., 13.],
          [14., 15.]],

         [[16., 17.],
          [18., 19.]],

         [[20., 21.],
          [22., 23.]]],


        [[[24., 25.],
          [26., 27.]],

         [[28., 29.],
          [30., 31.]],

         [[32., 33.],
          [34., 35.]]]])
tensor([[[[-1.3690, -1.2676],
          [-1.1661, -1.0647]],

         [[-1.3690, -1.2676],
          [-1.1661, -1.0647]],

         [[-1.3690, -1.2676],
          [-1.1661, -1.0647]]],


        [[[-0.1521, -0.0507],
          [ 0.0507,  0.1521]],

         [[-0.1521, -0.0507],
          [ 0.0507,  0.1521]],

         [[-0.1521, -0.0507],
          [ 0.0507,  0.1521]]],


        [[[ 1.0647,  1.1661],
          [ 1.2676,  1.3690]],

         [[ 1.0647,  1.1661],
          [ 1.2676,  1.3690]],

         [[ 1.0647,  1.1661],
          [ 1.2676,  1.3690]]]])
## 全局均值和方差,计算方式看前文 tensor([
1.3500, 1.7500, 2.1500]) tensor([11.5091, 11.5091, 11.5091])
复制代码

  我们就简单计算下前两个均值就好了,第一个均值:(0+1+2+3+12+13+14+15+24+25+26+27)/ 12 = 13.5,第二个均值:(4+5+6+7+16+17+18+19+28+29+30+31)/ 12 = 17.5大家可以看看我就计算了那些数字。

 BatchNorm3d:

torch.nn.BatchNorm3d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)

   有了前面的知识,其实我们也能猜到3d处理的就是输入tensor为(N,C,D,H,W)的情况,此时num_features应该定义C。给一个例子来说就是进行把最后三个维度的数字按照样本个数全部加起来,数字总数为N*D*H*W,最后依然会产生C个均值和方差。

 二、LayerNorm(层标准化):

torch.nn.LayerNorm(normalized_shape, eps=1e-05, elementwise_affine=True, device=None, dtype=None)

  参数看起来和BatchNorm差不多,但是LayerNorm不会记录全局的均值和方差。最重要的就是前三个参数。

  normalized_shape:可以设定为:int,列表,或者torch.Size([3, 4])

  eps:对输入数据进行归一化时加在分母上,防止除零。

  elementwise_affine:是否进行仿射变换,如果是True则此模块具有可学习的仿射参数weight和bias,使得能够使得数据不仅仅是服从N(0,1)正态分布。

normalized_shape传入整数

  如果normalized_shape传入的是整数,那么会在输入tensor的最后一维一定要和这个整数一样,比如normalized_shape=4,则tensor的最后一个维度一定要为4,而进行的正则化就是最最后一位的数字进行的,更具体的例子,输入tensor维度为(3,4),那么会对[0,0],[0,1],[0,2],[0,3]位置的加一起求均值和方差(一共只有4个数),那么就会出现3个均值和方差。看例子:

1
2
3
4
5
6
7
m = nn.LayerNorm(4, elementwise_affine=True)
tensor = torch.FloatTensor([i for i in range(12)]).reshape(3,4)
print(tensor)
output = m(tensor)
print(output)
print(m.weight)
print(m.bias)

  结果:

复制代码
tensor([[ 0.,  1.,  2.,  3.],
        [ 4.,  5.,  6.,  7.],
        [ 8.,  9., 10., 11.]])
tensor([[-1.3416, -0.4472,  0.4472,  1.3416],
        [-1.3416, -0.4472,  0.4472,  1.3416],
        [-1.3416, -0.4472,  0.4472,  1.3416]],
       grad_fn=<NativeLayerNormBackward0>)
Parameter containing:
tensor([1., 1., 1., 1.], requires_grad=True)
Parameter containing:
tensor([0., 0., 0., 0.], requires_grad=True)
复制代码

  大家可以自行计算下[0,1,2,3]这四个数正则化之后的结果。是不是就是图中所示。因此normalized_shape传入的是整数还是比较好理解的。

normalized_shape传入列表

  如果normalized_shape传入的是列表,比如[3,4],那么需要要求传入的tensor需要最后两个维度需要满足[3, 4],会把最后两个维度以用12个数据进行求均值和方差并正则化。具体一点的例子,传入的tensor维度为(N,C,3,4)那么会对【0,0,:,:】这12个数进行正则化,【0,1,:,:】这12个数进行正则化.....因此最后得到会得到N*C个均值和方差。看例子。

1
2
3
4
5
6
7
m = nn.LayerNorm([3,4], elementwise_affine=True)
tensor = torch.FloatTensor([i for i in range(12*4)]).reshape(2,2,3,4)
print(tensor)
output = m(tensor)
print(output)
print(m.weight)
print(m.bias)

  结果如下:

复制代码
tensor([[[[ 0.,  1.,  2.,  3.],
          [ 4.,  5.,  6.,  7.],
          [ 8.,  9., 10., 11.]],

         [[12., 13., 14., 15.],
          [16., 17., 18., 19.],
          [20., 21., 22., 23.]]],


        [[[24., 25., 26., 27.],
          [28., 29., 30., 31.],
          [32., 33., 34., 35.]],

         [[36., 37., 38., 39.],
          [40., 41., 42., 43.],
          [44., 45., 46., 47.]]]])
tensor([[[[-1.5933, -1.3036, -1.0139, -0.7242],
          [-0.4345, -0.1448,  0.1448,  0.4345],
          [ 0.7242,  1.0139,  1.3036,  1.5933]],

         [[-1.5933, -1.3036, -1.0139, -0.7242],
          [-0.4345, -0.1448,  0.1448,  0.4345],
          [ 0.7242,  1.0139,  1.3036,  1.5933]]],


        [[[-1.5933, -1.3036, -1.0139, -0.7242],
          [-0.4345, -0.1448,  0.1448,  0.4345],
          [ 0.7242,  1.0139,  1.3036,  1.5933]],

         [[-1.5933, -1.3036, -1.0139, -0.7242],
          [-0.4345, -0.1448,  0.1448,  0.4345],
          [ 0.7242,  1.0139,  1.3036,  1.5933]]]],
       grad_fn=<NativeLayerNormBackward0>)
Parameter containing:
tensor([[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]], requires_grad=True)
Parameter containing:
tensor([[0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.]], requires_grad=True)
复制代码

  大家也可以自行计算下[0, 1, 2, 3,4,5...11]这12个数字最后正则化之后的结果。

  这里要注意的是weight和bias在训练过程中是会更新的,并且会在一次正则化中使用多次,比如上面(2,2,3,4)的例子,(0,0,:,:)会使用weight和bias对应位置的数字,(0,1,:,:)也会使用对应位置数字。

 

参考网页:

BatchNorm2d — PyTorch 1.10 documentation

BatchNorm1d — PyTorch 1.10 documentation

BatchNorm3d — PyTorch 1.10 documentation

LayerNorm — PyTorch 1.10 documentation

pytorch LayerNorm参数详解,计算过程_拿铁大侠的博客-CSDN博客_nn.layernorm使用

pytorch BatchNorm参数详解,计算过程_拿铁大侠的博客-CSDN博客_batchnorm 参数

【PyTorch】详解pytorch中nn模块的BatchNorm2d()函数_安静-CSDN博客_nn.batchnorm2d

posted @ 2024-08-09 16:31  海_纳百川  阅读(991)  评论(0编辑  收藏  举报
本站总访问量