当我的神经网络不学习时该怎么办?


146

我正在训练一个神经网络,但是训练的损失并没有减少。我怎样才能解决这个问题?

我不是在问过度拟合或正则化。我问的是如何解决训练集上我的网络性能无法提高的问题。


这个问题是有意提出的,因此关于如何训练神经网络的其他问题可以作为该问题的副本来解决,其态度是:“如果给一个人一条鱼,你就可以给他喂一天,但是如果你教一个人,人钓鱼,你可以在他的余生中养活他。” 请参阅此Meta线程进行讨论:回答“我的神经网络不起作用,请解决”问题的最佳方法是什么?

如果您的神经网络不能很好地泛化,请参阅:当我的神经网络不能很好地泛化时,我该怎么办?


1
在这种情况下,NN无法前进。youtu.be/iakFfOmanJU?t=144
约书亚

4
伊万诺夫(Ivanov)的博客“ 您的神经网络无法正常工作的原因 ”,特别是第二,第三和第四部分可能会有所帮助。
user5228 '18

Answers:


186

单元测试是您的朋友

作家之间有一种说法,就是“所有写作都是重写”,也就是说,写作的大部分都在进行修改。对于程序员(或至少是数据科学家)而言,该表达可以改写为“所有编码都在调试”。

每次编写代码时,都需要验证其是否按预期工作。我找到的验证正确性的最佳方法是将您的代码分成小段,并验证每个段是否有效。这可以通过将段输出与您知道的正确答案进行比较来完成。这称为单元测试。编写好的单元测试是成为一名好的统计学家/数据科学家/机器学习专家/神经网络从业人员的关键。根本没有替代品。

在调整网络性能之前,您必须检查代码是否没有错误!否则,您不妨重新布置RMS Titanic上的躺椅。

神经网络的两个功能使验证比其他类型的机器学习或统计模型更为重要。

  1. 神经网络不是随机森林或逻辑回归那样的“现成”算法。即使对于简单的前馈网络,用户也有很大的责任来决定如何配置,连接,初始化和优化网络。这意味着编写代码,而编写代码意味着调试。

  2. 即使在执行神经网络代码而没有引发异常的情况下,网络仍然可能存在错误!这些错误甚至可能是网络将对其进行训练的隐患,但陷入了次优的解决方案,或者最终的网络没有所需的体系结构。(这是语法错误和语义错误之间区别的示例。)

Chase Roberts的这篇中级文章“ 如何对机器学习代码进行单元测试 ”详细讨论了机器学习模型的单元测试。我从文章中借用了这个错误代码示例:

def make_convnet(input_image):
    net = slim.conv2d(input_image, 32, [11, 11], scope="conv1_11x11")
    net = slim.conv2d(input_image, 64, [5, 5], scope="conv2_5x5")
    net = slim.max_pool2d(net, [4, 4], stride=4, scope='pool1')
    net = slim.conv2d(input_image, 64, [5, 5], scope="conv3_5x5")
    net = slim.conv2d(input_image, 128, [3, 3], scope="conv4_3x3")
    net = slim.max_pool2d(net, [2, 2], scope='pool2')
    net = slim.conv2d(input_image, 128, [3, 3], scope="conv5_3x3")
    net = slim.max_pool2d(net, [2, 2], scope='pool3')
    net = slim.conv2d(input_image, 32, [1, 1], scope="conv6_1x1")
    return net

看到错误了吗?实际上没有使用许多不同的操作,因为以前的结果被新变量覆盖。在网络中使用此代码块仍会进行训练,权重也会更新,并且损失甚至可能会减少-但该代码肯定没有达到预期的效果。(作者在使用单引号或双引号时也不一致,但这纯粹是风格。)

与神经网络有关的最常见的编程错误是

  • 变量被创建但从未使用(通常是由于复制粘贴错误);
  • 渐变更新的表达式不正确;
  • 不应用体重更新;
  • 损失函数的度量标准不正确(例如,交叉熵损失可以用概率或对数表示)
  • 该损失不适用于该任务(例如,对回归任务使用分类交叉熵损失)。

