关于深度神经网络的调试和性能优化,很多研究者认为并没有什么固定规律去遵循。因此,除了一些基本的原则之外,不得不具体问题具体分析,不断地在实战中去培养直觉。
下面介绍一些基本的思路。
咖哥问小冰:“在刚才的调试过程中,有没有觉得其实神经网络挺难以调控的。”
小冰说:“的确。”
咖哥说:“举个例子,在开始训练之前,我们根本不知道多少轮之后会开始出现过拟合的征兆,也就是验证损失不升反降。那么只有试着运行一次,比如运行100轮,才发现原来15轮才是比较正确的选择。想想看,大型网络的训练是超级浪费时间的,本来进行15轮就得到最佳结果,却需要先运行100轮。有没有可能一次性找到最合适的轮次点?”
同学们还未回答咖哥又接着说:“再举个例子,神经网络的训练过程中,梯度下降是有可能陷入局部最低点或者鞍点的,它们也称为训练过程中的高原区。此时验证集上损失值的改善会停止,同样的损失值每轮重复出现(数据集没有做特征缩放时就经常出现这种情况)。有没有可能在机器观察到这种情况的时候就调整学习速率这个参数,因为此时,增大或减小学习速率都是跳出高原区的有效策略。”
类似的运行时动态控制可以通过回调(callback)功能来实现。所谓回调,就是在训练进行过程中,根据一些预设的指示对训练进行控制。下面是几个常用的回调函数。
■Model Checkpoint:在训练过程中的不同时间点保存模型,也就是保存当前网络的所有权重。
■Early Stopping:如果验证损失不再改善,则中断训练。这个回调函数常与Model Checkpoint结合使用,以保存最佳模型。
■Reduce LROn Plateau:在训练过程中动态调节某些参数值,比如优化器的学习速率,从而跳出训练过程中的高原区。
■TensorBoard:将模型训练过程可视化。
那么如何用代码来实现回调功能呢?示例如下:
# 导入回调功能
from keras.callbacks import Model Checkpoint
from keras.callbacks import Early Stopping
from keras.callbacks import Reduce LROn Plateau
# 设定要回调的功能
earlystop = Early Stopping(monitor='val_acc', patience=20,
verbose=1, restore_best_weights=True)
reducelr = Reduce LROn Plateau(monitor='val_acc', factor=0.5,
patience=3, verbose=1, min_lr=1e-7)
modelckpt = Model Checkpoint(filepath='ann.h5', monitor='val_acc',
verbose=1, save_best_only=True, mode='max')
callbacks = [earlystop, reducelr, modelckpt] # 设定回调
history = ann.fit(X_train, y_train, # 指定训练集
batch_size=128, # 指定批量大小
validation_data = (X_test, y_test), # 指定验证集
epochs=100, # 指定轮次
callbacks=callbacks) # 指定回调功能
上面的这段代码能一次性找到100轮中最佳的迭代次数,也就是在过拟合出现之前把较好的模型和模型内部参数保存下来。
上面出现的TensorBoard又是什么呢?
TensorBoard是一个内置于Tensor Flow的可视化工具,用以帮助我们在训练过程中监控模型内部发生的信息。具体包括以下功能。
■在训练过程中监控指标。
■将模型的架构可视化。
■显示激活和梯度的直方图。
■以三维的形式显示词嵌入。
在Kaggle中,只需要用下面两句代码配置TensorBoard:
# 导入并激活TensorBoard
%load_ext tensorboard
%tensorboard --logdir logs
然后,在Keras中,通过在回调中指定TensorBoard,就可以调用它,显示训练过程中的信息,如下段代码所示。模型开始拟合之后,Notebook中出现TensorBoard界面,而且具有交互功能,很多的曲线图像,比如准确率曲线、损失曲线,就不用我们自己去费力绘制了(如下图所示)。当然, TensorBoard可以展示出来的信息还远远不止这些,同学们可以去深入研究一下。
# 显示TensorBoard
import tensorflow as tf # 导入Tensor Flow
tensorboard_callback = tf.keras.callbacks.TensorBoard("logs")
TensorBoard—训练信息可视化
调用TensorBoard的完整代码请大家参考本课源码包中的“C05-2 Using TensorBoard.ipynb”文件。
过拟合问题在所有机器学习模型(包括神经网络)中都是性能优化过程中最为关键的问题。
在损失函数图像上,当训练集上的损失越来越低,但是验证集(或测试集)上的损失到了一个点后显著上升,或者振荡,这就表示出现了过拟合的现象。
解决过拟合问题的基本思路主要有以下几种。
(1)首先,根据奥卡姆剃刀定律,在使用非常深的网络之前应三思,因为网络越大,越容易过拟合。如果能够用较简单的小型网络解决问题,就不要强迫自己使用大网络。
(2)一种思路是在训练大型网络之前使用少量数据训练一个较小的模型,小模型的泛化好,再去训练更深、更大的网络。不然的话,费了很多精力直接训练一个大网络,最后发现结果不好就白费力气了。
(3)另外,最常见且有效地降低神经网络过拟合的方法就是在全连接层之间添加一些Dropout层。这是很好用的标准做法,不过Dropout层会对训练速度稍有影响。
(4)最后,使用较低的学习速率配合神经元的权重正则化可能是解决过拟合问题的手段之一。
最后讲一下梯度消失问题和梯度爆炸(gradient exploding)问题。
网络层数的叠加对于大数据集来说,可以带来更优的效果,那么是否单纯地叠加层数就肯定可以获得一个更好的网络呢?事实显然不是这么简单。其中最主要的原因就是梯度反向传播过程中的梯度消失(也称梯度弥散),从而导致后面的训练困难,随着层数的增加,网络最终变得无法训练。神经网络梯度下降的原理是将来自输出损失的反馈信号反向传播到更底部的层。如果这个反馈信号的传播需要经过很多层,那么信号可能会变得非常微弱,甚至完全丢失,梯度无法传到的层就好比没有经过训练一样。这就是梯度消失。
而梯度爆炸则是指神经元权重过大时,网络中较前面层的梯度通过训练变大,而后面层的梯度呈指数级增大。
其实,梯度爆炸和梯度消失问题都是因为网络太深、网络权重更新不稳定造成的,本质上都是梯度反向传播中的连锁效应。
那么有哪些可以尝试的解决方案呢?
1.选择合适的激活函数
首先,选择合适的激活函数是最直接的方法。因为如果激活函数的导数为1,那么每层的网络都可以得到相同的更新速度。我们已经介绍过的Re LU、Leaky Re LU、e LU等新型激活函数,都是可用选择。
2.权重正则化
此外,还可以考虑对神经网络各层的神经元的权重进行正则化。这个方法不仅对过拟合有效,还能抑制梯度爆炸。
Keras中的权重正则化包括以下选项。
■keras.regularizers.l1:L1正则化,加入神经元权重的绝对值作为惩罚项。
■keras.regularizers.l2:L2正则化,加入神经元权重的平方作为惩罚项。
■keras.regularizers.l1_l2:同时加入L1和L2作为惩罚项。
示例代码如下:
from keras.layers import Dense # 导入Dense层
from keras.regularizers import l2 # 导入L2正则化工具
ann.add(Dense(32, # 输出维度, 就是神经元的个数
kernel_regularizer=l2(0.01), # 权重正则化
bias_regularizer=l2(0.01)))# 偏置正则化
3.批标准化
批标准化(batch normalization)有时称为批归一化,意思就是将数据标准化的思想应用于神经网络层的内部,使神经网络各层之间的中间特征的输入也符合均值为0、标准差为1的正态分布。
在批标准化出现之前,解决过拟合和梯度消失问题的方法是在迭代过程中调整学习速率,采取较小的学习速率,以及精细的初始化权重参数。这些都是非常麻烦的工作。而批标准化使网络中间层的输入数据分布变得均衡,因此可以得到更为稳定的网络训练效果,同时加速网络的收敛,减少训练次数。很多知名的大型深度网络都使用了批标准化技术。
在Keras中,批标准化也是网络中一种特殊的层组件,通常放在全连接层或者卷积层之后,对前一层的输入数据进行批量标准化,然后送入下一层进行处理。
示例代码如下:
from keras.layers.normalization import Batch Normalization # 导入批标准化组件
ann.add(Dense(64, input_dim=14, init='uniform')) # 添加输入层
ann.add(Batch Normalization()) # 添加批标准化层
ann.add(Dense(64, init='uniform')) # 添加中间层
4.残差连接
通过上面的种种方法,如选择合适的激活函数、权重正则化和批标准化,深度神经网络的性能有所改善,但是仍然没有从根本上解决梯度消失的问题。
真正解决梯度消失的“武器”是残差连接(residual connection)结构。
它的基本思想是:在大型深度网络中(至少10层以上),让前面某层的输出跨越多层直接输入至较靠后的层,形成神经网络中的捷径(shortcut)。这样,就不必担心过大的网络中梯度逐渐消失的问题了。残差连接结构在最新的深度神经网络结构中几乎都有出现,因为它对解决梯度消失问题非常有效。
残差连接结构是何凯明在论文《Deep Residual Learning for Image Recognition》中提出的。通过残差连接,可以很轻松地构建几百层,甚至上千层的网络,而不用担心梯度消失过快的问题。
要深入研究残差连接,同学们可以先去看看何凯明在ICML2016大会上介绍这个结构的演讲。