深度学习系列博文目录一览
————————————————

1 权重初始化


1.1 为什么需要权重初始化

  • ① 为了使神经网络在合理的时间内收敛.

  • ② 为了尽量避免在深度神经网络的正向(前向)传播过程中层激活函数的输出梯度出现爆炸或消失

1.2 如何进行初始化

如果将每个隐藏单元的参数都初始化为0.

那么在正向传播时每个隐藏单元将对于相同的输入会计算出相同的值,并传递至输出层。在反向传播中,每个隐藏单元的参数梯度值相等。因此,这些参数在使用基于梯度的优化算法迭代后值依然相等。这样所有隐层的单元都是对称的了,很难学到什么有效的信息,之后的迭代也是如此。

所以权重初始化不能将权重参数全部初始化为0,应打破这种对称性。

如果将参数都初始化为0周围极小的值

也不好,比如 如果用 sigmoid 做激活函数,它在0周围是近似线性的,如果我们的参数都初始化为0附近,那么可能数据经过神经元之后,输出值也在0附近,这样大部分都落在激活函数线性区,那么激活函数引入非线性的作用将被削减。

如果参数都初始化为较大的值,很显然容易使得输出落入饱和区。

所以合理的初始化时比较重要的,一般采用随机的初始化。

1.3 常见的几种初始化方式

均匀分布初始化:
将参数初始化为 均匀分布U(a,b) 上的随机值,pytorch的实现方案:

torch.nn.init.uniform_(tensor, a=0, b=1)

高斯初始化:
将参数初始化为 高斯分布N(0,1) 上的随机值,pytorch的实现方案:

torch.nn.init.normal_(tensor, mean=0, std=1)

常数初始化:
将参数初始化为一个 固定的常数val, pytorch实现方案为:

torch.nn.init.constant_(tensor, val)

上面三种初始化方法其实并不是十分理想的初始化方案,因为如果网络层数过深的话,依然会导致 梯度爆炸 或者 梯度弥散的 情况出现,
一般在使用中,可能就是神经网络中的某一两层可能会用到上述初始化方案,比如自己写的某几层,并不建议对所有的参数都使用上述初始化方案。对于一些较为普及的模型,比如VGG、ResNet等,往往会加载官方提供的预训练模型的参数值,对大网络进行参数初始化

1.4 Xavier 初始化方案

Xavier 初始化的基本思想是,若对于一层网络的输入和输出的方差尽量不变,这样就可以避免输出趋向于0,从而避免梯度弥散情况。

参考资料:
链接1
链接2
链接3

对于 $y=wx$ , 根据假设有:

当 $X, w_i$ 都符合均值为 0 的正态分布时,其期望 $E(X), E(w_i)$ 均为0, 则上式可以简化为:

若该层共有$n_i$个神经元,则有:

要使得$Var(Y) = Var(X)$, 则需要 $n_i * Var(W_i) = 1$, 则 $ Var(w_i) = 1/n_i$;

同理,当反向传播时, $Var(w_i) = 1/n_{i+1}$

因此,综合正向和反向传播考虑,最终取 $Var(w_i) = 1 / \frac{(n_i+n_{i+1})}{2} = 2/(n_i+n_{i+1})$

如果使用均匀分布U(a,b) 为基础进行初始化,则:

解得: $ a= -b = -\sqrt{6/(n_i+n_{i+1})} $

即$w_i$ 初始化为 $ U(-\sqrt{\frac{6}{(n_i+n_{i+1})}}, \sqrt{\frac{6}{(n_i+n_{i+1})}}) $

pytorch的实现方案①, 以均匀分布为基础

torch.nn.init.xavier_uniform_(tensor, gain=1)

将参数初始化为均匀分布 $U(-\alpha, \alpha) $上的随机值,其中 $\alpha$ 取值为:$ \alpha = gain * \sqrt{\frac{6}{(fan_{in} + fan_{out})}}$

  • fan_in 指输入端神经元的个数, fan_out 指输出端神经元的个数,pytorch它会自己根据网络计算。
  • gain 是一个增益, 默认为1;

pytorch的实现方案②, 以高斯分布为基础

torch.nn.init.xavier_normal_(tensor, gain=1)

将参数初始化为高斯分布 $N(0, std^2)$上的随机值,而 std 的取值是:$std = gain * \sqrt{\frac{2}{(fan_{in} + fan_{out})}}$

1.5 Kaiming初始化

Kaiming初始化基本思想是,当使用ReLU做为激活函数时,Xavier的效果不好。
原因在于:当ReLU的输入小于0时,其输出为0,影响了输出的分布模式。也就是激活函数输出均值不为0,我们上面的推导中是有假设E(X)=0的。

因此Kaiming He初始化,在Xavier的基础上,假设每层网络有一半的神经元被关闭,于是其分布的方差也会变小。所以提出一个新的假设,那就是前向传播中,如果采用Xiaver,方差会缩小为原来一半。那么为了修正这个缩减,就使得参数初始化的范围比原来扩大一倍。

pytorch的实现方案①, 以均匀分布为基础

torch.nn.init.kaiming_uniform_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu')

将参数初始化为均匀分布 $U(-bound, bound)$上的随机值,其中 bound 取值为

$bound = \sqrt{\frac{6}{(1+\alpha^2)*fan_{in} }}$

  • $\alpha$ : 该层后面一层的激活函数中负的斜率 (0 for ReLU by default)
  • 使用 mode = fan_in 保持 weights 的方差在前向传播中不变;
  • 使用 mode = fan_out 保持weights 的方差在反向传播中不变。

pytorch的实现方案②, 以高斯分布为基础

torch.nn.init.kaiming_normal_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu')

将参数初始化为高斯分布 $N(0, std^2)$上的随机值,而std的取值是:$std = \sqrt{\frac{2}{(1+\alpha^2)*fan_{in} }}$

1.6 pytorch常见的 权重初始化 操作方法

  • 方案一:直接对某层的data进行赋值
self.conv1.weight.data.normal_(0, 0.01)
self.conv1.bias.data.zero_()
  • 方案二:调用nn初始化模块:
torch.nn.init.normal_(self.conv1.weight, std=0.01)
torch.nn.init.constant_(self.conv1.bias, 0)

可搭配for循环使用:

for layer in self.children():
torch.nn.init.normal_(layer.weight, std=0.01)
torch.nn.init.constant_(layer.bias, 0)
  • 方案三: 定义一个自己的小函数
def _weight_init(m):
classname = m.__class__.__name__
if classname.find('Conv') != -1:
torch.nn.init.xavier_(m.weight)
torch.nn.init.xavier_(m.bias)
if classname.find('Linear') != -1:
torch.nn.init.normal_(m.weight, std=0.01)
torch.nn.init.constant_(m.bias, 0)

#或者不读取下划线,直接看是不是某个类,这种方法相对来说更安全一些
def _weights_init(m):
if isinstance(m, torch.nn.Conv2d):
torch.nn.xavier_(m.weight)
torch.nn.xavier_(m.bias)
if isinstance(m, torch.nn.Linear):
torch.nn.init.normal_(m.weight, std=0.01)
torch.nn.init.constant_(m.bias, 0)

# 然后对某个Net应用:
Net.apply(_weight_init)

以上三个均为示例,具体使用情况请依据自己的网络和具体的代码,请勿生搬硬套。</font>
.