1 介绍

在使用CNN搭建目标检测模型时,有一个很重要的步骤就是需要进行权重初始化,那么为什么需要进行权重初始化呢?

2 权重初始化的原因

关于为什么要进行权重初始化,请阅读知乎文章《神经网络中的权重初始化一览:从基础到Kaiming》,以下简称为《初始化概览》;

原因一:防止深度神经网络在正向(前向)传播过程中层激活函数的输出损失梯度出现爆炸或消失

如果发生任何一种情况,梯度值太大或太小,就无法有效地向后传播,并且即便可以向后传播,网络也需要花更长时间来达到收敛。

猜想1:矩阵乘法的连乘会导致数值爆炸

《初始化概览》中提到:让我们假设有一个没有激活函数的简单的100层网络,并且每层都有一个包含这层权重的矩阵a。为了完成单个正向传播,我们必须对每层输入和权重进行矩阵乘法,总共100次连续的矩阵乘法。……呃!在这100次矩阵乘法某次运算中,层输出变得非常大,甚至计算机都无法识别其标准差和均值。

这里实际上模拟的是FC层中的矩阵乘法,我们复现了这个实验,结果如下:

卷积核运算的权重 卷积核和权重_神经网络


实验代码请参考【exp_multiple_continued _matrix_multiplication】(check whether it would be blocked)

这说明矩阵乘法连乘是会导致数值爆炸的;

原因二:权重初始化使训练稳定(收敛加快)

既然不好的权重初始化会导致梯度消失等问题,那么好的初始化就会有助于收敛!
此原因来自于李沐老师的课程《14 数值稳定性 + 模型初始化和激活函数【动手学深度学习v2】》 权重初始化使训练稳定,其实现方式主要是使模型参数在训练过程中数值范围更加稳定,

李沐老师:“可以提高数值稳定性”。

这里可以看看有三老师的课件,

卷积核运算的权重 卷积核和权重_卷积核运算的权重_02

3 猜想:如何生成好的权重分布

在学习权重初始化时,我们一直在想一个奇怪的问题:为什么现在常用的初始化方法都是使用一些类似随机的分布,例如:随机分布或者正态分布,而不是使用一些类似确定性的初始化方法,例如:傅里叶变换基或者小波变换基呢?

后来想了一下,感觉还是不太容易实现的,

对于向量表示来说,我们期望找到的是尽可能“分布均匀”的一组向量表示,

在数学上来说,就是希望任意两个向量的点积的最大值尽可能小,也就是,

卷积核运算的权重 卷积核和权重_深度学习_03

其中,卷积核运算的权重 卷积核和权重_深度学习_04为取两向量夹角的函数,卷积核运算的权重 卷积核和权重_神经网络_05卷积核运算的权重 卷积核和权重_深度学习_06为需要生成向量的个数;

(这个跟正交基不太一样,正交基是希望两个向量的点积为0)

我之前以为卷积层的权重都是超定的,后来发现并不是,这里以ShuffleNetV2为例:

首先来看看论文中给出的通道结构图,

卷积核运算的权重 卷积核和权重_神经网络_07

  1. Stage1卷积核运算的权重 卷积核和权重_神经网络_08Stage2存在维数超定的情况,卷积核运算的权重 卷积核和权重_神经网络_09,可以看到在Stage1卷积核运算的权重 卷积核和权重_神经网络_08Stage2的过程中,shufflenetv2_2xStage2的维数是244,于是是维数超定的;
  2. 一般维数是欠定的,例如前面的0.5x1x1.5xStage2维数都小于216,都是欠定的情况。

3.1 总结

无法实现的原因,主要原因是:目前无法找到一个高效率(卷积核运算的权重 卷积核和权重_卷积核运算的权重_11)的算法,来快速生成这样一组分布尽可能均匀的向量。
所以目前使用的一个比较简单容易实现的方法:利用随机分布

4 PyTorch模块默认初始化方法

Conv2d: init.kaiming_uniform_

Conv2d默认使用的是Kaiming初始化,其代码如下:

def reset_parameters(self) -> None:
        # Setting a=sqrt(5) in kaiming_uniform is the same as initializing with
        # uniform(-1/sqrt(k), 1/sqrt(k)), where k = weight.size(1) * prod(*kernel_size)
        # For more details see: https://github.com/pytorch/pytorch/issues/15314#issuecomment-477448573
        init.kaiming_uniform_(self.weight, a=math.sqrt(5))
        if self.bias is not None:
            fan_in, _ = init._calculate_fan_in_and_fan_out(self.weight)
            bound = 1 / math.sqrt(fan_in)
            init.uniform_(self.bias, -bound, bound)

5 常用权重初始化方法

5.1 torch.nn.init.trunc_normal_()——截断正态分布

trunc_normal一个十分知名的应用就是Vit,所以还是很厉害的;
在看这个初始化函数之前,我们先来回顾一下trunc_normal的数学公式,(这里我们参考的是佛罗里达州立大学开源的 Truncated Normal Distribution的教材——《The Truncated Normal Distribution》by John Burkardt, Florida State University);

Truncated Normal Distribution

对于 Truncated Normal Distribution,其数学公式如下
卷积核运算的权重 卷积核和权重_机器学习_12
其中,卷积核运算的权重 卷积核和权重_卷积核运算的权重_13表示标准正态分布(Standard Normal Distribution)的概率密度函数,

6 函数写作模板

这里我们参考李沐老师在课程中讲述的初始化模板进行写作,

卷积核运算的权重 卷积核和权重_卷积核运算的权重_14

7 为什么权重初始化要使用正态分布?

其实这个问题很难回答的,我们不妨从最简单的一步步来看:

7.1 初始化可以将卷积核权重全部设为0吗?

这个显然是不行,如果“权重全部设为0”,那么特征图输出全为0,那么后面的梯度根本无法传递,因为这一层的输出都是0,就没有梯度了
那么我们继续发散一下,

7.2 可以将卷积核每个通道都设置成一样吗?

这里我们需要通过实验来验证一下,为了使实验更加简单,这里我们选择的是“CIFAR10_world”数据集,(这也是《Deep Learning with PyTorch: A 60 Minute Blitz | Training an image classifier》教程中使用的数据集);

实验说明:这里我们会将所有参数都设置为相同的值0.0042,然后观察网络是否能够收敛,在没有训练的情况下,精度大概在10%左右;

训练之后的结果如下图所示,

卷积核运算的权重 卷积核和权重_神经网络_15


训练后的精度可以达到23%,不过这个跟使用torch默认初始化设置的结果差远了,

为了达到这个结果,我做了几项“艰辛”的设置:

  1. 训练epoch数加到42:加长训练周期总会帮助模型渐渐收敛;
  2. batch-size加到16:大的batch-size有助于收敛;
  3. lr加到0.002:学习率随batch-size提高而增长,使用了“初始学习率线性提升策略”;

Colab代码:【Torch_exp_parameters_all_042
“训练epoch数增加”会大大延长训练时间,调参实验也花去了很多时间,尽管如此,才把精度提升到23%,说明了参数初始化算法的重要性!
最终证明如下结论
在参数初始化时卷积核每个通道是可以设置成一样的数值的,甚至于卷积层和全连接层中的每一个参数都可以设置为一样的值,不过这样的设置会使网络的收敛变得极为困难,所以这样的参数初始化是不可取的