深度学习调参有哪些技巧?
点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达
本文转自|视觉算法
深度学习的效果很大程度上取决于参数调节的好坏,那么怎么才能最快最好的调到合适的参数呢?求解
高质量回答
著作权归作者所有。
只想说一句:初始化
一次惨痛的教训是用normal初始化cnn的参数,最后acc只能到70%多,仅仅改成xavier,acc可以到98%。
还有一次给word embedding初始化,最开始使用了TensorFlow中默认的initializer(即glorot_uniform_initializer,也就是大家经常说的无脑使用xavier),训练速度慢不说,结果也不好。改为uniform,训练速度飙升,结果也飙升。
所以,初始化就跟黑科技一样,用对了超参都不用调;没用对,跑出来的结果就跟模型有bug一样不忍直视。
著作权归作者所有。
大概调了快一年CNN(2019年1月到今天),看到这个话题挺感兴趣的,以下是我的总结
做工程
卷积是CNN的主流组件。平时有设计一些解决分类,回归任务的网络,里面的卷积核基本都设置为,要说原因的话应该去问问VGG16吧。两个的卷积核堆叠能获得卷积核的感受野并且参数比卷积核少,所以是大量推荐使用的。
可以适当使用卷积。为什么要提这一点呢,这是因为卷积可以减少计算量,并且卷积可以在某个方向强调感受野,也就是说假如如果你要对一个长方形形状的目标进行分类,你可以使用的卷积核搭配的卷积核对长边方向设定更大的感受野,或许可以获得泛化性能的提升。
ACNet结构。这个研究来自于ICCV2019,可以在卷积的基础上加上和的旁路卷积核,最后在推理阶段把三个卷积核都fusion到卷积核上,在许多经典CV任务上都可以获得大概1个点的提升。大家可以看看这篇文章解读:3*3卷积+1*3卷积+3*1卷积=白给的精度提升
卷积核权重初始化方式。对于weight的初始化我一般都是使用xavier初始化。当然也可以可以尝试何凯明大神的He初始化。对于bias的初始化全置于0。
Batch Normalization。这是我一直在使用的技巧,可以很大程度的加快收敛速度。建议搭建自己网络的时候尽量加上BN,如果有BN了全连接层就没必要加Dropout了。
目标检测不能盲目去掉fpn结构。在针对自己的数据调检测任务如yolov3的时候不能盲目砍掉fpn结构,尽管你分析出某个分支的Anchor基本不可能会对你预测的目标起作用,但如果你直接去掉分支很可能会带来漏检。
优化器的选择。我基本都是带动量的SGD。如果优化不动可以试试Adam。
激活函数。可以先用ReLU做一版,如果想再提升精度可以将ReLU改成PReLU试试。我更倾向于直接使用ReLU。
batch_size:在不同类型的任务中,batch_size的影响也不同,大家可以看看这篇batch_size对模型性能影响的文章,来自公众号AI开发者。Batch_size是怎么影响模型性能的
初始学习率。一般我是从0.01开始设置,我个人认为这个学习率和学习率衰减策略是相关的,但不宜设置的过大过小,0.01和0.1应该是比较常用的。学习率衰减策略我一般使用multistep方式,step_size的设置要看视你的的max_iter而定。
数据与处理之zero-center。第一次见到这个词是在看cs231n的视频上。主要有2个步骤,第一个是减均值,第二个是除以方差。这样做下来最后的输入是满足均值为0方差为1的概率分布的,一般减均值是最常用的,后面的除以方差用不用可能需要自己动手试验一下看看效果。
残差结构和密集连接。resnet的残差结构和dense net密集连接结构,做工程的时候考虑到速度近乎不可能说完全使用完整版本的resnet和densenet的完整结构,但我们可以自己动手将我们网络的某些模块替换为残差结构和密集连接,替换的时候可以适当降低这俩结构的复杂度,类似于通道数减半,密集连接中只保留一半连接等等。这里需要做一些消融实验来验证改进后的精度。
关于loss。优秀的loss一般是对模型的泛化性能有所改善的,但在用loss的时候往往并不是直接替换loss那么简单,需要仔细思考loss背后的数学原理,要用对地方才可有提升。例如,如何将Focal Loss用到YOLOv3中提升map,大家可以看看这个帖子。https://www.zhihu.com/question/293369755。
找到模型调参时的可靠评价指标。在调整参数训练模型时一定要找到正确的评价指标,没调整一个参数就要记录一下模型的评价指标如准确率,map值,miou值等。并且在调参时建议将调整的参数和在测试集上的精度组合成一个字符串给模型重命令,方便之后快速review。
使用了带backbone的网络,如训练VGG16-SSD建议选择finetune的方式,从头训练不仅费时费力,甚至难以收敛。
在做分割实验的时候我发现用upsamling 加1*1卷积代替反卷积做上采样得到的结果更平滑,并且miou差距不大,所以我认为这两者都是都可以使用的。
一些Anchor-based目标检测算法为了提高精度,都是疯狂给框,ap值确实上去了,但也导致了fp会很多,并且这部分fp没有回归,在nms阶段也滤不掉。相比于ap提升而言,工程上减少fp更加重要。Gaussian yolov3的fp相比于yolov3会减少40%,Anchor-free算法暂时接触得不多,就不太了解了。
做比赛
特征提取。VGG16,VGG19,ResNet50,Xception是非常好用的几个特征提取模型。建议使用训练好的经典模型对数据集提取特征向量存储到本地,更方便使用,同时可以大幅度降低显存消耗。
ensemble:
将不同的经典网络提取出的特征向量,假设VGG16提取出的特征向量维度是[N,c1],ResNet50提取的特征向量维度是[N,c2],Xception提取的特征向量维度是[N, c3],那么我们可以使用三个系数a、b、c将其组合为形状为[N, a*c1+b*c2+c*c3],其中a、b、c三个参数的取值代表我们使用哪个模型的特征多一些,如果是分类回归比赛,我们在后面接特征处理网络就可以了。可以取不同的a、b、c得到不同的特征,然后对结果做voting,soft-voting等多种处理,一般结果不会太差啦。
可以使用不同的初始化方式训练出模型,然后做ensemble。
可以使用用不同超参数(如学习率,batch_size,优化器)训练出不同模型,然后做ensemble。
因为我就做了一点点入门级比赛,上面介绍的方法取得了还不错的结果,所以我就在这里献丑啦,方法确实挺无脑的,大家笑一笑就好啦。继续想了下,我好像除了这些有接触或者使用到,暂时没有什么其它的了,如果想起其他的了,之后补充下。
著作权归作者所有。
我和@杨军类似, 也是半路出家. 现在的工作内容主要就是使用CNN做CV任务. 干调参这种活也有两年时间了. 我的回答可能更多的还是侧重工业应用, 技术上只限制在CNN这块.
先说下我的观点, 调参就是trial-and-error. 没有其他捷径可以走. 唯一的区别是有些人盲目的尝试, 有些人思考后再尝试. 快速尝试, 快速纠错这是调参的关键.
看了杨军的回答. 对于这个回答, 下面的评论里面@纪秋佳说的很对. 这个回答主要内容更多的是侧重理解网络. 而非训练网络.
我要再强调下, 杨军的回答更多的涉及是理解网络而非训练网络. 是的, 没错. 你看完回答中的所有内容, 对不起, 你还是不知道怎么实际训练一个网络, 尤其是复杂任务下的网络(因为简单任务根本不需要, 直接上来效果就会很好, 除非你要刷简单任务的排行榜).
首先说下可视化:
我个人的理解, 对于可视化, 更多的还是帮助人类以自己熟悉的方式来观察网络. 因为, 你是不可能边观察网络, 还边调参的. 你只是训练完成后(或者准确率到达一个阶段后), 才能可视化. 在这之前, 网络没有学习到良好的参数, 你可视化了也没意义, 网络达到不错的准确率了, 你看看其实也就听个响. 同样, 你的网络训练的一塌糊涂, 你可视化也没什么意义, 唯一能够看到的就是中间结果乱七八糟, 或者全黑全白, 这时候你直接看最后准确率就可以知道这网络没救了.
关于权重的可视化[Visualize Layer Weights](现在是否强求smooth其实意义不大, 这个后面说.):
同样, 你看到一个不满足平滑结果的图像, 你知道, 这网络训练的不好, 但是为什么呢? 是数据不好? 没有预处理? 网络结构问题? Learning Rate太大或者太小? 或者就是差了一个LRN层(之前我就遇到, 加个LRN就能出smooth的weights, 当然这其实和预处理有关)?
Smooth是需要看一下的, 心里有个数. 但是具体调参怎么调是没辙的. 第一, 你不可能告诉网络, 这层你得学个边界检测的功能出来. 第二, 不同任务下会有不同的weights(虽然底层的特征有很大的通用性), 你觉得你凭什么来指导一个看图片比你快得多的机器?
再说现在是否需要强求smooth. 现在的趋势是鼓励使用小filter, 3x3大小, 多加层次(这样, 非线性更好点). 换句话说, 3x3的图片, 总共才9个像素, 你怎么判断smooth与否呢? 当然如果你使用大的filter, 一般5x5往上, 运气不差的话, 你是可以看到smooth的结果的.
咱们再说另外一个极端, 一个网络,运行的完美(满足应用要求就算完美), 打开一看, 这weights不smooth啊. 你告诉我, 你打算怎么办? 没错, 具有不平滑的权重的网络同样可以获得很好的结果(这种情况我都习以为常了).
那么可视化网络就不重要了?
非常重要, 但是不在训练这块, 而是帮助理解网络的原理这块. 理解网络原理后, 你才能在设计结构的时候心里有感觉(只是有感觉而已), 网络出了问题, 或者在某些情况下不满意, 有更好的直觉去调整.(没错, 只是直觉, 虽然有些情况下的调整从网络原理来看逻辑上应该可以工作, 但是人家就是不工作, 你能咬机器去么?)
那么怎样训练一个不错的网络呢?
这是一个很好的链接, 说明了如何从零开始不断的trial-and-error(其实这里面没遇到什么error):
Using convolutional neural nets to detect facial keypoints tutorial
========================================================
我自己的经验, 有下面这些:
基本原则:
快速试错
一些大的注意事项:
1. 刚开始, 先上小规模数据, 模型往大了放, 只要不爆显存, 能用256个filter你就别用128个. 直接奔着过拟合去. 没错, 就是训练过拟合网络, 连测试集验证集这些都可以不用.
为什么?
+ 你要验证自己的训练脚本的流程对不对. 这一步小数据量, 生成速度快, 但是所有的脚本都是和未来大规模训练一致的(除了少跑点循环)
+ 如果小数据量下, 你这么粗暴的大网络奔着过拟合去都没效果. 那么, 你要开始反思自己了, 模型的输入输出是不是有问题? 要不要检查自己的代码(永远不要怀疑工具库, 除非你动过代码)? 模型解决的问题定义是不是有问题? 你对应用场景的理解是不是有错? 不要怀疑NN的能力, 不要怀疑NN的能力, 不要怀疑NN的能力. 就我们调参狗能遇到的问题, NN没法拟合的, 这概率是有多小?
+ 你可以不这么做, 但是等你数据准备了两天, 结果发现有问题要重新生成的时候, 你这周时间就酱油了.
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没反映(但是, LR需要降低的情况也是这样, 这里可视化网络中间结果, 不是weights, 有效果, 俩者可视化结果是不一样的, 太小的话中间结果有点水波纹或者噪点的样子, 因为filter学习太慢的原因, 试过就会知道很明显)
+ 需要进一步降低了: loss在当前LR下一路降了下来, 但是半天不再降了.
+ 如果有个复杂点的任务, 刚开始, 是需要人肉盯着调LR的. 后面熟悉这个任务网络学习的特性后, 可以扔一边跑去了.
+ 如果上面的Loss设计那块你没法合理, 初始情况下容易爆, 先上一个小LR保证不爆, 等loss降下来了, 再慢慢升LR, 之后当然还会慢慢再降LR, 虽然这很蛋疼.
+ LR在可以工作的最大值下往小收一收, 免得ReLU把神经元弄死了. 当然, 我是个心急的人, 总爱设个大点的.
6 对比训练集和验证集的loss
判断过拟合, 训练是否足够, 是否需要early stop的依据, 这都是中规中矩的原则, 不多说了.
7 清楚receptive field的大小
CV的任务, context window是很重要的. 所以你对自己模型的receptive field的大小要心中有数. 这个对效果的影响还是很显著的. 特别是用FCN, 大目标需要很大的receptive field. 不像有fully connection的网络, 好歹有个fc兜底, 全局信息都有.
简短的注意事项:
预处理: -mean/std zero-center就够了, PCA, 白化什么的都用不上. 我个人观点, 反正CNN能学习encoder, PCA用不用其实关系不大, 大不了网络里面自己学习出来一个.
shuffle, shuffle, shuffle.
网络原理的理解最重要, CNN的conv这块, 你得明白sobel算子的边界检测.
Dropout, Dropout, Dropout(不仅仅可以防止过拟合, 其实这相当于做人力成本最低的Ensemble, 当然, 训练起来会比没有Dropout的要慢一点, 同时网络参数你最好相应加一点, 对, 这会再慢一点).
CNN更加适合训练回答是否的问题, 如果任务比较复杂, 考虑先用分类任务训练一个模型再finetune.
无脑用ReLU(CV领域).
无脑用3x3.
无脑用xavier.
LRN一类的, 其实可以不用. 不行可以再拿来试试看.
filter数量2^n.
多尺度的图片输入(或者网络内部利用多尺度下的结果)有很好的提升效果.
第一层的filter, 数量不要太少. 否则根本学不出来(底层特征很重要).
sgd adam 这些选择上, 看你个人选择. 一般对网络不是决定性的. 反正我无脑用sgd + momentum.
batch normalization我一直没用, 虽然我知道这个很好, 我不用仅仅是因为我懒. 所以要鼓励使用batch normalization.
不要完全相信论文里面的东西. 结构什么的觉得可能有效果, 可以拿去试试.
你有95%概率不会使用超过40层的模型.
shortcut的联接是有作用的.
暴力调参最可取, 毕竟, 自己的生命最重要. 你调完这个模型说不定过两天这模型就扔掉了.
机器, 机器, 机器.
Google的inception论文, 结构要好好看看.
一些传统的方法, 要稍微了解了解. 我自己的程序就用过1x14的手写filter, 写过之后你看看inception里面的1x7, 7x1 就会会心一笑..
著作权归作者所有。
1、首先,调参的时候你要整理好自己的心情。别误会,我的意思是让你狂躁一点。因为这玩意有时候的确是个玄学,经常调半天毛用都没有,然后只是换一个初始值,分分钟给你干到95%以上。是的,你其实啥也没做,但大部分时候这玩意儿跟人很像,“出身”很重要。
2、如上所述,好的初始点 + 合适的LR + 好的优化方法基本可以解决大部分问题。如果还不行可以考虑换个loss。其它的花样太多往往很虚。
3、一定要注意实时保存自己的结果,熟悉使用各种seed,养成好的习惯。有时候你觉得一个不太好的结果其实可能是你能调到的最好的结果了,你也不想因为没保存的缘故然后回头发现再也找不着了的对吧?所谓今天你看我不起,明天我让你高攀不起。别问我怎么知道的。
4、新手刚上来调参的时候没什么经验,所以一定要低调!啥叫低调?低调就是一开始调的时候千万别考虑太远,在能承受的范围内,尽可能先把滤波器搞得多多的,把数据搞得少少的,直奔过拟合去!所谓小步试错,快速迭代,互联网公司都是这么干的。虽然过拟合也不好搞,但总归是有不少套路的,比起过拟合,欠拟合的问题远远可怕的多。毕竟结果都训练不出来还扯什么远方?
5、有很多无脑的配置确实可以尝试,比如3x3的卷积核,relu激活函数,加shuffle,加数据增强,加BN,加Dropout等。dropout可以从0. 5往上加,optimizer可以用Adam或者SGD+0.8/0.9的Momentum。大部分时候这些经验比你自己辛苦挑出来的奇技淫巧要有价值的多,但不是绝对的。
6、一定要记得实时打印一些结果,比如训练loss、训练accuracy、验证accuracy,能画出图的就画个图。一边看图一边可以发现不少问题,尤其是关于学习率和过拟合的。另外,前面某位大神说的很对,看图的时候loss要比accuracy有用的多,因为accuracy就像女人一样经常善变,可能下一个step就差很多,而loss毕竟有个相对稳定下降的大趋势。
7、在你夜深人静的时候,或者不忙的时候,别忘了多想想原理。多研究研究别人的优秀结果,尤其是那些成熟的架构、和一些state-of-the-art的结果,自己的数据集也可以多翻翻,闲来无事搞点可视化什么的,既锻炼了技术又能帮助发现。除了调参,应用的时候不妨直接把别人的某些层拿过来自己用,能节省不少时间。
8、最后,什么都不是绝对的。很多原理性的文章,你看看就好。什么东西都是有条件的,离开了这个条件就啥也不是,而恰巧有时候这个条件可能仅仅就是运气而已。所以万一复现不出来,别较真,该丢则丢。调参虽苦,也一定不要忘了同时调整自己的心态:多行善事、及时记录、少吹牛逼、多逛知乎。
著作权归作者所有。
相信很多刚开始接触深度学习朋友,会感觉深度学习调参就像玄学一般,有时候参数调的好,模型会快速收敛,参数没调好,可能迭代几次loss值就直接变成Nan了。
记得刚开始研究深度学习时,做过两个小例子。一个是用tensorflow构建了一个十分简单的只有一个输入层和一个softmax输出层的Mnist手写识别网络,第一次我对权重矩阵W和偏置b采用的是正态分布初始化,一共迭代了20个epoch,当迭代完第一个epoch时,预测的准确度只有10%左右(和随机猜一样,Mnist是一个十分类问题),当迭代完二十个epoch,精度也仅仅达到了60%的样子。
然后我仅仅是将权重矩阵W初始化方法改成了全为0的初始化,其他的参数均保持不变,结果在训练完第一个epoch后预测精度就达到了85%以上,最终20个epoch后精度达到92%。另一个例子是回归问题的预测,当时采用的SGD优化器,一开始学习率设定的0.1,模型可以正常训练,只是训练速度有些慢,我试着将学习率调整到0.3,希望可以加速训练速度,结果没迭代几轮loss就变成Nan了。于是从那时起我就深刻的感受到参数调节在深度学习模型训练中的重要意义。
其实上述问题产生的原因也很好理解,对于参数初始化,因为我们学习的本来就是权重W与偏置b,如果初始化足够好,直接就初始化到最优解,那都不用进行训练了。良好的初始化,可以让参数更接近最优解,这可以大大提高收敛速度,也可以防止落入局部极小。对于学习率,学习率如果取太大,会使模型训练非常震荡,可以想象我们最小化一个二次抛物线,选取一个很大的学习率,那么迭代点会一直在抛物线的两边震荡,收敛不到最小值,甚至还有螺旋上升迭代点的可能。
下面对深度学习调参技巧谈些心得,虽说不能让你通过以下阅读成为一个调参高手,但最起码可以提供一些调参的思路。
1. 激活函数选择:
常用的激活函数有relu、leaky-relu、sigmoid、tanh等。对于输出层,多分类任务选用softmax输出,二分类任务选用sigmoid输出,回归任务选用线性输出。而对于中间隐层,则优先选择relu激活函数(relu激活函数可以有效的解决sigmoid和tanh出现的梯度弥散问题,多次实验表明它会比其他激活函数以更快的速度收敛)。另外,构建序列神经网络(RNN)时要优先选用tanh激活函数。
2、学习率设定:
一般学习率从0.1或0.01开始尝试。学习率设置太大会导致训练十分不稳定,甚至出现Nan,设置太小会导致损失下降太慢。学习率一般要随着训练进行衰减。衰减系数设0.1,0.3,0.5均可,衰减时机,可以是验证集准确率不再上升时,或固定训练多少个周期以后自动进行衰减。
3、防止过拟合:
一般常用的防止过拟合方法有使用L1正则项、L2正则项、dropout、提前终止、数据集扩充等。如果模型在训练集上表现比较好但在测试集上表现欠佳可以选择增大L1或L2正则的惩罚力度(L2正则经验上首选1.0,超过10很少见),或增大dropout的随机失活概率(经验首选0.5);或者当随着训练的持续在测试集上不增反降时,使用提前终止训练的方法。当然最有效的还是增大训练集的规模,实在难以获得新数据也可以使用数据集增强的方法,比如CV任务可以对数据集进行裁剪、翻转、平移等方法进行数据集增强,这种方法往往都会提高最后模型的测试精度。
4、优化器选择:
如果数据是稀疏的,就用自适应方法,即 Adagrad, Adadelta, RMSprop, Adam。整体来讲,Adam 是最好的选择。SGD 虽然能达到极小值,但是比其它算法用的时间长,而且可能会被困在鞍点。如果需要更快的收敛,或者是训练更深更复杂的神经网络,需要用一种自适应的算法。
5、残差块与BN层:
如果你希望训练一个更深更复杂的网络,那么残差块绝对是一个重要的组件,它可以让你的网络训练的更深。
BN层具有加速训练速度,有效防止梯度消失与梯度爆炸,具有防止过拟合的效果,所以构建网络时最好要加上这个组件。
6.自动调参方法:
(1)Grid Search:网格搜索,在所有候选的参数选择中,通过循环遍历,尝试每一种可能性,表现最好的参数就是最终的结果。其原理就像是在数组里找最大值。缺点是太费时间了,特别像神经网络,一般尝试不了太多的参数组合。
(2)Random Search:经验上,Random Search比Gird Search更有效。实际操作的时候,一般也是先用Gird Search的方法,得到所有候选参数,然后每次从中随机选择进行训练。另外Random Search往往会和由粗到细的调参策略结合使用,即在效果比较好的参数附近进行更加精细的搜索。
(3)Bayesian Optimization:贝叶斯优化,考虑到了不同参数对应的 实验结果值,因此更节省时间,贝叶斯调参比Grid Search迭代次数少, 速度快;而且其针对非凸问题依然稳健。
7.参数随机初始化与数据预处理:
参数初始化很重要,它决定了模型的训练速度与是否可以躲开局部极小。relu激活函数初始化推荐使用He normal,tanh初始化推荐使用Glorot normal,其中Glorot normal也称作Xavier normal初始化;数据预处理方法一般也就采用数据归一化即可。
王惠东,京东数字科技-个人服务群组-个人风险管理中心-智能模型实验室-算法工程师
交流群
欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~