机器学习入门之 — 神经网络权值初始化

2018-04-08  本文已影响0人  DayDayUpppppp

在初始化权值的时候,常用的一个函数是 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]之间。

z的分布

但是,如果我们的激活函数是sigmod的话,那么就会遇到这样一个问题。也就是梯度消失的问题。

sigmod函数

sigmod函数的导数:


image.png

以sigmoid函数为例,当z的绝对值变大时,函数值越来越平滑,趋于饱和,这个时候函数的导数趋于0。

例如,在z=2时,函数的导数约为1/10,而在z=10时,函数的导数已经变成约为1/22000,也就是说,激活函数的输入是10的时候比2的时候神经网络的学习速率要慢2200倍!

为了神经网络保持一个很好的性能,我们希望z的值绝大多数分布在[-5,+5]之间。

对于梯度消失,有很多种解决办法,比如:

这里我们只分析更改初始化的方式。有一个很有意思的trick:
一种简单的做法是修改w的分布,使得z服从均值为0、方差为1的标准正态分布。根据正太分布期望与方差的特性,将w除以sqrt(n = 输入的结点个数) 即可。

这个可以简单的理解,在正常初始化weight之后,然后给它除以权值个数的平方根。
weight= np.random.randn(inputnode_num)/np.sqrt(inputnode_num)
我的理解是,这样做的方法是将输出重新归一化到均值是0,方差是1。

调整weight的分布之后z的分布.png

如果把它放到之前的图的坐标系上面,即[-100,100]上面。效果更加明显。返回z的值的分布更加集中。

如果把它放到之前的图的坐标系上面.png
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激活函数。


参考:

  1. 斯坦福 cs231n
上一篇下一篇

猜你喜欢

热点阅读