修正的线性激活函数(Relu)如何避免梯度消失

    2019年的第一篇博客。主要译自Machine Learning Mastery,加上了一点点自己的想法。如有问题,欢迎批评指正~ 

    消失梯度问题是在训练深度神经网络时可能遇到的不稳定问题之一。它描述了深度多层前馈网络或循环神经网络无法将有用的梯度信息从模型的输出端传播回模型输入端附近的层的情况。其结果是,具有许多层的模型通常无法在给定的数据集上学习或过早地收敛到较差的解决方案。

    目前,已经提出和研究了许多解决方案和变通方法,如改变权值初始化方案、提前使用无监督、分层训练和梯度下降变体等。也许最常见的方法是使用修正的线性激活函数,它已经替代了自90年代末至21世纪初这十年一直被使用的双曲正切激活函数,成为新的默认值。

    在本教程中,您将了解如何在训练神经网络模型时诊断消失梯度问题,以及如何使用其他激活函数和权重初始化方案来修复它。

    完成本教程后,您将知道:

  1. 梯度消失问题限制了具有双曲正切等经典激活函数的神经网络的发展。
  2. 如何利用ReLU和He权值初始化修正深度神经网络多层感知器的分类问题。
  3. 如何利用TensorBoard对消失梯度问题进行诊断,确定ReLU对模型中梯度流动的影响。

    让我们开始吧。

教程概述

    本教程分为五个部分;它们是:

  1. 梯度消失问题(总体介绍)
  2. 二圆二分类问题(构建数据集)
  3. 两圆问题的多层感知器模型(构建MLP模型)
  4. 基于ReLU的两圆问题的更深层次的MLP模型(加深模型及引入Relu激活函数)
  5. 在训练中回顾平均梯度大小(使用Tensorboard对梯度进行可视化)

 梯度消失问题

    采用随机梯度下降方法对神经网络进行训练,这包括首先计算模型的预测误差,然后利用误差来评估用于更新网络中每个权重的梯度,从而减少下一次的误差。这个误差梯度通过网络从输出层向后传播到输入层。

    对多层神经网络进行训练是可取的,因为多层神经网络的加入增加了网络的容量,使其能够学习大量的训练数据集,并能有效地表示从输入到输出的更复杂的映射函数。

    多层训练网络(如深度神经网络)的一个问题是,当梯度在网络中向后传播时,梯度会急剧减小。当它到达接近模型输入的层时,误差可能非常小,以至于影响很小。因此,这个问题被称为“消失梯度”问题。

    消失的梯度让我们很难知道参数应该向哪个方向移动以改善损失函数......

                                                                                                                                         — Page 290, Deep Learning, 2016.

     事实上,误差梯度在深度神经网络中是不稳定的,不仅会消失,而且会爆炸,梯度在网络中向后传播时呈指数增长。这就是所谓的“爆炸梯度”问题。

     消失梯度一词指的是,在前馈网络(FFN)中,反向传播的误差信号通常会随着距离最后一层的距离呈指数形式减少(或增加)。

                                                              — Random Walk Initialization for Training Very Deep Feedforward Networks, 2014.

 

    在循环神经网络中,梯度消失是一个特别的问题,因为网络的更新涉及到为每个输入时间步长展开网络,实际上创建了一个非常深的网络,需要更新权重。一个适度的循环神经网络可能有200到400个输入时间步长,在概念上产生一个非常深的网络。

    梯度消失问题可能在多层感知机中表现为训练过程中模型的改进速度较慢,可能是过早收敛,例如,继续训练不会导致进一步的改进。检查训练过程中权重的变化,我们会发现在靠近输出层的层中发生了更多的变化(即更多的学习),而在靠近输入层的层中发生的变化更少。 

    有许多技术可以用来减少前馈神经网络的梯度消失问题的影响,最著名的是替代权值初始化方案和使用替代激活函数。

     针对消失梯度问题,研究并应用了不同的深度网络训练方法(前馈和循环),如预处理、更好的随机初始缩放、更好的优化方法、特定的体系结构、正交初始化等。

                                                              — Random Walk Initialization for Training Very Deep Feedforward Networks, 2014.

     在本教程中,我们将进一步研究如何使用其他的权值初始化方案和激活函数来训练更深层次的神经网络模型。

 二圆二分类问题

    作为我们探索的基础,我们将使用一个非常简单的两类或二元分类问题。

    scikit-learn类提供了make_circles()函数,该函数可用于创建具有指定样本数量和统计噪声的二进制分类问题。

    每个示例都有两个输入变量,用于定义二维平面上该点的x和y坐标。这两个类的点被排成两个同心圆(它们有相同的中心)。

    数据集中点的数量由一个参数指定,其中一半将从每个圆中绘制。通过定义噪声标准差的“噪声”参数对采样点进行采样时,可以加入高斯噪声,其中0.0表示没有噪声,或者从圆中准确地画出点。伪随机数生成器的种子可以通过“random_state”参数指定,该参数允许每次调用函数时采样相同的点。

    下面的示例从两个带有噪声和值1的循环中生成1,000个示例,用于生成伪随机数生成器。

