在初始化权值的时候,常用的一个函数是 np.random.randn() 函数。这个函数会产生一个均值是0,方差是1的的分布。
import numpy as np
import matplotlib.pyplot as plt
w= np.random.randn(10000) #产生1*1w的数组
print(w.mean())
print(w.var())
plt.hist(w,bins=100) #绘制数据分布的直方图
plt.show()
数据分布直方图,如下图所示:
绝大多数的随机数都产生在0附近,从0开始到+4和-4的区间上面,数据量越来越小。(服从正态分布)
但是,这样的初始化方式,放入神经网络训练的时候,在比较深的网络里面,往往效果一般。
举个例子,分析一下。
Z = weight * X+bias
我们来看一下Z的分布:
z分布在范围是(-100 -- +100 )之间。绝大多数数据分布在[-50,+50]之间。
但是,如果我们的激活函数是sigmod的话,那么就会遇到这样一个问题。也就是梯度消失的问题。
sigmod函数的导数:
以sigmoid函数为例,当z的绝对值变大时,函数值越来越平滑,趋于饱和,这个时候函数的导数趋于0。
例如,在z=2时,函数的导数约为1/10,而在z=10时,函数的导数已经变成约为1/22000,也就是说,激活函数的输入是10的时候比2的时候神经网络的学习速率要慢2200倍!
为了神经网络保持一个很好的性能,我们希望z的值绝大多数分布在[-5,+5]之间。
对于梯度消失,有很多种解决办法,比如:
- batch normalization
- 使用relu
- 更改初始化的方式
这里我们只分析更改初始化的方式。有一个很有意思的trick:
一种简单的做法是修改w的分布,使得z服从均值为0、方差为1的标准正态分布。根据正太分布期望与方差的特性,将w除以sqrt(n = 输入的结点个数) 即可。
这个可以简单的理解,在正常初始化weight之后,然后给它除以权值个数的平方根。
weight= np.random.randn(inputnode_num)/np.sqrt(inputnode_num)
我的理解是,这样做的方法是将输出重新归一化到均值是0,方差是1。
如果把它放到之前的图的坐标系上面,即[-100,100]上面。效果更加明显。返回z的值的分布更加集中。
import numpy as np
import matplotlib.pyplot as plt
def show_weight_distribution():
w= np.random.randn(10000)
print(w.mean())
print(w.var())
plt.hist(w,bins=100)
plt.show()
def train():
train_num=1000
z_output=[]
for i in range(train_num):
bias=0
x=np.ones(1000)
weight= np.random.randn(1000)
z=np.sum(weight * x)+bias
z_output.append(z)
print (" mean : ",np.mean(z_output)) #均值是1
print (" var : ",np.var(z_output)) #方差是1000
plt.hist(z_output,bins=100)
plt.show()
def update_train():
train_num=1000
z_output=[]
for i in range(train_num):
inputnode_num=50001
bias=0
x=np.ones(inputnode_num)
weight= np.random.randn(inputnode_num)/np.sqrt(inputnode_num) #修改的地方
z=np.sum(weight * x)+bias
z_output.append(z)
print (" mean : ",np.mean(z_output)) #均值是0
print (" var : ",np.var(z_output)) #方差是1
plt.hist(z_output,bins=100)
plt.show()
def update_train2():
train_num=1000
z_output=[]
for i in range(train_num):
inputnode_num=5000
bias=0
x=np.ones(inputnode_num)
weight= np.random.randn(inputnode_num)/np.sqrt(inputnode_num) #修改的地方
z=np.sum(weight * x)+bias
z_output.append(z)
print (" mean : ",np.mean(z_output)) #均值是0
print (" var : ",np.var(z_output)) #方差是1
plt.xlim([-100,100])
plt.hist(z_output,bins=100)
plt.show()
if __name__ =="__main__":
update_train2()
关于梯度消失和梯度爆炸的问题:
梯度消失的表现:
对于下图所示的含有3个隐藏层的神经网络,梯度消失问题发生时,接近于输出层的hidden layer 3等的权值更新相对正常,但前面的hidden layer 1的权值更新会变得很慢,导致前面的层权值几乎不变,仍接近于初始化的权值。
这就导致hidden layer 1相当于只是一个映射层,对所有的输入做了一个同一映射,这是此深层网络的学习就等价于只有后几层的浅层网络的学习了。
为什么会产生这样的情况?
以下图的反向传播为例(假设每一层只有一个神经元且对于每一层)
如下公式所示:
网络结构如图所示:
可以推到出:
而sigmoid的导数如下图所示:
这样,梯度爆炸问题的出现原因就显而易见了
sigmod的导数的最大值是1/4,如果w的权值小于1 的话,那么 | sigmod‘ * w | 会小于1。如果网络有很多层的话,那么这个导数会指数倍减小。也就是前几层的权值基本上不改变。因而导致梯度消失的情况出现。
如果 | sigmod‘ * w | > 1 , (也就是w比较大的情况)(虽然这种情况很少出现)这样就是梯度爆炸的情况了。
so,小结一下:
其实梯度爆炸和梯度消失问题都是因为网络太深,网络权值更新不稳定造成的,本质上是因为梯度反向传播中的连乘效应。对于更普遍的梯度消失问题,可以考虑用ReLU激活函数取代sigmoid激活函数。
- 由于网络太深,导致反向传播出现一个连乘的效应。梯度指数倍减小。
- sigmod的导数两边都很小
参考:
- 斯坦福 cs231n