走路前爬行;跑步前先走

机器学习中的热点之一是宽而深的神经网络,以及带有奇特布线的神经网络。但是这些网络并没有完全形成。他们的设计师从较小的单位开始为他们服务。首先,建立一个具有单个隐藏层的小型网络,并验证其是否正常运行。然后逐步增加其他模型的复杂性,并验证每个模型是否同样有效。

  • 少量 的神经元中的层可以限制网络学习,下配合使表示。太多的神经元会导致过度拟合,因为网络会“记忆”训练数据。

    即使可以从数学上证明只需要少量的神经元即可对问题进行建模,通常情况下,拥有“更多”神经元会使优化器更容易找到“良好”配置。(但是我认为没有人完全理解为什么会这样。)我在这里的XOR问题的背景下提供了一个示例:我的迭代是否不需要为MSE <0.001太高的XOR训练NN?

  • 选择隐藏层的数量可使网络从原始数据中学习抽象。如今,深度学习风靡一时,具有大量层次的网络已显示出令人印象深刻的结果。但是添加过多的隐藏层可能会导致过度拟合的风险或使网络优化变得非常困难。

  • 选择一个聪明的网络布线可以为您完成很多工作。您的数据源是否适合专用网络体系结构?卷积神经网络可以在“结构化”数据源,图像或音频数据上取得令人印象深刻的结果。递归神经网络可以很好地处理顺序数据类型,例如自然语言或时间序列数据。残留连接可以改善深度前馈网络。

神经网络训练就像撬锁一样

为了获得最先进的结果,甚至仅仅获得良好的结果,您必须设置所有配置为可以很好地协同工作的部件。设置一个实际学习的神经网络配置就像选择一把锁:所有部分都必须正确排列仅仅在正确的位置放置一个翻转开关是不够的,仅正确地设置架构或优化器也是不够的。

调整配置选择并不是真正简单地说一种配置选择(例如学习率)比另一种(例如学习单元数)重要,因为所有这些选择都与所有其他选择相互作用,因此选择可以与其他地方的选择结合起来很好。

这是配置选项的非穷举列表,它们也不是正则化选项或数值优化选项。

所有这些主题都是活跃的研究领域。

非凸优化很难

神经网络的目标函数仅在没有隐藏单元,所有激活都是线性的且设计矩阵为全秩时才凸出-因为这种配置同样是一个普通的回归问题。

在所有其他情况下,优化问题是非凸的,非凸优化是困难的。训练神经网络的挑战是众所周知的(请参阅:为什么很难训练深度神经网络?)。此外,神经网络具有大量参数,这将我们限制为仅一阶方法(请参阅:为什么牛顿法没有广泛用于机器学习中?)。这是一个非常活跃的研究领域。

  • 学习速率设置得太大会导致优化方案发散,因为您将从“峡谷”的一侧跳到另一侧。将此值设置得太小将阻止您取得任何实际进展,并可能使SGD中固有的噪声使您的梯度估计值不堪重负。

  • 如果渐变阈值高于某个阈值,则渐变裁剪会重新缩放该范数。我曾经以为这是一个“设置后遗忘”参数,通常为1.0,但是我发现通过将其设置为0.25,可以使LSTM语言模型大大改善。我不知道为什么。

  • 学习率安排可能会降低训练过程中的学习率。以我的经验,尝试使用调度与regex很像:它用两个问题(“如何在某个时期后学会继续学习”代替一个问题(“如何在某个时期后学会继续学习”?)。吗?”和“如何选择合适的时间表?”)。其他人则坚持计划是必不可少的。我让你决定。

  • 选择一个合适的小批量可以间接影响学习过程,因为较大的小批量将比较小的小批量具有较小的方差()。您希望小批量生产足够大以提供有关梯度方向的信息,但又要足够小以使SGD可以规范您的网络。

  • 随机梯度下降有许多变种,它们使用动量,自适应学习率,Nesterov更新等来改善原始SGD。设计更好的优化器是非常活跃的研究领域。一些例子:

  • 刚推出时,Adam优化器引起了很多兴趣。但是最近的一些研究发现,具有动量的SGD可以胜过神经网络的自适应梯度法。“ 机器学习中自适应梯度方法的边际价值 ”,作者:Ashia C. Wilson,Rebecca Roelofs,Mitchell Stern,Nathan Srebro,Benjamin Recht

  • 但是,另一方面,这篇最近的论文提出了一种新的自适应学习速率优化器,它可以缩小自适应速率方法和SGD之间的差距。“ 关闭自适应梯度法的训练深层神经网络的泛化峡 ”,由孟京辉陈,顾拳拳

    在训练深度神经网络中,采用历史梯度信息来自动调整学习速率的自适应梯度方法普遍被认为比具有动量的随机梯度下降法(SGD)更差。这使得如何弥合自适应梯度法的泛化差距成为一个悬而未决的问题。在这项工作中,我们表明自适应梯度方法(例如Adam,Amsgrad)有时会“过度自适应”。我们设计了一种新算法,称为部分自适应动量估计方法(Padam),该算法将Adam / Amsgrad与SGD结合在一起,从而实现了两全其美。在标准基准测试上的实验表明,Padam可以像Adam / Amsgrad一样保持快速收敛速度,同时可以在训练深度神经网络时推广SGD和SGD。

