1 介绍
在使用CNN搭建目标检测模型时,有一个很重要的步骤就是需要进行权重初始化,那么为什么需要进行权重初始化呢?
2 权重初始化的原因
关于为什么要进行权重初始化,请阅读知乎文章《神经网络中的权重初始化一览:从基础到Kaiming》,以下简称为《初始化概览》;
原因一:防止深度神经网络在正向(前向)传播过程中层激活函数的输出损失梯度出现爆炸或消失
如果发生任何一种情况,梯度值太大或太小,就无法有效地向后传播,并且即便可以向后传播,网络也需要花更长时间来达到收敛。
猜想1:矩阵乘法的连乘会导致数值爆炸
《初始化概览》中提到:让我们假设有一个没有激活函数的简单的100层网络,并且每层都有一个包含这层权重的矩阵a。为了完成单个正向传播,我们必须对每层输入和权重进行矩阵乘法,总共100次连续的矩阵乘法。……呃!在这100次矩阵乘法某次运算中,层输出变得非常大,甚至计算机都无法识别其标准差和均值。
这里实际上模拟的是FC层中的矩阵乘法,我们复现了这个实验,结果如下:
实验代码请参考【exp_multiple_continued _matrix_multiplication】(check whether it would be blocked)
这说明矩阵乘法连乘是会导致数值爆炸的;
原因二:权重初始化使训练稳定(收敛加快)
既然不好的权重初始化会导致梯度消失等问题,那么好的初始化就会有助于收敛!
此原因来自于李沐老师的课程《14 数值稳定性 + 模型初始化和激活函数【动手学深度学习v2】》 权重初始化使训练稳定,其实现方式主要是使模型参数在训练过程中数值范围更加稳定,
李沐老师:“可以提高数值稳定性”。
这里可以看看有三老师的课件,
3 猜想:如何生成好的权重分布
在学习权重初始化时,我们一直在想一个奇怪的问题:为什么现在常用的初始化方法都是使用一些类似随机的分布,例如:随机分布或者正态分布,而不是使用一些类似确定性的初始化方法,例如:傅里叶变换基或者小波变换基呢?
后来想了一下,感觉还是不太容易实现的,
对于向量表示来说,我们期望找到的是尽可能“分布均匀”的一组向量表示,
在数学上来说,就是希望任意两个向量的点积的最大值尽可能小,也就是,
其中,为取两向量夹角的函数,,为需要生成向量的个数;
(这个跟正交基不太一样,正交基是希望两个向量的点积为0)
我之前以为卷积层的权重都是超定的,后来发现并不是,这里以ShuffleNetV2为例:
首先来看看论文中给出的通道结构图,
- Stage1Stage2存在维数超定的情况,,可以看到在Stage1Stage2的过程中,
shufflenetv2_2x
Stage2的维数是244,于是是维数超定的; - 一般维数是欠定的,例如前面的
0.5x
、1x
、1.5x
Stage2维数都小于216,都是欠定的情况。
3.1 总结
无法实现的原因,主要原因是:目前无法找到一个高效率()的算法,来快速生成这样一组分布尽可能均匀的向量。
所以目前使用的一个比较简单容易实现的方法:利用随机分布。
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,其数学公式如下
其中,表示标准正态分布(Standard Normal Distribution)的概率密度函数,
6 函数写作模板
这里我们参考李沐老师在课程中讲述的初始化模板进行写作,
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%左右;
训练之后的结果如下图所示,
训练后的精度可以达到23%,不过这个跟使用torch默认初始化设置的结果差远了,
为了达到这个结果,我做了几项“艰辛”的设置:
- 训练epoch数加到42:加长训练周期总会帮助模型渐渐收敛;
- batch-size加到16:大的batch-size有助于收敛;
- lr加到0.002:学习率随batch-size提高而增长,使用了“初始学习率线性提升策略”;
Colab代码:【Torch_exp_parameters_all_042】
“训练epoch数增加”会大大延长训练时间,调参实验也花去了很多时间,尽管如此,才把精度提升到23%,说明了参数初始化算法的重要性!
最终证明如下结论:
在参数初始化时卷积核每个通道是可以设置成一样的数值的,甚至于卷积层和全连接层中的每一个参数都可以设置为一样的值,不过这样的设置会使网络的收敛变得极为困难,所以这样的参数初始化是不可取的。