认识批量数据

先来理解归一化层的输入数据的格式,输入格式包括4个符号:N、C、H、W
N:batch–我们通常将一个批量数据输入模型进行训练。
C:通道数,可以理解为特征的个数。
H*W:表示一个通道内数据的维度,这里是二维,若是NCABC,则数据维度是三维,依此类推,NCABCD…。
以输入格式为NCHW为例N:样本轴,代表一个批量(batchsize);
C:通道轴,代表特征个数;
F:每个通道中特征的维度,以输入格式为NCHW为例,F=H*W,所以每个通道中特征为二维,依此类推。

上图中,左图为LayerNorm,右图为BatchNorm,原因如下:
LayerNorm :同一样本在不同通道上做归一化。
BatchNorm:不同样本在同一特征上做归一化。

上图的另一种理解方法:
下图:横轴:N,样本轴;纵轴:C,通道轴;F:每个通道中特征的维度,一维。图中:N=3,C=5,F=1,此批量数据格式为(3,5,1);
Alt
F:每个通道中特征的维度,两维。图中:N=3,C=3,F=2*2,此批量数据格式为(3,3,2,2);
Alt
F:每个通道中特征的维度,三维。图中:N=3,C=3,F=3*2*2,此批量数据格式为(3,3,3,2,2);
Alt

四种归一化的异同点

相同点异同点
均是通过 x ^ i = x i − μ β σ β 2 + ϵ 、 y i = γ x ^ i + β \hat x_i = \frac{x_i - \mu_\beta}{\sqrt {\sigma^2_\beta+\epsilon}} 、y_i = \gamma \hat x_i + \beta x^i=σβ2+ϵ xiμβyi=γx^i+β 来进行归一化操作计算 μ β 、 σ β 2 \mu_\beta、\sigma^2_\beta μβσβ2的方式不同

调用pytorch内的函数,讲解相关参数

以nn.BatchNorm2d函数为例,其他的函数大同小异:
running_mean 和 running_var 会采用指数加权移动平均来跟踪不同batch数据,来计算均值和方差。

num_featuresepsmomentumaffinetrack_running_stats
输入数据,这里定义好了输入数据的格式,详见实验小节保证除法进行动量,用于计算running_mean 和 running_var的值bool值,是否进行仿射变换。仿射变换就是 y i = γ x ^ i + β y_i = \gamma \hat x_i + \beta yi=γx^i+β若affine = false,则 y i = x ^ i y_i = \hat x_i yi=x^ibool值,是否追踪之前batch数据的mean和var,详见下表
训练期间测试期间
track_running_stats=Truerunning_mean 和 running_var会跟踪不同batch数据的mean和variancerunning_mean 和 running_var会跟踪训练时的mean和variance
track_running_stats=False这时候running_mean 和running_var不跟踪不同batch数据,计算当前每个batch的mean和var作为running_mean 和 running_var这时候running_mean 和running_var不跟踪不同batch数据,计算当前每个batch的mean和var作为running_mean 和 running_var

实验理解,手动计算,结合pytorch内的函数

BatchNorm

batch_size = 2
num_features = 5
momentum = 0.3

feature_shape = (2,2)

feature_map = torch.ones(feature_shape)                                              # 1-D
feature_maps = torch.stack([feature_map*(i+1) for i in range(num_features)],dim=0)   # 2-D
feature_maps_bs = torch.stack([feature_maps for i in range(batch_size)],dim=0) # 3-D

bn = nn.BatchNorm2d(num_features=num_features,momentum=momentum)
running_mean, running_var = 0, 1 # 默认值

for i in range(2):
    outputs = bn(feature_maps_bs)

    print("\niteration:{}, running mean: {} ".format(i, bn.running_mean))
    print("iteration:{}, running var:{} ".format(i, bn.running_var))

    mean_t, var_t = 2, 0 # 第二个特征的均值和方差

    running_mean = (1 - momentum) * running_mean + momentum * mean_t
    running_var = (1 - momentum) * running_var + momentum * var_t

    print("iteration:{}, 第二个特征的running mean: {} ".format(i, running_mean)) # 计算和bn.running_mean结果一样
    print("iteration:{}, 第二个特征的running var:{}".format(i, running_var))     # 计算和bn.running_var结果一样

