深度学习的这些坑你都遇到过吗?神经网络11大常见陷阱及应对方法【新智元导读】如果你的神经网络不工作,该怎么办?本文作者列举了搭建神经网络时可能遇到的11个常见问题,包括预处理数据、正则化、学习率、激活函数、网络权重设置等,并提供解决方法和原因解释,是深度学习实践的有用资料。如果你的神经网络不工作,该怎么办?作者在这里列出了建神经网络时所有可能做错的事情,以及他自己的解决经验。1.忘记规范化数据2.忘记检查结果3.忘记预处理数据4.忘记使用正则化5.使用的batch太大6.使用了不正确的学习率7.在最后层使用了错误的激活函数8.你的网络包含了BadGradients9.初始化网络权重不正确10.你使用的网络太深了11.使用隐藏单元的数量不对忘记规范化数据了问题描述在使用神经网络时,思考如何正确地规范化数据是非常重要的。这是一个无法改变的步骤——假如这一步骤没有小心、正确地做,你的网络就几乎不可能工作。由于这个步骤非常重要,在深度学习社区中也是众所周知的,所以它很少在论文中被提及,因此初学者常常在这一步出错。怎样解决?一般来说,规范化(normalization)的意思是:将数据减去均值,再除以其方差。通常这是对每个输入和输出特征单独做的,但你可能经常会希望对特征组做或特别主翼处理某些特征的规范化。为什么?我们需要对数据进行规范化的主要原因是大部分的神经网络流程假设输入和输出数据都以一个约是1的标准差和约是0的均值分布。这些假设在深度学习文献中到处都是,从权重初始化、激活函数到训练网络的优化算法。还需要注意未训练的神经网络通常会输出约在-1到1范围之间的值。如果你希望输出其他范围的值(例如RBG图像以0-255范围的字节存储)会出现一些问题。在开始训练时,网络会非常不稳定,因为比如说预期值是255,网络产生的值是-1或1——这会被大多数用于训练神经网络的优化算法认为是严重的错误。这会产生过大的梯度,可能导致梯度爆炸。如果不爆炸,那么训练的前几个阶段就是浪费的,因为网络首先学习的是将输出值缩小到大致是预期的范围。如果规范化了数据(在这种情况下,你可以简单地将数值除以128再减去1),就不会发生这些问题。一般来说,神经网络中特征的规模也决定了其重要性。如果输出中的有一个特征规模很大,那么与其他特征相比它会产生更大的错误。类似地,输入中的大规模特征将主导网络并导致下游发生更大的变化。因此,使用神经网络库的自动规范化往往是不够的,这些神经网络库会在每个特征的基础上盲目地减去平均值并除以方差。你可能有一个输入特征,通常范围在0.0到0.001之间——这个特征的范围如此之小,因为它是一个不重要的特征(在这种情况下,你可能不想重新scale),或者因为与其他特征相比它有一些小的单元(在这种情况下,你可能想重新scale)?类似地,要小心具有这样一个较小范围的特征,它们的方差接近或等于0,如果将它们规范化,则会导致NaN不稳定。仔细考虑这些问题很重要——考虑你的每个特征真正代表什么,并将所有输入特征的“units”相等,将这一过程视为规范化。这是我认为深度学习中人在这个loop中真正需要的几个方面之一。你忘记检查结果了问题描述你已经训练了几个epochs的网络,也看到错误在减少。这是否意味着已经完成了?不幸地告诉你,几乎可以肯定你的代码中还有某些问题。在数据预处理、训练代码、甚至inference中都可能有bug。只是因为错误率下降了并不意味着你的网络在学习有用的东西。怎样解决?在流程的每个阶段都检查数据是否正确是非常重要的。通常,你需要找到一些可视化结果的方法。如果是图像数据,那么这很简单,动画数据也不需要很麻烦就能可视化。但如果是其他类型的数据,你必须找到能够检查结果的方法,以确保在预处理、训练和推断的每个流程都正确,并将结果与groundtruth数据进行比较。为什么?与传统的编程不同,机器学习系统几乎在所有情况下都会悄悄地发生失败。传统编程过程中,我们习惯了计算机在发生错误时抛出错误,并将其作为信号返回去检查bug。不幸的是,这个过程不适用于机器学习,因此,我们应该非常小心,在每个阶段用人眼去检查流程,以便知道何时出现bug,何时需要返回并更彻底地检查代码。还需要注意有很多方法可以检查网络是否正常工作。一部分方法是为了确切地说明所报告的训练错误是什么意思。可视化应用于训练集的网络的结果——你的网络的结果与实践中的groundtruth相比较如何?你可能会在训练期间将错误从100降到1,但是如果1的错误仍然是不可接受的结果,那结果仍然无法使用。如果网络在训练集上工作,那就检查验证集——它仍然适用于以前没有见过的数据吗?我的建议是从一开始就习惯于可视化所有内容——不要只在网络不工作时才可视化——要确保在开始尝试使用不同的神经网络结构之前,你已经检查过完整的流程。这是准确评估一些潜在的不同方法的唯一方法。你忘记预处理数据了问题描述大多数数据是很棘手的——通常我们知道的数据是类似的,可以用非常不同的数字表示。以角色动画(characteranimation)为例:如果我们使用角色的关节相对于运动捕捉的studio的中心的3D位置来表示数据,那么在某个位置或面向某个方向执行动作时,相较于在不同的位置、或不同的方向执行同一个动作,可能会产生大量不同的数字表示。那么我们需要以不同的方式表示数据——例如在一些局部reference框架(例如相对于角色的质量中心),以便相似的动作有相似的数值表示。怎样解决?思考你的特征表示什么——是否有一些简单的transformation,可以确保表示相似东西的数据点总是得到相似的数值表示?是否有一个局部的坐标系统可以更自然地表示数据——或许是更好的颜色空间——不同的格式?为什么?对于作为输入的数据,神经网络仅作一些基本的假设,其中之一是数据所处空间是连续的——对于大部分空间来说,两个数据点之间的点至少有一些“mix”,两个相邻的数据点某种意义上表示“相似”的东西。在数据空间中存在较大的不连续性(discontinuities),或存在表示同样事物的大量分离数据(separateddata),将使得学习任务变得更加困难。还需要注意数据预处理的另一种方法是试着减少所需数据变化的组合爆炸。例如,如果在角色动画数据训练的神经网络必须在每个位置和每个方向学习相同的动作组合,那么网络有大量容量被浪费了,并且大部分的学习过程是重复的。忘记使用正则化了问题描述正则化(Regularization)——通常以dropout、noise或网络随机过程的某种形式进行,是训练神经网络的另一个无法改变的方面。即使你认为你拥有比参数多得多的数据量,或过拟合不重要的情况,或没出现过拟合,你仍然应该添加dropout或其他形式的noise。怎样解决?正则化神经网络的最基本方法是在网络的每个线性层(卷积层或dense层)之前添加dropout。从中等到高的retainmentprobability开始,例如0.75或0.9。根据过拟合的可能性进行调整。如果你仍然认为不可能出现过拟合,那么可以将retainmentprobability设置到很高,例如0.99。为什么?正则化不仅仅是有关控制过拟合。通过在训练过程中引入一些随机过程,你在某种意义上是“平滑”(smoothing)了损失格局。这可以加快训练速度,帮助处理数据中的异常值,并防止网络的极端权重配置。还需要注意数据增强(dataaugmentation)或其他类型的noise也可以像dropout一样作为正则化的方式。虽然通常dropout被认为是将序偶多随机子网络的预测结合起来的技术,但也可以将dropout视为通过在训练过程中产生许多类似输入数据的变化来动态地扩展训练集大小的方法。而且我们知道,避免过拟合和提高网络准确性的最佳方式是拥有更多网络未见过的数据。使用的Batch太大问题描述使用太大的batch可能会对网络在训练过程中的准确性产生负面影响,因为这样会降低梯度下降的随机性。怎样解决?找到在训练时你能接受的最小的batch。在训练时能够最大限度利用GPU并行性的批量大小,对于准确性来说可能并不是最好的,因为在某些时候,更大的batch需要训练更多回(epoch)才能达到相同的准确度。不要担心从非常小的batch开始,比如16、8甚至是1。为什么?使用更小的batch生产更方便(choppier)、更随机的权重更新。这样做有两大好处。首先,能帮助训练“跳出”原本可能被卡住的局部最小值;其次,可以使训练在“更平坦”的最小值结束,一般而言,后者会代表更好的泛化性能。还需要注意数据中的其他元素有时也能像批量大小一样生效。例如,在处理图像时,将分辨率翻倍,可能会有把批量大小×4类似的效果。直观一点看,在CNN中,每个滤波器的权重更新将在输入图像的所有像素以及批处理中的每个图像上进行平均。将图像分辨率翻番,将产生超过四倍像素的平均效果,就像将批量大小提高了4倍一样。总之,重要的是考虑在每次迭代中最终的渐变更新将被平均多少,并在负面影响与尽可能多地利用GPU并行性之间保持平衡。学习率不正确问题描述学习率可能会对网络好不好训练有很大的影响。如果你刚刚入行,在常见深度学习框架各种默认选项的影响下,几乎可以肯定你没有把学习率设置对。怎样解决?把梯度剪裁(gradientclipping)关掉。找到在训练时不会发生错误的最高的学习率的值。然后将学习率设置得比这个值低一点点——这很可能非常接近最佳学习率了。为什么?许多深度学习框架默认会启用梯度裁剪。这个选项可以防止训练过程中过度优化,它会在每个步骤中强制改变权重,让权重发生最大限度的改变。这可能有用,特别是当数据中含有许多异常值的时候,因为异常值会产生很大的错误,从而导致大的梯度和权重更新。但是,默认开启这个选项也会让用户很难手动找到最佳的学习率。我发现大多数深度学习的新手都因为梯度裁剪的原因将学习率设得太高,使得整体训练行为变慢,也使改变学习率的效果不可预测。还需要注意如果你正确清理了数据,删除了大部分异常值并且正确设置学习率,那么你实际上并不需要梯度裁剪。在关闭梯度裁剪后,如果你发现训练错误偶尔会爆发,那么你完全可以重新打开梯度裁剪这个选项。但是,需要记住,训练错误频发的原因几乎总是表明你数据的一些其他异常——裁剪只是一种临时的补救方法。在最后一层使用了错误的激活函数问题描述在最后一层使用激活函数,有时可能意味着你的网络无法产生所需的全部范围的值。最常见的错误是在最后一层使用ReLU,从而导致网络只能输出正值。怎样解决?如果你做一个回归,那么在绝大多数时候你不会想在最后一层使用任何类型的激活函数,除非你确切地知道你想要输出的值的种类是什么为什么?再想想你的数据值实际代表什么,以及它们在标准化以后的范围。最可能的情况是,你的输出值为unbounded正数或负数——在这种情况下,你不应在最终层使用激活函数。如果你的输出值只在某些范围内有意义,例如由0-1内的概率组成,那么最终层应该有使用特定的激活函数,例如Sigmoid激活函数。还需要注意在最后一层使用激活函数有许多需要注意的地方。也许你知道你的系统最终会将输出裁剪到[-1,1]。那么,将这个裁剪过程添加到最终层的激活当中就是有意义的,因为这将确保你的网络错误函数不会惩罚大于1或小于-1的值。但是,没有错误也意味着这些大于1或小于-1的值也不会有梯度——这在某些情况下会使你的网络无法训练。或者,你可能会尝试在最后一层使用tanh,因为这个激活函数输出的值的范围是[-1,1],但这也可能带来问题,因为这个函数的梯度在1或-1附近变得非常小,而为了产生-1或1可能使你的权重变得非常大。一般来说,最好保险起见,不要在最后一层使用激活函数。有时候聪明反被聪明误。网络里有坏的梯度问题描述使用ReLU激活函数的深层网络通常会受所谓“死神经元”的影响,而后者是由不良梯度引起的。这可能会对网络的性能产生负面影响,在某些情况下甚至完全无法训练。怎样解决?如果你发现训练误差经过多个epoch后都没有变化,可能是