作者 | Christopher Dossman 编译 | ronghuaiyang 来源 | AI公园
在我们的机器学习实验室,我们在许多高性能的机器已经积累了成千上万个小时的训练。然而,并不是只有计算机在这个过程中学到了很多东西:我们自己也犯了很多错误,修复了很多错误。
在这里,我们根据我们的经验(主要基于 TensorFlow)提出了一些训练深度神经网络的实用技巧。有些建议对你来说可能是显而易见的,但对我们中的某个人来说却不是。其他的建议可能不适用,甚至对你的特定任务来说是不好的建议:谨慎使用!
我们承认这些都是众所周知的方法。我们也站在巨人的肩膀上!我们这篇文章的目的仅仅是对它们进行高层次的总结,以便在实践中使用。
通用 Tips
使用 ADAM 优化器。它真的很好用。比起传统的优化方法,如原始的梯度下降,我更喜欢它。注意:如果要保存和恢复权重,记得在设置好AdamOptimizer之后设置Saver ,因为 ADAM 也有需要恢复的状态(即每个权重的学习率)。ReLU 是最好的非线性(激活函数)。这有点像说 Sublime 是最好的文本编辑器。但实际上,ReLUs 是快速的、简单的,而且令人惊讶的是,它们能够工作,并且没有梯度衰减的问题。虽然 sigmoid 是一种常见的教科书式激活函数,但它不能很好地通过 DNNs 传播梯度。不要在输出层使用激活函数。这应该是显而易见的,但这是一个很容易犯的错误,如果你用一个共享函数构建每个层:一定要在输出处关闭激活函数。一定要在每一层添加一个偏差。这是 ML 101:偏差本质上是将飞机转换成最佳位置。在y=mx+b中,b 是偏差,允许直线向上或向下移动到“最合适”的位置。使用 variance-scaled 初始化。在 Tensorflow 中,就像tf.contrib.layers.variance_scaling_initializer()。在我们的经验中,这比常规的高斯分布、截断的正太分布和 Xavier 更能泛化/缩放。粗略地说, variance scaling 初始化根据每一层的输入或输出的数量来调整初始随机权重的方差(TensorFlow 中的默认值是输入的数量),从而帮助信号更深入地传播到网络中,而不需要额外的“技巧”,比如 clipping 或 batch normalization。Xavier 是很相似的方法,但是 Xavier 的所有层的方差几乎相同,在那些层的形状变化很大的网络(通常是卷积网络)中,可能不能很好地处理每一层相同的变化。
白化(归一化)你的输入数据。训练时,减去数据集的均值,然后除以其标准差。你需要向各个方向拉伸和拉伸的幅度越少,你的网络学习就会越快、越容易。保持输入数据的均值以不变的方差为中心有助于解决这个问题。你还必须对每个测试输入执行相同的标准化,因此要确保你的训练集与真实数据相似。以合理保留其动态范围的方式缩放输入数据。这与归一化有关,但应该在归一化之前进行。例如,实际范围为[0,140000000]的数据“x”通常可以用tanh(x)或tanh(x/C)来处理,其中 C是某个常数,它拉伸曲线以适应 tanh 函数动态的、倾斜的部分中的更多输入范围。特别是在输入数据的一端或两端可能是无界的情况下,神经网络在(0,1)之间可以更好地学习。不要费心降低学习速度(通常)。学习率下降在 SGD 中更为常见,但 ADAM 自然地处理了这个问题。如果你绝对想要榨干每一盎司的表现:在训练结束后短时间内降低学习速度,你可能会看到一个突然的,非常小的误差下降,然后它会再次变平。如果你的卷积层有 64 或 128 个滤波器,那可能就足够了。特别是对于深度网络。实际上,128 已经很多了。如果你已经有了大量的滤波器,那么添加更多的滤波器可能不会改善性能。池化用于转换不变性。池化本质上是让网络学习图像“那部分”的“大意”。例如,最大池可以帮助卷积网络对图像中特征的平移、旋转和缩放变得健壮。调试神经网络
如果你的网络没有学习(意思是:在训练过程中,损失没有收敛,或者你没有得到你期望的结果),试试下面的建议:
过拟合 !如果你的网络没有在学习,首先要做的就是在单个数据样本上让网络过拟合。这样的话,准确度应该是 100%或 99.99%,或者接近于 0 的误差。如果你的神经网络不能对单个数据点进行过拟合,那么可能是体系结构出现严重问题,但问题可能很微妙。如果你可以过拟合一个数据点,但是在更大的集合上的训练仍然不收敛,请尝试以下建议。降低学习率。你的网络学习速度会变慢,但它可能会进入一个以前无法进入的最小值,因为之前它的步长太大了。(直觉上,当你真正想进入沟底时,你的错误是最低的,想象一下跨过路边的水沟。)提高学习率。这将加快训练,帮助收紧反馈回路,这意味着你会更早知道你的网络是否在工作。虽然网络应该更快地收敛,但它的结果可能不会很好,而且“收敛”的过程实际上可能会跳来跳去。(使用 ADAM 的时候,我们发现~0.001 是一个非常好的值,在许多实验中都是这样。)减小 minibatch 大小。将 minibatch 大小减少到 1 可以提供与权重更新相关的更细粒度的反馈,你可以使用 TensorBoard(或其他调试/可视化工具)报告这些更新。
去掉 batch normalization。随着批大小减少到 1,这样做可以梯度消失或梯度爆炸。几个星期以来,我们的网络都没有收敛,当我们删除了 batch normalization 之后,我们意识到在第二次迭代时输出都是 NaN。Batch norm 的作用是给需要止血带的东西贴上创可贴。它有它用的位置,但只有在你网络是没有 bug 的情况下才可以用。增加 minibatch 大小。更大的 minibatch — 如果可以的话,使用整个训练集 — 减少梯度更新中的方差,使每次迭代更精确。换句话说,让权重更新的方向是正确的。但是!它的有用性有一个有效的上限,物理内存的限制。通常,我们发现这不如前两个建议那么有用,这两个建议将 minibatch 大小减少到 1 并删除 batch normalization。检查一下 reshaping。剧烈的 reshaping(比如改变图像的 X、Y 维度)会破坏空间的局部性,使得网络更难学习,因为它也必须学习 reshaping。(自然景观变得支离破碎。自然特征在空间上是局部的,这就是为什么 conv 网如此有效的原因。如果使用多个图像/通道进行 reshape,要特别小心,使用numpy.stack()进行适当的对齐。仔细检查你的损失函数。如果使用复合函数,尝试将其简化为 L1 或 L2。我们发现 L1 对异常值的敏感度较低,当遇到有噪声的批处理或训练点时,L1 的调整幅度较小。仔细检查你的可视化效果,如果适用的话。你的可视化库(matplotlib, OpenCV 等)是调整数值的比例,还是剪切它们?还可以考虑使用一种感觉上一致的配色方案。用一个例子来学习一下
为了使上面描述的过程更接近实际,这里有一些损失图(通过 TensorBoard 画出来的),用于我们构建的卷积神经网络的一些实际回归实验。
起初,这个网络根本没有学习:
我们尝试对值进行 clipping,以防止它们超出界限:
嗯。看看这些没做平滑的值有多疯狂。学习率太高?我们试着降低学习速度,只对一个输入进行训练:
你可以看到学习率的最初几个变化发生在什么地方(大约在第 300 步和第 3000 步)。显然,我们衰减得太快了。所以,在衰减之前给它更多的时间,它可以做得更好:
你可以看到我们在 2000 步和 5000 步时衰减。这个更好,但仍然不是很好,因为它没有趋近于 0。
然后,我们禁用了学习率衰减,并尝试将值移动到一个更窄的范围内,不过不是通过输入 tanh。虽然这明显使错误值低于 1,但我们仍然不能过拟合训练集:
通过删除 batch normalization,我们发现,在经过一两次迭代之后,网络可以快速输出 NaN。我们禁用了 batch normalization,并将初始化更改为 variance scaling。这些改变了一切!我们能够过拟合我们的测试集,只是一个或两个输入。虽然底部的图表盖住了 Y 轴,但初始误差值远远高于 5,表明误差减少了近 4 个数量级:
上面的图表非常平滑,但是你可以看到它与测试输入过拟合的速度非常快,随着时间的推移,整个训练集的损失降到了 0.01 以下。这并没有降低学习率。在学习率下降一个数量级后,我们继续训练,得到了更好的结果:
这些结果好多了!但是如果我们以几何的方式衰减学习率而不是把训练分成两部分呢?
将每一步的学习率乘以 0.9995,结果并不好:
大概是因为衰减太快了,乘数为 0.999995 的情况要好一些,但结果几乎等于完全不衰减。我们从这个特殊的实验序列中得出结论,batch normalization 隐藏了糟糕的初始化所导致的急剧变化的梯度,降低学习率对 ADAM 优化器并没有特别的帮助,除了在最后可能会故意降低。与 batch normalization 一起,clipping 只是掩盖了真正的问题。我们还通过将高方差输入值放入 tanh 来处理它们。
我们希望随着你对构建深度神经网络越来越熟悉,你会发现这些基本技巧非常有用。通常,只是一些简单的事情就能改变一切。