关于深度学习实践
迁移学习
不要做重复的工作。许多深度学习软件平台都有 VGG19、ResNet、Inception v3 这样的预训练模型。从头开始训练非常耗费时间。就像 2014 年 VGG 论文中所说的,「VGG 模型是用 4 块英伟达 Titan Black GPU 训练的,根据架构训练单个网络需要 2-3 周的时间。」
许多预训练模型可用于解决深度学习难题。例如,我们使用预训练 VGG 模型提取图像特征,并将这些特征反馈到 LSTM 模型来生成描述。许多预训练模型都用 ImageNet 数据集训练,如果你的目标数据和 ImageNet 差别不大,我们将固定大部分模型参数,只重新训练最后几个完全连接的层。否则,我们就要使用训练数据集对整个网络进行端到端的重训练。但是在这两种情况下,由于模型已经过预训练,再训练所需的迭代将大大减少。由于训练时间较短,即使训练数据集不够大,也可以避免过拟合。这种迁移学习在各个学科都很有效,例如用预先训练好的英语模型训练汉语模型。
然而,这种迁移学习仅适用于需要复杂模型来提取特征的问题。在我们的项目中,我们的示例与 ImageNet 不同,我们需要对模型进行端到端的重新训练。然而,当我们只需要相对简单的潜在因素(颜色)时,来自 VGG19 的训练复杂度太高。因此,我们决定建立一个新的更简单的 CNN 特征提取模型。
成本函数
并非所有的成本函数都是等价的,它会影响模型的训练难度。有些成本函数是相当标准的,但有些问题域需要仔细考虑。
分类问题:交叉熵,折页损失函数(SVM)
回归: 均方误差(MSE)
对象检测或分割:交并比(IoU)
策略优化:KL 散度
词嵌入:噪音对比估计(NCE)
词向量:余弦相似度
在理论分析中看起来不错的成本函数在实践中可能不太好用。例如,GAN 中鉴别器网络的成本函数采用了更为实用也更经得起实验考验的方法,而不是理论分析中看起来不错的方法。在一些问题域中,成本函数可以是部分猜测加部分实验,也可以是几个成本函数的组合。
正则化
L1 正则化和 L2 正则化都很常见,但 L2 正则化在深度学习中更受欢迎。
L1 正则化有何优点?L1 正则化可以产生更加稀疏的参数,这有助于解开底层表示。由于每个非零参数会往成本上添加惩罚,与 L2 正则化相比,L1 更加青睐零参数,即与 L2 正则化中的许多微小参数相比,它更喜欢零参数。L1 正则化使过滤器更干净、更易于解释,因此是特征选择的良好选择。L1 对异常值的脆弱性也较低,如果数据不太干净,运行效果会更好。然而,L2 正则化仍然更受欢迎,因为解可能更稳定。
梯度下降
始终密切监视梯度是否消失或爆炸,梯度下降问题有许多可能的原因,这些原因难以证实。不要跳至学习速率调整或使模型设计改变太快,小梯度可能仅仅由编程 Bug 引起,如输入数据未正确缩放或权重全部初始化为零。
如果消除了其他可能的原因,则在梯度爆炸时应用梯度截断(特别是对于 NLP)。跳过连接是缓解梯度下降问题的常用技术。在 ResNet 中,残差模块允许输入绕过当前层到达下一层,这有效地增加了网络的深度。
缩放
缩放输入特征。我们通常将特征缩放为以零为均值在特定范围内,如 [-1, 1]。特征的不适当缩放是梯度爆炸或降低的一个最常见的原因。有时我们从训练数据中计算均值和方差,以使数据更接近正态分布。如果缩放验证或测试数据,要再次利用训练数据的均值和方差。
批归一化和层归一化
每层激活函数之前节点输出的不平衡性是梯度问题的另一个主要来源,必要时需要对 CNN 应用批量归一化(BN)。如果适当地标准化(缩放)输入数据,DN 将学习得更快更好。在 BN 中,我们从每批训练数据中计算每个空间位置的均值和方差。例如,批大小为 16,特征图具有 10 X10 的空间维度,我们计算 100 个平均值和 100 个方差(每个位置一个)。每个位置处的均值是来自 16 个样本的对应位置平均值,我们使用均值和方差来重新归一化每个位置的节点输出。BN 提高了准确度,同时缩短了训练时间。
然而,BN 对 RNN 无效,我们需要使用层归一化。在 RNN 中,来自 BN 的均值和方差不适合用来重新归一化 RNN 单元的输出,这可能是因为 RNN 和共享参数的循环属性。在层归一化中,输出由当前样本的层输出计算的平均值和方差重新归一化。一个含有 100 个元素的层仅使用来自当前输入的一个平均值方差来重新归一化该层。
Dropout
可以将 Dropout 应用于层以归一化模型。2015 年批量归一化兴起之后,dropout 热度降低。批量归一化使用均值和标准差重新缩放节点输出。这就像噪声一样,迫使层对输入中的变量进行更鲁棒的学习。由于批量归一化也有助于解决梯度下降问题,因此它逐渐取代了 Dropout。
结合 Dropout 和 L2 正则化的好处是领域特定的。通常,我们可以在调优过程中测试 dropout,并收集经验数据来证明其益处。
激活函数
在 DL 中,ReLU 是最常用的非线性激活函数。如果学习速率太高,则许多节点的激活值可能会处于零值。如果改变学习速率没有帮助,我们可以尝试 leaky ReLU 或 PReLU。在 leaky ReLU 中,当 x < 0 时,它不输出 0,而是具有小的预定义向下斜率(如 0.01 或由超参数设置)。参数 ReLU(PReLU)往前推动一步。每个节点将具有可训练斜率。
拆分数据集
为了测试实际性能,我们将数据分为三部分: 70 % 用于训练,20 % 用于验证,10 % 用于测试。确保样本在每个数据集和每批训练样本中被充分打乱。在训练过程中,我们使用训练数据集来构建具有不同超参数的模型。我们使用验证数据集来运行这些模型,并选择精确度最高的模型。但是保险起见,我们使用 10 % 的测试数据进行最后的错乱检查。如果你的测试结果与验证结果有很大差异,则应将数据打乱地更加充分或收集更多的数据。
基线
设置基线有助于我们比较模型和 Debug,例如我们可使用 VGG19 模型作为分类问题的基线。或者,我们可以先扩展一些已建立的简单模型来解决我们的问题。这有助于我们更好地了解问题,并建立性能基线进行比较。在我们的项目中,我们修改了已建立的 GAN 实现并重新设计了作为基线的生成网络。
检查点
我们定期保存模型的输出和度量以供比较。有时,我们希望重现模型的结果或重新加载模型以进一步训练它。检查点允许我们保存模型以便以后重新加载。但是,如果模型设计已更改,则无法加载所有旧检查点。我们也使用 Git 标记来跟踪多个模型,并为特定检查点重新加载正确的模型。我们的设计每个检查点占用 4gb 空间。在云环境中工作时,应相应配置足够的存储。我们经常启动和终止 Amazon 云实例,因此我们将所有文件存储在 Amazon EBS 中,以便于重新连接。
自定义层
深度学习软件包中的内建层已经得到了更好的测试和优化。尽管如此,如果想自定义层,你需要:
用非随机数据对前向传播和反向传播代码进行模块测试;
将反向传播结果和朴素梯度检查进行对比;
在分母中添加小量的ϵ或用对数计算来避免 NaN 值。
归一化
深度学习的一大挑战是可复现性。在调试过程中,如果初始模型参数在 session 间保持变化,就很难进行调试。因此,我们明确地对所有随机发生器初始化了种子。我们在项目中对 python、NumPy 和 TensorFlow 都初始化了种子。在精调过程中,我们我们关闭了种子初始化,从而为每次运行生成不同的模型。为了复现模型的结果,我们将对其进行 checkpoint,并在稍后重新加载它。
优化器
Adam 优化器是深度学习中最流行的优化器之一。它适用于很多种问题,包括带稀疏或带噪声梯度的模型。其易于精调的特性使得它能快速获得很好的结果。实际上,默认的参数配置通常就能工作得很好。Adam 优化器结合了 AdaGrad 和 RMSProp 的优点。Adam 对每个参数使用相同的学习率,并随着学习的进行而独立地适应。Adam 是基于动量的算法,利用了梯度的历史信息。因此,梯度下降可以运行得更加平滑,并抑制了由于大梯度和大学习率导致的参数振荡问题。
Adam 优化器调整
Adam 有 4 个可配置参数:
学习率(默认 0.001);
β1:第一个矩估计的指数衰减率(默认 0.9);
β2:第二个矩估计的指数衰减率(默认 0.999),这个值在稀疏梯度问题中应该被设置成接近 1;
ϵ(默认值 1e^-8)是一个用于避免除以零运算的小值。
β(动量)通过累积梯度的历史信息来平滑化梯度下降。通常对于早期阶段,默认设置已经能工作得很好。否则,最可能需要改变的参数应该是学习率。
总结
流程:
熟悉数据:了解它们的分布并寻找模式。
端到端训练/评估框架+获取基线
固定随机种子。
尽可能简单,确保禁用任何不必要的假设。在此阶段,请务必关闭任何数据扩充的策略。
在评估中添加重要的数字。绘制测试损失图时,对整个(大型)测试集运行评估。不要只绘制批次的测试损失图,然后依靠在Tensorboard中平滑它们。我们追求正确,非常愿意为了保持理智而放弃时间。
验证损失@init。确保loss从正确的损失值开始。例如,如果正确初始化最后一层,则应在初始化时在softmax上测量-log(1/n_class)。对于L2回归、Huber损失等,可以导出相同的默认值。
init well。正确初始化最终层的权重。例如,如果您对一些平均值为50的值进行回归,则将最终偏差初始化为50。如果您有一个正:负比率为1:10的不平衡数据集,则在您的登logits上设置偏差,以便网络在初始化时预测概率为0.1。正确设置这些参数将加快收敛速度并消除“hockey stick”损失曲线,在最初的几次迭代中,您的网络基本上只是在学习偏差。
human基线。监控除损失以外的人类可解释和可检查的指标(例如准确性)。尽可能评估自己(人类)的准确性并与之进行比较。或者,对测试数据进行两次注释,对于每个示例,将一次注释视为预测,第二次注释视为基本真理。
输入相关的基线。训练输入独立的基线(例如,最简单的方法是将所有输入设置为零)。这应该比实际插入数据而不将其归零的情况更糟糕。即:您的模型是否学习从输入中提取任何信息?
过拟合一个batch。使用少数几个样本(例如,仅两三个样本)对单个批次进行过拟合。为此,我们增加了模型的容量(例如添加层或filters),并验证我们可以达到最低的可实现损耗(例如零)。我还喜欢在同一个图中可视化标签和预测,并确保一旦我们达到最小损失,它们最终会完美对齐。如果他们不这样做,那么肯定在某个地方存在一个bug,我们无法继续下一阶段。
验证是否减少了训练loss,在这个阶段,我们更加希望看到在数据集上欠拟合,因为你正在使用一个玩具模型。试着稍微增加它的容量。你的训练损失有没有像应该的那样减少?
在net之前可视化,可视化数据的明确正确位置就在y_hat=model(x)(或sess.run in tf)之前。也就是说,您希望准确地可视化进入网络的内容,将数据和标签的原始张量解码为可视化。这是唯一的“真理之源”。我无法计算这节省了我多少时间,并暴露了数据预处理和扩充方面的问题。
可视化预测动态。在训练过程中,我喜欢在固定的测试批次上可视化模型的预测。这些预测的“动态”会让你对训练的进展有非常好的直觉。很多时候,如果网络以某种方式摆动过多,暴露出不稳定性,人们可能会感觉到网络在努力适应数据。非常低或非常高的学习率在抖动量上也很容易被注意到。
使用backprop来图表来依赖关系。深度学习代码通常会包含复杂的、矢量化的和广播式的操作。我曾经遇到过的一个相对常见的错误是,人们错误地理解了这一点(例如,他们在某处使用视图而不是转置/置换),无意中在批处理维度中混合了信息。这是一个令人沮丧的事实,您的网络通常仍能正常训练,因为它将学会忽略其他样本中的数据。调试此问题(以及其他相关问题)的一种方法是将损耗设置为微不足道的值
泛化一个特例。这更像是一个通用的编码技巧,从头开始编写一个相对通用的功能。我喜欢为我现在正在做的事情编写一个非常具体的函数,让它工作起来。
过拟合
选择模型。要达到良好的训练效果,您需要为数据选择合适的结构。在选择这个问题上,我的第一条建议是:不要做英雄。我见过很多人,他们热衷于疯狂和创造性地将神经网络工具箱中的乐高积木堆积在各种对他们认为有意义的结构中。在项目的早期阶段强烈抵制这种诱惑。我总是建议人们简单地找到最相关的论文,然后复制粘贴他们最简单的体系结构,以获得良好的性能。例如,如果您正在对图像进行分类,请不要成为英雄,只需在第一次运行时复制粘贴ResNet-50即可。你可以在以后做一些更习惯的事情,并战胜它;
am会相对安全。在设定基线的早期阶段,我喜欢使用学习率为3e-4的Adam。根据我的经验,Adam对超参数(包括糟糕的学习率)更为宽容。对于ConvNets,经过良好调整的SGD几乎总是略优于Adam,但最佳学习速率区域要窄得多,且针对具体问题(注意:如果您使用RNN和相关序列模型,Adam则更常用。在项目的初始阶段,再次强调,不要做英雄,而要遵循最相关的论文。)
一次只复杂化一个。如果您有多个信号要插入分类器,我建议您一个接一个地插入它们,每次都要确保获得预期的性能提升。不要一开始就把厨房的水槽扔向你的模型。还有其他增加复杂性的方法-例如,您可以尝试先插入较小的图像,然后再将其放大,等等。
不要相信学习速率衰减默认值。如果您打算从其他领域重新编写代码,请务必非常小心使用学习率衰减。您不仅希望针对不同的问题使用不同的衰减计划,而且更糟糕的是,在典型schedule实现中,该计划将基于当前epoch,而当前epoch数仅取决于数据集的大小,可能会有很大的变化。例如,ImageNet将在第30 epoch时衰减10。如果您不训练ImageNet,那么您几乎肯定不希望这样。如果您不小心,您的代码可能会过早地秘密地将您的学习率降至零,从而导致您的模型无法收敛。在我自己的工作中,我总是禁用学习速率完全衰减(我使用一个常数LR),并在最后一直调整它。
正则化
获取更多数据。
数据扩充。
创造性的数据增加。如果有一半的假数据不起作用,假数据也可能起到作用。人们正在寻找扩展数据集的创造性方法;例如,域随机化、模拟的使用、巧妙的混合,例如将(可能模拟的)数据插入场景,甚至是GANs。
预训练:如果可以的话,即使你有足够的数据,使用预先训练好的网络也不会有什么坏处。
坚持监督学习。不要对无监督的预训练过度兴奋。据我所知,与2008年的那篇博文所告诉你的不同,目前还没有一个版本的NLP在现代计算机视觉方面取得了很好的效果(尽管这段时间来使用BERT处理NLP问题非常好,很可能是因为文本到特殊性质,以及更高的信噪比)。
较小的输入维度。删除可能包含虚假信号的功能。如果您的数据集很小,任何添加的虚假输入都只是另一个过拟合的机会。同样,如果低级细节无关紧要,请尝试输入较小的图像。
更小的模型size。在许多情况下,您可以使用网络上的领域知识约束来减小其大小。例如,过去流行在ImageNet主干的顶部使用完全连接的层,但后来这些层被简单的平均池所取代,从而消除了过程中的大量参数。
减少batch大小。由于batch范数内的标准化,较小的batch大小在某种程度上对应于更强的正则化。这是因为batch经验平均值/std是完整平均值/std的更近似版本,因此比例和偏移量会使批次更容易“摆动”。
加上drop。将dropout2d(空间dropout)用于CONVnet。谨慎使用此选项,因为辍学似乎不能很好地处理批处理规范化。
权重衰减。增加weight衰减惩罚。
早停。根据验证损失停止训练,以便在模型即将过度拟合时捕捉模型。
试试大一点的模型。我最后一次提到这一点,而且是在提前停止之后,但我发现在过去的几次中,大型车型当然最终会过度拟合,但它们的“提前停止”性能通常会比小型车型好得多。
调模型
随机网格搜索。
超参数优化。
再挤挤
集成。模型集成是一种几乎可以保证在任何情况下获得2%准确率的方法。如果您在测试时负担不起计算,请考虑使用暗知识将您的集成提取到网络中。
留着训练。我经常看到人们试图在验证损失趋于平稳时停止模型培训。根据我的经验,网络会持续很长时间的训练。有一次,我在寒假期间不小心离开了,留着一个模特训练,当我在一月份回来的时候,那是SOTA。
以下是对深度学习项目的主要步骤的简单总结:
• Define task (Object detection, Colorization of line arts) • Collect dataset (MS Coco, Public web sites) ◦ Search for academic datasets and baselines ◦ Build your own (From Twitter, News, Website,…) • Define the metrics ◦ Search for established metrics • Clean and preprocess the data ◦ Select features and transform data ◦ One-hot vector, bag of words, spectrogram etc... ◦ Bucketize, logarithm scale, spectrogram ◦ Remove noise or outliers ◦ Remove invalid and duplicate data ◦ Scale or whiten data • Split datasets for training, validation and testing ◦ Visualize data ◦ Validate dataset • Establish a baseline ◦ Compute metrics for the baseline ◦ Analyze errors for area of improvements • Select network structure ◦ CNN, LSTM… • Implement a deep network ◦ Code debugging and validation ◦ Parameter initialization ◦ Compute loss and metrics ◦ Choose hyper-parameters ◦ Visualize, validate and summarize result ◦ Analyze errors ◦ Add layers and nodes ◦ Optimization • Hyper-parameters fine tunings • Try our model variants
参考资料
https://medium.com/@jonathan_hui/how-to-start-a-deep-learning-project-d9e1db90fa72
神经网络调参经验大汇总。(原文A Recipe for Training Neural Networks)
Last updated
Was this helpful?