输出结果:
手动计算:
第一个样本和第二个样本均如下所示:
[[[1., 1.],
[1., 1.]],

[[2., 2.],
[2., 2.]],

[[3., 3.],
[3., 3.]],

[[4., 4.],
[4., 4.]],

[[5., 5.],
[5., 5.]]]
第一个样本和第二个样本的第一个特征为:
[[1., 1.],
[1., 1.]]
均值=1,方差=0(虽然 σ β 2 \sigma^2_\beta σβ2为0,但还有eps保证除法进行),依此类推两个样本的其他特征
手动计算running_mean 和 running_var:
以上代码计算了两轮running_mean 和 running_var,下面
首先,初始化running_mean=0.0、running_var=1.0
第一次计算running_mean 和 running_var:
running_mean = (1-0.3)* 0 + 0.3 * 2 = 0.6
running_var= (1-0.3)* 1 + 0.3 * 0 = 0.7

第二次计算running_mean 和 running_var:
running_mean = (1-0.3)* 0.6 + 0.3 * 2 = 1.02
running_var= (1-0.3)* 0.7 + 0.3 * 0 = 0.49

torch.Size([2, 5, 2, 2])
tensor([[[[1., 1.],
          [1., 1.]],
          
         [[2., 2.],
          [2., 2.]],
          
         [[3., 3.],
          [3., 3.]],
          
         [[4., 4.],
          [4., 4.]],
          
         [[5., 5.],
          [5., 5.]]],
          
        [[[1., 1.],
          [1., 1.]],
          
         [[2., 2.],
          [2., 2.]],
          
         [[3., 3.],
          [3., 3.]],
          
         [[4., 4.],
          [4., 4.]],
          
         [[5., 5.],
          [5., 5.]]]])

iteration:0, running mean: tensor([0.3000, 0.6000, 0.9000, 1.2000, 1.5000]) 
iteration:0, running var:tensor([0.7000, 0.7000, 0.7000, 0.7000, 0.7000]) 
iteration:0, 第二个特征的running mean: 0.6 
iteration:0, 第二个特征的running var:0.7

iteration:1, running mean: tensor([0.5100, 1.0200, 1.5300, 2.0400, 2.5500]) 
iteration:1, running var:tensor([0.4900, 0.4900, 0.4900, 0.4900, 0.4900]) 
iteration:1, 第二个特征的running mean: 1.02 
iteration:1, 第二个特征的running var:0.48999999999999994

Process finished with exit code 0

LayerNorm

batch_size = 2 # batch_size也就是层数是两层,two layers
num_features = 3
feature_shape = (2,2)

feature_map = torch.ones(feature_shape)                                              # 1-D
feature_maps = torch.stack([feature_map*(i+1) for i in range(num_features)],dim=0)   # 2-D
feature_maps_bs = torch.stack([feature_maps for i in range(batch_size)],dim=0) # 3-D

print(feature_maps_bs) # feature_maps_bs.Size([2, 3, 2, 2]): 2层网络,每个网络层上数据的shape=(3,2,2)
print(np.mean(feature_maps_bs[0].numpy())) # 计算一层网络上数据的mean = 2.0
print(np.std(feature_maps_bs[0].numpy()))  # 计算一层网络上数据的var = 0.8164966

# elementwise_affine=True:仿射变换的可学习参数,分别初始化为(gamma(又称weight)=1.0,beta(又称bias)=0.0),elementwise_affine=False,weight=bias=None
# normalized_shape:每个网络层上数据的shape
a = nn.LayerNorm(normalized_shape = feature_maps_bs.size()[1:],eps=0.0,elementwise_affine=True)
o = a(feature_maps_bs) # input:(N, *)
print(o) # output:(N, *)

