文章

模型超参数调整策略

模型超参数调整策略

我们都知道目前的神经网络是一个黑盒,里面有太多我们无法解释的地方了,各种超参数又对网络的性能产生了特别大的影响,所以说调参是深度学习中一个非常重要的部分,但是我们目前已知的一些调参方式几乎都是根据前人的经验得来的,很多地方并没有严格的公式证明,我下面介绍的一些方法也是根据前辈的经验得到的,有些地方可能并不一定很严谨,但是以下的调参方式在大多数的场景中还是非常有效的。

1. 超参数概念

1.1 参数和超参数的区别?

区分两者最大的一点就是是否通过数据来进行调整,模型参数通常是用数据来驱动调整,超参数则不需要数据来驱动,而是在训练前或者训练中人为的进行调整的参数。

1.2 神经网络中包含哪些超参数?

  • 一类需要从数据中学习和估计得到,称为模型参数(Parameter)—即模型本身的参数。比如,卷积核和BN层的参数。
  • 一类则是机器学习算法中的调优参数(tuning parameters),需要人为设定,称为超参数(Hyperparameter)。比如学习率(learning rate)、批样本数量(batch size)、不同优化器的参数以及部分损失函数的可调参数。

(注意!卷积核尺寸、卷积核数量与卷积核内的参数是不同的,卷积核数量、卷积核尺寸这些也是超参数,因为这些也是人为设计的)

1.3 为什么要进行超参数调优?

本质上,这是模型优化寻找最优解和正则项之间的关系。网络模型优化调整的目的是为了寻找到全局最优解(或者相比更好的局部最优解),而正则项又希望模型尽量拟合到最优。两者通常情况下存在一定的对立,但两者的目标是一致的,即最小化期望风险。模型优化希望最小化经验风险,而容易陷入过拟合,正则项用来约束模型复杂度。所以如何平衡两者之间的关系,得到最优或者较优的解就是超参数调整优化的目的。

1.4 超参数的重要性顺序

  • 学习率。在网络参数、优化参数、正则化参数中最重要的超参数可能就是学习率了。 学习率直接控制着训练中网络梯度更新的量级,直接影响着模型的有效容限能力,Pytorch中内置多种的学习率调整策略。
  • 批样本数量,动量优化器(Gradient Descent with Momentum)的动量参数 **β** *。 批样本决定了数量梯度下降的方向。过小的批数量,例如batch size为 1 ,即每个样本都去修正一次梯度方向,样本之间的差异越大越难以收敛。若网络中存在批归一化(batchnorm),batch size过小则更难以收敛,甚至垮掉。这是因为数据样本越少,统计量越不具有代表性,噪声也相应的增加。而过大的batch size,会使得梯度方向基本稳定,容易陷入局部最优解,降低精度。一般参考范围会取在 [1 : 1024] 之间,当然这个不是绝对的,需要结合具体场景和样本情况;动量衰减参数 *β 是计算梯度的指数加权平均数,并利用该值来更新参数,设置为 0.937 是一个常见且效果不错的选择;
  • Adam优化器的超参数、权重衰减系数、丢弃法比率(dropout)和网络参数。在这里说明下,这些参数重要性放在最后并不等价于这些参数不重要。而是表示这些参数在大部分实践中不建议过多尝试,例如Adam优化器中的 β1, β2,ϵ,常设为 0.9 、0.999 、10−8就会有不错的表现。权重衰减系数通常会有个建议值,例如 0.0005,使用建议 值即可,不必过多尝试。 dropout通常会在全连接层之间使用防止过拟合,建议比率控制在 [0.2,0.5] 之间。

    使用dropout时需要特别注意两点:

    1. 在RNN中,如果直接放在memory cell中,循环会放大噪声,扰乱学习。一般会建议放在输入和输出层;
    2. 不建议dropout后直接跟上batchnorm,dropout很可能影响batchnorm计算统计量,导致方差偏移,这种情况下会使得推理阶段出现模型完全垮掉的极端情况;网络参数通常也属于超参数的范围内,通常情况下增加网络层数能增加模型的容限能力,但模型真正有效的容限能力还和样本数量和质量、层之间的关系等有关,所以一般情况下会选择先固定网络层数,调优到一定阶段或者有大量的硬件资源支持可以在网络深度上进行进一步调整。

1.5 详细分析Batch Size大小对网络训练的影响

以下实验是参考文献[1]这个大佬做的,有些地方不是太严谨,但是我们实际训练中确实是这样的。

实验环境:1080Ti × 1 Pytorch 0.4.1

超参数:SGD(lr = 0.02, momentum=0.5)

  • 迭代速度

5f0e2285b17afdc28e8b91b7ecf2d47b

其实纯粹cuda计算的角度来看,完成每个iter的时间大batch和小batch区别并不大,这可能是因为本次实验中,反向传播的时间消耗要比正向传播大得多,所以batch size的大小对每个iter所需的时间影响不明显,未来将在大一点的数据库和更复杂的模型上做一下实验(因为反向的过程取决于模型的复杂度,与batchsize的大小关系不大,而正向则同时取决于模型的复杂度和batch size的大小。而本次实验中反向的过程要比正向的过程时间消耗大得多,所以batch size的大小对完成每个iter所需的耗时影响不大。)

