结论写在前。Pytorch线性层采取的默认初始化方式是Kaiming初始化,这是由我国计算机视觉领域专家何恺明提出的。我的探究主要包括:
采取固定的分布?
当考虑怎么初始化权重矩阵这个问题时,可以想到应该使得初始权重具有随机性。提到随机,自然的想法是使用均匀分布或正态分布,那么我们如果采用与模型无关的固定分布(例如标准正态分布(均值为0,方差为1))怎么样?下面我们分析如果对模型本身不加考虑,采取固定的分布,会有什么问题:
这里举一个例子,假如一个网络使用了多个sigmoid作为中间层(这个函数具有两边导数趋于0的特点):
前面的问题提示我们要根据模型的特点(维度,规模)决定使用的随机化方法(分布的均值、方差),xavier初始化应运而生,它可以使得输入值经过网络层后方差不变。pytorch中这一点是通过增益值gain来实现的,下面的函数用来获得特定层的gain:
torch.nn.init.calculate_gain(nonlinearity, param=None)
增益值表(图片摘自https://blog.csdn.net/winycg/article/details/86649832)
Xavier初始化可以采用均匀分布 U(-a, a),其中a的计算公式为:
a = g a i n × 6 f a n _ i n + f a n _ o u t a = gain \times \sqrt[]{\frac{6}{fan\_in+fan\_out}} a=gain×fan_in+fan_out6
Xavier初始化可以采用正态分布 N(0, std),其中std的计算公式为:
s t d = g a i n × 2 f a n _ i n + f a n _ o u t std = gain \times \sqrt[]{\frac{2}{fan\_in+fan\_out}} std=gain×fan_in+fan_out2
其中fan_in和fan_out分别是输入神经元和输出神经元的数量,在全连接层中,就等于输入输出的feature数。
Xavier初始化在Relu层表现不好,主要原因是relu层会将负数映射到0,影响整体方差。所以何恺明在对此做了改进提出Kaiming初始化,一开始主要应用于计算机视觉、卷积网络。
Kaiming均匀分布的初始化采用U(-bound, bound),其中bound的计算公式为:(a 的概念下面再说)
b o u n d = 6 ( 1 + a 2 ) × f a n _ i n bound = \sqrt[]{\frac{6}{(1 + a ^2) \times fan\_in}} bound=(1+a2)×fan_in6
这里补充一点,pytorch中这个公式也通过gain作为中间变量实现,也就是:
b o u n d = g a i n × 3 f a n _ i n bound = gain \times \sqrt[]{\frac{3}{ fan\_in}} bound=gain×fan_in3
其中:
g a i n = 2 1 + a 2 gain = \sqrt{\frac{2}{1 + a^2}} gain=1+a22
Kaiming正态分布的初始化采用N(0,std),其中std的计算公式为:
s t d = 2 ( 1 + a 2 ) × f a n _ i n std = \sqrt[]{\frac{2}{(1 + a ^2) \times fan\_in}} std=(1+a2)×fan_in2
这里稍微解释一下a的含义,源码中的解释为
the negative slope of the rectifier used after this layer
简单说,是用来衡量这一层中负数比例的,负数越多,Relu层会将越多的输入“抹平”为0,a用来平衡这种“抹平”对于方差的影响。
pytorch的线性层进行的默认初始化的例子:
fc1 = torch.nn.Linear(28 * 28, 256)
在Linear类中通过
self.reset_parameters()
这个函数来完成随机初始化的过程,后者使用的是
init.kaiming_uniform_(self.weight, a=math.sqrt(5))
可见是我们前面提到的Kaiming均匀分布的初始化方式,这个函数的内容和前面的公式相符(使用gain作为中间变量):
fan = _calculate_correct_fan(tensor, mode)
gain = calculate_gain(nonlinearity, a)
std = gain / math.sqrt(fan)
bound = math.sqrt(3.0) * std # Calculate uniform bounds from standard deviation
with torch.no_grad():
return tensor.uniform_(-bound, bound)
同时将参数a 的值设置为5。
简单起见,我没有按照pytorch的封装方法分层实现初始化过程,后者主要为了提供多种不同的初始化方式。我直接按照线性层默认的初始方式——Kaiming均匀分布的公式用numpy实现了get_torch_initialization,其中a值取5, 代码如下:
def get_torch_initialization(numpy = True):
a = 5
def Kaiming_uniform(fan_in,fan_out,a):
bound = 6.0 / (1 + a * a) / fan_in
bound = bound ** 0.5
W = np.random.uniform(low=-bound, high=bound, size=(fan_in,fan_out))
return W
W1 = Kaiming_uniform(28 * 28, 256, a)
W2 = Kaiming_uniform(256, 64, a)
W3 = Kaiming_uniform(64, 10, a)
return W1,W2,W3