神经网络调参trick和keras相关经验
常见调参技巧
- Learning Rate
- 优化器选择
- Dropout
- Batch Size
- 激活函数
- BN
- …
问题和解决方法
- 关于过拟合问题的讨论
- Loss为NAN
- Loss为负数
- Loss不下降
- 模型训练加速
- OOM
keras相关经验
训练集,验证集和测试集
查看模型的评价指标
保存keras输出的loss,val
绘制精度和损失曲线
将整型 label 转换成 one-hot 形式
自制回调函数 callback
网格超参数搜索
编写自己的层
keras保存和加载自定义损失模型
PRF 值计算
keras 获取中间层的输出
categorical_crossentropy vs. sparse_categorical_crossentropy
通过生成器的方式训练模型,节省内存
多类别预测概率转换
CNN+LSTM的思考
使用预训练模型的权重
常见调参技巧
超参上,learning rate 最重要,推荐了解 cosine learning rate 和 cyclic learning rate,其次是 batchsize 和 weight decay。当你的模型还不错的时候,可以试着做数据增广和改损失函数锦上添花了。
Learning Rate
推荐一篇fastai首席设计师「Sylvain Gugger」的一篇博客:How Do You Find A Good Learning Rate[1]
以及相关的论文Cyclical Learning Rates for Training Neural Networks[2]。
一般来说,越大的batch-size使用越大的学习率(一般来说Batch Size变成原始几倍,学习率就增加几倍)。原理很简单,越大的batch-size意味着我们学习的时候,收敛方向的confidence越大,我们前进的方向更加坚定,而小的batch-size则显得比较杂乱,毫无规律性,因为相比批次大的时候,批次小的情况下无法照顾到更多的情况,所以需要小的学习率来保证不至于出错。
在显存足够的条件下,最好采用较大的batch-size进行训练,找到合适的学习率后,可以加快收敛速度。
另外,较大的batch-size可以避免batch normalization出现的一些小问题
- 采用较小的学习率,则收敛缓慢,也有可能收敛到局部最优解,导致loss没变化;
- 采用较大的学习率,会使得 loss 上下波动较大,导致loss爆炸=Nan或者无法收敛;
调参技巧:
- 优化器推荐使用AdamW或者SGD with Momentum
- 初始值建议 3e-4 (SGD可以选0.1)
学习率衰减策略采用cosine learning rate 和 cyclic learning rate
使用动态的学习率:CLR、余弦退火、SGDR、switch Adam to SGD等(含代码)
- Warm up:用一个小的学习率先训练几个epoch,这是因为网络的参数是随机初始化的,假如一开始就采用较大的学习率容易出现数值不稳定,这也是为什么要使用Warm up。然后等到训练过程基本上稳定了就可以使用原始的初始学习率进行训练了
- 训练策略:使用cosine learning rate+warmup的方法(最终结果差不太多)
优化器选择
“Optimization” refers to the process of adjusting a model to get the best performance possible on the training data (the “learning” in “machine learning”),
优化方法使用
- Adam,Adade,RMSprop结果都差不多,Nadam因为是adam的动量添加的版本,在收敛效果上会更出色。
- 优化器公用参数 clipnorm 和 clipvalue
- 参数一:clipnorm 对梯度进行裁剪,最大值为1
- 参数二:clipvalue 对梯度范围进行裁剪,范围(-x,x)
- 一般的起手式: Adam(推荐使用3e-4)
- Keras 推薦 RNN 使用 RMSProp
- 在訓練 RNN 需要注意 explosive gradient 的問題 => clip gradient 的暴力美學
opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6)
- SGD+monmentum:
1
sgd = SGD(lr=learning_rate, decay=learning_rate/nb_epoch, momentum=0.9, nesterov=True)
Dropout
随机丢弃,抑制过拟合,提高模型鲁棒性。
dropout是指在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率将其暂时从网络中丢弃。注意是「暂时」,对于随机梯度下降来说,由于是随机丢弃,故而每一个mini-batch都在训练不同的网络。
通常我们在全连接层部分使用dropout,在卷积层则不使用。在全连接层部分,采用较大概率的dropout而在卷积层采用低概率或者不采用dropout。dropout对小数据防止过拟合有很好的效果,值一般设为0.2-0.5
Dropout作用
- 一方面缓解过拟合,另一方面引入的随机性,可以平缓训练过程,加速训练过程,处理outliers
- Dropout可以看做ensemble,特征采样,相当于bagging很多子网络;训练过程中动态扩展拥有类似variation的输入数据集。(在单层网络中,类似折中Naiive bayes(所有特征权重独立)和logistic regression(所有特征之间有关系);
- 一般对于越复杂的大规模网络,Dropout效果越好,是一个强regularizer!
- 最好的防止over-fitting就是有大量不重复数据
Batch Size
- 太小可能导致不收敛(梯度更新方向变化太大)
- 太大可能收敛慢
- 一般选择64
如果可以容忍训练时间过长,最好开始使用尽量小的batch size(16,8,1)。batch size=1是一个很不错的选择, 起码在某些task上,这也有可能是很多人无法复现alex graves实验结果的原因之一,因为他总是把batch size设成1。
激活函数
RELU用极简的方式实现非线性激活,缓解梯度消失
- 尽量不要用sigmoid,可以用relu之类的激活函数.
- 最后一层不要用relu,例如分类问题最后一层用softmax,回归问题可以不用。
- PReLU是一个不错的选择
BatchNormalization
BatchNormalization可以加快收敛速度。
有BN的全连接层没必要加Dropout
Batchnormalization层的放置问题
1
x = (x - x.mean()) / x.std()
BN层针对数据分布进行优化,对于BN来说其不但可以防止过拟合,还可以防止梯度消失等问题,并且可以加快模型的收敛速度,但是加了BN,模型训练往往会变得慢些。具体放置位置试!
1
2
3BatchNormalization(mode=0, axis=1) # 输入是形如(samples,channels,rows,cols)的4D图像张量,需要设置axis=1
Dense()
BatchNormalization(mode=1) # 按样本规范化,该模式默认输入为2D
加深网络
都说深度网络精度更高,但深度不是盲目堆起来的,一定要在浅层网络有一定效果的基础上,增加深度。深度增加是为了增加模型的准确率,如果浅层都学不到东西,深了也没效果。开始一般用3-8层,当效果不错时,为了得到更高的准确率,再尝试加深网络
隐层神经元的数量
太多:训练慢,难去除噪声(over-fitting)
太少:拟合能力下降
一般:256-1024
调参技巧:
- 分类任务:初始尝试5-10倍类别个数
- 回归任务:初始尝试2-3倍输入/输出特征数
- 这里直觉很重要
- 最终影响其实不大,只是训练过程比较慢,多尝试
CNN的trick
- pooling或卷积尺寸和步长不一样,增加数据多样性
- data augumentation,避免过拟合,提高泛化,加噪声扰动
- weight regularization
- SGD使用decay的训练方法
- 最后使用pooling(avgpooling)代替全连接,减少参数量
- maxpooling代替avgpooling,避免avgpooling带来的模糊化效果
- 2个3x3代替一个5x5等,减少参数,增加非线性映射,使CNN对特征学习能力强
- 3x3,2x2窗口
- 预训练方法等
- 数据预处理后(PCA,ZCA)喂给模型
- 输出结果窗口ensemble
- 中间节点作为辅助输出节点,相当于模型融合,同时增加反向传播的梯度信号,提供了额外的正则化
- 1x1卷积,夸通道组织信息,提高网络表达,可对输出降维,低成本,性价比高,增加非线性映射,符合Hebbian原理
- NIN增加网络对不同尺度的适应性,类似Multi-Scale思想
- Factorization into small convolution,7x7用1x7和7x1代替,节约参数,增加非线性映射
- BN减少Internal Covariance Shift问题,提高学习速度,减少过拟合,可以取消dropout,增大学习率,减轻正则,减少光学畸变的数据增强
- 模型遇到退化问题考虑shortcut结构,增加深度
- 等等
RNN的trick
小的细节和其他很像,简单说两句个人感觉的其他方面吧,其实RNN也是shortcut结构
- 一般用LSTM结构防止BPTT的梯度消失,GRU拥有更少的参数,可以优先考虑
- 预处理细节,padding,序列长度设定,罕见词语处理等
- 一般语言模型的数据量一定要非常大
- Gradient Clipping
- Seq2Seq结构考虑attention,前提数据量大
- 序列模型考率性能优良的CNN+gate结构
- 一般生成模型可以参考GAN,VAE,产生随机变量
- RL的框架结合
- 数据量少考虑简单的MLP
- 预测采用层级结构降低训练复杂度
- 设计采样方法,增加模型收敛速度
- 增加多级shortcut结构
问题和解决方法
关于过拟合问题的讨论
当数据集较小而模型较大时会出现过拟合现象,作者指出了为避免过拟合的经验规律,也即当我们将模型大小扩大8倍时需要将数据集大小扩大5倍。
- 防止过拟合的方法
- 第一种就是添加dropout层,dropout可以放在很多类层的后面,用来抑制过拟合现象,常见的可以直接放在Dense层后面,一般在Dropout设置0.5。Dropout相当于Ensemble,dropout过大相当于多个模型的结合,一些差模型会拉低训练集的精度。
- 通常只加在 hidden layer,不會加在 output layer,因為影響太大了,除非 output layer 的 dimension 很大。
- Dropout 會讓 training performance 變差
- 參數少時,regularization
- 第二种是使用参数正则化,也就是在一些层的声明中加入L1或L2正则化系数,在一定程度上提升了模型的泛化能力。
kernel_regularizer=regularizers.l2(0.001)
- Reducing the network’s size: The simplest way to prevent overfitting is to reduce the size of the model, i.e. the number of learnable parameters in the model
- Early Stopping
- 希望在 Model overfitting 之前就停止 training
- Early Stopping in Keras
from keras.callbacks import EarlyStopping
early_stopping=EarlyStopping(monitor='val_loss', patience=3)
- 第一种就是添加dropout层,dropout可以放在很多类层的后面,用来抑制过拟合现象,常见的可以直接放在Dense层后面,一般在Dropout设置0.5。Dropout相当于Ensemble,dropout过大相当于多个模型的结合,一些差模型会拉低训练集的精度。
loss的值为NAN
- 学习率太高: loss爆炸, 或者nan
- 学习率太小: 半天loss没反映
- relu作为激活函数?
- training sample中出现了脏数据&异常值(nan, inf等)!措施:重整你的数据集
- 如果是自己定义的损失函数,这时候可能是你设计的损失函数有问题
- https://zhuanlan.zhihu.com/p/89588946
loss为负数
- 如果出现loss为负,是因为之前多分类的标签哪些设置不对,现在是5分类的,写成了2分类之后导致了Loss为负数
- 也可能是损失函数选择错误导致
Loss不下降
train loss 不断下降,test loss不断下降,说明网络仍在学习;
train loss 不断下降,test loss趋于不变,说明网络过拟合;
train loss 趋于不变,test loss不断下降,说明数据集100%有问题;
train loss 趋于不变,test loss趋于不变,说明学习遇到瓶颈,需要减小学习率或批量数目;
train loss 不断上升,test loss不断上升,说明网络结构设计或超参数设置不当,数据集经过清洗等问题。
train loss下降一点后不再下降,学习率过大过小都不收敛
Loss维持在0.69附近(二分类)
loss下降到0.69附近就不下降了,一直停在那里,acc在0.5左右?
0.69是个什么数?
一般采用的都是cross entropy loss value,定义如下:
发现就是网络预测给出的二类概率向量为[0.5,0.5],即a和1-a都是0.5,不管y取值0/1,整个的平均loss就是-ln(0.5)=0.69. 也就是训练过程中,无论如何调节网络都不收敛。
为啥预测的a老是为0.5呢?
a的值是softmax的输出,在二分类的情况下为0.5,表明输入softmax的值x是(近似)相等的。
进一步观察发现,x几乎都很小,随着训练的进行,还有进一步变小的趋势,可怕!
解决办法
调整初始化和激活函数无法间接保证与调节数据分布,那就强上BN层
- 即在网络的每一层都加上Batch Normalization层操作,归一化强力保证其分布,果然彻底解决了0.69问题。
改了Dense层的初始化方式×
1
x = Dense(1,kernel_initializer='he_normal',activation='sigmoid',kernel_regularizer=l2(0.00001))(x)
可能是激活层的激活方式与损失函数不匹配。
一般使用sigmoid,损失函数使用binary_crossentropy ;使用softmax,损失函数使用categorical_crossentropy
改为softmax loss + sparse_categorical_crossentropy!×
contrastive_loss ×
- 取消relu激活 ×
训练数据需要打乱,要检查每此batch是否都是一个类别,如果是,则没有办法优化;×
- 检查网络是不是没有回传梯度,而是只做了前向运算;×
- 二分类问题中 0.5 的 acc 接近随机猜测的值,可以检查下标签是否标错;×
- 尝试不同的 Learning Rate (1e-6、2e-5、3e-4);×
- 检查是否在 logit 那层加了激活函数,导致 logits 有问题,例如全为 0,经过 softmax 后就是 0.5了
- 修改欧式距离为cos距离×
- 改为差和乘积的拼接;×
- 过拟合?尝试Dropout(0.5)×
- BERT模型无法共享参数使用?
- 数据集本身的问题
- 数据本身以及label是否有异常
- 数据是否过于脏乱,没有经过清洗
- 数据输入是否有问题,比如图片与label是否一致
- 数据经过预处理后,是否丢失特征或者因预处理而出现别的问题
- 数据量是否过少,网络出现过拟合的现象
Bad Gradient(Dead Neurons)
使用ReLU激活函数,由于其在小于零范围梯度为0,可能会影响模型性能,甚至模型不会在更新
当发现模型随着epoch进行,训练error不变化,可能所以神经元都“死”了。这时尝试更换激活函数如leaky ReLU,ELU,再看训练error变化
- 使用ReLU时需要给参数加一点噪声,打破完全对称避免0梯度,甚至给biases加噪声
- 相对而言对于sigmoid,因为其在0值附近最敏感,梯度最大,初始化全为0就可以啦
- 任何关于梯度的操作,比如clipping, rounding, max/min都可能产生类似的问题
- ReLU相对Sigmoid优点:单侧抑制;宽阔的兴奋边界;稀疏激活性;解决梯度消失
模型训练加速
关于模型训练加速,论文提到了2点,一是使用更大的Batch Size,二是使用低精度(如FP16)进行训练(也是我们常说的混合精度训练)。关于使用更大的Batch Size进行训练加速,作者指出一般只增加Batch Size的话,效果不会太理想,需要结合如下调参方案:
- 增大学习率。因为更大的Batch Size意味着每个Batch数据计算得到的梯度更加贴近整个数据集,从数学上来说就是方差更小,因此当更新方向更加准确之后,迈的步子也可以更大,一般来说Batch Size变成原始几倍,学习率就增加几倍。
- Warm up。Warm up指的是用一个小的学习率先训练几个epoch,这是因为网络的参数是随机初始化的,假如一开始就采用较大的学习率容易出现数值不稳定,这也是为什么要使用Warm up。然后等到训练过程基本上稳定了就可以使用原始的初始学习率进行训练了。作者在使用Warm up的过程中使用线性增加的策略。举个例子假如Warm up阶段的初始学习率是0,warmup阶段共需要训练m个batch的数据(论文实现中m个batch共5个
epoch
),假设训练阶段的初始学习率是L,那么在第个batch的学习率就设置为。 - 每一个残差块后的最后一个BN层的参数初始化为0。我们知道BN层的,参数是用来对标注化后的数据做线性变换的,公式表示为:,其中我们一般会把设为1,而这篇论文提出初始化为则更容易训练。
- 不对Bias参数做权重惩罚。但是对权重还是要做的。。
ResourceExhaustedError: OOM
- 意思就是GPU的内存不够了
- 解决:检查下是否有其他程序占用,不行就重启下IDE,或kill 进程ID
Keras相关经验
1. 训练集,验证集和测试集
- 验证集是从训练集中抽取出来用于调参的,在validation_split中设置
- 用 Keras 的
validation_split
之前要記得把資料先弄亂,因為它會從資料的最尾端開始取,如果沒有弄亂的話切出來的資料 bias 會很大。可以使用np.shuffle
來弄亂
- 用 Keras 的
- 测试集是和训练集无交集的,用于测试所选参数用于该模型的效果的。在evaluate函数里设置
- 尽量对数据做shuffle
2. 查看模型的评价指标
1 |
|
3. 保存keras输出的loss,val
1 |
|
4. 绘制精度和损失曲线
1 |
|
5. 将整型 label 转换成 one-hot 形式
1 |
|
6. 自制回调函数 callback
1 |
|
7. 网格超参数搜索
Keras/Python深度学习中的网格搜索超参数调优(附源码)
先grid search,再random search(由粗到细)
1 |
|
8. 编写自己的层
对于简单的定制操作,我们或许可以通过使用layers.core.Lambda层来完成。要定制自己的层,你需要实现下面三个方法:
- build(input_shape):这是定义权重的方法
- call(x):这是定义层功能的方法,除非你希望你写的层支持masking,否则你只需要关心call的第一个参数:输入张量
- compute_output_shape(input_shape):如果你的层修改了输入数据的shape,你应该在这里指定shape变化的方法,这个函数使得Keras可以做自动shape推断
1 |
|
9. keras保存和加载自定义损失模型
如果使用了自定义的loss函数, 则需要在加载模型的时候,指定load_model函数提供的一个custom_objects参数:在custom_objects参数词典里加入keras的未知参数,如:
1 |
|
10. PRF 值计算
1 |
|
11. keras 获取中间层的输出
1 |
|
12. keras指定显卡且限制显存用量
keras在使用GPU的时候有个特点,就是默认全部占满显存。需要修改后端代码:
1 |
|
1 |
|
PS: 需要注意的是,虽然代码或配置层面设置了对显存占用百分比阈值,但在实际运行中如果达到了这个阈值,程序有需要的话还是会突破这个阈值。换而言之如果跑在一个大数据集上还是会用到更多的显存。以上的显存限制仅仅为了在跑小数据集时避免对显存的浪费而已。
13. Keras 切换后端(Theano和TensorFlow)
1 |
|
14. categorical_crossentropy vs. sparse_categorical_crossentropy
There are two ways to handle labels in multi-class classification: Encoding the labels via “categorical encoding” (also known as “one-hot encoding”) and using categorical_crossentropy
as your loss function. Encoding the labels as integers and using the sparse_categorical_crossentropy
loss function.
15. 通过生成器的方式训练模型,节省内存
1 |
|
16. CNN+LSTM的思考
Because RNNs are extremely expensive for processing very long sequences, but 1D convnets are cheap, it can be a good idea to use a 1D convnet as a preprocessing step before a RNN, shortening the sequence and extracting useful representations for the RNN to process.
17. 使用预训练模型的权重
1 |
|