# generate circles
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)

     我们可以创建数据集的图形,绘制输入变量(x)的x和y坐标,并用类值(0或1)为每个点着色。下面列出了完整的示例。

# scatter plot of the circles dataset with points colored by class
from sklearn.datasets import make_circles
from numpy import where
from matplotlib import pyplot
# generate circles
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
# select indices of points with each class label
for i in range(2):
	samples_ix = where(y == i)
	pyplot.scatter(X[samples_ix, 0], X[samples_ix, 1], label=str(i))
pyplot.legend()
pyplot.show()

    运行该示例将创建一个绘图,显示生成的1000个数据点,每个点的类值用于为每个点着色。我们可以看到0类的点是蓝色的,代表外圆,1类的点是橙色的,代表内圆。

    生成的样本的统计噪声意味着两个圆之间存在一些点的重叠,增加了问题的模糊性,使其非平凡性。这是可取的,因为神经网络可能会从许多可能的解决方案中选择一个来分类两个圆之间的点,并且总是会犯一些错误。

修正的线性激活函数(Relu)如何避免梯度消失_第1张图片

     现在我们已经定义了一个问题作为我们探索的基础,我们可以考虑开发一个模型来解决它。

 两圆问题的多层感知器模型

    我们可以开发一个多层感知器模型来解决这两个圆的问题。这将是一个简单的前馈神经网络模型,就像我们在20世纪90年代末和21世纪初学到的那样。

    首先,我们将从两个圆问题中生成1000个数据点,并将输入重新缩放到范围[- 1,1](数据几乎已经在这个范围内了,做这一步仅仅是确保万无一失)。

    通常,我们将使用训练数据集准备数据缩放,并将其应用于测试数据集。为了在本教程中简化流程,我们将在将所有数据分割为训练集和测试集之前,将它们缩放到一起。

# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
# scale input data to [-1,1]
scaler = MinMaxScaler(feature_range=(-1, 1))
X = scaler.fit_transform(X)

    接下来,我们将把数据分为训练集和测试集。

    一半的数据将用于培训和剩余的500例子将被用作测试集。在本教程中,测试集也将作为验证数据这样我们可以了解模型的执行期间如何在控制外的集上训练。

# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]

    接下来,我们将定义模型。

    对于数据集中的两个变量,该模型将有一个具有两个输入的输入层,一个具有五个节点的隐藏层,以及一个具有一个节点的输出层,用于预测类概率。隐藏层将使用双曲正切激活函数(tanh),输出层将使用logistic激活函数(sigmoid)来预测class 0或class 1或介于两者之间的值。

    在隐层中使用双曲正切激活函数是20世纪90年代和21世纪初的最佳实践,在隐层中使用双曲正切激活函数的效果通常优于logistic函数。将网络权值从均匀分布初始化为小的随机值也是一种很好的做法。在这里,我们将从范围[0.0,1.0]中随机初始化权重。

# define model
model = Sequential()
init = RandomUniform(minval=0, maxval=1)
model.add(Dense(5, input_dim=2, activation='tanh', kernel_initializer=init))
model.add(Dense(1, activation='sigmoid', kernel_initializer=init))

     该模型采用二元交叉熵损失函数,采用学习速率为0.01、动量较大的随机梯度下降法进行优化。

# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])

    对模型进行500个epochs的训练,并在每个时点结束时对测试数据集和训练数据集进行评估。

# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, verbose=0)

    模型拟合好后,在训练数据集和测试数据集上对模型进行评估,并显示准确率得分。

# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))

    最后,将模型在训练过程中每一步的准确性绘制成直线图,展示了模型在学习问题时的动态。