完成每个epoch运算的所需的全部时间主要卡在:

  1. load数据的时间,
  2. 每个epoch的iter数量。

因此对于每个epoch,不管是纯计算时间还是全部时间,大体上还是大batch能够更节约时间一点,但随着batch增大,iter次数减小,完成每个epoch的时间更取决于加载数据所需的时间,此时也不见得大batch能带来多少的速度增益了。

  • 梯度平滑程度

604e78318326ece35d2412c074734352

由于现在绝大多数的框架在进行mini-batch的反向传播的时候,默认都是将batch中每个instance的loss平均化之后在进行反向传播,所以相对大一点的batch size能够防止loss震荡的情况发生。

从这两张图中可以看出batch size越小,相邻iter之间的loss震荡就越厉害,相应的,反传回去的梯度的变化也就越大,也就越不利于收敛。同时很有意思的一个现象,batch size为 1 的时候,loss到后期会发生爆炸,这主要是 lr=0.02 设置太大,所以某个异常值的出现会严重扰动到训练过程。

这也是为什么对于较小的batchsize,要设置小lr的原因之一,避免异常值对结果造成的扰巨大扰动。而对于较大的batchsize,要设置大一点的lr的原因则是大batch每次迭代的梯度方向相对固定,大lr可以加速其收敛过程。

  • 收敛速度

在衡量不同batch size的优劣这一点上,我选用衡量不同batch size在同样参数下的收敛速度快慢的方法。

下表中可以看出,在minst数据集上,从整体时间消耗上来看(考虑了加载数据所需的时间),同样的参数策略下 (lr = 0.02, momentum= 0.5 ),要模型收敛到accuracy在 98 左右,batchsize在 6-60 这个量级能够花费最少的时间,而batchsize为 1 的时候,收敛不到 98 ;batchsize过大的时候,因为模型收敛快慢取决于梯度方向和更新次数,所以大batch尽管梯度方向更为稳定,但要达到 98 的accuracy所需的更新次数并没有量级上的减少,所以也就需要花费更多的时间,当然这种情况下可以配合一些调参策略比如warmup LR,衰减LR等等之类的在一定程度上进行解决(这个先暂且按下不表),但也不会有本质上的改善。

不过单纯从计算时间上来看,大batch还是可以很明显地节约所需的计算时间的,原因前面讲过了,主要因为本次实验中纯计算时间中,反向占的时间比重远大于正向。

64604fa06c449b34745c33ee60e3ab0e

  • 实验的漏洞

为了保证独立变量,我在实验中不同batch设置了同样的lr,然后比较收敛速度,这样是不公平的,毕竟大batch还是要配合更大的初始lr,所以后续还要做一下实验,固定每个batch size,看lr的变化对不同batch size收敛素的的影响。

1.6 常见的超参数范围

1.6.1 从粗到细分阶段调参

实践中,一般先进行初步范围搜索,然后根据好结果出现的地方,再缩小范围进行更精细的搜索。

  1. 建议先参考相关论文,以论文中给出的参数作为初始参数。至少论文中的参数,是个不差的结果。
  2. 如果找不到参考,那么只能自己尝试了。可以先从比较重要,对实验结果影响比较大的参数开始,同时固定其他参数,得到一个差不多的结果以后,在这个结果的基础上,再调其他参数。例如学习率一般就比正则值,dropout值重要的话,学习率设置的不合适,不仅结果可能变差,模型甚至会无法收敛。
  3. 如果实在找不到一组参数,可以让模型收敛。那么就需要检查,是不是其他地方出了问题,例如模型实现,数据等等。

1.6.2 提高速度

调参只是为了寻找合适的参数,而不是产出最终模型。一般在小数据集上合适的参数,在大数据集上效果也不会太差。因此可以尝试对数据进行精简,以提高速度,在有限的时间内可以尝试更多参数。

  • 对训练数据进行采样。例如原来100W条数据,先采样成1W,进行实验看看。
  • 减少训练类别。例如手写数字识别任务,原来是10个类别,那么我们可以先在2个类别上训练,看看结果如何。

1.6.3 超参数范围

建议优先在对数尺度上进行超参数搜索。比较典型的是学习率和正则化项,我们可以从诸如 0.001/0.01/0.1/1/10 ,以 10 为阶数进行尝试。因为他们对训练的影响是相乘的效果。不过有些参数,还是建议在原始尺度上进行搜索,例如dropout值: 0.3/ 0.5/ 0.7 )。

1.6.4 经验参数

