1、爲什麼要Normalization?
深度學習中存在Internal Covariate Shift(ICS):數據尺度/分佈異常,導致訓練困難;
在權值初始化部分介紹了深度神經網絡訓練過程中的數據尺度變化問題:
從公式中可以知道網絡層之間的尺度是連乘關係,如果的值大於1,則連乘會導致梯度爆炸,如果小於1,則會導致梯度消失。
Normalization會控制數據的尺度分佈,有助於模型訓練,這就是爲什麼經常在深度學習中使用Normalization。
2、常見的Normalization——BN、LN、IN and GN
這四種正則化方法相同的地方在於其計算公式相同:四種正則化方法不同的地方在於均值和方差的求取方式。
2.1 Layer Normalization(LN)
起因:BN不適用於變長的網絡,例如RNN;
思路:逐層計算均值和方差;
注意事項:
- 不再有running_mean和running_var;
- gamma和beta爲逐元素;BN中的gamma和beta的維度爲特徵個數;
BN不適用變長的網絡,RNN不同數據的長度可能是不同的,這就導致BN沒辦法計算數據的均值和方差。考慮到BN的缺點,LN考慮對每一個數據的不同特徵間計算均值和方差,也就是基於Layer計算均值和方差,如上圖,假設一個數據(一層)上有五個神經元(特徵),計算這五個神經元的均值和方差。這是基於一個網絡層的Normalization,不是基於批量的Normalization。
2.1.1 nn.LayerNorm
主要參數:
- normalized_sha[e:該層特徵形狀;
- eps:分母修正項;
- elementwise_affine:是否需要affine transform
注意:當輸入是卷積的特徵圖,則求平均數的數據爲channelHW;
nn.LayerNorm(normalized_shape,eps=1e-5,elementwise_affine=True)
nn.LayerNorm在代碼中的具體使用如下:
batch_size = 8
num_features = 6 # 每個數據的特徵個數
features_shape = (3, 4) # 特徵維度
feature_map = torch.ones(features_shape) # 2D
feature_maps = torch.stack([feature_map * (i + 1) for i in range(num_features)], dim=0) # 3D
feature_maps_bs = torch.stack([feature_maps for i in range(batch_size)], dim=0) # 4D
# feature_maps_bs shape is [8, 6, 3, 4], B * C * H * W
ln = nn.LayerNorm(feature_maps_bs.size()[1:], elementwise_affine=True) # LN不需要將batch_size傳入
# ln = nn.LayerNorm(feature_maps_bs.size()[1:], elementwise_affine=False) # 注意elementwise_affine的具體作用,elementwise_affine的作用是不適用affine transform
# ln = nn.LayerNorm([6, 3, 4])
# ln = nn.LayerNorm([6, 3])
output = ln(feature_maps_bs)
print("Layer Normalization")
print(ln.weight.shape) # 維度爲[6,3,4]
print(feature_maps_bs[0, ...])
print(output[0, ...])
代碼的輸出爲:
Layer Normalization
torch.Size([3, 4, 4])
tensor([[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]],
[[2., 2., 2., 2.],
[2., 2., 2., 2.],
[2., 2., 2., 2.],
[2., 2., 2., 2.]],
[[3., 3., 3., 3.],
[3., 3., 3., 3.],
[3., 3., 3., 3.],
[3., 3., 3., 3.]]])
tensor([[[-1.2247, -1.2247, -1.2247, -1.2247],
[-1.2247, -1.2247, -1.2247, -1.2247],
[-1.2247, -1.2247, -1.2247, -1.2247],
[-1.2247, -1.2247, -1.2247, -1.2247]],
[[ 0.0000, 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000, 0.0000]],
[[ 1.2247, 1.2247, 1.2247, 1.2247],
[ 1.2247, 1.2247, 1.2247, 1.2247],
[ 1.2247, 1.2247, 1.2247, 1.2247],
[ 1.2247, 1.2247, 1.2247, 1.2247]]], grad_fn=<SelectBackward>)
2.2 Instance Normalization(IN)
起因:BN在圖像生成(Image Generation)中不適用;
思路:逐Instance(channel)計算均值和方差;
圖像生成中,一個batch不同圖像有不同的遷移風格,所以不能對圖片的batch進行BN,所以提出了逐通道的LN。
下面通過一個示意圖分析LN的具體作用,假如現在有三個樣本,每個樣本有三個特徵圖,每個特徵圖的大小爲2*2,因爲每個樣本代表的風格是不同的,不能將batch_size大小的圖片進行BN。
LN通過逐通道(每個特徵圖)計算均值和方差,如上圖中,第一個樣本有三個特徵圖,在每一個特徵圖中計算均值和方差。
2.2.1 nn.InstanceNorm
主要參數:
- num_features:一個樣本特徵數量(最重要)
- eps:分母修正項;
- momentum:指數加權平均估計當前mean/var;
- affine:是否需要affine transform;
- track_running_stats:是訓練狀態還是測試狀態;
nn.InstanceNorm2d(num_feature,eps=1e-5,momentum=0.1,affine=False,track_running_stats=False)
nn.InstanceNorm的使用和BN差不多,同樣有nn.InstanceNorm1d,nn.InstanceNorm2d,nn.InstanceNorm3d,其使用方法和BN使用方法差不多,其使用過程如下所示:
batch_size = 3
num_features = 3
momentum = 0.3
features_shape = (2, 2)
feature_map = torch.ones(features_shape) # 2D
feature_maps = torch.stack([feature_map * (i + 1) for i in range(num_features)], dim=0) # 3D
feature_maps_bs = torch.stack([feature_maps for i in range(batch_size)], dim=0) # 4D
print("Instance Normalization")
print("input data:\n{} shape is {}".format(feature_maps_bs, feature_maps_bs.shape))
instance_n = nn.InstanceNorm2d(num_features=num_features, momentum=momentum)
for i in range(1):
outputs = instance_n(feature_maps_bs)
print(outputs)
代碼輸出如下所示:
Instance Normalization
input data:
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.]]],
[[[1., 1.],
[1., 1.]],
[[2., 2.],
[2., 2.]],
[[3., 3.],
[3., 3.]]]]) shape is torch.Size([3, 3, 2, 2])
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.]]],
[[[0., 0.],
[0., 0.]],
[[0., 0.],
[0., 0.]],
[[0., 0.],
[0., 0.]]]])
因爲IN是基於通道計算均值和方差,因此得到的output的均值和方差爲零。
2.3 Group Normalization(GN)
起因:小batch樣本中,BN估計的均值和方差不準確;
思路:數據不夠,通道來湊;
注意事項:
- 不再有running_mean和running_var;
- gamma和beta爲逐通道(channel)的;
應用場景:大模型(小batch size)任務;大模型任務佔據很大的內存,batch_size只能很小;
如圖,batch_size非常少,如果用BN,因爲batch_size非常小導致估計的均值和方差不準確,會導致BN失效。GN會在通道上進行分組,再基於分組後得到的數據計算均值和方差。
2.3.1 nn.GroupNorm
主要參數:
- num_groups:分組數,通常爲2的n次方;
- num_channels:通道數(特徵數);
- eps:分母修正項;
- affine:是否需要affine transform;
nn.GroupNorm(num_groups,num_channels,eps=1e-5,affine=True)
下面觀察一下GN在代碼中的使用情況:
batch_size = 2
num_features = 4
num_groups = 2 # 3 Expected number of channels in input to be divisible by num_groups
features_shape = (2, 2)
feature_map = torch.ones(features_shape) # 2D
feature_maps = torch.stack([feature_map * (i + 1) for i in range(num_features)], dim=0) # 3D
feature_maps_bs = torch.stack([feature_maps * (i + 1) for i in range(batch_size)], dim=0) # 4D
gn = nn.GroupNorm(num_groups, num_features)
outputs = gn(feature_maps_bs)
print("Group Normalization")
print(gn.weight.shape)
print(outputs[0])
代碼輸出爲:
Group Normalization
torch.Size([4])
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]]], grad_fn=<SelectBackward>)
3、Normalization小結
BN、LN、IN和GN都是爲了克服Internal Covariate Shift(ICS)