[译] 调试神经网络的清单
训练深度学习模型是非常耗时的工作,没有什么比花费了好几天训练而最终结果效果不佳更让人沮丧的了。因此翻译了这篇文章: Checklist for debugging neural networks,希望能给你一点启发,尽早发现模型中的问题。原文地址:https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21 ,略有删减。点击阅读原文可以跳转到该文章,需要翻墙哦!
众所周知,机器学习代码很难调试。就连简单的前馈神经网络,您也经常需要围绕网络架构、权重值初始化和网络优化做出决策 - 所有这些都可能导致机器学习代码中隐藏BUG。
正如Chase Roberts在一篇精彩的关于“如何单元测试机器学习代码”的文章中写道,他的挫折源于常见的陷阱:
-
代码永远不会崩溃、引发异常,甚至变慢。
-
网络持续训练,损失仍在不断减少。
-
几个小时后会收敛,但结果却很糟糕。
那么该怎么办呢?
本文将提供一个框架来帮助您调试神经网络:
-
简单处开始
-
确认你的模型损失(loss)
-
检查中间输出和连接
-
诊断参数
-
跟踪你的工作
您可以随意跳到特定部分或顺序阅读下面的内容!请注意:我们不涉及数据预处理或特定模型算法选择。这些主题有很多很好的在线资源(例如,阅读“选择合适的机器学习算法”)。
1.简单处开始
具有正则化和学习速率调度器的复杂架构的神经网络将比简单网络更难调试。这个第一点可能有些投机取巧,因为它与调试您已经建立的网络没有关系,但它仍值得重点推荐!
从简单开始:
-
首先构建一个更简单的模型
-
在单个数据点上训练模型
构建一个更简单的模型
作为起点,构建一个具有单个隐藏层的小型网络,并验证一切正常,然后逐渐添加模型复杂性,同时检查模型结构的每个方面(层、参数等)是否有效。
在单个数据点上训练模型
作为一种快速检查,您可以使用一组或两组训练数据点来确认模型是否会产生过拟合。神经网络应立即过拟合,训练精度为100%,验证准确度与您随机猜测的模型相当。如果模型不能在那些数据点上过拟合,那么要么数据集太小,要么有错误。
即使您已经确认模型可以工作,也请尝试在正式训练之前进行一个(或几个)epoch的训练。
2.确认你的模型损失
模型的损失是评估模型性能的主要方式,而模型在评估过程中设置重要的参数,因此您需要确保:
-
损失适合于当前任务(使用分类交叉熵损失进行多分类问题或使用焦点损失来解决类别不平衡问题)
-
损失函数都以正确的比例因子进行度量。如果您在网络中使用多种类型的损失,例如MSE、对抗式、L1、特性丢失,那么请确保所有损失都以相同的比例正确缩放。
注意初始损失也很重要。如果您的模型是通过随机猜测开始的,请检查初始损失是否接近您的预期损失。在Stanford CS231n课程中,Andrej Karpathy建议如下:
出于性能考虑寻找正确的损失。使用小参数初始化时,请确保获得预期的损失。最好先只检查数据损失(因此将正则化强度设置为零)。例如,对于具有Softmax分类器的CIFAR-10,我们预期初始损失为2.302,因为我们期望每个类别的扩散概率为0.1(因为有10个类别),而Softmax损失是正确的类别的负的对数概率,所以:-ln(0.1) = 2.302。
对于二分类例子而言,您只需对每个类别执行类似的计算。假设你的数据是20%的0类别和80%的1类别。预期的初始损失将达到 -0.2ln(0.5)-0.8ln(0.5) = 0.693147。如果您的初始损失远大于1,则可能表明神经网络的权重未平衡(即初始化得较差)或您的数据未正则化。
3.检查中间输出和连接
为了调试神经网络,理解神经网络内部的动态以及各个中间层所起的作用以及层的连接方式通常很有用。您可能遇到以下错误:
-
梯度更新的算式不正确
-
未应用权重更新
-
消失或爆炸的梯度
如果您的梯度值是零,这可能意味着优化器中的学习率太小,或者你遇到了上述的错误#1:不正确的梯度更新算式。
除了查看梯度更新的绝对值之外,还要确保监视每个层的激活幅度、权重和更新。例如,参数更新的幅度(权重和偏差)应为1-e3。
存在一种称为“死亡ReLU”或“消失梯度问题”的现象,其中ReLU神经元在学习其权重的大负偏差项后将输出零。那些神经元永远不会再在任何数据点上激活。
您可以使用梯度检查,通过使用数值方法逼近梯度来检查这些错误。如果它接近计算的梯度,则反向传播实现是正确的。要实现梯度检查,请在此处和此处查看CS231中的这些优秀资源以及Andrew Ng关于该主题的相关课程。
Faizan Shaikh撰写了关于可视化神经网络的三种主要方法:
-
初步方法 - 向我们展示训练模型的整体结构的简单方法。这些方法包括打印出神经网络的各个层的形状或过滤器以及每层中的参数。
-
基于激活的方法 - 在这些方法中,我们破译单个神经元或一组神经元的激活,以直观的了解他们正在做什么。
-
基于梯度的方法 - 这些方法倾向于在训练模型时计算前向和后向梯度。
有许多有用的工具可用于可视化各个层的激活和连接,例如ConX和Tensorboard。
使用ConX进行的动态渲染可视化示例
使用图像数据? Erik Rippel发表了一篇精彩的文章“使用Keras和Cats可视化部分卷积神经网络”。
4.诊断参数
神经网络具有大量彼此相互作用的参数,使得优化变得困难。请注意,这是一个积极研究的领域,所以下面的建议只是起点。
-
批量大小(技术上称为mini-batch) - 您希望批量大到足以准确估计误差梯度,但小到足以使mini-batch随机梯度下降(SGD)可以正则化网络。小的批量大小将会使得学习过程快速收敛,但可能会以增加噪声为代价,并可能导致优化困难。论文“On Large-Batch Training for Deep Learning: Generalization Gap and Sharp Minima”解释了为什么会这样:
在实践中已经观察到,当使用较大批次时,通过泛化的能力衡量的模型的质量会降低。我们研究了大批量体系中这种泛化下降的原因,并提供了支持大批量方法倾向于收敛到训练和测试函数的局部最小化的观点的数据证据 - 众所周知,局部的最小值导致较差的泛化。相比之下,小批量方法始终如一地收敛到平面最小化,我们的实验支持一种普遍看法,这是由于梯度估计中的固有噪声。
-
学习率- 学习率太低会导致收敛缓慢或陷入局部最小值的风险,而学习率太大会导致优化发散,因为存在跳过损失函数更深、但更窄部分的风险。可以考虑进行学习速率调度,在训练过程时降低学习速率。CS231n课程有很多关于实现退火学习率的不同技术。
机器学习框架,如Keras、Tensorflow、PyTorch、MXNet现在都有关于使用学习速率调度器/递减的文档或示例:
Keras - https://keras.io/callbacks/#learningratescheduler
Tensorflow - https://www.tensorflow.org/api_docs/python/tf/train/exponential_decay
PyTorch - https://pytorch.org/docs/stable/_modules/torch/optim/lr_scheduler.html
MXNet - https://mxnet.incubator.apache.org/versions/master/tutorials/gluon/learning_rate_schedules.html
-
梯度裁剪- 这将在反向传播期间用最大值或最大范数剪切参数的梯度。这用于解决您在上述错误#3中可能遇到的任何梯度爆炸。
-
批量标准化- 批量标准化用于标准化每层的输入,以对抗内部协变量移位问题。如果您同时使用Dropout和批量标准化,请务必阅读下面关于Dropout的观点。
来自Dishank Bansal的文章“Pitfalls of Batch Norm in TensorFlow and Sanity Checks for Training Networks”是批处理标准化常见错误的重要参考资源。
-
随机梯度下降(SGD)- 有几种SGD使用动量、自适应学习率和Nesterov更新,但在训练表现和泛化方面都没有明显的优势(参见Sebastian Ruder的优秀文章’梯度下降优化算法概述‘和这个有趣的实验’SGD > Adam?‘)。推荐的开始选择Adam或普通的带Nesterov动量的SGD。
-
正则化- 正则化对于构建泛化模型至关重要,因为它增加了对模型复杂性或极端参数值的惩罚。它显著降低了模型的方差,但没有明显增加其偏差。如CS231n课程中所讲的:
通常情况是,损失函数是数据损失和正则化损失的总和(例如,权重上的L2惩罚)。需要注意的一个危险是正则化损失可能会压倒数据损失,在这种情况下,梯度将主要来自正则化(通常具有更简单的梯度表达式)。这可以掩盖数据损失梯度的错误实现。
要对此进行审核,您应该关闭正则化并独立检查数据损失梯度。
-
Dropout - Dropout是另一种正则化网络以防止过度拟合的技术。在训练时,通过仅以某个概率p(超参数)保留神经元的激活来实现Dropout,否则将其设置为零。结果,网络必须在每个训练批次中使用不同的参数子集,这减少了特定参数的变化,防止某些参数占主导地位。
这里重要的注意事项是:如果您同时使用Dropout和批量规范化(batch norm),请谨慎处理这些操作的顺序,甚至谨慎一起使用它们。这仍然是一个活跃的研究领域,但您可以看到最新的讨论:
来自Stackoverflow用户MiloMinderBinder:“Dropout意味着完全阻止来自某些神经元的信息,以确保神经元不会共同适应。因此,批量规范化必须在Dropout之后,否则您将通过规范化统计传递信息。“
来自arXiv:通过方差转换理解Dropout和批量标准化之间的不统一(Xiang Li,Shuo Chen,Xiaolin Hu,Jian Yang) - “从理论上讲,我们发现网络从训练状态转移到测试时,Dropout会改变特定神经元的方差。但是,BN将在测试阶段保持其整个学习过程累积的统计方差。该方差的不一致性(我们将该方案命名为“方差偏移”)导致推理中不稳定的数值行为,当在BN之前应用Dropout时,最终导致更多错误的预测。
5.跟踪你的工作
在您忘记所使用的学习率或类别权重之前,很容易忽略记录实验的重要性。通过更好的跟踪,您可以轻松查看和重现以前的实验,以减少重复工作(也就是遇到相同的错误)。
但是,手动记录信息可能很难做到,也很难扩大到多次实验。 像Comet.ml这样的工具可以帮助自动跟踪数据集、代码更改、实验历史和产品模型(这包括模型的关键信息,如超参数、模型性能指标和环境详细信息)。
您的神经网络对数据、参数甚至包版本的微小变化都非常敏感 - 导致模型性能下降,并可能累积。跟踪您的工作是开始标准化环境和建模工作流程的第一步。
快速回顾
我们希望这篇文章能为调试神经网络提供坚实的起点。总结其关键点,您应该:
-
从简单开始- 首先构建一个更简单的模型,然后通过对几个数据点的训练进行测试
-
确认模型损失- 检查您是否使用了正确的损失并检查初始损失
-
检查中间输出和连接- 使用梯度检查和可视化来检查图层是否正确连接,以及梯度是否按预期更新
-
诊断参数- 从SGD到学习率,确定正确的组合(或找出错误的组合)
-
跟踪您的工作- 作为基准,跟踪您的实验过程和关键建模工件