这里给出一些参数的经验值,避免大家调参的时候毫无头绪。

  • learning rate: 1/ 0.1 /0.01 /0.001 , 一般从 1 开始尝试。很少见learning rate大于 10 的。学习率一般要随着训练进行衰减。衰减系数一般是 0.5 。 衰减时机,可以是验证集准确率不再上升时,或固定训练多少个周期以后。不过更建议使用自适应梯度的办法,例如adam,adadelta,rmsprop等,这些一般使用相关论文提供的默认值即可,可以避免再费劲调节学习率。对RNN来说,有个经验,如果RNN要处理的序列比较长,或者RNN层数比较多,那么learning rate一般小一些比较好,否则有可能出现结果不收敛,甚至Nan等问题。
  • 网络层数:先从 1 层开始。
  • 每层结点数:16/32/128,超过 1000 的情况比较少见。
  • batch size: 128 上下开始。batch size值增加,的确能提高训练速度。但是有可能收敛结果变差。如果显存大小允许,可以考虑从一个比较大的值开始尝试。因为batch size太大,一般不会对结果有太大的影响,而batch size太小的话,结果有可能很差。
  • clip c(梯度裁剪): 限制最大梯度,其实是value = sqrt(w12….),如果value超过了阈值,就算一个衰减系系数,让value的值等于阈值: 5,10,15 2+w2
  • dropout: 0.5
  • L2正则: 1.0 ,超过 10 很少见。
  • 词向量embedding大小:128,256
  • 正负样本比例: 这个是非常忽视,但是在很多分类问题上,又非常重要的参数。很多人往往习惯使用训练数据中默认的正负类别比例,当训练数据非常不平衡的时候,模型很有可能会偏向数目较大的类别,从而影响最终训练结果。除了尝试训练数据默认的正负类别比例之外,建议对数目较小的样本做过采样,例如进行复制。提高他们的比例,看看效果如何,这个对多分类问题同样适用。 在使用mini-batch方法进行训练的时候,尽量让一个batch内,各类别的比例平衡,这个在图像识别等多分类任务上非常重要。

2. 网络训练中的超参调整策略

2.1 如何调试模型?

在讨论如何调试模型之前,我们先来纠正一个误区。通常理解如何调试模型的时候,我们想到一系列优秀的神经网络模型以及调试技巧。但这里需要指出的是数据才是模型的根本,如果有一批质量优秀的数据,或者说你能将数据质量处理的很好的时候,往往比挑选或者设计模型的收益来的更大。那在这之后才是模型的设计和挑选以及训练技巧上的事情。

  1. 探索和清洗数据。探索数据集是设计算法之前最为重要的一步,以图像分类为例,我们需要重点知道给定的数据集样本类别和各类别样本数量是否平衡,图像之间是否存在跨域问题(例如网上爬取的图像通常质量各异,存在噪声)。若是类别数远远超过类别样本数(比如类别 10000 ,每个类别却只有 10 张图像),那通常的方法可能效果并不显著,这时候few-shot learning或者对数据集做进一步增强可能是你比较不错的选择。再如目标检测,待检测目标在数据集中的尺度范围是对检测器的性能有很大影响的部分。因此重点是检测大目标还是小目标、目标是否密集完全取决于数据集本身。所以,探索和进一步清洗数据集一直都是深度学习中最重要的一步。这是很多新手通常会忽略的一点。
  2. 探索模型结果。探索模型的结果,通常是需要对模型在验证集上的性能进行进一步的分析,这是如何进一步提升模型性能很重要的步骤。将模型在训练集和验证集都进行结果的验证和可视化,可直观的分析出模型是否存在较大偏差以及结果的正确性。以图像分类为例,若类别间样本数量很不平衡时,我们需要重点关注少样本类别在验证集的结果是否和训练集的出入较大,对出错类别可进一步进行模型数值分析以及可视化结果分析,进一步确认模型的行为。
  3. 监控训练和验证误差。首先很多情况下,我们忽略代码的规范性和算法撰写正确性验证,这点上容易产生致命的影响。在训练和验证都存在问题时,首先请确认自己的代码是否正确。其次,根据训练和验证误差进一步追踪模型的拟合状态。若训练数据集很小,此时监控误差则显得格外重要。确定了模型的拟合状态对进一步调整学习率的策略的选择或者其他有效超参数的选择则会更得心应手。

    训练集损失下降 ,验证集损失下降–》网络正在学习(理想状态) 训练集损失下降 ,验证集损失不变–》网络过拟合(尝试dropout、L2等手段)

    训练集损失不变 ,验证集损失下降–》数据集有问题(检查数据集)

    训练集损失不变 ,验证集损失不变–》网络遇到学习瓶颈(减小learning rate或batch size)

    训练集损失上升 ,验证集损失上升–》网络结构设计不当、参数设置不当、数据集经过清洗

2.2 为什么要做学习率调整?

学习率可以说是模型训练最为重要的超参数。通常情况下,一个或者一组优秀的学习率既能加速模型的训练,又能得到一个较优甚至最优的精度。过大或者过小的学习率会直接影响到模型的收敛。

