在深度学习中,神经网络的权重初始化方法( w e i g h t weight weight i n i t i a l i z a t i o n initialization initialization)对模型的收敛速度和性能有着至关重要的影响。说白了,神经网络其实就是对权重参数 w w w的不停迭代更新,以达到更好的性能。因此,对权重 w w w的初始化则显得至关重要,一个好的权重初始化虽然不能完全解决梯度消失和梯度爆炸的问题,但是对于处理这两个问题是有很大的帮助的,并且十分有利于模型性能和收敛速度。
本文将介绍以下五种常见的权重初始化的方法:
权重初始化为
0
0
0
权重随机初始化
X
a
v
i
e
r
Xavier
Xavier
i
n
i
t
i
a
l
i
z
a
t
i
o
n
initialization
initialization
H
e
He
He
i
n
i
t
i
a
l
i
z
a
t
i
o
n
initialization
initialization
预训练权重
权重初始化为
0
0
0
如果将权重初始化全部为 0 0 0的话,这样的操作等同于等价于一个线性模型,将所有权重设为 0 0 0时,对于每一个 w w w而言,损失函数的导数都是相同的,因此在随后的迭代过程中所有权重都具有相同的值,这会使得隐藏单元变得对称,并继续运行设置的 n n n次的迭代,会导致网络中同一个神经元的不同权重都是一样的。下面代码为权重初始化为 0 0 0的代码:
def initialize_parameters_zeros(layers_dims):
"""
Arguments:
layer_dims -- python array (list) containing the size of each layer.
Returns:
parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
W1 -- weight matrix of shape (layers_dims[1], layers_dims[0])
b1 -- bias vector of shape (layers_dims[1], 1)
...
WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1])
bL -- bias vector of shape (layers_dims[L], 1)
"""
parameters = {}
np.random.seed(3)
L = len(layers_dims) # number of layers in the network
for l in range(1, L):
parameters['W' + str(l)] = np.zeros((layers_dims[l], layers_dims[l - 1]))
parameters['b' + str(l)] = np.zeros((layers_dims[l], 1))
return parameters
让我们来看看权重初始化为
0
0
0之后其
c
o
s
t
cost
cost
f
u
n
c
t
i
o
n
function
function是如何变化的,从图中可以看出,当代价函数降到
0.64
0.64
0.64(迭代
1000
1000
1000次)后,梯度逐渐消失,再训练迭代已经不起什么作用了。
权重随机初始化
权重随机初始化是比较常见的做法,即 W W W随机初始化。随机初始化的代码如下:
def initialize_parameters_random(layers_dims):
"""
Arguments:
layer_dims -- python array (list) containing the size of each layer.
Returns:
parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
W1 -- weight matrix of shape (layers_dims[1], layers_dims[0])
b1 -- bias vector of shape (layers_dims[1], 1)
...
WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1])
bL -- bias vector of shape (layers_dims[L], 1)
"""
np.random.seed(3) # This seed makes sure your "random" numbers will be the as ours
parameters = {}
L = len(layers_dims) # integer representing the number of layers
for l in range(1, L):
parameters['W' + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1])*0.01
parameters['b' + str(l)] = np.zeros((layers_dims[l], 1))
return parameters
上述代码中权重乘
0.01
0.01
0.01是因为要把
W
W
W随机初始化到一个相对较小的值,因为如果
X
X
X很大的话,
W
W
W又相对较大,会导致
Z
Z
Z非常大,这样如果激活函数是
s
i
g
m
o
i
d
sigmoid
sigmoid,就会导致
s
i
g
m
o
i
d
sigmoid
sigmoid的输出值
1
1
1或者
0
0
0,然后会导致一系列问题(比如
c
o
s
t
cost
cost
f
u
n
c
t
i
o
n
function
function计算的时候,
l
o
g
log
log里是
0
0
0,这样会有点麻烦)。随机初始化后,
c
o
s
t
cost
cost
f
u
n
c
t
i
o
n
function
function随着迭代次数的变化示意图如下图
2
2
2所示为
能够看出,
c
o
s
t
cost
cost
f
u
n
c
t
i
o
n
function
function的变化是比较正常的。但是随机初始化也有缺点,
n
p
.
r
a
n
d
o
m
.
r
a
n
d
n
(
)
np.random.randn()
np.random.randn()其实是一个均值为
0
0
0,方差为
1
1
1的高斯分布中采样。当神经网络的层数增多时,会发现越往后面的层的激活函数(使用
t
a
n
H
tanH
tanH)的输出值几乎都接近于
0
0
0,极易出现梯度消失。如下图
3
3
3所示:
X a v i e r Xavier Xavier i n i t i a l i z a t i o n initialization initialization
在使用以上两种方法来初始化权重极易出现梯度消失的问题,而 X a v i e r Xavier Xavier i n i t i a l i z a t i o n initialization initialization出现就解决了上面问题。其思想倒就是尽可能的让输入和输出服从相同的分布,这样就能够避免后面层的激活函数的输出值趋向于 0 0 0。本文主要介绍 P y t o r c h Pytorch Pytorch当中 X a v i e r Xavier Xavier均匀分布和 X a v i e r Xavier Xavier正态分布初始化这两种方式。
我们来简单推导一下 X a v i e r Xavier Xavier初始化的原理:首先我们定义一层的卷积运算为如下公式,其中 n i {n_i} ni表示输入的个数。
y = w 1 x 1 + ⋅ ⋅ ⋅ + w n i x n i + b y = w_1x_1 + ··· + w_{ni}x_{ni}+ b y=w1x1+⋅⋅⋅+wnixni+b
根据我们学过的概率论统计知识可以得到如下的方差公式:
V a r ( w i x i ) = E [ w i ] 2 V a r ( x i ) + E [ x i ] 2 V a r ( w i ) + V a r ( w i ) V a r ( x i ) Var(w_ix_i)=E[w_i]^2Var(x_i) + E[x_i]^2Var(w_i) + Var(w_i)Var(x_i) Var(wixi)=E[wi]2Var(xi)+E[xi]2Var(wi)+Var(wi)Var(xi)
当我们假设输入和输入的权重的均值都是 0 0 0(使用BN层以后很容易就可以满足)时,上式可以简化为:
V a r ( w i x i ) = V a r ( w i ) V a r ( x i ) Var(w_ix_i)=Var(w_i)Var(x_i) Var(wixi)=Var(wi)Var(xi)
进一步我们假设输入 x x x和权重 w w w都是独立同分布,则可以得到:
V a r ( y ) = n i V a r ( w i ) V a r ( x i ) Var(y) = n_iVar(w_i)Var(x_i) Var(y)=niVar(wi)Var(xi)
于是按照 X a v i e r Xavier Xavier的要求保证输入和输出的方差要一致,则可以得到:
V a r ( w i ) = 1 n i Var(w_i) = \frac{1}{n_i} Var(wi)=ni1
对于一个多层的网络,某一层的方差可以用累积的形式表达:
V a r [ z i ] = V a r [ x ] ∏ i = 0 i − 1 n i V a r [ W i ‘ ] Var[z^i] = Var[x]\prod_{i^{}=0}^{i-1}n_i^Var[W^{i^`}] Var[zi]=Var[x]i=0∏i−1niVar[Wi‘]
对于误差反向传播也有类似的表达形式,如下所示,其中 n i + 1 n_{i+1} ni+1表示输出个数
V a r [ ∂ C o s t ∂ s i ] = V a r [ ∂ C o s t ∂ s d ] ∏ i = i d n i + 1 V a r [ W i ‘ ] Var[\frac{\partial Cost}{\partial s^i}] = Var[\frac{\partial Cost}{\partial s^d}]\prod_{i^{}=i}^{d}n_{i^+1}Var[W^{i^`}] Var[∂si∂Cost]=Var[∂sd∂Cost]i=i∏dni+1Var[Wi‘]
综上,为了保证前向传播和反向传播时每一层的方差一致,应满足:
∀ i , n i V a r [ W i ] = 1 \forall_i ,n_iVar[W^i]=1 ∀i,niVar[Wi]=1
∀ i , n i + 1 V a r [ W i ] = 1 \forall_i ,n_{i+1}Var[W^i]=1 ∀i,ni+1Var[Wi]=1
但是,实际当中输入与输出的个数往往不相等,于是为了均衡考量输出和输入,最终我们的权重的方差应满足如下要求:
∀ i , V a r [ W i ] = 2 n i + n i + 1 \forall_i ,Var[W^i]= \frac{2}{n_i + n_{i+1}} ∀i,Var[Wi]=ni+ni+12
1、 X a v i e r Xavier Xavier均匀分布初始化
对于均匀分布来说,我们都知道区间 [ a , b ] [a,b] [a,b]的方差为:
V a r = ( b − a ) 2 12 Var=\frac{(b-a)^2}{12} Var=12(b−a)2
那么就需要将均匀分布的方差等于我们在上面推导出来的 X a v i e r Xavier Xavier的权重方差,即:
( b − a ) 2 12 = 2 n i + n i + 1 \frac{(b-a)^2}{12} = \frac{2}{n_i + n_{i+1}} 12(b−a)2=ni+ni+12
经过化解后 ( a + b = 0 ) (a+b=0) (a+b=0)可以得到 X a v i e r Xavier Xavier均匀初始化后权重的取值范围为:
W − U [ − 6 n i + n i + 1 , 6 n i + n i + 1 ] W - U[-\frac{\sqrt{6}}{\sqrt{n_i+n_{i+1}}}, \frac{\sqrt{6}}{\sqrt{n_i+n_{i+1}}}] W−U[−ni+ni+16,ni+ni+16]
原理我们讲完了,现在来看一下在 P y t o r c h Pytorch Pytorch中是如何调用 X a v i e r Xavier Xavier均匀分布初始化的:
# tensor表示要初始化的张量,gain表示缩放因子
torch.nn.init.xavier_uniform(tensor, gain=1)
# 举例说明:
w = torch.Tensor(3, 5)
nn.init.xavier_uniform(w, gain=math.sqrt(2))
2、 X a v i e r Xavier Xavier正态分布初始化
我们都知道均值为 0 0 0,标准差为 σ \sigma σ的正态分布方差为
V a r = σ 2 Var=\sigma^2 Var=σ2
同样的,需要将正态分布的方差等于我们在上面推导出来的 X a v i e r Xavier Xavier的权重方差,即:
σ 2 = 2 n i + n i + 1 \sigma^2 = \frac{2}{n_i + n_{i+1}} σ2=ni+ni+12
经过化解后可以得到 X a v i e r Xavier Xavier正态分布初始化后权重的标准差为:
σ = 2 n i + n i + 1 \sigma = \sqrt{\frac{2}{n_i + n_{i+1}}} σ=ni+ni+12
那么我们再来看一下在 P y t o r c h Pytorch Pytorch中是如何调用 X a v i e r Xavier Xavier正态分布初始化的:
# tensor表示要初始化的张量,gain表示缩放因子
torch.nn.init.xavier_normal(tensor, gain=1)
# 举例说明:
w = torch.Tensor(3, 5)
nn.init.xavier_normal(w)
3、 X a v i e r Xavier Xavier权重初始化表现效果
如下图
4
4
4所示为采用
X
a
v
i
e
r
Xavier
Xavier
i
n
i
t
i
a
l
i
z
a
t
i
o
n
initialization
initialization后每层的激活函数输出值的分布,从图中我们可以看出,深层的激活函数输出值还是非常服从标准高斯分布。
虽然
X
a
v
i
e
r
Xavier
Xavier
i
n
i
t
i
a
l
i
z
a
t
i
o
n
initialization
initialization能够很好的适用于
t
a
n
H
tanH
tanH 激活函数,但对于目前神经网络中最常用的
R
e
L
U
ReLU
ReLU激活函数,还是无能能力,如下图
5
5
5所示为采用
R
e
L
U
ReLU
ReLU激活函数后,
X
a
v
i
e
r
Xavier
Xavier
i
n
i
t
i
a
l
i
z
a
t
i
o
n
initialization
initialization初始化的每层激活函数输出值的分布,从图中可以看出当达到
5
5
5、
6
6
6层后几乎又开始趋向于
0
0
0,更深层的话很明显又会趋向于
0
0
0。
由此可见,
X
a
v
i
e
r
Xavier
Xavier权重初始化方式比较适用于
t
a
n
H
tanH
tanH和
S
i
g
m
o
i
d
Sigmoid
Sigmoid激活函数,而对于
R
e
L
U
ReLU
ReLU这种非对称性的激活函数还是容易出现梯度消失的现象。
H e He He i n i t i a l i z a t i o n initialization initialization
H e He He i n i t i a l i z a t i o n initialization initialization是由何凯明大神提出的一种针对 R e L U ReLU ReLU激活函数的初始化方法。 H e He He i n i t i a l i z a t i o n initialization initialization的思想是:和 X a v i e r Xavier Xavier初始化方式一样,都希望初始化使得正向传播时,状态值的方差保持不变,反向传播时,关于激活值的梯度的方差保持不变。由于小于 0 0 0的值经过 R e L U ReLU ReLU激活函数都会变成 0 0 0,而大于 0 0 0的值则保持原值。因此在 R e L U ReLU ReLU网络中,假定每一层有一半的神经元被激活,另一半为 0 0 0,所以,要保持 v a r i a n c e variance variance不变,只需要在 X a v i e r Xavier Xavier的基础上再除以2即可。本文主要介绍 P y t o r c h Pytorch Pytorch当中 H e He He i n i t i a l i z a t i o n initialization initialization均匀分布和 H e He He i n i t i a l i z a t i o n initialization initialization正态分布初始化这两种方式。
对于 H e He He i n i t i a l i z a t i o n initialization initialization的推导来说前面和 X a v i e r Xavier Xavier i n i t i a l i z a t i o n initialization initialization是相似的,但在方差推到过程中,需要将式子左侧除以 2 2 2,如下所示:
V a r ( y ) = 1 2 n i V a r ( w i ) V a r ( x i ) Var(y) = \frac{1}{2}n_iVar(w_i)Var(x_i) Var(y)=21niVar(wi)Var(xi)
为了保证输出和输入的方差一直,则可以得到权重的方差为:
V a r ( w i ) = 2 n i Var(w_i) = \frac{2}{n_i} Var(wi)=ni2
对于 B a c k w a r d Backward Backward来说和 F o r w a r d Forward Forward思路是相似的,只不过需要考虑到链式求导法则,这里不予以推导,只给出最终的结果为:
V a r ( w i + 1 ) = 2 n i + 1 Var(w_{i+1}) = \frac{2}{n_{i+1}} Var(wi+1)=ni+12
1、 H e He He i n i t i a l i z a t i o n initialization initialization均匀分布初始化
和 X a v i e r Xavier Xavier均匀分布初始化操作一样我们得到 H e He He i n i t i a l i z a t i o n initialization initialization的取值范围为:
W − U [ − 6 n i + ( a 2 + 1 ) , 6 n i + ( a 2 + 1 ) ] W - U[-\frac{\sqrt{6}}{\sqrt{n_i+(a^2+1)}}, \frac{\sqrt{6}}{\sqrt{n_i+(a^2+1)}}] W−U[−ni+(a2+1)6,ni+(a2+1)6]
在 P y t o r c h Pytorch Pytorch中 H e He He i n i t i a l i z a t i o n initialization initialization也叫做 k a i m i n g kaiming kaiming,调用代码如下:
# tensor表示要初始化的张量
# a表示这层之后使用的rectifier的斜率系数(ReLU的默认值为0)
# mode可以为“fan_in”(默认)或“fan_out”。
# “fan_in”保留前向传播时权值方差的量级,“fan_out”保留反向传播时的量级。
torch.nn.init.kaiming_uniform(tensor, a=0, mode='fan_in')
# 举例说明:
w = torch.Tensor(3, 5)
nn.init.kaiming_uniform(w, mode='fan_in')
2、 H e He He i n i t i a l i z a t i o n initialization initialization正态分布初始化
和 X a v i e r Xavier Xavier正态分布初始化操作一样我们得到 H e He He i n i t i a l i z a t i o n initialization initialization的标准差为:
σ = 2 n i + ( a 2 + 1 ) \sigma = \sqrt{\frac{2}{n_i + (a^2+1)}} σ=ni+(a2+1)2
在 P y t o r c h Pytorch Pytorch中 X a v i e r Xavier Xavier正态分布初始化的调用代码如下:
# tensor表示要初始化的张量
# a表示这层之后使用的rectifier的斜率系数(ReLU的默认值为0)
# mode可以为“fan_in”(默认)或“fan_out”。
# “fan_in”保留前向传播时权值方差的量级,“fan_out”保留反向传播时的量级。
torch.nn.init.kaiming_normal(tensor, a=0, mode='fan_in')
# 举例说明:
w = torch.Tensor(3, 5)
nn.init.kaiming_normal(w, mode='fan_out')
3、 H e He He i n i t i a l i z a t i o n initialization initialization权重初始化表现效果
如下图
6
6
6所示为采用
H
e
He
He
i
n
i
t
i
a
l
i
z
a
t
i
o
n
initialization
initialization方式初始化权重后,隐藏层使用
R
e
L
U
ReLU
ReLU时,激活函数的输出值的分布情况,从图中可知,针对
R
e
L
U
ReLU
ReLU激活函数,
H
e
He
He
i
n
i
t
i
a
l
i
z
a
t
i
o
n
initialization
initialization效果是比
X
a
v
i
e
r
Xavier
Xavier
i
n
i
t
i
a
l
i
z
a
t
i
o
n
initialization
initialization好很多。
由此可见,
H
e
He
He
i
n
i
t
i
a
l
i
z
a
t
i
o
n
initialization
initialization权重初始化方式是非常适用于
R
e
L
U
ReLU
ReLU激活函数。
预训练模型
目前更多的使用已经针对相似任务已经训练好的模型,称之为预训练模型。在训练开始时就已经有了非常好的初始化参数,只需要将最后的全连接层进行冻结,训练其他部分即可。
总结
1、权重采用初始化为 0 0 0和随机初始化都比较容易出现梯度消失的问题,因此不常用。
2、 X a v i e r Xavier Xavier权重初始化方式主要针对于 t a n H tanH tanH和 s i g m o i d sigmoid sigmoid激活函数。
3、 H e He He i n i t i a l i z a t i o n initialization initialization权重初始化方式主要针对于 R e L U ReLU ReLU激活函数。
4、如果有相似任务已经训练好的模型,也可以考虑采用预训练模型来作权重初始化。