输出结果:
手动计算:
首先LayerNorm是对每一个网络层进行归一化,所以这里的一层网络中的数据为:
[[[1., 1.],
[1., 1.]],

[[2., 2.],
[2., 2.]],

[[3., 3.],
[3., 3.]]]
这里求得 μ β = 2.0 , σ β 2 = 0.8164966 \mu_\beta=2.0,\sigma^2_\beta=0.8164966 μβ=2.0,σβ2=0.8164966
在nn.LayerNorm函数中,由于elementwise_affine=True:是否启用仿射变换的可学习参数,分别初始化为(gamma(又称weight=1.0,beta(又称bias=0.0
其中 μ β = 2.0 , σ β 2 = 0.8164966 \mu_\beta=2.0,\sigma^2_\beta=0.8164966 μβ=2.0,σβ2=0.8164966 :

  • -1.2247 = (1-2)/ 0.8164966 ,weight*(-1.2247)+bias = -1.2247
  • 0 = (2-2)/ 0.8164966 ,weight*(0)+bias =0
  • 1.2247 = (3-2)/ 0.8164966 ,weight*(1.2247)+bias = 1.2247
tensor([[[[1., 1.],
          [1., 1.]],

         [[2., 2.],
          [2., 2.]],

         [[3., 3.],
          [3., 3.]]],


        [[[1., 1.],
          [1., 1.]],

         [[2., 2.],
          [2., 2.]],

         [[3., 3.],
          [3., 3.]]]])
2.0 # 计算第一层网络上数据的mean = 2.0
0.8164966 # 计算第一层网络上数据的var = 0.8164966
tensor([[[[-1.2247, -1.2247],
          [-1.2247, -1.2247]],

         [[ 0.0000,  0.0000],
          [ 0.0000,  0.0000]],

         [[ 1.2247,  1.2247],
          [ 1.2247,  1.2247]]],


        [[[-1.2247, -1.2247],
          [-1.2247, -1.2247]],

         [[ 0.0000,  0.0000],
          [ 0.0000,  0.0000]],

         [[ 1.2247,  1.2247],
          [ 1.2247,  1.2247]]]], grad_fn=<NativeLayerNormBackward0>)
         
Process finished with exit code 0

InstanceNorm

batch_size = 2
num_features = 3
feature_shape = (2,2)
momentum = 0.3

feature_map = torch.ones(feature_shape)                                              # 1-D
feature_maps = torch.stack([feature_map*(i+1) for i in range(num_features)],dim=0)   # 2-D
feature_maps_bs = torch.stack([feature_maps for i in range(batch_size)],dim=0) # 3-D

print(feature_maps_bs)

a = nn.InstanceNorm2d(num_features,eps=0.0,momentum=momentum,affine=True) # input:(N, C, H, W)
o = a(feature_maps_bs)
print(o)
print(a.weight.data) # a.weight.size : tensor([1., 1., 1.]) 3个数=特征个数num_features ,表示InstanceNorm是对每个特征进行归一化
print(a.bias.data)   # a.bias.size : tensor([0., 0., 0.])  3个数=特征个数num_features ,表示InstanceNorm是对每个特征进行归一化

输出结果:
手动计算:
第一个特征为
[[1., 1.],
[1., 1.]],
第二个特征为
[[2., 2.],
[2., 2.]],
第三个特征为
[[3., 3.],
[3., 3.]]
每个特征的 μ β 分 别 为 1.0 、 2.0 、 3.0 \mu_\beta分别为1.0、2.0、3.0 μβ1.02.03.0 σ β 2 分 别 为 0.0 、 0.0 、 0.0 \sigma^2_\beta分别为0.0、0.0、0.0 σβ20.00.00.0(虽然 σ β 2 \sigma^2_\beta σβ2为0,但还有eps保证除法进行)
所以 计算结果为全0

tensor([[[[1., 1.],
          [1., 1.]],

         [[2., 2.],
          [2., 2.]],

         [[3., 3.],
          [3., 3.]]],


        [[[1., 1.],
          [1., 1.]],

         [[2., 2.],
          [2., 2.]],

         [[3., 3.],
          [3., 3.]]]])
tensor([[[[0., 0.],
          [0., 0.]],

         [[0., 0.],
          [0., 0.]],

         [[0., 0.],
          [0., 0.]]],


        [[[0., 0.],
          [0., 0.]],

         [[0., 0.],
          [0., 0.]],

         [[0., 0.],
          [0., 0.]]]], grad_fn=<ViewBackward0>)
tensor([1., 1., 1.])  # 3个数=特征个数num_features ,表示InstanceNorm是对每个特征进行归一化
tensor([0., 0., 0.]) # 3个数=特征个数num_features ,表示InstanceNorm是对每个特征进行归一化

Process finished with exit code 0

GroupNorm

batch_size = 2
num_features = 4
feature_shape = (2,2)
num_groups = 2

feature_map = torch.ones(feature_shape)                                              # 1-D
feature_maps = torch.stack([feature_map*(i+1) for i in range(num_features)],dim=0)   # 2-D
feature_maps_bs = torch.stack([feature_maps for i in range(batch_size)],dim=0) # 3-D

print(np.mean(feature_maps[2:].numpy())) # 后一个group的mean=3.5
print(np.std(feature_maps[2:].numpy()))     # 后一个group的var=0.5

gn = nn.GroupNorm(num_groups,num_features) # input:(N, C, *)
o = gn(feature_maps_bs)
print(gn.weight.data)  # gn.weight.size:(num_features)
print(gn.bias.data)    # gn.bias.size:(num_features)
print(o.size())
print(o)

输出结果:
手动计算:
分组的方法和计算方法都在图中
Alt

tensor([[[[1., 1.],
          [1., 1.]],

         [[2., 2.],
          [2., 2.]],

         [[3., 3.],
          [3., 3.]],

         [[4., 4.],
          [4., 4.]]],


        [[[1., 1.],
          [1., 1.]],

         [[2., 2.],
          [2., 2.]],

         [[3., 3.],
          [3., 3.]],

         [[4., 4.],
          [4., 4.]]]])
3.5  # 后一个group的mean=3.5
0.5  # 后一个group的var=0.5
tensor([1., 1., 1., 1.])
tensor([0., 0., 0., 0.])
torch.Size([2, 4, 2, 2])
tensor([[[[-1.0000, -1.0000],
          [-1.0000, -1.0000]],

         [[ 1.0000,  1.0000],
          [ 1.0000,  1.0000]],

         [[-1.0000, -1.0000],
          [-1.0000, -1.0000]],

         [[ 1.0000,  1.0000],
          [ 1.0000,  1.0000]]],


        [[[-1.0000, -1.0000],
          [-1.0000, -1.0000]],

         [[ 1.0000,  1.0000],
          [ 1.0000,  1.0000]],

         [[-1.0000, -1.0000],
          [-1.0000, -1.0000]],

         [[ 1.0000,  1.0000],
          [ 1.0000,  1.0000]]]], grad_fn=<NativeGroupNormBackward0>)

Process finished with exit code 0

总结:每种归一化的优缺点,以及演变历程

BatchNormLayerNormInstanceNormGroupNorm
计算mean 和 var在batch上计算在同一个样本的不同通道计算在每个channel 维度上,求每个通道(单个特征)的mean 和 var在channel维度上分组后,求分组后多个通道(多个特征)的mean 和 var
优点适用于大的batch size。可以用更大学习率,加速模型收敛。可以不用精心设计权值初始化可以不用dropout或较小的dropout。可以不用L2或者较小的weight decay。可以不用LRN(local response normalization)弥补了BN的缺点,适用于变长数据(RNN)弥补了LN的缺点,适用于生成式网络,可以对不同通道的特征图进行归一化适用于大模型,小batch size的情形,因为大模型通常gpu吃不了太大的batchsize,而小batch size通过BN计算的mean和var会不准确,所以针对小batchsize的Normalization的GroupNorm应允而生。
缺点不适用于变长数据:text、speech不适用生成式网络,因为不同通道的特征图风格不一样,不可以一同做归一化暂未发现暂未发现

更多推荐