1 说明
调参基本上是每个深度学习从业者的日常之一,就是吃饭一样,甚至有些时候每天的工作就只是改动一两个参数然后等着分析结果(这让我想起了研二时候,每周就只是改动几个参数,竟然也能整好几页 ppt 给老板汇报)。
这篇文章在自己工作的基础上,再结合同事以及网上一些牛人的经验,汇总成一篇调参总结,以供自己后续查阅。
2 深度学习调参总结
2.1 养成良好的编码习惯
由于深度学习实验超参众多,养成良好的代码风格,可以让你的人工或者自动调参更加省力,有以下几点可能对你有帮助:
- 将各个参数的设置部分集中在一起。如果参数的设置分布在代码的各个地方,那么修改的过程想必会非常痛苦
- 可以输出模型的损失函数值以及训练集和验证集上的准确率,打印一些中间结果有助于查看模型的进展(诸如收敛等等)
- 可以考虑设计一个子程序,可以根据给定的参数,启动训练并监控和周期性保存评估结果。再由一个主程序,分配参数以及并行启动一系列子程序
2.2 画出模型结构图
在一张白纸上画出设计的模型结构图,同时将一些参数设置在图中标出来便于随时查看。
画图是一个很好的习惯,一般是训练数据遍历一轮以后,就输出一下训练集和验证集准确率。同时画到一张图上。这样训练一段时间以后,如果模型一直没有收敛,那么就可以停止训练,尝试其他参数了,以节省时间。
如果训练到最后,训练集,测试集准确率都很低,那么说明模型有可能欠拟合。那么后续调节参数方向,就是增强模型的拟合能力。例如增加网络层数,增加节点数,减少 dropout 值,减少 L2 正则值等等。
如果训练集准确率较高,测试集准确率比较低,那么模型有可能过拟合,这个时候就需要向提高模型泛化能力的方向,调节参数。
2.3 调参由粗粒度到细粒度
实践中,一般先进行初步范围搜索,然后根据好结果出现的地方,再缩小范围进行更精细的搜索。
建议先参考相关论文,以论文中给出的参数作为初始参数。至少论文中的参数,是个不差的结果。如果找不到参考,那么只能自己尝试了。可以先从比较重要、对实验结果影响比较大的参数开始,同时固定其他参数,得到一个差不多的结果以后,在这个结果的基础上,再调其他参数。例如学习率一般就比正则值、dropout 值重要的话,学习率设置的不合适,不仅结果可能变差,模型甚至会无法收敛。
如果实在找不到一组参数可以让模型收敛,那么就需要检查,是不是其他地方出了问题,例如模型实现,数据等等。
2.4 先在小数据集上做尝试
调参只是为了寻找合适的参数,而不是产出最终模型。一般在小数据集上合适的参数,在大数据集上效果也不会太差。因此可以尝试对数据进行精简,以提高速度,在有限的时间内可以尝试更多参数。较常用的有以下几种方法:
- 对训练数据进行采样: 例如原来 100W 条数据,先采样成 1W,进行实验看看
- 减少训练类别: 例如手写数字识别任务,原来是 10 个类别,那么我们可以先在 2 个类别上训练,看看结果如何
2.5 常用超参数探索方式
建议优先在对数尺度上进行超参数搜索。比较典型的是学习率和正则化项,我们可以从诸如 0.001、0.01、0.1、1、10,以 10 为阶数进行尝试。因为他们对训练的影响是相乘的效果。不过有些参数,还是建议在原始尺度上进行搜索,例如 dropout 值: 0.3、0.5、0.7。
2.6 一些基本的经验参数
这里给出一些参数的经验值,避免在调参的时候,毫无头绪。
- 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 的情况比较少见,超过 1W 的从来没有见过
- batch size: 128 上下开始,batch size 值增加,的确能提高训练速度,但是有可能收敛结果变差。如果显存大小允许,可以考虑从一个比较大的值开始尝试。因为 batch size 太大,一般不会对结果有太大的影响,而 batch size 太小的话,结果有可能很差
- clip c(梯度裁剪): 限制最大梯度其实是 value =sqrt(w1^2+w2^2….),如果 value 超过了阈值,就算一个衰减系系数,让 value 的值等于阈值: 5、10、15
- dropout:0.5
- L2 正则:1.0,超过 10 的很少见
- 词向量 embedding 大小:128,256
- 正负样本比例: 这个是非常忽视,但是在很多分类问题上,又非常重要的参数。很多人往往习惯使用训练数据中默认的正负类别比例,当训练数据非常不平衡的时候,模型很有可能会偏向数目较大的类别,从而影响最终训练结果。除了尝试训练数据默认的正负类别比例之外,建议对数目较小的样本做过采样,例如进行复制。提高他们的比例,看看效果如何,这个对多分类问题同样适用。在使用 mini-batch 方法进行训练的时候,尽量让一个 batch 内,各类别的比例平衡,这个在图像识别等多分类任务上非常重要。
2.7 自动调参
人工一直盯着实验,毕竟太累。自动调参当前也有不少研究。下面介绍几种比较实用的办法:
(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。
2.8 参数初始化
下面几种方式,随便选一个,结果基本都差不多,但是一定要做,否则可能会减慢收敛速度,影响收敛结果,甚至造成 Nan 等一系列问题。
ninninn_in 为网络的输入大小,noutnoutn_out 为网络的输出大小,nnn 为 ninninn_in 或 (nin+nout)∗0.5(nin+nout)∗0.5(n_in+n_out)*0.5
(1)uniform 均匀分布初始化
1 | w = np.random.uniform(low=-scale, high=scale, size=[n_in,n_out]) |
Xavier 初始法,适用于普通激活函数 (tanh,sigmoid):scale = np.sqrt(3/n);
He 初始化,适用于 ReLU:scale = np.sqrt(6/n);
(2)normal 高斯分布初始化
1 | w = np.random.randn(n_in,n_out) * stdev |
stdev 为高斯分布的标准差,均值设为 0。
Xavier 初始法,适用于普通激活函数 (tanh,sigmoid):stdev = np.sqrt(n);
He 初始化,适用于 ReLU:stdev = np.sqrt(2/n)
(3)svd 初始化
对 RNN 有比较好的效果,参考论文:https://arxiv.org/abs/1312.6120。
2.9 模型融合
Ensemble 是论文刷结果的终极核武器,深度学习中一般有以下几种方式:
- 同样的参数,不同的初始化方式
- 不同的参数,通过 cross-validation, 选取最好的几组
- 同样的参数,模型训练的不同阶段,即不同迭代次数的模型
- 不同的模型,进行线性融合:例如 RNN 和传统模型