当模型训练到一定程度的时候,损失将不再减少,这时候模型的一阶梯度接近零,对应Hessian矩阵通常是两种情况,一、正定,即所有特征值均为正,此时通常可以得到一个局部极小值,若这个局部极小值接近全局最小则模型已经能得到不错的性能了,但若差距很大,则模型性能还有待于提升,通常情况下后者在训练初最常见。二,特征值有正有负,此时模型很可能陷入了鞍点,若陷入鞍点,模型性能表现就很差。以上两种情况在训练初期以及中期,此时若仍然以固定的学习率,会使模型陷入左右来回的震荡或者鞍点,无法继续优化。所以,学习率衰减或者增大能帮助模型有效的减少震荡或者逃离鞍点。

2.3 自动化超参数搜索方法有哪些?

目前自动化搜索主要包含网格搜索,随机搜索,基于模型的超参优化

  • 网格搜索:

通常当超参数量较少的时候,可以使用网格搜索法。即列出每个超参数的大致候选集合。利用这些集合进行逐项组合优化。在条件允许的情况下,重复进行网格搜索会当优秀,当然每次重复需要根据上一步得到的最优参数组合,进行进一步的细粒度的调整。网格搜索最大的问题就在于计算时间会随着超参数的数量指数级的增长。

  • 随机搜索:

随机搜索,是一种用来替代网格搜索的搜索方式。随机搜索有别于网格搜索的一点在于,我们不需要设定一个离散的超参数集合,而是对每个超参数定义一个分布函数来生成随机超参数。随机搜索相比于网格搜索在一些不敏感超参上拥有明显优势。例如网格搜索对于批样本数量(batch size),在 [16,32,64] 这些范围内进行逐项调试,这样的调试显然收益更低下。当然随机搜索也可以进行细粒度范围内的重复的搜索优化。

c2147e86e56cc66ff0e8ea598075dabf

  • 基于模型的超参优化:

有别于上述两种的搜索策略,基于模型的超参调优问题转化为了优化问题。直觉上会考虑是否进行一个可导建模,然后利用梯度下降进行优化。但不幸的是我们的超参数通常情况下是离散的,而且其计算代价依旧很高。

基于模型的搜索算法,最常见的就是贝叶斯超参优化。有别于的网格搜索和随机搜索独立于前几次搜索结果的搜索,贝叶斯则是利用历史的搜索结果进行优化搜索。其主要有四部分组成,

  1. 目标函数,大部分情况下就是模型验证集上的损失。
  2. 搜索空间,即各类待搜索的超参数。
  3. 优化策略,建立的概率模型和选择超参数的方式。
  4. 历史的搜索结果。首先对搜索空间进行一个先验性的假设猜想,即假设一种选择超参的方式,然后不断的优化更新概率模型,最终的目标是找到验证集上误差最小的一组超参数。

2.4 什么是神经网络架构搜索(NAS)

2015至2017年间,是CNN网络设计最兴盛的阶段,大多都是由学者人工设计的网络结构。这个过程通常会很繁琐。其主要原因在于对不同模块组件的组成通常是个黑盒优化的问题,此外,在不同结构超参数以及训练超参数的选择优化上非凸优化问题,或者是个混合优化问题,既有离散空间又有连续空间。NAS(Neural Architecture Search)的出现就是为了解决如何通过机器策略和自动化的方式设计出优秀高效的网络。而这种策略通常不是统一的标准,不同的网络结合实际的需求通常会有不同的设计,比如移动端的模型会在效率和精度之间做平衡。NAS通常会分为三个方面,搜索空间(在哪搜索),搜索策略(如何搜索)及评价预估。

  • 搜索空间,即在哪搜索,定义了优化问题所需变量。不同规模的搜索空间的变量其对于的难度也是不一样的。早期由于网络结构以及层数相对比较简单,参数量较少,因此会更多的使用遗传算法等进化算法对网络的超参数和权重进行优化。深度学习发展到目前,模型网络结构越来越复杂,参数量级越来越庞大,这些进化算法已经无法继续使用。但若我们先验给定一些网络结构和超参数,模型的性能已经被限制在给定的空间,此时搜索的空间已变得有限,所以只需对复杂模型的架构参数和对应的超参数进行优化即可。
  • 搜索策略, 即如何搜索,定义了如何快速、准确找到最优的网络结构参数配置的策略。常见的搜索方法包括:随机搜索、贝叶斯优化、强化学习、进化算法以及基于模型的搜索算法。其中主要代表为2017年谷歌大脑的使用强化学习的搜索方法。
  • 评价预估,定义了如何高效对搜索的评估策略。深度学习中,数据规模往往是庞大的,模型要在如此庞大的数据规模上进行搜索,这无疑是非常耗时的,对优化也会造成非常大的困难,所以需要一些高效的策略做近似的评估。 这里一般会有如下三种思路:
    • 使用些低保真的训练集来训练模型。低保真在实际中可以用不同的理解,比如较少的迭代次数,用一小部分数据集或者保证结构的同时减少通道数等。这些方法都可以在测试优化结构时大大降低计算时间,当然也会存在一定的偏差。但架构搜索从来并不是要一组固定的参数,而是一种优秀的模型结构。最终选取时,只需在较优秀的几组结构中进行全集训练,进行择优选取即可。
    • 使用代理模型。除了低保真的训练方式外,学者们提出了一种叫做代理模型的回归模型,采用例如插值等策略对已知的一些参数范围进行预测,目的是为了用尽可能少的点预测到最佳的结果。
    • 参数级别的迁移。例如知识蒸馏等。用已训练好的模型权重参数对目标问题搜索,通常会让搜索拥有一个优秀的起点。由于积累了大量的历史寻优数据,对新问题的寻优将会起到很大的帮助。

