在之前的《人人都能懂的机器学习》系列文章中我们介绍了什么是人工神经网络,并且训练了我们自己的网络。但是那些网络其实并不‘深’,仅仅拥有几个隐藏层而已。但如果我们需要解决一些复杂的问题,比如从高清图像中识别几百种物品,那该怎么办呢?你也许需要训练一个‘深’得多的网络,可能有10层或以上,每层可能有几百个神经元,有成百上千的连接。训练深度神经网络不是一件随心所欲的事情,你可能会遇到这些问题:
- 首先,你可能会面临棘手的梯度消失或者梯度爆炸的问题。这是指在训练中数据通过DNN向后流动时,梯度变得越来越小,或者越来越大。这两个问题都使得训练难以进行。
- 其次,对于如此庞大的网络,你可能没有足够的训练数据,或者为训练做标记成本的太高。
- 第三,训练可能会非常缓慢。
- 第四,一个拥有数百万参数的模型,有很大的风险会发生过拟合,特别是训练数据不够,或者数据噪声过大的时候。
在接下来的文章当中,我们将详细讨论这些问题,并介绍一些解决这些问题最流行的方法。我们还将讨论迁移学习和无监督预训练,它们可以在已标记数据较少的时候帮助我们解决复杂的问题。我们还会学习各种能够极大加快模型训练的优化器,最后我们还会介绍几种常用的大型神经网络正则化技术。
掌握了这些工具,我们就可以训练非常深的神经网络了。
那么,
欢迎来到深度学习
梯度消失/爆炸问题
在我们之前的文章中介绍过,反向传播算法将误差梯度从输出层传播至输入层。一旦算法计算出了代价函数关于网络中每个参数的梯度,它就会使用这些梯度对每个参数使用梯度下降法进行更新。
不幸的是,随着算法深入到底层,梯度常常变得越来越小。因此,底层连接的权值几乎无法受到梯度下降的影响,训练就永远不会收敛到一个好的结果。这就是为什么我们称它为梯度消失问题。在某些情况下,可能会出现相反的情况:梯度会变得越来越大,直到某一层的权值更新大得离谱,结果导致算法发散。这就是在循环神经网络中出现的梯度爆炸问题。概括来讲,深度神经网络受到梯度不稳定的影响,不同层的学习速率可能会有很大差距。
在很久之前研究人员就发现了这个问题,并且这也是在21世纪初,深度学习几乎要被抛弃的原因之一。因为在那个时候,造成梯度如此不稳定的原因尚不明确。然而Xavier Glorot和Yoshua Bengio发表了一篇名为《Understanding the Difficulty of Training Deep Feedforward Neural Networks》的文章,文章中找到一些引起梯度不稳定的可能原因,包括当时最流行的sigmoid激活函数与权重初始化技术结合(即,正态分布的初始化,均值为0,标准差为1)。简言之,他们证明了用这个激活函数和初始化方法,每一层的输出方差要远远大于其输入方差。在网络中,方差在每一层之后不断增加,直到激活函数在网络顶层饱和。并且sigmoid函数的均值为0.5,而不是0,这会让这种饱和情况变得更严重。
让我们来看一下sigmoid函数,理解一下什么是所谓的饱和(见图)。当输入的绝对值变大,激活函数的输出值会无限接近于0或者1,此时其导数会无限接近0。此时当反向传播算法开始工作时,由于导数几乎为0,那么就几乎没有梯度可以通过网络进行反向传播,并且这个少量的梯度还将在自顶向下传播时不断被稀释,所以传递至下层时,几乎什么梯度都没了。
Glorot和He初始化
Glorot和Bengio在他们的文章中提出了一种能够显著缓解梯度不稳定问题的方法。他们指出,在网络中的数据信号应该向两个方向进行传播:在做预测时正向传播,相反方向用于反向传播梯度。我们肯定不希望信号消失,也不希望信号爆炸或饱和。在正向传播时,我们需要每一层的输出方差与输入的方差相同,并且在反向传播时,每一层的梯度方差在经过每一层时也是相同的。不过这实际上是无法同时保证两个方向的,除非层都有相同数量的输入和神经元(这个数量被称为层的扇入和扇出)。但是Glorot和Bengio提出了一种在实际工作中十分有效的方案:每一层的连接权重都必须随机初始化,初始化方法如下:
初始化为正态分布,均值为0,方差为1;或者初始化为均匀分布,分布区间为-r到+r,r为3
其中,,即为扇入扇出数量的均值。这个初始化方法称为Xavier初始化,或者Glorot初始化(就是将作者的名字拆分开)。在Xavier初始化方法中,如果我们将
替换成
,就会得到Yann LeCun在1990年代提出的初始化方法,作者称之为LeCun初始化。Genevieve Orr和Klaus-Robert Müller甚至在他们1998年出版的《神经网络:权衡的技巧》(施普林格出版)中推荐了这个初始化方法。实际上当
时,LeCun初始化与Xavier初始化是相同的。也就是,研究员们花了十多年的时间才意识到,这个方法的重要性。Xavier初始化可以极大加快训练速度,这也是深度学习能够成功发挥威力的重要技巧之一。
还有一些文章提供了一些类似的激活函数方法,不过这些方法基本只是方差范围不同或者使用或者
的区别。在表2.1中展示了一些方法,其中ReLu激活函数(包括其变体,包括之后会介绍的ELU激活函数)的初始化方法称为He初始化(是由作者的姓名命名的)。SELU激活函数是与LeCun初始化搭配使用的(并且最好是使用正态分布),我们也会在未来的文章中详细介绍。
表2.1 各种激活函数的初始化参数
初始化 | 激活函数 | |
---|---|---|
Xavier | 无,Tanh,Logistic,Softmax | |
He | ReLU及各种变体 | |
LeCun | SELU |
Keras默认使用均匀分布的Xavier初始化。在创建层时,可以设置初始化方法,比如想设置为He初始化,就可以输入kernel_initializer="he_uniform"
或者kernel_initializer="he_normal"
,就像这样:
keras.layers.Dense(10, activation="relu",
kernel_initializer="he_normal")
如果你想用平均分布的He初始化,并且设置为而不是
,那么就可以使用VarianceScaling初始化器,就像这样:
he_avg_init = keras.initializers.VarianceScaling(scale=2.,
mode='fan_avg',
distribution='uniform')
keras.layers.Dense(10, activation="sigmoid",
kernel_initializer=he_avg_init)
好的,这篇文章我们简单了解了一下深度神经网络在训练的时候可能会遇到的问题,并且理解了梯度消失和爆炸的原因,最后给出了可以缓解梯度不稳定问题的初始化方法。在接下的文章中,我们还将讲述更多的技术,比如不会发生饱和的激活函数,批标准化等等。
敬请期待吧!