# plot training history
pyplot.plot(history.history['acc'], label='train')
pyplot.plot(history.history['val_acc'], label='test')
pyplot.legend()
pyplot.show()

     将所有这些结合在一起,下面列出了完整的示例。

 

# mlp for the two circles classification problem
from sklearn.datasets import make_circles
from sklearn.preprocessing import MinMaxScaler
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.initializers import RandomUniform
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
# scale input data to [-1,1]
scaler = MinMaxScaler(feature_range=(-1, 1))
X = scaler.fit_transform(X)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
init = RandomUniform(minval=0, maxval=1)
model.add(Dense(5, input_dim=2, activation='tanh', kernel_initializer=init))
model.add(Dense(1, activation='sigmoid', kernel_initializer=init))
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, verbose=0)
# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
# plot training history
pyplot.plot(history.history['acc'], label='train')
pyplot.plot(history.history['val_acc'], label='test')
pyplot.legend()
pyplot.show()

    运行这个示例只需几秒钟就可以适应这个模型。计算并显示了模型在训练集和测试集上的性能。由于学习算法的随机性,具体结果可能会有所不同。考虑运行该示例几次。

    我们可以看到,在这种情况下,模型很好地学习了这个问题,在训练集和测试数据集上都达到了大约81.6%的准确性。

    在训练和测试集上创建了模型精度的直线图,显示了500个epochs内的性能变化。

    图中显示,对于这次运行,训练集和测试集的性能在epoch=300左右开始下降,准确率都在80%左右。

修正的线性激活函数(Relu)如何避免梯度消失_第2张图片

     现在我们已经了解了如何使用tanh激活函数开发一个经典的MLP来解决两个圆的问题,我们可以看看如何修改模型,使其具有更多的隐藏层。

 两个圆问题的更深层次的MLP模型

    传统上,开发深层多层感知器模型是具有挑战性的。使用双曲正切激活函数的深层模型不容易训练,这种糟糕的性能在很大程度上要归咎于渐近梯度问题。

    我们可以尝试使用上一节中开发的MLP模型来研究这一点。隐藏层的数量可以从1增加到5;例如:

# define model
init = RandomUniform(minval=0, maxval=1)
model = Sequential()
model.add(Dense(5, input_dim=2, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(1, activation='sigmoid', kernel_initializer=init))

    然后我们可以重新运行示例并查看结果。下面列出了更深入的MLP的完整示例。

# deeper mlp for the two circles classification problem
from sklearn.datasets import make_circles
from sklearn.preprocessing import MinMaxScaler
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.initializers import RandomUniform
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
scaler = MinMaxScaler(feature_range=(-1, 1))
X = scaler.fit_transform(X)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
init = RandomUniform(minval=0, maxval=1)
model = Sequential()
model.add(Dense(5, input_dim=2, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(1, activation='sigmoid', kernel_initializer=init))
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, verbose=0)
# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
# plot training history
pyplot.plot(history.history['acc'], label='train')
pyplot.plot(history.history['val_acc'], label='test')
pyplot.legend()
pyplot.show()

    运行该示例首先在训练集和测试数据集中打印fit模型的性能。由于学习算法的随机性,具体结果可能会有所不同。考虑运行该示例几次。

    在这种情况下,我们可以看到训练集和测试集的性能都很差,达到了大约50%的准确率。这表明所配置的模型不能了解问题,也不能泛化解决方案。

     模型训练过程中在训练集和测试集上的精度曲线表明了相似的事情。我们可以看到,这种表现很差,而且随着训练的进行,这种表现会越来越差。

修正的线性激活函数(Relu)如何避免梯度消失_第3张图片

 基于ReLU的两圆问题的更深层次的MLP模型

    在开发多层感知器网络以及其他网络类型(如CNNs)时,校正后的线性激活函数已经取代双曲正切激活函数成为新的首选默认值。这是因为Relu的外观和行为都像一个线性函数,使得它更容易训练和不太可能饱和,但实际上,它是一个非线性函数,迫使负输入值为0。它被认为是一种可能的方法来解决消失梯度问题时,训练更深的模型。(Relu起作用的原因)

    在使用修正的线性激活函数(ReLU)时,使用权重初始化方案是一种很好的做法。我们可以使用ReLU和He初始化定义带有五个隐藏层的MLP,如下所示。

# define model
model = Sequential()
model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='sigmoid'))

     下面列出了完整的代码示例。