2.5 网络设计中,为什么卷积核设计尺寸都是奇数

我们发现在很多大部分网络设计时都会使用例如 3×3/5×5/7×7 等奇数尺寸卷积核,主要原因有两点:

  • 保证像素点中心位置,避免位置信息偏移
  • 填充边缘时能保证两边都能填充,原矩阵依然对称

2.6 网络设计中,权重共享的形式有哪些,为什么要权重共享

权重共享的形式:

  • 深度学习中,权重共享最具代表性的就是卷积网络的卷积操作。卷积相比于全连接神经网络参数大大减少;
  • 多任务网络中,通常为了降低每个任务的计算量,会共享一个骨干网络。
  • 一些相同尺度下的结构化递归网络

权重共享的好处:

权重共享一定程度上能增强参数之间的联系,获得更好的共性特征。同时很大程度上降低了网络的参数,节省计算量和计算所需内存(当然,结构化递归并不节省计算量)。此外权重共享能起到很好正则的作用。正则化的目的是为了降低模型复杂度,防止过拟合,而权重共享则正好降低了模型的参数和复杂度。

因此一个设计优秀的权重共享方式,在降低计算量的同时,通常会较独享网络有更好的效果。

权重共享不仅在人工设计(human-invented)的网络结构中有简化参数,降低模型复杂度的作用,在神经网络搜索(NAS)的网络结构中可以使得child model的计算效率提升,使得搜索过程可以在单卡GPU上复现。

3. 一些调参小技巧

作者:Tang AI 来源:知乎[3]

  1. 做新模型的时候,最开始不要加激活函数,不要加batchnorm,不要加dropout,先就纯模型。然后再一步一步的实验,不要过于信赖经典的模型结构(除非它是预训练的),比如加了dropout一定会有效果,或者加了batchnorm一定会有提升所以先加上,首先你要确定你的模型处于什么阶段,到底是欠拟合还是过拟合,然后再确定解决手段。
  2. 如果是欠拟合,直接模型宽度深度增加,一般 2 倍递增或者数据集增强,特别在nlp领域可以用大量数据增强方式,比如同义词替换,随机抛弃词语,句子改写,句子转译等。这些方法都试过可以,考虑增加relu,mish等作为某些层的激活函数不过在此做之前建议最好把每一层的参数打印出来,看是否符合正态分布,一般来说relu可能有奇效,but最后一层千万不要relu,因为relu可能把很多数值都干为 0 ,所以使用relu需要慎重,如果效果严重下降,建议看看relu的参数分布。
  3. 如果过拟合,首先是dropout,然后batchnorm,过拟合越严重dropout+bn加的地方就越多,有些直接对embedding层加,有奇效。
  4. 对于数据量巨大的推荐系统的模型来说一个epoch足矣,再多就会过拟合。
  5. 做召回的同学,不要迷信专家们说的embedding做l2 norm,笔者就踩过这个坑,直接对embedding l2结果效果贼垃圾,查了半天,直接去掉l2,效果正常了。
  6. 做推荐的同学,一般特征不要直接全怼进去,最好是一个一个的加特征进行效果测试,因为有些特征可能导致模型过拟合,最好还是一个一个加
  7. 学习率最好是从高到底 2 倍速度递减一般从 0.01 开始。
  8. 对于稀疏特征多的模型采用adagrad,稠密特征多的模型用adam
  9. 召回负采样负样本效果肯定高于曝光未点击负样本
  10. 粗排用精排后topk做负样本,基本是有效果的
  11. batch size对于推荐来说 32 − 64 − 128 − 512 测试效果再高一般也不会正向了,再低训练太慢了。
  12. 对于负样本太多的数据集,测试集的loss降并不代表没有过拟合,试试看看f1或者auc有没有降低,因为有可能负样本学的爆好,所以loss降低,但是正样本凉了
  13. 对于长文本来说longformer的效果高于各种bert变体
  14. 对于图像和nlp,效果一直不提高,可以尝试自己标注一些模型经常分错的case,然后加入训练会有奇效。
  15. 对于推荐序列来说pooling和attention的效果差别真的不大,基本没有diff
  16. 对于推荐来说senet和lhuc,是深度学习领域为数不多的可以观察特征重要性的模型结构
  17. 一般不要尝试用强化学习优化,因为真的很费力气,而且效果很一般
  18. bert不要太过于相信cls的embedding能力,还是要看看它有没有做过相关任务,特别对于文本匹配场景
  19. 序列特征才用layernorm,正常用batchnorm。
  20. 推荐召回用softmax效果比sigmoid更好,意思就是召回更适合对比学习那种listwise学习方式。
  21. 参数初始化用xavier和truncated_normal可以加速收敛,但是,同样是tensorflow和pytorch用同样的初始化,pytorch可能存在多跑一段时间才开始收敛的情况,所以,如果出现loss不下降的情况,大哥请耐心一点,多跑几个epoch,当然你也可以用tensorflow实现一把,看看效果是不是一样
  22. 对于推荐系统的老汤模型,建议热启动,但是热启动大概率也是追平,小概率找到了之前没有找到的关键特征,可以超越,其他操作笔者的实验都是负向,除了热启动,而且就算热启动,如果只启动embedding层也是不行的,必须embedding+DNN全部热启动,然后仅对倒数第一层作扰动,或者类似wide&deep,新加一个结构对最后一层个性化扰动,为啥最后一层,因为,你扰动太多又负向了。
  23. 对于nlp任务,采用embedding扰动的方式有奇效,比如Fast Gradient Method(FGM)和Projected Gradient Descent(PGD)
  24. 推荐多目标任务可以考虑采用Gradient Surgery的方式,帕累托就算复杂度又高,效果也没有好很多,不推荐。
  25. Focal loss对于极大不平衡的数据集确实有奇效,其中gamma因子可以成 10 10 10倍数衰减
  26. 对于embedding可视化的问题最好是先用pca降维,然后采用t-sne进行可视化,但是,对于类别很多的情况,个人建议还是肉眼抽样观察,t-sne只能对于类目不多的情况的embedding进行可视化。
  27. 显存不够用的时候,gradient checkpointing可以起到降低显存的效果
  28. 对于机器阅读任务,在bert层后加bi-attention或者coattention有奇效。
  29. 在推荐系统中,神经网络模型对于连续数值的处理是不如xgb的,所以,最好对连续数值分箱,一般等频分箱足矣,但是,个人建议还是观察数据分布,把outlier单独一箱,如果还想完美一点可以,用IV值看看分箱的优劣。