正常化

数据的规模可以在训练上产生很大的不同。

正则化

选择和调整网络正则化是构建泛化效果好的模型(即,不会过度拟合训练数据的模型)的关键部分。但是,当您的网络正在努力减少训练数据的损失时(当网络不学习时),正则化可以掩盖问题所在。

当我的网络无法学习时,我会关闭所有正则化功能,并验证非正则化网络是否正常工作。然后,我将每个正则化片重新添加回去,并验证它们中的每一个都能正常工作。

此策略可以指出某些正则化设置不正确的地方。一些例子是

保留实验记录

设置神经网络时,我不会对任何参数设置进行硬编码。相反,我在一个配置文件(例如JSON)中执行此操作,该文件已读取并用于在运行时填充网络配置详细信息。我保留所有这些配置文件。如果我进行任何参数修改,我将创建一个新的配置文件。最后,我将所有因训练和验证而损失的每个周期附加为注释。

k

例如,我想学习LSTM语言模型,因此我决定制作一个Twitter机器人,该机器人可以编写新的推文来响应其他Twitter用户。我在业余时间(从研究生到工作之间)从事这项工作。用了大约一年的时间,我遍历了150多个不同的模型,然后找到了一个符合我想要的模型:生成有意义的新英语文本。(一个关键的症结,也是进行大量尝试的部分原因是,仅仅获得低的样本外损失是不够的,因为早期的低损失模型已经成功地记住了训练数据,因此,它只是逐字地复制德语文本块以响应提示-进行了一些调整,以使模型更自发且仍然具有较低的损失。)


11
那里有很多好的建议。有趣的是,您的评论中有多少与我为调试带有MCMC采样方案的复杂模型的参数估计或预测所做的(或已经看到的其他评论)相似。(例如,当代码未正确实现时,该代码似乎可以正常工作。)
Glen_b

11
@Glen_b在大多数统计/机器学习课程中,我认为编码最佳实践没有得到足够的重视,这就是为什么我如此强调这一点。我看过许多NN帖子,OP在其中留下了类似“哦,我发现一个错误,现在可以正常工作”的注释
。– Sycorax

7
我用python讲授了数据科学程序设计课程,实际上,第一天我们作为主要概念进行了功能和单元测试。打好仗。
马修·德鲁里

8
+1表示“所有编码都在调试”。令我如此惊讶的是,有如此多的张贴者似乎认为编码是一项简单的工作,几乎不需要花力气;谁期望他们的代码在首次运行时能够正常运行;以及似乎无法继续的人。有趣的是,它们是正确的一半:编码容易-但是编程很难。
Bob Jarvis

