这篇文章的主要内容来自作者的自身经验和一些在线资源(如最出名的斯坦福大学的CS231n课程讲义),是关于如何调试卷积神经网络从而提升其性能的。
文章主要关注深度神经网络架构下的有监督学习方式。虽然这个指南基于Python3.6坏境使用tensorflow(TF)编程,但它仍然可以作为一种语言无关的指南来使用。
假设我们有一个卷积神经网络来训练和评估,并假设评估结果比预期的更糟。
下文是排除故障并逐步提高性能的步骤,第一部分是进行故障排除之前的必备事项和良好实践。每个后续的章节标题都对应着一个问题,该部分专门用于解决这个问题。
我们会先抛出“更为常见”的问题,在每个标题下,也会优先给出“最容易实现”的解决方案。
故障排除前…
以下是在编写深度学习算法时要遵循的最佳实践,关于这个主题的很好的资料来源于CS231n课程讲义以及Bengio的综述论文。
Stanford's CS231n课程讲义:
http://cs231n.github.io/
Bengio的综述论文:
https://arxiv.org/pdf/1206.5533v2.pdf
1.使用适当的日志记录和有意义的变量名称。在TF中能够通过名称来跟踪不同的变量,并在TF的可视化工具TensorBoard中显示出计算图。
最重要的是,每隔几个训练步骤都要确保记录了相关的值,例如:step_number, accuracy, loss, learning_rate。适用的话,加上更具体的度量标准(例如在图像分割任务中使用mean_intersection_over_union,即mean_iou)。之后,就可以根据训练步骤画出损失曲线。
译者注:IoU,预测窗口与标记窗口之间的交并比。
2. 确保你的网络连接正确。使用TensorBoard或其他调试技术确保计算图中每个操作的输入和输出都准确无误,还要确保在将数据和标签送入网络之前对其进行适当的预处理和匹配。
3. 实施数据扩充(data augmentation)技术。虽然这一点并不是对所有情况都适用,但是如果处理图像的话,应用简单的数据扩充技术(例如镜像,旋转,随机裁剪和尺度变换,添加噪声,elastically deforming等),大部分时候会带来巨大的性能提升。而且TF具有大多数这些操作的内置函数,十分良心了。
4. 对所有层使用权重初始化和正则化。不要将权重初始化为相同的值,更糟的是将其初始化为0。这样做会带来对称性(symmetry)和潜在的梯度弥散问题,在大多数情况下会导致可怕的结果。通常,如果在权重初始化时遇到问题,可以考虑将Batch Normalization层添加到网络中。
BN论文链接:
https://arxiv.org/abs/1502.03167。
5. 确保正则项不会“压倒”损失函数中的其他项。关闭正则化,找出“损失”的数量级,然后适当地调整正则项大小。确保随着正则化强度的增加,损失也在增加。
6. 尝试过拟合一个小数据集。关闭正则化/随机失活/数据扩充,使用训练集的一小部分,让神经网络训练几个周期。确保可以实现零损失,如果没有,那么很可能什么地方出错了。
在某些情况下,将损失降到0尤其具有挑战性。例如,在图像语义分割中,如果你的损失涉及每个像素的softmax-ed logits和ground truth labels之间的交叉熵,那么可能真的难以将其降低到0。不过,你应当寻求达到接近100%的准确率,通过获取softmax-ed logits的argmax并将其与ground truth labels进行比较来计算。
译者注:在机器学习中,“ground truth”一词指的是监督学习技术中训练集分类的准确性,简单地说就是正确标注的数据。
7. 在过拟合上述小数据集的同时,找到合适的学习率。下面的内容直接引自Bengio的论文:最优学习率通常接近于不会增加训练误差的最大学习率,一种可以指导启发式设置学习率的观测方法是,例如,以较大的学习率开始,如果训练误差发散,就用最大学习率除以3再试试,直到观察不到发散为止。
8. 执行梯度检验(gradient checks)。如果你在计算图中使用自定义操作——即不是内置的TF操作,则梯度检验尤其重要。下面的链接有一些实现梯度检验的技巧。
Gradient checks:
http://cs231n.github.io/neural-networks-3/
如果损失(Loss Value)没有改善…
如果你训练了几个周期,损失还是没有改善,甚至还越来越大,则应该考虑下面几个步骤了:
1.确保使用了合理的损失函数,而且优化的是正确的张量。此处提供常见的损失函数列表。
https://en.wikipedia.org/wiki/Loss_functions_for_classification
2. 使用一个得当的优化器,此处提供了常用优化器列表。
https://keras.io/optimizers/
3. 确保变量真的在训练。为了检查这一点,你可以查看TensorBoard的直方图,或者编写一个脚本,在几个不同的训练实例中计算每个张量的范数(L1或 L∞),并打印出这些张量的名称。
如果你的变量未按预期进行训练,请参阅下列文章
https://gist.github.com/zeyademam/0f60821a0d36ea44eef496633b4430fc#variable-not-training
4. 调整初始学习率并实施适当的学习率计划(learning rate schedule),这可能是最具影响力的“修复”。如果损失越来越严重,可能是初始学习率太大了。另一方面,如果损失几乎不变,可能是初始学习率太小了。无论如何,一旦确定了有效的初始学习率,就应该进行学习率衰减。像ADAM这样的优化器会在内部实现学习率衰减,但是,这些学习率的更新通常会很慢,在优化器之上实现自己的学习率计划会是个好主意。
5. 确保没有过拟合。有一些方法可以实现过拟合,也有一些方法可以避免它。绘制损失值与训练周期的曲线图,如果曲线看起来像抛物线,那么很可能过拟合了。
请参阅这篇文章:
https://gist.github.com/zeyademam/0f60821a0d36ea44eef496633b4430fc#overfitting
如果变量没在训练…
像上面说的那样,使用TensorBoard的直方图,或编写一个脚本,在几个不同的训练实例中计算每个张量的范数,并打印出这些张量的名称。如果变量未按预期进行训练:
1. 确保TF将其视为可训练的变量。查看TF GraphKeys以获取更多详细信息。
https://www.tensorflow.org/api_docs/python/tf/GraphKeys
2. 确保没发生梯度弥散。如果下游变量(接近输出的变量)训练正常但上游变量(接近输入的变量)几乎不变,则可能遇上了梯度弥散的问题。
请参阅下面的文章:
https://gist.github.com/zeyademam/0f60821a0d36ea44eef496633b4430fc#vanishingexploding-gradients
3. 确保ReLus还在“放电”。如果大部分神经元“电压”被“钳制”为零,那么应该重新修正权重初始化策略,尝试使用不太激进的学习率计划,并尝试减少正则化(权重衰减)。
译者注:ReLu,线性整流函数,又称修正线性单元,是一种人工神经网络中常用的激活函数。
梯度弥散/梯度爆炸…
1. 考虑使用更好的权重初始化策略。如果在训练开始时梯度更新非常小,则这点尤其重要。
2. 考虑换一下激活函数。如果正在使用ReLus,请考虑使用leaky ReLu或MaxOut激活函数替换它们。你应该完全避免sigmoid激活函数,并远离tanh。
3.如果用递归神经网络的话,考虑使用LSTM。
详情请看这篇文章:
https://medium.com/@karpathy/yes-you-should-understand-backprop-e2f06eab496b
过拟合…
过拟合就是网络“记住”了训练数据的情况。如果网络在训练集和验证集上,准确率差别很大,可能它就过拟合了。
参见此处的Train / Val准确率部分:
http://cs231n.github.io/neural-networks-3/
1. 实施数据扩充技术。可上翻至本文第一节“故障排除前”中的内容。
2. 实施随机失活(dropout)。随机失活指在训练期间每个步骤随机地忽略掉一些神经元,在前向传播期间这些神经元的贡献被移除并且在反向传播期间它们不被更新。
了解更多信息:
https://machinelearningmastery.com/dropout-regularization-deep-learning-models-keras/
3. 增加正则化。
4. 实施批规范化操作(Batch normalization)。
详情请看这里:
https://arxiv.org/abs/1502.03167
5. 实施基于验证集的早期停止(early stopping)。由于网络训练了太多个周期,因此可能会发生过拟合,早期停止有助于消除这个问题。
参考这里:
https://en.wikipedia.org/wiki/Early_stopping#Validation-based_early_stopping
6. 如果其他一切都失败了,请使用较小的网络。这真的应该是你的最后手段,事实上这里的课程讲义对这种做法保持谨慎。
还能调试些什么…
1. 考虑使用加权的损失函数(weighted loss function)。例如,在图像语义分割中,神经网络对输入图像中的每个像素进行分类。与其他类相比,某些类可能很少出现,在这种情况下,权衡少见的类可能会改进mean_iou度量。
2. 更网络架构。你之前的网络可能太深或太浅。
3. 考虑使用集成模型。
4. 用小数步长卷积(strided convolutions)替换最大值汇合层和平均值汇合层。
5. 执行彻底的超参数搜索。
6. 更改随机数种子。
7. 如果上面的方法都失败了,还是去寻找更多数据吧。
相关报道:
https://gist.github.com/zeyademam/0f60821a0d36ea44eef496633b4430fc