4. 李飞飞高徒Karpathy的33个神经网络炼丹技巧

4.1 梳理数据

训练神经网络的第一步是不要碰代码,先彻底检查自己的数据。这一步非常关键。我喜欢用大量时间浏览数千个样本,理解它们的分布,寻找其中的模式。幸运的是,人类大脑很擅长做这件事。有一次,我发现数据中包含重复的样本,还有一次我发现了损坏的图像/标签。我会查找数据不均衡和偏差。我通常还会注意自己的数据分类过程,它会揭示我们最终探索的架构。比如,只需要局部特征就够了还是需要全局语境?标签噪声多大?

此外,由于神经网络是数据集的压缩/编译版本,你能够查看网络(错误)预测,理解预测从哪里来。如果网络预测与你在数据中发现的不一致,那么一定是什么地方出问题了。

在你对数据有了一些感知之后,你可以写一些简单的代码来搜索/过滤/排序标签类型、标注规模、标注数量等,并沿任意轴可视化其分布和异常值。异常值通常能够揭示数据质量或预处理中的 bug。

4.2 配置端到端训练/评估架构、获取基线结果

现在我们已经理解了数据,那我们就可以开始构建高大上的多尺度 ASPP FPN ResNet 并训练强大的模型了吗?当然还不到时候,这是一个充满荆棘的道路。我们下一步需要构建一个完整的训练、评估架构,并通过一系列实验确定我们对准确率的置信度。

在这个阶段,你们最好选择一些不会出错的简单模型,例如线性分类器或非常精简的 ConvNet 等。我们希望训练这些模型,并可视化训练损失、模型预测和其它度量指标(例如准确率)。当然在这个过程中,我们还需要基于一些明确假设,从而执行一系列对照实验(ablation experiments)。

