pytorch BatchNorm LayerNorm InstanceNorm GroupNorm 通俗易懂理解
pytorch BatchNorm LayerNorm InstanceNorm GroupNorm 通俗易懂理解
认识批量数据
先来理解归一化层的输入数据的格式,输入格式包括4个符号:N、C、H、W
N:batch–我们通常将一个批量数据输入模型进行训练。
C:通道数,可以理解为特征的个数。
H*W:表示一个通道内数据的维度,这里是二维,若是NCABC,则数据维度是三维,依此类推,NCABCD…。
N:样本轴,代表一个批量(batchsize);
C:通道轴,代表特征个数;
F:每个通道中特征的维度,以输入格式为NCHW为例,F=H*W,所以每个通道中特征为二维,依此类推。
上图中,左图为LayerNorm,右图为BatchNorm,原因如下:
LayerNorm :同一样本在不同通道上做归一化。
BatchNorm:不同样本在同一特征上做归一化。
上图的另一种理解方法:
下图:横轴:N,样本轴;纵轴:C,通道轴;F:每个通道中特征的维度,一维。图中:N=3,C=5,F=1,此批量数据格式为(3,5,1);

F:每个通道中特征的维度,两维。图中:N=3,C=3,F=2*2,此批量数据格式为(3,3,2,2);

F:每个通道中特征的维度,三维。图中:N=3,C=3,F=3*2*2,此批量数据格式为(3,3,3,2,2);

四种归一化的异同点
| 相同点 | 异同点 |
|---|---|
| 均是通过 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_features | eps | momentum | affine | track_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^i | bool值,是否追踪之前batch数据的mean和var,详见下表 |
| 训练期间 | 测试期间 | |
|---|---|---|
| track_running_stats=True | running_mean 和 running_var会跟踪不同batch数据的mean和variance | running_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.0、2.0、3.0,
σ
β
2
分
别
为
0.0
、
0.0
、
0.0
\sigma^2_\beta分别为0.0、0.0、0.0
σβ2分别为0.0、0.0、0.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)
输出结果:
手动计算:
分组的方法和计算方法都在图中

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
总结:每种归一化的优缺点,以及演变历程
| BatchNorm | LayerNorm | InstanceNorm | GroupNorm | |
|---|---|---|---|---|
| 计算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 | 不适用生成式网络,因为不同通道的特征图风格不一样,不可以一同做归一化 | 暂未发现 | 暂未发现 |
更多推荐


所有评论(0)