归一化层及在模型中的使用

在卷积神经网络CNN中,如BN、GN等归一化层得到了普遍使用。这些层之间有什么区别、分别在什么情况下使用我一直是模棱两可,现在特意整理记录一下,搞清楚这些层的原理和区别对设计模型网络也大有裨益。

此外,本文将特意花一些篇幅探讨对各种归一化方法来说,其前边的卷积层是否需要bias,这部分内容在本文第二部分

归一化层区分

归一化用于解决深度学习中的内部协变量移位(internal covariate shift)现象,可以用于缓解梯度爆炸、提高模型收敛速度,同时能起到正则化的作用,防止过拟合。

目前的主流归一化层有Batch Norm(BN)、Layer Norm(LN)、Instance Norm(IN)、Group Norm(GN)。其区别可以参考下图,这张图来自于论文Group Normalization[1]。

norms

归纳起来说,无论是哪一种归一化,本质上都是将特征图分为若干个部分,分别对每个部分做归一化使其数值范围符合特定的正态分布,所以对第i个部分的归一化均可以用下式表示: 不同归一化的区别仅在于如何划分特征图。

定义有一个特征图,我们要对这个特征图做不同的归一化。

Batch Norm(BN)[2]

BN的应用应该说是最广的,但是其对batchsize的大小很敏感,只建议在batchsize不低于8的时候使用。

BN逐通道地对整个batch的特征图做归一化,也就是说每次做归一化的特征图维度为。下边通过代码表示一下计算均值和方差的过程:

1
2
3
# BN
mean = x.mean(dim=(0,2,3)) # shape: [C,]
std = x.std(dim=(0,2,3)) # shape: [C,]

Layer Norm(LN)[3]

LN多用于RNN,在卷积神经网络上很少使用。

与BN相反,LN逐输入地对所有通道的特征图做归一化,每次归一化的特征图维度为,代码表示为:

1
2
3
# LN
mean = x.mean(dim=(1,2,3)) # shape: [N,]
std = x.std(dim=(1,2,3)) # shape: [N,]

Instance Norm(IN)[4]

IN主要用于生成式模型,如基于GAN的图像生成、图像风格迁移等。

IN可以看作是BN或者LN的特例(时的BN或者时的LN),其只对单个特征图做归一化,单特征图维度为,代码表示为:

1
2
3
# IN
mean = x.mean(dim=(2,3)) # shape: [N. C]
std = x.std(dim=(2,3)) # shape: [N, C]

Group Norm(GN)

由于GN的性能不受batchsize影响,在batchsize比较小的时候,可以用GN代码BN。

GN将特征图沿通道均分为组,分别为每一组做归一化,每次归一化的特征图维度为,代码表示为:

1
2
3
4
5
6
7
# GN
x_groups = x.chunk(G, dim=1)
x = torch.cat(x_groups, dim=0) # shape: [N*G, C//G, H, W]
mean = x.mean(dim=(1,2,3)) # shape: [N*G,]
mean = mean.view(N, G) # shape: [N, G]
std = x.std(dim=(1,2,3)) # shape: [N*G,]
std = std.view(N, G) # shape: [N, G]

从卷积说起——是否需要bias?

我们在设计模型结构的时候应该经常看到类似下边的代码:

1
2
3
4
5
6
7
8
9
10
class BaseConv(nn.Module):
def __init__(self, in_channels, out_channels) -> None:
super().__init__()
self.conv = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=2, padding=1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True)
)
def forward(self, x):
return self.conv(x)

nn.Conv2d中其他参数都很熟悉,bias=False是为什么呢?

本节将探讨这一问题,解决什么归一化不需要bias、为什么不需要的问题。

卷积实现

在pytorch中,卷积通过类 nn.Conv2d()实现,卷积层的参数有权重(weight)和偏置(bias)

定义一个卷积层,其输入通道数为,输出通道数为,卷积核尺寸为

1
conv = nn.Conv2d(C_in, C_out, kernel_size=k, stride=2, padding=1)

那么有

也就是说,对于每个输出层来说,都有一个维度为的卷积核在输入特征图上滑动计算,计算的结果再与一个偏置值相加得到。所以说直接作用在通道方向上,逐通道地加。

逐通道是不是很熟悉?没错,BN也是逐通道的,那么在使用BN的情况下,即使加了bias最后也会在减去均值的时候被去掉,因此,这时候添加bias将不会产生任何作用,反而白白占用显存和运算量。

数学推理证明

我们可以以BN为例推导一下这个过程。为简化过程,只考虑输出特征图的一个通道特征图。那么可以如下计算: 归一化的首先将特征转换为标准正态分布,这个过程计算为: 从过程中可以很明显看到,偏差在计算过程中被完全消去了。

实际上,如果我们分别对LN、IN和GN都按上式计算的话就会发现,bias对于IN来说也是没有意义的,但是对于LN和GN则有意义。

结论

当归一化在与通道垂直的方向上做(逐通道)时,就不应该添加bias。如果模型中使用BN和IN,那么都不应该加bias,而GN和LN则应该加bias。

reference

[1]. Yuxin Wu and Kaiming He. Group Normalization. In ECCV, 2018.

[2]. Sergey Ioffe and Christian Szegedy. Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift. In ICML, 2015.

[3]. Lei Jimmy Ba, Jamie Ryan Kiros, and Geoffrey E. Hinton. Layer normalization. arXiv:1607.06450, 2016.

[4]. Dmitry Ulyanov, Andrea Vedaldi and Victor S. Lempitsky. Instance normalization: The missing ingredient for fast stylization. arXiv:1607.08022, 2016.