该阶段的一些技巧与注意事项:

  • 固定随机 seed:始终使用固定的随机 seed 能保证很多属性,例如在我们两次运行相同代码时能得到相同的输出。这能消除变化因子,从进行合理的判断。
  • 简化:确保禁用不必要的技巧。例如,在这个阶段肯定需要关闭数据增强。数据增强可以在后期引入,并作为一种强大的正则化策略。不过在这个阶段引入的话,它就有机会带来一些愚蠢的 bug。
  • 使用多数据、少次数的验证评估:当我们在绘制测试损失时,我们需要在整个比较大的测试集中执行评估。不要过几个批量就绘制一次测试损失,然后再依赖 TensorBoard 的平滑处理。我们虽然追求的是准确率,但也要防止犯这些低级错误。
  • 在初始化中验证损失:验证你的损失函数在初始化中有比较合理的损失值。例如,如果你正确地初始化最终层,那么你应该通过-log(1/n_classes) 度量初始化的 Softmax 值。L2 回归和 Huber 损失函数等都有相同的默认值。
  • 优秀的初始化:正确地初始化最终层。例如,如果你正在对均值为 50 的一些数据做回归处理,那么初始化的最终偏置项就应该为 50 。如果你有一个非平衡数据集(两类样本数 1:10),那么就需要在 logits 上设置偏置项,令模型在初始化时预测概率为 0.1 。正确配置这些偏置项将加快收敛速度,因为网络在前面几次迭代中基本上只在学习偏置。
  • 人类基线结果:监控损失值等其他度量指标(例如准确度),这些指标应该是人类能解释并检查的。尽可能评估你自己(人类)获得的准确率,并与构建的模型做对比。或者对测试数据进行两次标注,其中一次为预测值,另一次为标注值。
  • 独立于输入的基线结果:训练一个独立于输入的基线模型,例如最简单的方法就是将所有输入都设置为 0 。这样的模型应该比实际输入数据表现更差,你的模型是否准备好从任何输入中抽取任何信息?
  • 在批数据上过拟合:在单个批数据上使得过拟合(两个或多个少样本)。为此,我们需要增加模型拟合能力,并验证我们能达到的最低损失值(即 0)。我还想在同一张图中显示标签和预测值,并确保损失值一旦达到最小,它们就能完美地对齐了。
  • 验证训练损失的下降:在这一阶段,你可能希望在数据集上实现欠拟合,该阶段的模型应该是极简的。然后我们尝试增加一点模型的拟合能力,再看看训练损失是否稍微下降了一些。
  • 在输入网络前可视化:在运行模型之前,我们需要可视化数据。也就是说,我们需要可视化输入到网络的具体数据,即可视化原始张量的数据和标签。这是唯一的「真实来源」,我有很多次都是因为这个过程而节省了大量时间,并揭示了数据预处理和数据增强过程中的问题。
  • 可视化预测过程:我喜欢在训练过程中对一个固定的测试批数据进行模型预测的可视化。这展示了预测值如何变化的过程,能为我们提供关于训练过程的优秀直觉。很多时候,如果网络以某种方式小幅度波动,那么模型最可能在尝试拟合数据,这也展示了一些不稳定性。太低或太高的学习率也很容易注意到,因为抖动量比较大。
  • 使用反向传播绘制依赖性:你的深度学习代码通常包括复杂的、矢量化的、Boardcast 操作。一个常见的 bug 是,人们会无意间使用 view 而不是 transpose/permute,从而混合了批量数据中的维度信息。然而,你的网络仍然可以正常训练,只不过它们学会忽略了其它样本中的数据。一种 debug 的方法是将某些样本 i 的损失设置为 1.0 ,然后运行反向传播一直到输入,并确保第 i 个样本的梯度不为零。更一般的,梯度为我们提供了网络中的依赖性关系,它们在 debug 中非常有用。
  • 一般化特殊案例:这是一种更为通用的代码技巧,但是我经常看到人们在使用这些技巧时会新产生 Bug,尤其是在从头构建一般函数时。相反,我喜欢直接写非常具体的函数,它只包含我现在需要做的事情。我会先让这个函数能 work,然后再一般化好函数,并确保能取得相同的结果。通常这个过程会体现在向量化代码中,我会先用循环编写某个过程,然后再一次一个循环地将它们转化为向量化化代码。

4.3 过拟合

到了这个阶段,我们应该对数据集有所了解了,而且有了完整的训练+评估流程。对于任何给定的模型,我们可以计算出我们信任的度量。而且还为独立于输入的基线准备了性能,一些 dumb 基线的性能(最好超过这些),我们人类的表现有大致的了解(并希望达到这一点)。现在,我们已经为迭代一个好的模型做好了准备。

我准备用来寻找好模型的方法有两个阶段:首先获得足够大的模型,这样它能够过拟合(即关注训练损失),然后对其进行适当的正则化(弃掉一些训练损失以改进验证损失)。我喜欢这两个阶段的原因是,如果我们不能用任何模型实现较低的误差率,则可能再次表明一些问题、bug 和配置错误。

该阶段的一些技巧与注意事项:

  • 选择模型:为了达到理想的训练损失,我们可能希望为数据选择一个合适的架构。当我们在挑选模型时,我的第一个建议即别好高骛远。我看到很多人都非常渴望一开始就堆叠一些新的模块,或创造性地用于各种异质架构,从而想一步到位做好。我建议可以找最相关的论文,并直接利用它们的简单架构,从而获得良好性能。后面再基于这个架构做修改和改进,并将我们的想法加进去就行了。
  • Adam 是一般选择:在配置基线模型地早期阶段,我喜欢使用 Adam 算法(学习率为 3e )。在我的经验中,Adam 对超参数的容忍度更高,不太好的学习率也能获得一般的效果。对于卷积网络来说,一般经过仔细调整的 SGD 几乎总会略优于 Adam,但最佳学习率的可能区域要窄得多。 −4
  • 一次复杂化一个:如果你有多个特性插入分类器,我建议你一个个插入,从而确保能获得期待的性能提升。不要在最开始时就一次性全加上,这样你会弄不清楚性能提升到底是哪个特性带来的。还有其它增加复杂性的方法,例如你可以先尝试插入较小的图像,然后再慢慢地加大。
  • 别相信默认的学习率衰减:如果你修改来自其它领域的代码,你应该小心使用学习率衰减方法。对于不同问题,你不仅希望使用不同的衰减策略,同时因为 Epoch 的数量不同,衰减过程也会不一样。例如数据集的大小,会影响 Epoch 的数量,而很多学习率衰减策略是直接与 Epoch 相关的。在我自己的工作中,我经常整个地关闭学习率衰减,即使用常数学习率。