# deeper mlp with relu for the two circles classification problem
from sklearn.datasets import make_circles
from sklearn.preprocessing import MinMaxScaler
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.initializers import RandomUniform
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
scaler = MinMaxScaler(feature_range=(-1, 1))
X = scaler.fit_transform(X)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='sigmoid'))
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, verbose=0)
# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
# plot training history
pyplot.plot(history.history['acc'], label='train')
pyplot.plot(history.history['val_acc'], label='test')
pyplot.legend()
pyplot.show()

    运行该示例将在训练集和测试数据集上打印模型的性能。

    由于学习算法的随机性,具体结果可能会有所不同。考虑运行该示例几次。

    在这种情况下,我们可以看到这个小的变化使模型能够了解问题,在两个数据集上实现了84%的准确性,比使用tanh激活函数的单层模型的性能要好。

    在训练周期内,建立了训练集和测试集模型精度的线图。这个图显示了与我们目前所看到的完全不同的动态。该模型似乎可以快速地了解问题,在大约100个epochs内汇聚到一个解决方案。

 修正的线性激活函数(Relu)如何避免梯度消失_第4张图片

    使用ReLU激活函数使我们能够为这个简单的问题拟合一个更深入的模型,但是这种能力不会无限扩展。例如,增加层的数量会导致学习速度变慢,达到大约20层,此时模型不再能够学习问题,至少对于所选的配置是这样。

    例如,下面是同一模型的15个隐含层的训练和测试精度的线段图,表明该模型仍然能够学习问题。

修正的线性激活函数(Relu)如何避免梯度消失_第5张图片

     下面是同一模型20层的具有500个epochs的训练和测试精度线图,显示配置不再能够学习问题。

 修正的线性激活函数(Relu)如何避免梯度消失_第6张图片

     虽然ReLU的使用是有效的,但是我们不能确信tanh函数的使用因为梯度的消失而失败,而ReLU的成功是因为它克服了这个问题。

 在训练中回顾平均梯度大小

    本节假设您正在使用带有Keras的TensorFlow后端。如果不是这样,您可以跳过这一部分。

    在使用tanh激活函数的情况下,我们知道网络有足够的能力来学习这个问题,但是层数的增加阻止了它这样做。很难将消失梯度诊断为性能差的原因。一个可能的信号是检查每个训练历元每层梯度的平均大小。

    我们希望靠近输出的层比靠近输入的层有更大的平均梯度。

    Keras提供了TensorBoard回调函数,可用于在训练期间记录模型的属性,如每层的平均梯度。然后可以使用TensorFlow提供的TensorBoard接口查看这些统计信息。

    我们可以配置这个回调函数来记录每层每训练的平均梯度,然后确保这个回调函数被用作模型训练的一部分。

# prepare callback
tb = TensorBoard(histogram_freq=1, write_grads=True)
# fit model
model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, verbose=0, callbacks=[tb])

    我们可以使用这个回调函数,首先使用双曲正切激活函数研究深度模型拟合中梯度的动力学,然后使用修正的线性激活函数将动力学与相同的模型拟合进行比较。

    首先,下面列出了使用tanh和TensorBoard回调的deep MLP模型的完整示例。

# deeper mlp for the two circles classification problem with callback
from sklearn.datasets import make_circles
from sklearn.preprocessing import MinMaxScaler
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.initializers import RandomUniform
from keras.callbacks import TensorBoard
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
scaler = MinMaxScaler(feature_range=(-1, 1))
X = scaler.fit_transform(X)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
init = RandomUniform(minval=0, maxval=1)
model = Sequential()
model.add(Dense(5, input_dim=2, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(5, activation='tanh', kernel_initializer=init))
model.add(Dense(1, activation='sigmoid', kernel_initializer=init))
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
# prepare callback
tb = TensorBoard(histogram_freq=1, write_grads=True)
# fit model
model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, verbose=0, callbacks=[tb])

    运行该示例将创建一个新的“logs/”子目录,其中包含训练期间回调记录的统计信息。我们可以在TensorBoard web界面中查看统计数据。可以从命令行启动该接口,这要求您指定到日志目录的完整路径。

    例如,如果您在“/code”目录中运行代码,那么日志目录的完整路径将是“/code/logs/”。

    下面是在命令行(命令提示符/powershell)上启动TensorBoard接口的命令。请确保将路径更改为您的logs目录。

