1、梯度消失与梯度爆炸
梯度消失与梯度爆炸的产生是权值与激活函数相互作用的共同结果。针对一个神经网络:
H
2
=
H
1
∗
W
2
H_{2} = H_{1}*W_{2}
H2=H1∗W2
∂
L
o
s
s
∂
W
2
=
∂
L
o
s
s
∂
o
u
t
∗
∂
o
u
t
∂
H
2
∗
∂
H
2
∂
W
2
=
∂
L
o
s
s
∂
o
u
t
∗
∂
o
u
t
∂
H
2
∗
H
1
\frac{\partial Loss}{\partial W_{2}}= \frac{\partial Loss}{\partial out}* \frac{\partial out}{\partial H_{2}}* \frac{\partial H_{2}}{\partial W_{2}} =\frac{\partial Loss}{\partial out}* \frac{\partial out}{\partial H_{2}}*H_{1}
∂W2∂Loss=∂out∂Loss∗∂H2∂out∗∂W2∂H2=∂out∂Loss∗∂H2∂out∗H1
梯 度 消 失 : H 1 → 0 ⇒ ∂ L o s s ∂ W 2 → 0 梯度消失:H_{1}\rightarrow0 \Rightarrow\frac{\partial Loss}{\partial W_{2}}\rightarrow0 梯度消失:H1→0⇒∂W2∂Loss→0
梯 度 爆 炸 : H 1 → ∞ ⇒ ∂ L o s s ∂ W 2 → 0 梯度爆炸:H_{1}\rightarrow\infty \Rightarrow\frac{\partial Loss}{\partial W_{2}}\rightarrow0 梯度爆炸:H1→∞⇒∂W2∂Loss→0
公式书写参考:
https://www.cnblogs.com/lcchuguo/p/5061692.html
梯度消失或者梯度爆炸会导致模型无法训练,从公式求解角度来看,要想避免此类问题的产生,就要严格控制网络输出层输出值的尺度范围,就是让每个网络输出值不能太大也不能太小
2、全连接网络权值初始化
代码如下(示例):
class MLP(nn.Module):
def __init__(self, neural_num, layers):
super(MLP, self).__init__()
self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)])
self.neural_num = neural_num
def forward(self, x):
for (i, linear) in enumerate(self.linears):
x = linear(x)
x = torch.relu(x)
print("layer:{}, std:{}".format(i, x.std()))
if torch.isnan(x.std()):
print("output is nan in {} layers".format(i))
break
return x
def initialize(self):#权值初始化函数
for m in self.modules():#对每个模块进行for循环
if isinstance(m, nn.Linear):#判断linear是否是线形层,是的话就对权值初始化
nn.init.normal_(m.weight.data)
layer_nums = 100
neural_nums = 256
batch_size = 16
net = MLP(neural_nums, layer_nums)
net.initialize()
inputs = torch.randn((batch_size, neural_nums)) # normal: mean=0, std=1,随机的输入,采用标准正态分布
output = net(inputs)
print(output)
#运行结果:
layer:0, std:9.352246284484863
layer:1, std:112.47123718261719
layer:2, std:1322.8056640625
layer:3, std:14569.42578125
layer:4, std:154672.765625
layer:5, std:1834038.25
layer:6, std:18807982.0
layer:7, std:209553056.0
layer:8, std:2637502976.0
layer:9, std:32415455232.0
layer:10, std:374825549824.0
layer:11, std:3912853356544.0
layer:12, std:41235926482944.0
layer:13, std:479620541448192.0
layer:14, std:5320927608832000.0
layer:15, std:5.781226125891994e+16
layer:16, std:7.022147146707108e+17
layer:17, std:6.994718592201654e+18
layer:18, std:8.473500708665033e+19
layer:19, std:9.339794309346954e+20
layer:20, std:9.56936220412742e+21
layer:21, std:1.1762745601866065e+23
layer:22, std:1.482641490484093e+24
layer:23, std:1.6921342454001848e+25
layer:24, std:1.9741449097941338e+26
layer:25, std:2.1257213262324592e+27
layer:26, std:2.191710967109107e+28
layer:27, std:2.525450570646784e+29
layer:28, std:3.221309179111329e+30
layer:29, std:3.5309529208927896e+31
layer:30, std:4.5253540317472455e+32
layer:31, std:4.7150121712384475e+33
layer:32, std:5.36959116472917e+34
layer:33, std:inf
layer:34, std:inf
layer:35, std:nan
output is nan in 35 layers
tensor([[3.2626e+36, 0.0000e+00, 7.2932e+37, ..., 0.0000e+00, 0.0000e+00,
2.5465e+38],
[3.9237e+36, 0.0000e+00, 7.5033e+37, ..., 0.0000e+00, 0.0000e+00,
2.1274e+38],
[0.0000e+00, 0.0000e+00, 4.4932e+37, ..., 0.0000e+00, 0.0000e+00,
1.7016e+38],
...,
[0.0000e+00, 0.0000e+00, 2.4222e+37, ..., 0.0000e+00, 0.0000e+00,
2.5295e+38],
[4.7380e+37, 0.0000e+00, 2.1580e+37, ..., 0.0000e+00, 0.0000e+00,
2.6028e+38],
[0.0000e+00, 0.0000e+00, 6.0878e+37, ..., 0.0000e+00, 0.0000e+00,
2.1695e+38]], grad_fn=<ReluBackward0>)
在这里,利用标准差来表明每一层输出参数之间的变化情况。以上示例代码表明网络层输出的标准差不断增大,直到layer:33之后,超出了计算机的表示范围。这是因为:
设
E
(
X
)
=
E
(
Y
)
=
0
,
D
(
X
)
=
D
(
Y
)
=
1
设E(X) = E(Y)=0,D(X)=D(Y)=1
设E(X)=E(Y)=0,D(X)=D(Y)=1
则:
D
(
X
∗
Y
)
=
D
(
X
)
∗
D
(
Y
)
D(X*Y) = D(X)*D(Y)
D(X∗Y)=D(X)∗D(Y)
对于图片中第一个隐藏层的第一个神经元,有:
H
11
=
∑
i
=
0
n
X
i
∗
W
1
i
H_{11} =\sum_{i=0}^n X_{i}*W_{1i}
H11=i=0∑nXi∗W1i
D
(
H
11
)
=
∑
i
=
0
n
D
(
X
i
)
∗
D
(
W
1
i
)
=
n
∗
1
∗
1
=
n
D(H_{11} )=\sum_{i=0}^nD(X_{i})*D(W_{1i})=n*1*1=n
D(H11)=i=0∑nD(Xi)∗D(W1i)=n∗1∗1=n
s
t
d
(
H
11
)
=
D
(
H
11
)
=
n
std(H_{11} ) =\sqrt D(H_{11} )= \sqrt n
std(H11)=D(H11)=n
•由此可见每层网络输出的方差相对于前一层扩大了n倍
其中n代表每一个网络神经元个数。
•输出层的方差由三个因素决定:网络层神经元个数n,输入神经元方差,网络层权值的方差。
故若想要每一个网络层输出方差尺度保持不变,就应该令方差等于1。公式上:
令
D
(
H
1
)
=
n
∗
D
(
X
)
∗
D
(
W
)
=
1
令D(H_{1})= n*D(X)*D(W) = 1
令D(H1)=n∗D(X)∗D(W)=1
D
(
W
)
=
1
n
⇒
s
t
d
(
W
)
=
1
n
D(W) =\frac {1}{n}\Rightarrow std(W)=\sqrt {\frac{1}{n}}
D(W)=n1⇒std(W)=n1
在代码中,只需要将上面代码的初始化代码改为:
nn.init.normal_(m.weight.data,std=np.sqrt(1/self.neural_num))
#运行结果为:
layer:0, std:0.584515392780304
layer:1, std:0.4393407702445984
layer:2, std:0.3229506015777588
layer:3, std:0.22231179475784302
layer:4, std:0.147507444024086
layer:5, std:0.10931719839572906
layer:6, std:0.07006519287824631
layer:7, std:0.04879037290811539
layer:8, std:0.03838071972131729
layer:9, std:0.029481684789061546
layer:10, std:0.021306365728378296
layer:11, std:0.013901247642934322
layer:12, std:0.009156215004622936
layer:13, std:0.006656072102487087
layer:14, std:0.004615169018507004
layer:15, std:0.003134008962661028
layer:16, std:0.002379195997491479
layer:17, std:0.0014811892760917544
layer:18, std:0.001121458481065929
layer:19, std:0.0007725696777924895
layer:20, std:0.0004947244306094944
layer:21, std:0.0003800748090725392
layer:22, std:0.000299417064525187
layer:23, std:0.00021357736841309816
layer:24, std:0.0001557325740577653
layer:25, std:0.00010480615310370922
layer:26, std:6.75373084959574e-05
layer:27, std:4.8638408770784736e-05
layer:28, std:3.877509880112484e-05
layer:29, std:2.656393735378515e-05
layer:30, std:2.1278112399159e-05
...
3. 具有激活函数时的权值初始化
以sigmoid和tanh函数为例,sigmoid函数及其导数图像如下:
tanh函数及其导数图像如下:
诸如sigmoid函数和tanh函数,由于其梯度基本都小于1,在构建模型的过程中容易产生梯度消失问题
在上面代码中的forward函数模块加入非线性激活函数:
def forward(self, x):
for (i, linear) in enumerate(self.linears):
x = linear(x)
x = torch.tanh(x)#加入tanh函数作为激活函数
#运行结果(数据越来越小,并逐渐导致梯度的消失):
layer:1, std:0.48910173773765564
layer:2, std:0.4099564850330353
layer:3, std:0.35637012124061584
layer:4, std:0.32117360830307007
layer:5, std:0.2981105148792267
layer:6, std:0.27730831503868103
layer:7, std:0.2589356303215027
layer:8, std:0.2468511462211609
layer:9, std:0.23721906542778015
layer:10, std:0.22171513736248016
...
layer:90, std:0.07225216180086136
layer:91, std:0.0712454691529274
layer:92, std:0.07088855654001236
layer:93, std:0.0730612725019455
layer:94, std:0.07276969403028488
layer:95, std:0.07259569317102432
layer:96, std:0.0758652538061142
layer:97, std:0.07769152522087097
layer:98, std:0.07842093706130981
layer:99, std:0.08206242322921753
4. Xavier初始化
在具有饱和激活函数的网络中,Xavier初始化可以有效解决梯度消失的问题。
方差一致性:保持数据尺度维持在恰当范围,通常方差为1
针对于前向传播和反向传播,有以下公式:
n
i
∗
D
(
W
)
=
1
n_{i}*D(W) = 1
ni∗D(W)=1
n
i
+
1
∗
D
(
W
)
=
1
n_{i+1}*D(W) = 1
ni+1∗D(W)=1
⇒
D
(
W
)
=
2
n
i
+
n
i
+
1
(
1
)
\Rightarrow D(W) = \frac{2}{n_{i}+n_{i+1}}\qquad (1)
⇒D(W)=ni+ni+12(1)
n
i
表
示
输
入
神
经
元
个
数
,
n
i
+
1
表
示
输
出
神
经
元
个
数
n_{i}表示输入神经元个数,n_{i+1}表示输出神经元个数
ni表示输入神经元个数,ni+1表示输出神经元个数
通常,Xavie采用均匀分布:
W
服
从
于
U
[
−
a
,
a
]
W 服从于 U[-a,a]
W服从于U[−a,a]
D
(
W
)
=
(
−
a
−
a
)
2
12
=
a
2
3
(
2
)
D(W)=\frac{(-a-a)^2}{12}=\frac{a^2}{3}\qquad (2)
D(W)=12(−a−a)2=3a2(2)
联立(1),(2)
⇒
a
=
6
(
n
i
+
n
i
+
1
)
\Rightarrow a = \frac {\sqrt 6}{\sqrt (n_{i}+n_{i+1})}
⇒a=(ni+ni+1)6
⇒
W
服
从
于
U
[
−
6
(
n
i
+
n
i
+
1
)
,
6
(
n
i
+
n
i
+
1
)
]
\Rightarrow W 服从于 U[-\frac {\sqrt 6}{\sqrt (n_{i}+n_{i+1})},\frac {\sqrt 6}{\sqrt (n_{i}+n_{i+1})}]
⇒W服从于U[−(ni+ni+1)6,(ni+ni+1)6]
针对于以上程序,仅对初始化部分程序进行修改:
(1)通过手动计算编写程序:
class MLP(nn.Module):
def __init__(self, neural_num, layers):
super(MLP, self).__init__()
self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)])
self.neural_num = neural_num
def forward(self, x):
for (i, linear) in enumerate(self.linears):
x = linear(x)
#x = torch.relu(x)
x = torch.tanh(x)
print("layer:{}, std:{}".format(i, x.std()))
if torch.isnan(x.std()):
print("output is nan in {} layers".format(i))
break
return x
def initialize(self):#权值初始化函数
for m in self.modules():#对每个模块进行for循环
if isinstance(m, nn.Linear):#判断linear是否是线形层,是的话就对权值初始化
# nn.init.normal_(m.weight.data, std=np.sqrt(1/self.neural_num)) # normal: mean=0, 权值的标准差std = 1/n,每个网络层输出的标准差都是1
a = np.sqrt(6 / (self.neural_num + self.neural_num))#权值的上下限
tanh_gain = nn.init.calculate_gain('tanh')#计算tanh的增益
a *= tanh_gain
nn.init.uniform_(m.weight.data, -a, a)#均匀分布,设置下限是-a,上限是a,来初始化权值
#运行结果:
layer:0, std:0.7571136355400085
layer:1, std:0.6924336552619934
layer:2, std:0.6677976846694946
layer:3, std:0.6551960110664368
layer:4, std:0.655646800994873
layer:5, std:0.6536089777946472
layer:6, std:0.6500504612922668
layer:7, std:0.6465446949005127
layer:8, std:0.645668625831604
layer:9, std:0.6414617896080017
layer:10, std:0.6423627734184265
...
layer:90, std:0.6536460518836975
layer:91, std:0.6497945785522461
layer:92, std:0.6458892226219177
layer:93, std:0.6458885669708252
layer:94, std:0.6530362963676453
layer:95, std:0.6515855193138123
layer:96, std:0.643466055393219
layer:97, std:0.6426210403442383
layer:98, std:0.6407480835914612
layer:99, std:0.6442216038703918
(2)调用pytorch提供的Xavier方法(l两种方法计算结果基本一致):
def initialize(self):#权值初始化函数
for m in self.modules():#对每个模块进行for循环
if isinstance(m, nn.Linear):#判断linear是否是线形层,是的话就对权值初始化
tanh_gain = nn.init.calculate_gain('tanh')#计算tanh的增益
nn.init.xavier_uniform_(m.weight.data, gain=tanh_gain)#调用pytorch提供的Xavier方法
5. Kaiming初始化
后期,非饱和激活函数,ReLu函数被广泛使用,但ReLu函数同样有梯度增长快的问题。可以通过在上述代码的forward模块加入relu观察结果。
针对于ReLu函数的问题,后提出Kaiming初始化方法。
Kaiming初始化方法针对的激活函数:ReLU及其变种
遵循的原则:方差一致性,即保持数据尺度维持在恰当范围,通常方差为1
公式实现:
D
(
W
)
=
2
n
i
D(W) = \frac{2}{n_{i}}
D(W)=ni2
针对于ReLu函数的变种:
D
(
W
)
=
2
(
1
+
a
2
)
∗
n
i
D(W) = \frac{2}{(1+a^2)*n_{i}}
D(W)=(1+a2)∗ni2
其
中
a
代
表
R
e
L
u
变
种
函
数
在
负
半
轴
的
斜
率
,
n
i
代
表
输
入
神
经
元
个
数
其中a代表ReLu变种函数在负半轴的斜率,n_{i}代表输入神经元个数
其中a代表ReLu变种函数在负半轴的斜率,ni代表输入神经元个数
因此权值的标准差为:
s
t
d
(
W
)
=
2
(
1
+
a
2
)
∗
n
i
std(W) =\sqrt \frac{2}{(1+a^2)*n_{i}}
std(W)=(1+a2)∗ni2
#1:采用最初全连接神经网络计算公式的方法
class MLP(nn.Module):
def __init__(self, neural_num, layers):
super(MLP, self).__init__()
self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)])
self.neural_num = neural_num
def forward(self, x):
for (i, linear) in enumerate(self.linears):
x = linear(x)
x = torch.relu(x)
print("layer:{}, std:{}".format(i, x.std()))
if torch.isnan(x.std()):
print("output is nan in {} layers".format(i))
break
return x
def initialize(self):#权值初始化函数
for m in self.modules():#对每个模块进行for循环
if isinstance(m, nn.Linear):#判断linear是否是线形层,是的话就对权值初始化
nn.init.normal_(m.weight.data, std=np.sqrt(2 / self.neural_num))#采用态分布,0均值,表准差是2/输入神经元个数
#2:采用pytorch提供的Kaiming初始化方法(不需要手动计算标准差):(只需更换以上最后一行代码)
def initialize(self):#权值初始化函数
for m in self.modules():#对每个模块进行for循环
if isinstance(m, nn.Linear):#判断linear是否是线形层,是的话就对权值初始化
nn.init.kaiming_normal_(m.weight.data)#采用pytorch提供的Kaiming初始化方法
#运行结果:
layer:0, std:0.826629638671875
layer:1, std:0.8786815404891968
layer:2, std:0.9134422540664673
layer:3, std:0.8892471194267273
layer:4, std:0.834428071975708
layer:5, std:0.874537467956543
layer:6, std:0.7926971316337585
layer:7, std:0.7806458473205566
layer:8, std:0.8684563636779785
layer:9, std:0.9434137344360352
layer:10, std:0.964215874671936
layer:11, std:0.8896796107292175
layer:12, std:0.8287257552146912
layer:13, std:0.8519769906997681
layer:14, std:0.8354345560073853
layer:15, std:0.802306056022644
layer:16, std:0.8613607287406921
layer:17, std:0.7583686709403992
layer:18, std:0.8120225071907043
layer:19, std:0.791111171245575
layer:20, std:0.7164372801780701
...
#两种方法计算结果基本一致,网络层标准差基本维持在一个相同的尺度上,没有过大或者过小
6. Pytorch提供的初始化方法
- Xavie r均匀分布
- Xavie r正态分布
- Kaiming均匀分布
- Kaiming正态分布
- 均匀分布
- 正态分布
- 常数分布
- 正交矩阵初始化
- 单位矩阵初始化
- 稀疏矩阵初始化
nn.init.calculate_gain(nonlinearity, param=None)
nn.init.calculate_gain
主要功能:计算激活函数的方差变化尺度
主要参数(方差变化尺度:输入数据的方差除以经过激活函数之后输出数据的方差)
• nonlinearity: 激活函数名称
• param: 激活函数的参数,如Leaky ReLU
的negative _slop
#以下代码说明针对0均值1标准差的数据,经过tanh之后,它的标准差会减小1.6倍左右
x = torch.randn(10000)
out = torch.tanh(x)
gain = x.std() / out.std()#手动计算方差变化尺度
print('gain:{}'.format(gain))
tanh_gain = nn.init.calculate_gain('tanh')#pytorch提供的nn.init.calculate_gain计算方差变化尺度
print('tanh_gain in PyTorch:', tanh_gain)
运行结果:
gain:1.5982500314712524
tanh_gain in PyTorch: 1.6666666666666667
总结
不良的权值初始化会使得输出层输出值过大或者过小,从而引发梯度消失或者梯度爆炸,最终导致模型无法训练。为了避免这个现象的发生,需要控制网络输出层的尺度范围,通过公式推导可知,要使每个网络层输出的方差尽量是1,遵循方差一致性原则,保持网络层输出值的方差在1附近,使我们的输出值不能太大,也不能太小。