4.4 正则化

理想情况下,我们现在至少有了一个拟合训练集的大模型。现在是时候对它进行正则化,并通过放弃一些训练准确率来提升验证准确率了。技巧包括:

  • 更多数据:首先,在当前任何实际环境中正则化模型的最好方式是增加更多真实的训练数据。在你能收集更多数据时,花费大量工程时间试图从小数据集上取得更好结果是很常见的一个错误。我认为增加更多数据是单调提升一个较好配置神经网络性能的唯一可靠方式。
  • 数据增强:比真实数据较次的方法是半假数据,试验下更激进的数据增强。
  • 创造性增强:如果半假数据也没有,假数据也还可以。人们在寻求扩展数据集的创造性方法。例如,域随机化、使用模拟数据、把数据插入场景这样机智的混合方法,甚至可以用 GAN。
  • 预训练:即使你有足够的数据,你也可以使用预训练网络,基本没什么损失。
  • 坚持监督式学习:不要对无监督学习过于激动。据我所知,没有什么无监督学习方法在当前计算机视觉任务上有很强的结果(尽管 NLP 领域现在有了 BERT 和其他类似模型,但这更多归功于文本更成熟的本质以及对噪声比更好的信号)。
  • 更小的输入维度:移除可能包含假信号的特征。如果你的数据集很小,任何加入的假输入只会增加过拟合的可能。类似地,如果低级细节作用不大,试试输入更小的图像。
  • 更小的模型:在许多情况下,你可以在网络上使用域知识约束来降低模型大小。例如,在 ImageNet 主干网络顶部使用全连接层一度很流行,但它们后来被简单的平均池化取代,消除了这一过程中大量的参数。
  • 减小批大小:由于 BN 基于批量大小来做归一化,较小的批量大小具有更强的正则化效果。这主要因为一个批量的统计均值与标准差是实际均值和标准差的近似,所以缩放量和偏移量在小批量内波动地更大。
  • drop:增加 dropout。在卷积网络上使用 dropout2d(空间 dropout)。保守谨慎的使用 dropout,因为它对 batch 归一化好像不太友好。
  • 权重衰减:增加权重衰减惩罚。
  • 早停(early stopping):基于你得到的验证损失停止训练,从而在即将过拟合之前获取模型。
  • 尝试更大的模型:我过去多次发现更大模型最终都会很大程度的过拟合,但它们「早停」后的性能要比小模型好得多。

最后,为了更加确保网络是个合理的分类器,我喜欢可视化网络第一层的权重,确保自己获得了有意义的边缘。如果第一层的滤波器看起来像噪声,那需要去掉些东西。类似地,网络内的激活函数有时候也会揭示出一些问题。

4.5 精调

现在你应该位于数据集一环,探索取得较低验证损失的架构模型空间。这一步的一些技巧包括:

  • 随机网格搜索:在同时精调多个超参数时,使用网格搜索听起来更诱惑,能够确保覆盖到所有环境。但记住,使用随机搜索反而是最佳方式。直观上,因为神经网络对一些参数更为敏感。在极限情况下,如果参数 a 很重要,改变 b 却没有影响,然后相比于多次在固定点采样,你宁可彻底采样 a。
  • 超参数优化:如今社区内有大量好的贝叶斯超参数优化工具箱,我的一些朋友用过后觉得很成功。但我的个人经验是,探索好的、宽的模型空间和超参数的最佳方法是找个实习生。开玩笑而已,哈哈哈。

4.6 最后的压榨

一旦你找到最好的架构类型和超参数,依然可以使用更多的技巧让系统变得更好:

  • 集成:模型集成是能将准确率稳定提升 2% 的一种好方式。如果你承担不起测试阶段的计算成本,试着使用《Distilling the Knowledge in a Neural Network》中的方法把你的模型蒸馏到一个网络。
  • 一直训练:我经常看到一些人在验证损失趋平时会中断模型训练,以我的经验来看,网络会长时间保持非直观的训练。寒假时有一次我忘了关掉模型训练,一月回来后发现它取得了 SOTA 结果。

结论

一旦你做到了这些,你就具备了成功的所有要素:对神经网络、数据集和问题有了足够深的了解,配置好了完整的训练/评估体系,取得高置信度的准确率,逐渐探索更复杂的模型,提升每一步的表现。现在万事俱备,就可以去读大量论文,尝试大量实验并取得 SOTA 结果了。

参考文献

[1] https://zhuanlan.zhihu.com/p/83626029

[2] https://zhuanlan.zhihu.com/p/24720954

[3] https://www.zhihu.com/question/25097993/answer/2718208647

[4] https://karpathy.github.io/2019/04/25/recipe/

[5] https://github.com/WangQvQ/DeepLearning-500-questions

本文由作者按照 CC BY 4.0 进行授权