41

发布的答案很棒,我想添加一些过去对我有很大帮助的“健全性检查”。

1)在单个数据点上训练模型。如果这可行,请在具有不同输出的两个输入上训练它。

这验证了一些事情。首先,它通过检查模型是否可以拟合数据来快速向您显示模型能够学习。就我而言,对于二进制预测,我经常会犯傻与做Dense(1,activation='softmax')vs的错误,Dense(1,activation='sigmoid')第一个给出垃圾结果。

如果您的模型无法拟合几个数据点,则可能是模型太小(在当今时代这不太可能),或者其结构或学习算法有问题。

2)注意您最初的损失。

L=0.3ln(0.5)0.7ln(0.5)0.7

0.3ln(0.99)0.7ln(0.01)=3.2

您可以通过在几千个示例上对模型进行预测,然后对输出进行直方图来进一步研究。这对于检查数据是否正确规范化特别有用。例如,如果您期望输出严重偏向0,则最好取期望输出的平方根来转换期望输出(您的训练数据)。这将避免在输出端出现饱和的S型曲线的梯度问题。

3)概括模型输出以进行调试

例如,假设您正在使用LSTM根据时序数据进行预测。也许在您的示例中,您只关心最新的预测,因此LSTM输出单个值而不是序列。将LSTM切换为在每个步骤都返回预测(以keras为单位return_sequences=True)。然后,您可以在每个步骤之后查看隐藏状态的输出,并确保它们实际上是不同的。此方法的一个应用是确保在屏蔽序列时(即用数据填充序列以使其长度相等),LSTM正确地忽略了屏蔽的数据。如果不对模型进行泛化,您将永远找不到这个问题

4)查看各个图层

Tensorboard提供了一种可视化图层输出的有用方法。这可以帮助确保输入/输出在每一层中都正确归一化。它还可以捕获错误的激活。您还可以根据一批预测查询keras中的图层输出,然后查找具有可疑的偏斜激活(全部为0或全部为非零)的图层。

5)首先建立一个更简单的模型

您已确定解决问题的最佳方法是将CNN与边界框检测器结合使用,进一步处理图像裁剪,然后使用LSTM结合所有内容。GPU初始化模型只需要10分钟。

相反,制作一批假数据(形状相同),然后将模型分解为组件。然后,用虚拟模型代替每个组件(您的“ CNN”可能只是一个2x2 20步长卷积,只有2个隐藏单元的LSTM)。这将帮助您确保模型结构正确并且没有多余的问题。我用这样的模型苦苦挣扎了一段时间,当我尝试一个简单的版本时,我发现由于keras错误,其中一个图层没有被正确屏蔽。您可以轻松(快速)查询内部模型层,并查看是否正确设置了图形。

6)标准化预处理和程序包版本

特别是神经网络对数据的微小变化极为敏感。例如,两个流行的图像加载包是cv2PIL。仅依靠打开 JPEG,这两个软件包将产生略有不同的图像。差异通常很小,但是由于这种情况,您偶尔会看到模型性能下降。它还使调试成为一场噩梦:您在训练期间获得了验证分数,然后在以后使用不同的加载器并在同一织补数据集上获得了不同的准确性。

因此,如果您要从github下载某人的模型,请密切注意他们的预处理。他们使用什么图像加载器?他们使用什么图像预处理程序?调整图像大小时,它们使用什么插值?他们是否先调整大小然后对图像进行归一化?还是相反?RGB图像的通道顺序是什么?

标准化程序包的最安全方法是使用一个requirements.txt文件,该文件概述您的所有程序包,就像在培训系统设置上一样,直至keras==2.1.5版本号。从理论上讲,将Docker与您的培训系统上的GPU一起使用将产生相同的结果。


7
(+1)检查初始损失是一个很好的建议。很遗憾我没有回答。
Sycorax

7
确保模型可以过拟合是一个好主意。我习惯于将过度拟合视为一种弱点,以至于我从没有明确地认为(直到您提到)过度拟合的能力实际上是一种优势。
约翰·科尔曼