python -m tensorboard.main --logdir=/code/logs/

    接下来,在命令行(命令提示符/powershell)中将出现一个网址,复制并在浏览器打开。如果一切顺利,您将看到TensorBoard web界面。

    这里可能会有小问题。比如我刚刚开始打开时是酱紫的:

修正的线性激活函数(Relu)如何避免梯度消失_第7张图片

     后来参考了一下tensorboard 无法显示的问题这篇博客,原来问题出现在了路径上。所以,我的解决方法是:在logs的上一层目录shift+右击-在此处打开powershell窗口(当然也可能是cmd或者其他),输入:

python -m tensorboard.main --logdir=logs

    复制网址在浏览器打开后即可看到:

修正的线性激活函数(Relu)如何避免梯度消失_第8张图片

     在界面的“DISTRIBUTIONS”和“HISTOGRAMS”选项卡中可以查看每个训练历元每层的平均梯度图。可以使用搜索过滤器“kernel_0_grad”过滤这些图,使其仅显示密集层的梯度,排除偏差。

    我提供了下面的图的副本,尽管由于学习算法的随机性,您的具体结果可能会有所不同。

    首先,为6个层(5个隐藏层,1个输出层)中的每个层创建行图。图的名称表示层,其中“dense_1”表示输入层之后的隐藏层,“dense_6”表示输出层。

    我们可以看到输出层在整个运行过程中有很多活动,每个历元的平均梯度在0.05到0.1之间。我们还可以在第一个隐藏层中看到类似范围的一些活动。因此,渐变到达第一个隐藏层,但是最后一层和最后一层看到的是大部分活动。

修正的线性激活函数(Relu)如何避免梯度消失_第9张图片

修正的线性激活函数(Relu)如何避免梯度消失_第10张图片

     我们可以利用ReLU激活函数从深层MLP中收集相同的信息。下面列出了完整的示例。

# deeper mlp with relu for the two circles classification problem with callback
from sklearn.datasets import make_circles
from sklearn.preprocessing import MinMaxScaler
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.callbacks import TensorBoard
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
scaler = MinMaxScaler(feature_range=(-1, 1))
X = scaler.fit_transform(X)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='sigmoid'))
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
# prepare callback
tb = TensorBoard(histogram_freq=1, write_grads=True)
# fit model
model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, verbose=0, callbacks=[tb])

     如果你是新手,TensorBoard界面可能会让你感到困惑。为了简单起见,在运行第二个示例之前删除“logs”子目录。一旦运行,您可以以相同的方式启动TensorBoard接口,并通过web浏览器访问它。

    与使用tanh的深度模型的梯度相比,每个训练历元的每层平均梯度图显示了不同的情况。

    我们可以看到,第一个隐藏层的梯度更大,与较大的扩展更一致,可能是0.2到0.4,而tanh的梯度为0.05和0.1。我们还可以看到中间的隐藏层有很大的渐变。

修正的线性激活函数(Relu)如何避免梯度消失_第11张图片

修正的线性激活函数(Relu)如何避免梯度消失_第12张图片

     ReLU激活函数允许在训练过程中有更多的梯度向后流过模型,这可能是性能提高的原因。

 拓展

     本节列出了一些扩展教程的想法,您可能希望对其进行研究。

  • Weight Initialization:使用tanh激活更新deep MLP,使用Xavier均匀权值初始化并报告结果。
  • Learning Algorithm:.使用tanh激活更新deep MLP,使用Adam等自适应学习算法并报告结果。
  • Weight Changes:更新tanh和relu的例子,记录并绘制模型权值每一历元的L1向量范数,作为训练过程中每一层变化量的代理,并比较结果。
  • Study Model Depth:使用激活tanh的MLP创建一个实验,当隐藏层的数量从1增加到10时,报告模型的性能。
  • Increase Breadth:激活tanh后,将MLP隐藏层中的节点数量从5个增加到25个,当层数从1个增加到10个时,报告性能。

 总结

    在本教程中,您了解了如何在训练神经网络模型时诊断消失梯度问题,以及如何使用备用激活函数和权重初始化方案来修复它。具体来说,你学会了:

  • 梯度消失问题限制了具有双曲正切等经典激活函数的神经网络的发展。
  • 如何利用ReLU和He权值初始化修正深度神经网络多层感知器的分类问题。
  • 如何利用Relu对消失梯度问题进行诊断,确定ReLU对模型中梯度流动的影响。

你可能感兴趣的:(machine,learning,deep,learning,深度学习入门)