深度学习炼金术
专题:深度学习炼金术——如何提高深度学习精确度
参考资料:
深度学习调参经验汇总
深度学习网络调参技巧
深度学习调参技巧
深度学习调参技巧
【Deep Learning】深度学习调参技巧的总结
深度学习调参有哪些技巧
一、参数
参数其实是个比较泛化的称呼,因为它不仅仅包括一些数字的调整,它也包括了相关的网络结构的调整和一些函数的调整。
1、数据处理(或预处理)相关参数:
- enrich data(丰富数据库)
- feature normalization and scaling(数据泛化处理)
- batch normalization(BN处理)
2、训练过程与训练相关的参数:
- momentum term(训练动量)
- BGD, SGD, mini batch gradient descent
- number of epoch
- learning rate(学习率)
- objective function(衰减函数)
- weight initialization(权值初始化)
- regularization(正则化相关方法)
3、网络相关参数:
- number of layers
- number of nodes
- number of filters
- classifier(分类器的选择)
二、训练可能出现的情况
1、没法进行有效地训练,整个网络是错误的,完全不收敛。两种原因:
- 错误的input data,网络无法学习。
- 错误的网络,网络无法学习.
解决方案:
检测自己的数据是否存在可以学习的信息,这个数据集中的数值是否泛化(防止过大或过小的数值破坏学习)。
如果是错误的数据则你需要去再次获得正确的数据,如果是数据的数值异常我们可以使用zscore函数来解决这个问题。
如果是网络的错误,则希望调整网络,包括:网络深度,非线性程度,分类器的种类等等。
2、部分收敛。多种多样的原因的,本质是网络分类器的复杂度导致的,可以总结为:
- underfitting
- overfitting
解决方案:
underfitting:
- 增加网络的复杂度(深度)
- 降低learning rate
- 优化数据集
- 增加网络的非线性度(ReLu)
- 采用batch normalization
overfitting:
- 丰富数据
- 增加网络的稀疏度
- 降低网络的复杂度(深度)
- L1 regularization
- L2 regulariztion
- 添加Dropout
- Early stopping
- 适当降低Learning rate
- 适当减少epoch的次数
3、收敛但是精确度不高。极好的开始,接下来所需要的就是进行参数的微调。
三、调参的一些技巧
1、好的实验环境是成功的一半:
(1)将各个参数的设置部分集中在一起。如果参数的设置分布在代码的各个地方,那么修改的过程想必会非常痛苦。
(2)可以输出模型的损失函数值以及训练集和验证集上的准确率。
(3)可以考虑设计一个子程序,可以根据给定的参数,启动训练并监控和周期性保存评估结果。再由一个主程序,分配参数以及并行启动一系列子程序。
2、画图:
画图是一个很好的习惯,一般是训练数据遍历一轮以后,就输出一下训练集和验证集准确率。同时画到一张图上。这样训练一段时间以后,如果模型一直没有收敛,那么就可以停止训练,尝试其他参数了,以节省时间。 如果训练到最后,训练集,测试集准确率都很低,那么说明模型有可能欠拟合。那么后续调节参数方向,就是增强模型的拟合能力。例如增加网络层数,增加节点数,减少dropout值,减少L2正则值等等。 如果训练集准确率较高,测试集准确率比较低,那么模型有可能过拟合,这个时候就需要向提高模型泛化能力的方向,调节参数。
3、从粗到细分阶段调参:
(1)建议先参考相关论文,以论文中给出的参数作为初始参数。至少论文中的参数,是个不差的结果。
(2)如果找不到参考,那么只能自己尝试了。可以先从比较重要,对实验结果影响比较大的参数开始,同时固定其他参数,得到一个差不多的结果以后,在这个结果的基础上,再调其他参数。例如学习率一般就比正则值,dropout值重要的话,学习率设置的不合适,不仅结果可能变差,模型甚至会无法收敛。
(3)如果实在找不到一组参数,可以让模型收敛。那么就需要检查,是不是其他地方出了问题,例如模型实现,数据等等。
4、提高速度:
调参只是为了寻找合适的参数,而不是产出最终模型。一般在小数据集上合适的参数,在大数据集上效果也不会太差。因此可以尝试对数据进行精简,以提高速度,在有限的时间内可以尝试更多参数。
(1)对训练数据进行采样。例如原来100W条数据,先采样成1W,进行实验看看。
(2)减少训练类别。例如手写数字识别任务,原来是10个类别,那么我们可以先在2个类别上训练,看看结果如何。
5、超参数范围:
建议优先在对数尺度上进行超参数搜索。比较典型的是学习率和正则化项,我们可以从诸如0.001、0.01、0.1、1、10,以10为阶数进行尝试。因为他们对训练的影响是相乘的效果。不过有些参数,还是建议在原始尺度上进行搜索,例如dropout值: 0.3 0.5 0.7)。
四、如何调参:针对参数微调来说
1、公认参数:
这些参数我们就没有必要太多的调整
激活函数:Relu
momentum:选择0.9-0.95之间
weight decay:选择0.005
filter的个数:奇数
dropout:标配的存在
2、一些参数的经验调整值
(1)learning rate:
1 0.1 0.01 0.001, 一般从1开始尝试。很少见learning rate大于10的。学习率一般要随着训练进行衰减。衰减系数一般是0.5。 衰减时机,可以是验证集准确率不再上升时,或固定训练多少个周期以后。
不过更建议使用自适应梯度的办法,例如adam,adadelta,rmsprop等,这些一般使用相关论文提供的默认值即可,可以避免再费劲调节学习率。对RNN来说,有个经验,如果RNN要处理的序列比较长,或者RNN层数比较多,那么learning rate一般小一些比较好,否则有可能出现结果不收敛,甚至Nan等问题。
(2)网络层数:
先从1层开始。
(3)每层结点数:
16 32 128,超过1000的情况比较少见。超过1W的从来没有见过。
(4)batch size:
128上下开始。batch size值增加,的确能提高训练速度。但是有可能收敛结果变差。如果显存大小允许,可以考虑从一个比较大的值开始尝试。因为batch size太大,一般不会对结果有太大的影响,而batch size太小的话,结果有可能很差。
(5)clip c(梯度裁剪):
限制最大梯度,其实是value = sqrt(w12+w22….),如果value超过了阈值,就算一个衰减系系数,让value的值等于阈值: 5,10,15
(6)dropout:
dropout对小数据防止过拟合有很好的效果,值一般设为0.5。小数据上dropout+sgd在我的大部分实验中,效果提升都非常明显.因此可能的话,建议一定要尝试一下。
dropout的位置比较有讲究, 对于RNN,建议放到输入->RNN与RNN->输出的位置.关于RNN如何用dropout,可以参考这篇论文:http://arxiv.org/abs/1409.2329
(7)L2正则:1.0,超过10的很少见。
(8)词向量embedding大小:128,256
(9)正负样本比例:
这个是非常忽视,但是在很多分类问题上,又非常重要的参数。很多人往往习惯使用训练数据中默认的正负类别比例,当训练数据非常不平衡的时候,模型很有可能会偏向数目较大的类别,从而影响最终训练结果。除了尝试训练数据默认的正负类别比例之外,建议对数目较小的样本做过采样,例如进行复制。提高他们的比例,看看效果如何,这个对多分类问题同样适用。
在使用mini-batch方法进行训练的时候,尽量让一个batch内,各类别的比例平衡,这个在图像识别等多分类任务上非常重要。
(10)激活函数:
除了gate之类的地方,需要把输出限制成0-1之外,尽量不要用sigmoid,可以用tanh或者relu之类的激活函数.
sigmoid函数在-4到4的区间里,才有较大的梯度。之外的区间,梯度接近0,很容易造成梯度消失问题。
输入0均值,sigmoid函数的输出不是0均值的。
3、寻找合适的学习率(learning rate)
学习率是一个非常非常重要的超参数,这个参数呢,面对不同规模、不同batch-size、不同优化方式、不同数据集,其最合适的值都是不确定的,我们无法光凭经验来准确地确定lr的值,我们唯一可以做的,就是在训练中不断寻找最合适当前状态的学习率。
fastai首席设计师Sylvain Gugger的一篇博客:How Do You Find A Good Learning Rate
确定初始学习率的时候,从一个很小的值(例如 1e-7)开始,然后每一步指数增大学习率(例如扩大1.05 倍)进行训练。训练几百步应该能观察到损失函数随训练步数呈对勾形,选择损失下降最快那一段的学习率即可。
4、learning-rate与batch-size的关系
https://zhuanlan.zhihu.com/p/277487038
一般来说,越大的batch-size使用越大的学习率。原理很简单,越大的batch-size意味着我们学习的时候,收敛方向的confidence越大,我们前进的方向更加平滑,而小的batch-size则显得比较杂乱,毫无规律性,因为相比批次大的时候,批次小的情况下无法照顾到更多的情况,所以需要小的学习率来保证不至于出错。
看下图损失Loss与学习率Lr的关系:
在显存足够的条件下,最好采用较大的batch-size进行训练,找到合适的学习率后,可以加快收敛速度。
另外,较大的batch-size可以避免batch normalization出现的一些小问题
5、权重初始化
权重初始化相比于其他的trick来说在平常使用并不是很频繁。
因为大部分人使用的模型都是预训练模型,使用的权重都是在大型数据集上训练好的模型,当然不需要自己去初始化权重了。只有没有预训练模型的领域会自己初始化权重,或者在模型中去初始化神经网络最后那几个全连接层的权重。
常用的权重初始化算法kaiming_normal或者xavier_normal。
6、dropout
dropout是指在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率将其暂时从网络中丢弃。注意是「暂时」,对于随机梯度下降来说,由于是随机丢弃,故而每一个mini-batch都在训练不同的网络。
Dropout类似于bagging ensemble减少variance。也就是投通过投票来减少可变性。通常我们在全连接层部分使用dropout,在卷积层则不使用。但「dropout」并不适合所有的情况,不要无脑上Dropout。
Dropout一般适合于全连接层部分,而卷积层由于其参数并不是很多,所以不需要dropout,加上的话对模型的泛化能力并没有太大的影响。
我们一般在网络的最开始和结束的时候使用全连接层,而hidden layers则是网络中的卷积层。所以一般情况,在全连接层部分,采用较大概率的dropout而在卷积层采用低概率或者不采用dropout。
7、Ensemble
Ensemble在深度学习中一般有以下几种方式
- 同样的参数,不同的初始化方式
- 不同的参数,通过cross-validation,选取最好的几组
- 同样的参数,模型训练的不同阶段,即不同迭代次数的模型。
如果一个model的随机种子没有固定,多次预测得到的结果可能不同。
- 不同的模型,进行线性融合。例如RNN和传统模型。
8、差分学习率与迁移学习
迁移学习是一种很常见的深度学习技巧,我们利用很多预训练的经典模型直接去训练我们自己的任务。虽然说领域不同,但是在学习权重的广度方面,两个任务之间还是有联系的。
上图,我们拿来「model A」训练好的模型权重去训练我们自己的模型权重(「Model B」),其中,modelA可能是ImageNet的预训练权重,而ModelB则是我们自己想要用来识别猫和狗的预训练权重。
但是我们直接拿来其他任务的训练权重,在进行optimize的时候,如何选择适当的学习率是一个很重要的问题。
一般地,我们设计的神经网络(如下图)一般分为三个部分,输入层,隐含层和输出层,随着层数的增加,神经网络学习到的特征越抽象。因此,下图中的卷积层和全连接层的学习率也应该设置的不一样,一般来说,卷积层设置的学习率应该更低一些,而全连接层的学习率可以适当提高。这就是差分学习率的意思,在不同的层设置不同的学习率,可以提高神经网络的训练效果。Transfer Learning using differential learning rates
9、多尺度训练
多尺度训练是一种通过输入不同尺度的图像数据集,因为神经网络卷积池化的特殊性,让神经网络充分地学习不同分辨率下图像的特征,可以提高机器学习的性能。
也可以用来处理过拟合效应,在图像数据集不是特别充足的情况下,可以先训练小尺寸图像,然后增大尺寸并再次训练相同模型
需要注意的是:多尺度训练并不是适合所有的深度学习应用,多尺度训练可以算是特殊的数据增强方法,在图像大小这一块做了调整。如果有可能最好利用可视化代码将多尺度后的图像近距离观察一下,看看多尺度会对图像的整体信息有没有影响,如果对图像信息有影响的话,这样直接训练的话会误导算法导致得不到应有的结果。
10、Cross Validation 交叉验证
交叉验证往往是对实际应用中「数据不充足」而采用的,基本目的就是重复使用数据。在平常中我们将所有的数据分为训练集和验证集就已经是简单的交叉验证了,可以称为1折交叉验证。注意,交叉验证和测试集没关系,测试集是用来衡量我们的算法标准的,不参与到交叉验证中来。
交叉验证只针对训练集和验证集。交叉验证是Kaggle比赛中特别推崇的一种技巧,我们经常使用的是5-折(5-fold)交叉验证,将训练集分成5份,随机挑一份做验证集其余为训练集,循环5次,这种比较常见计算量也不是很大。还有一种叫做leave-one-out cross validation留一交叉验证,这种交叉验证就是n-折交叉,n表示数据集的容量,这种方法只适合数据量比较小的情况,计算量非常大的情况很少用到这种方法。
11、优化算法
不同的优化算法适合于不同的任务,不过我们大多数采用的优化算法还是是adam和SGD+monmentum。
Adam 可以解决一堆奇奇怪怪的问题(有时 loss 降不下去,换 Adam 瞬间就好了),也可以带来一堆奇奇怪怪的问题(比如单词词频差异很大,当前 batch 没有的单词的词向量也被更新;再比如Adam和L2正则结合产生的复杂效果)。用的时候要胆大心细,万一遇到问题找各种魔改 Adam(比如 MaskedAdam, AdamW 啥的)抢救。
adam的相比SGD,收敛快,但泛化能力差,更优结果似乎需要精调SGD。adam是不需要特别调lr,sgd要多花点时间调lr和initial weights。
如果使用sgd的话,可以选择从1.0或者0.1的学习率开始,隔一段时间,在验证集上检查一下,如果cost没有下降,就对学习率减半。 看过很多论文都这么搞,自己实验的结果也很好。 当然,也可以先用ada系列先跑,最后快收敛的时候,更换成sgd继续训练。同样也会有提升。据说adadelta一般在分类问题上效果比较好,adam在生成问题上效果比较好。
五、注意事项
1、刚开始, 先上小规模数据,模型往大了放,只要不爆显存,能用256个filter你就别用128个。直接奔着过拟合去。没错,就是训练过拟合网络, 连测试集验证集这些都可以不用。如果小数据量下,这么粗暴的大网络奔着过拟合去都没效果,那么有可能是:模型的输入输出是不是有问题? 代码错误? 模型解决的问题定义是不是有问题? 你对应用场景的理解是不是有错?
2、Loss设计要合理。一般来说分类就是Softmax, 回归就是L2的loss. 但是要注意loss的错误范围(主要是回归), 你预测一个label是10000的值, 模型输出0, 你算算这loss多大, 这还是单变量的情况下. 一般结果都是nan. 所以不仅仅输入要做normalization, 输出也要这么弄。多任务情况下, 各loss想法限制在一个量级上, 或者最终限制在一个量级上, 初期可以着重一个任务的loss。
3、观察loss胜于观察准确率。准确率虽然是评测指标,但是训练过程中还是要注意loss的。你会发现有些情况下,准确率是突变的,原来一直是0, 可能保持上千迭代, 然后突然变1。要是因为这个你提前中断训练了, 只有老天替你惋惜了. 而loss是不会有这么诡异的情况发生的, 毕竟优化目标是loss。给NN一点时间, 要根据任务留给NN的学习一定空间. 不能说前面一段时间没起色就不管了. 有些情况下就是前面一段时间看不出起色, 然后开始稳定学习。
4、确认分类网络学习充分。分类网络就是学习类别之间的界限. 你会发现, 网络就是慢慢的从类别模糊到类别清晰的. 怎么发现? 看Softmax输出的概率的分布. 如果是二分类, 你会发现, 刚开始的网络预测都是在0.5上下, 很模糊. 随着学习过程, 网络预测会慢慢的移动到0,1这种极值附近. 所以, 如果你的网络预测分布靠中间, 再学习学习。
5、Learning Rate设置合理。太大: loss爆炸, 或者nan。太小: 半天loss没反映。需要进一步降低了: loss在当前LR下一路降了下来, 但是半天不再降了。如果上面的Loss设计那块你没法合理, 初始情况下容易爆, 先上一个小LR保证不爆, 等loss降下来了, 再慢慢升LR, 之后当然还会慢慢再降LR。
6、对比训练集和验证集的loss。 判断过拟合, 训练是否足够, 是否需要early stop的依据。
7、清楚receptive field的大小。CV的任务, context window是很重要的. 所以你对自己模型的receptive field的大小要心中有数. 这个对效果的影响还是很显著的. 特别是用FCN, 大目标需要很大的receptive field。
8、GPU 上报错时尽量放在 CPU 上重跑,错误信息更友好。例如 GPU 报 “ERROR:tensorflow:Model diverged with loss = NaN” 其实很有可能是输入 ID 超出了 softmax 词表的范围。
9、简明注意事项
- 预处理:-mean/std zero-center就够了, PCA, 白化什么的都用不上。
- shuffle, shuffle, shuffle(洗牌、打乱)。
- 网络原理的理解最重要,例如CNN的conv这块,得明白sobel算子的边界检测。
- Dropout, Dropout, Dropout:不仅仅可以防止过拟合, 其实这相当于做人力成本最低的Ensemble, 当然,训练起来会比没有Dropout的要慢一点, 同时网络参数最好相应加一点。
- CNN更加适合训练回答是否的问题, 如果任务比较复杂, 考虑先用分类任务训练一个模型再finetune。
- CV领域无脑用ReLU
- 无脑用3x3
- 无脑用xavier
- LSTM 的forget gate的bias,用1.0或者更大的值做初始化,可以取得更好的结果,来自这篇论文:http://jmlr.org/proceedings/papers/v37/jozefowicz15.pdf, 我这里实验设成1.0,可以提高收敛速度.实际使用中,不同的任务,可能需要尝试不同的值.
- LRN一类的, 可以不用,不行可以再拿来试试看。
- filter数量2^n
- 多尺度的图片输入(或者网络内部利用多尺度下的结果)有很好的提升效果。
- 第一层的filter, 数量不要太少. 否则根本学不出来(底层特征很重要)。
- sgd adam 这些选择上, 看个人选择。一般对网络不是决定性的无脑用sgd + momentum。
- batch normalization可以提升效果,参考论文:Accelerating Deep Network Training by Reducing Internal Covariate Shift
- 95%概率不会使用超过40层的模型
- shortcut的联接是有作用的
六、自动调参
1、Gird Search。
这个是最常见的。具体说,就是每种参数确定好几个要尝试的值,然后像一个网格一样,把所有参数值的组合遍历一下。优点是实现简单暴力,如果能全部遍历的话,结果比较可靠。缺点是太费时间了,特别像神经网络,一般尝试不了太多的参数组合。
2、Random Search
Bengio在Random Search for Hyper-Parameter Optimization中指出,Random Search比Gird Search更有效。实际操作的时候,一般也是先用Gird Search的方法,得到所有候选参数,然后每次从中随机选择进行训练。
3、Bayesian Optimization
贝叶斯优化,考虑到了不同参数对应的实验结果值,因此更节省时间。和网络搜索相比简直就是老牛和跑车的区别。具体原理可以参考论文: Practical Bayesian Optimization of Machine Learning Algorithms ,这里同时推荐两个实现了贝叶斯调参的Python库,可以上手即用:
- jaberg/hyperopt,比较简单
- fmfn/BayesianOptimization,比较复杂,支持并行调参