15

不要训练神经网络开始!

所有的答案都很棒,但是有一点需要提及:从您的数据中可以学到什么吗?(可以认为是某种测试)。

如果您尝试预测的标签与您的功能无关,那么培训损失可能会很难减少。

相反,应开始校准线性回归,随机森林(或您喜欢的任何其超参数数量少且行为可以理解的方法)。

然后,如果您在这些模型上取得了不错的性能(优于随机猜测),则可以开始调整神经网络(@Sycorax的答案将解决大多数问题)。


5
xk

11

从根本上讲,训练NN / DNN模型的基本工作流程或多或少总是相同的:

  1. 定义NN体系结构(多少层,哪种层,层之间的连接,激活功能等)

  2. 从某些来源(互联网,数据库,一组本地文件等)读取数据,查看一些示例(以确保导入顺利进行),并在需要时执行数据清理。这一步并不像人们通常认为的那么简单。原因是,对于DNN,当我们拟合更多标准非线性参数统计模型(理论上,NN属于该家族)时,我们通常会处理比过去大几个数量级的巨大数据集。

  3. 以某种方式标准化或标准化数据。由于NN是非线性模型,因此对数据进行归一化不仅会影响数值稳定性,还会影响训练时间以及NN输出(归一化之类的线性函数不会与非线性分层函数对接)。

  4. 将数据分成训练/验证/测试集,如果使用交叉验证,则分成多个数据。

  5. 训练神经网络,同时控制验证集的损失。在这里,您可以享受非凸优化的令人心动的乐趣,在这里您不知道是否存在任何解决方案,是否存在多个解决方案,就泛化错误和达到的接近程度而言,这是最好的解决方案它。训练损失和验证损失曲线之间的比较当然可以为您提供指导,但不要低估NN(尤其是DNN)的顽固态度:即使您有训练有素的情况,它们通常也会(可能缓慢地)减少训练/验证损失破坏代码中的错误

  6. 检查测试仪的准确性,并制作一些诊断图/表。

  7. 返回到第1点,因为结果不好。重申ad nauseam

当然,具体细节将根据特定的用例而改变,但是考虑到这个粗糙的画布,我们可以想到更可能出错的地方。

基本架构检查

这可能是问题的根源。通常,我会进行以下初步检查:

  • 寻找一种可以很好地解决您的问题的简单体系结构(例如,在图像分类的情况下为MobileNetV2)并应用适当的初始化(在此级别,通常可以使用random)。如果这可以正确地对您的数据进行训练,那么至少您知道数据集中没有明显的问题。如果找不到适合您情况的简单,经过测试的架构,请考虑一个简单的基准。例如,通过朴素的贝叶斯分类器进行分类(甚至只是对最常见的分类进行分类),或者使用ARIMA模型进行时间序列预测

  • 构建单元测试。忽略此操作(以及使用血腥的Jupyter Notebook)通常是我要检查的NN代码中出现问题的根本原因,尤其是当该模型应该在生产中部署时。由于最受好评的答案已经涵盖了单元测试,因此我将添加一个库,该库支持NN的单元测试开发(不幸的是,仅在Tensorflow中)。

训练套

仔细检查您的输入数据。例如,查看您是否颠倒了训练集和测试集的标签(一次发生在我身上-___-),或者是否导入了错误的文件。查看一些输入样本以及相关的标签,并确保它们有意义。检查归一化的数据是否真的被归一化(看看它们的范围)。此外,现实世界的数据集很脏:对于分类,可能会有高水平的标签噪声(样本具有错误的类别标签),或者对于多元时间序列预测,某些时间序列成分可能缺少很多数据(我已经看到一些输入的数字高达94%)。

训练期间将训练集馈入网络的顺序可能会产生影响。尝试随机调整训练集(不破坏输入和输出之间的关联),看看训练损失是否减少。

