如何使用ReLU修复消失渐变问题
渐变消失问题是训练深度神经网络时可能遇到的不稳定行为的一个示例。
它描述了深多层前馈网络或递归神经网络无法将有用的梯度信息从模型的输出端传播回模型输入端附近的层的情况。
其结果是,具有多个层的模型通常无法在给定的数据集上学习或过早收敛到较差的解决方案。
已经提出和研究了许多修复和解决方法,例如交替权重初始化方案、无监督预训练、分层训练和梯度下降的变化。也许最常见的改变是使用了修正后的线性激活函数,它已经成为新的默认函数,而不是在20世纪90年代末和21世纪初一直是默认的双曲正切激活函数。
在本教程中,你将了解如何在训练神经网络模型时诊断消失梯度问题,以及如何使用备用激活函数和权重初始化方案修复该问题。
完成本教程后,你将了解:
- 梯度消失问题限制了具有经典流行的激活函数(如双曲正切)的深度神经网络的发展。
- 如何利用ReLU和权重初始化确定用于分类的深层神经网络多层感知器。
- 如何使用TensorBoard诊断消失的梯度问题,并确认ReLU的影响,以改善通过模型的梯度流动。
我们开始吧。
教程概述。
本教程分为五个部分;它们是:
- 消失渐变问题。
- 双圆二分分类问题。
- 双圆问题的多层感知器模型。
- 双圆问题的带ReLU的更深MLP模型。
- 检查训练期间的平均梯度大小。
消失渐变问题。
神经网络的训练采用随机梯度下降法。
这包括首先计算由模型产生的预测误差,并且使用该误差来估计用于更新网络中的每个权重的梯度,以便下次产生较少的误差。该误差梯度通过网络从输出层向后传播到输入层。
希望训练具有多层的神经网络,因为更多层的增加增加了网络的容量,使其能够学习大的训练数据集,并有效地表示从输入到输出的更复杂的映射函数。
具有多层的训练网络(例如,深度神经网络)的一个问题是,当梯度通过网络向后传播时,梯度显著减小。当误差到达模型输入附近的层时,误差可能会非常小,以至于可能影响很小。因此,这个问题被称为“消失梯度”问题。
消失梯度使得很难知道参数应该朝哪个方向移动以改进成本函数。
事实上,在深度神经网络中,误差梯度可能是不稳定的,不仅消失,而且还会爆炸,其中梯度在网络中反向传播时呈指数级增加。这被称为“爆炸梯度”问题。
术语消失梯度是指这样的事实,即在前馈网络(FFN)中,反向传播的误差信号通常作为离最后层的距离的函数指数地减小(或增加)。
递归神经网络的一个特殊问题是梯度消失,因为网络的更新涉及为每个输入时间步长展开网络,实际上创建了需要权重更新的非常深的网络。一个适度的递归神经网络可能具有200到400个输入时间步长,这在概念上导致了一个非常深的网络。
在多层感知器中,消失梯度问题可能通过训练期间模型的缓慢改善速率以及可能过早收敛而显现,例如,继续训练不会导致任何进一步的改善。检查训练期间权重的变化,我们会看到更多的变化(即更多的学习)发生在靠近输出层的层中,而发生在靠近输入层的层中的变化较少。
有许多技术可用于减少前馈神经网络的消失梯度问题的影响,最显著的是交替加权初始化方案和交替激活函数的使用。
不同的训练深度网络(前馈和递归)的方法已经被研究和应用,例如预先训练、更好的随机初始尺度、更好的优化方法、特定的体系结构、正交初始化等。
在本教程中,我们将更仔细地了解如何使用交替权重初始化方案和激活函数来训练更深层次的神经网络模型。
两圆二分分类问题
作为我们探索的基础,我们将使用一个非常简单的两类或二分类问题。
scikit-learn类提供了make_circles()函数,该函数可用于创建具有指定样本数和统计噪声的二进制分类问题。
每个示例都有两个输入变量,它们定义二维平面上点的x和y坐标。这两个类的点排列在两个同心圆中(它们具有相同的中心)。
数据集中的点数由一个参数指定,其中一半将从每个圆中绘制。通过定义噪波标准偏差的“Noise”参数采样点时,可以添加高斯噪波,其中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()
运行该示例将创建一个绘图,显示生成的1,000个数据点,每个点的类值用于为每个点着色。
我们可以看到,类0的点是蓝色的,表示外圆,类1的点是橙色的,表示内圆。
生成样本的统计噪声意味着两个圆之间有一些点重叠,增加了问题的一些模糊性,使其不是平凡的。这是合乎需要的,因为神经网络可能从许多可能的解决方案中选择一个来对两个圆之间的点进行分类,并且总是犯一些错误。
既然我们已经定义了一个问题作为我们探索的基础,我们可以考虑开发一个模型来解决它。
双圆问题的多层感知器模型
我们可以开发一个多层感知器模型来解决这两个圆问题。
这将是一个简单的前馈神经网络模型,按照我们在20世纪90年代末和21世纪初学到的设计。
首先,我们将从两个圆问题生成1,000个数据点,并将输入重新缩放到范围[-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:]
接下来,我们将定义模型。
对于数据集中的两个变量,该模型将有一个具有两个输入的输入层,一个具有五个节点的隐藏层,以及一个具有用于预测分类概率的具有一个节点的输出层。隐藏层将使用双曲正切激活函数(TOH),而输出层将使用逻辑激活函数(Sigmoid)来预测类别0或类别1或介于两者之间的东西。
在隐藏层中使用双曲线切线激活函数是20世纪90年代和21世纪头10年的最佳实践,在隐藏层中使用时通常比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,大动量为0.9。
# compile model opt = SGD(lr=0.01, momentum=0.9) model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
该模型针对500个训练时段进行训练,并且在每个时段结束时与训练数据集一起评估测试数据集。
# 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['accuracy'], label='train') pyplot.plot(history.history['val_accuracy'], 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['accuracy'], label='train') pyplot.plot(history.history['val_accuracy'], label='test') pyplot.legend() pyplot.show()
运行该示例只需几秒钟即可拟合模型。
计算并显示训练和测试集上的模型性能。由于学习算法的随机性,你的特定结果可能会有所不同。考虑将该示例运行几次。
我们可以看到,在这种情况下,模型很好地学习了问题,在训练和测试数据集上的准确率都达到了81.6%左右。
Train: 0.816, Test: 0.816
在训练和测试集上创建模型精度的线状图,显示所有500个训练周期的性能变化。
对于这次运行,曲线图表明,对于训练和测试集,性能在历元300左右开始以大约80%的准确率开始放缓。
现在我们已经看到了如何使用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['accuracy'], label='train') pyplot.plot(history.history['val_accuracy'], label='test') pyplot.legend() pyplot.show()
运行该示例首先在训练和测试数据集中打印FIT模型的性能。
由于学习算法的随机性,你的特定结果可能会有所不同。考虑将该示例运行几次。
在这种情况下,我们可以看到,在训练和测试集上的性能都相当差,达到了50%左右的准确率。这表明配置的模型既不能了解问题,也不能概括解决方案。
Train: 0.530, Test: 0.468
训练期间训练和测试集上模型精度的线状图讲述了类似的故事。我们可以看到,表现很差,而且随着训练的进行,实际上会变得更糟。
双圆问题的带ReLU的更深MLP模型
在开发多层感知器网络以及其他网络类型(如CNN)时,校正的线性激活函数已取代双曲正切激活函数作为新的首选默认设置。
这是因为激活函数看起来和行为都像线性函数,使其更容易训练并且不太可能饱和,但实际上是一个非线性函数,将负输入强制为值0。它被认为是在训练更深的模型时解决消失梯度问题的一种可能的方法。
当使用校正的线性激活函数(或简称ReLU)时,最好使用加权初始化方案。我们可以使用ReLU和初始化来定义具有五个隐藏层的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['accuracy'], label='train') pyplot.plot(history.history['val_accuracy'], label='test') pyplot.legend() pyplot.show()
运行该示例将打印训练和测试数据集上模型的性能。
由于学习算法的随机性,你的特定结果可能会有所不同。考虑将该示例运行几次。
在这种情况下,我们可以看到,这个小的改变允许模型学习问题,在两个数据集上都达到了大约84%的准确率,超过了使用TANH激活函数的单层模型。
Train: 0.836, Test: 0.840
还创建了训练时段上的训练和测试集上的模型精度的线状图。这个情节显示了与我们到目前为止所看到的完全不同的动态。
该模型似乎能快速学习问题,在大约100个纪元内收敛到一个解。
ReLU激活函数的使用使我们能够为这个简单的问题拟合一个更深层次的模型,但这种能力并不是无限延伸的。例如,增加层数会导致较慢的学习速度,直到大约20层处,此时模型不再能够学习问题,至少对于所选配置是这样。
例如,下面是具有15个隐层的同一模型的训练和测试精度的折线图,表明它仍然具有学习问题的能力。
下面是具有20层的相同模型的历元训练和测试精度的折线图,显示该配置不再能够学习问题。
虽然ReLU的使用起作用了,但我们不能确信使用tanh函数失败是因为渐变消失,而ReLU成功是因为它克服了这个问题。
检查训练期间的平均梯度大小。
本节假设你正在将TensorFlow后端与Keras一起使用。如果不是这样,你可以跳过此部分。
在使用TOH激活功能的情况下,我们知道网络有足够的容量来学习问题,但是层的增加阻止了它这样做。
很难将渐变消失诊断为性能不佳的原因。一种可能的信号是检查每个训练时段每层梯度的平均大小。
我们预计靠近输出的图层比靠近输入的图层具有更大的平均渐变。
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回调的深层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/”。
下面是启动TensorBoard界面的命令,该界面将在命令行(命令提示符)上执行。请确保更改日志目录的路径。
python -m tensorboard.main --logdir=/code/logs/
接下来,打开你的Web浏览器并输入以下URL:
- http://localhost:6006。
如果一切顺利,你将看到TensorBoard Web界面。
可以在界面的“分布”和“直方图”选项卡下查看每个训练时段的每层平均梯度的曲线图。使用搜索过滤器“kernel_0_grad”,可以过滤绘图以仅显示密集层的渐变,不包括偏移。
我已经提供了以下图表的副本,尽管考虑到学习算法的随机性,你的具体结果可能会有所不同。
首先,为6个图层(5个隐藏,1个输出)中的每个图层创建线条图。地块的名称表示图层,其中“dense_1”表示输入图层之后的隐藏图层,“dense_6”表示输出图层。
我们可以看到,输出层在整个运行过程中非常活跃,每个历元的平均渐变在0.05到0.1之间。我们还可以在第一个隐藏层看到一些类似范围的活动。因此,渐变可以通过第一个隐藏层,但最后一层和最后一个隐藏层可以看到大部分活动。
我们可以使用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。我们还可以看到中间的隐藏层看到了很大的渐变。
拓展
本节列出了一些你可能希望了解的扩展教程的想法。
- 权重初始化。使用tanh激活更新深度MLP,以使用Xavier均匀权重初始化,并报告结果。
- 学习算法。使用tanh激活更新深层MLP,以使用自适应学习算法(如ADAM)并报告结果。
- 重量变化。更新tanh和relu示例,以记录和绘制每个历元的模型权重的L1向量范数,作为每个层在训练和比较结果期间有多少变化的代理。
- 研究模型深度。使用具有tanh激活的MLP创建实验,并报告隐藏层数从1增加到10时模型的性能。
- 增加广度。将具有tanh激活的MLP的隐藏层中的节点数从5增加到25,并随着层数从1增加到10来报告性能。
进一步阅读
如果你想深入了解,本节提供了更多关于该主题的资源。