最后,检查培训集是否存在问题的最佳方法是使用另一个培训集。如果要进行图像分类,请使用标准数据集,例如CIFAR10或CIFAR100(或ImageNet,如果可以负担得起的话),而不是所收集的图像。这些数据集已经过充分的测试:如果您的训练损失在这里下降了,但不是原始数据集上的下降,那么您的数据集中可能存在问题。

做黄金测试

有两个测试,我称为黄金测试,这对于在未经训练的NN中查找问题非常有用:

  • 将训练集减少为1或2个样本,并以此训练。NN应该立即过度拟合训练集,非常快地达到训练集的100%的准确度,而验证/测试集的准确度将为0%。如果这没有发生,则您的代码中存在错误。

  • 相反的测试:您保留了完整的训练集,但对标签进行了混洗。NN现在可以学习的唯一方法是记忆训练集,这意味着训练损失将非常缓慢地减少,而测试损失将非常迅速地增加。特别是,您应该在测试集上达到随机机会损失。这意味着,如果您有1000个班级,则应达到0.1%的准确度。如果在改组标签之前和之后看不到训练损失之间的任何区别,则表明您的代码有错误(请记住,我们已经在之前的步骤中检查了训练集的标签)。

检查您的训练指标是否合理

如果您的班级失衡严重,准确性(0-1损失)是一个糟糕的指标。尝试一些更有意义的事情,例如交叉熵损失:您不仅想正确分类,而且想以高精度分类。

拿出大枪

如果没有任何帮助,现在该是摆弄超参数的时候了。这很容易成为NN训练中最糟糕的部分,但是它们是巨大的,无法识别的模型,其参数可以通过求解非凸优化来拟合,因此通常无法避免这些迭代。

  • 尝试使用不同的优化程序:SGD的训练速度较慢,但​​会导致较低的泛化误差,而Adam的训练速度较快,但测试损失会停滞于较高的值
  • 尝试减小批量大小
  • 首先提高学习率,然后衰减它,或使用周期性学习率
  • 添加层
  • 添加隐藏的单位
  • 逐渐删除正则化(也许将批处理规范切换几层)。训练损失现在应该减少,但是测试损失可能会增加。
  • 可视化每一层的权重和偏差分布。我从来没有到过这里,但是,如果您使用的是BatchNorm,您将期望获得近似标准的正态分布。查看权重的范数是否随着时代的变化而异常增加。
  • 如果您在训练时遇到一些错误,请在Google上搜索该错误。一天早晨,我在试图修复一个完美运行的体系结构时浪费了时间,却发现我安装的Keras版本具有多GPU支持,因此我不得不对其进行更新。有时我不得不做相反的事情(降级软件包版本)。
  • 更新您的简历,并开始寻找其他工作:-)

+1,但“血腥Jupyter笔记本”?愿意对此发表评论吗?:)
变形虫

2
这就是为什么我讨厌Jupyter笔记本。TL; DR:隐藏状态,比较困难,存在安全问题,它会鼓励不良的编程习惯,例如不使用单元/回归/集成测试。培训NN已经足够困难,而人们却不会忘记编程的基础知识。
DeltaIV

2
我可能太否定了,但是坦率地说,我已经受够了从GitHub克隆Jupyter Notebooks的人们,认为将代码适应他们的用例需要花费几分钟的时间,然后来找我抱怨没有用。出于薄煎饼的缘故,请使用真实的IDE(例如PyCharm或VisualStudio Code)并创建结构良好的代码,而不是准备笔记本!特别是如果您计划将模型交付生产,它将使事情变得容易得多。
DeltaIV

2
大声笑。“ Jupyter笔记本”和“单元测试”是互不相关的。
Sycorax

2
(+1)这是一个很好的写作。随机测试的建议确实是解决存在漏洞的网络的好方法。
Sycorax '18

6

如果模型没有学习,则很有可能您的反向传播无法正常工作。但是,像神经网络这样的黑匣子模型有很多事情会出错,您需要检查很多事情。我认为Sycorax和Alex都提供了很好的综合答案。只是想添加一种尚未讨论的技术。

ϵ

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.