关于神经网络的一些调参经验
训练损失不下降
关于训练损失不下降,根据我的一些经验和理论知识,可以从以下角度来分析
首先,从数据集来分析:
- 是否数据集存在比较多的标注错误?
- 针对类似分类的项目,是否数据集分布不均衡,某些类别的样本数量太少?
- 数据集特征不明显,举个验证码的例子,很多字符类的验证码,为了不让别人破解,会加上背景、斑点和干扰线等,如果这些干扰元素影响很大,人类肉眼分辨都存在难度,那直接丢给神经网络学习也是很难的,你需要想办法去除干扰元素,使数据特征尽可能的明显。
其次,从数据预处理来分析:
- 是否未进行归一化,比如图片像素值是否除以255;
- 是否未打乱数据集,不打乱数据集的话,会导致网络在学习过程中产生一定的偏见问题;
- 是否正确对应数据和标签,比如在预处理后将训练数据和标签输入到神经网络时,因为写了个bug,导致对应错乱;
最后,从模型结构和参数来分析:
激活函数、损失函数是否选错了?
优化器和学习速率不合适,需要调整;
模型结构太简单,学习能力不够;
正则化过度,
L1、L2和dropout
是用来防止过拟合的,如果训练集loss下不来时,就要考虑一下是不是正则化过度,导致模型欠拟合;Batch_Size过大会导致收敛很慢,损失下降也就很慢,给人一种训练损失不下降的感觉;
模型训练遇到瓶颈
这里的瓶颈一般包括:梯度消失、大量神经元失活、梯度爆炸和弥散、学习率过大或过小等。
补充:
关于数据一个数据预处理我犯过两次的错误,值得一提
在神经网络中,图像被表示成
[c, h, w]
格式或者[n, c, h, w]
格式,但如果想要将图像以np.ndarray
形式输入,因np.ndarray
默认将图像表示成[w, h, c]
个格式,需要对其进行转化。我是头脑一热用了reshape,实际上reshape操作是把三维的数据平摊成一维,然后按顺序重新组成新的矩阵,这样就导致整个图像的特征全部错乱。
解决办法是使用
numpy
的transpose()
image_chw = np.transpose(image_hwc, (2,0,1))
关于模型训练遇到瓶颈,我是想到了
BN
层BN 层
可以改善流经网络的梯度,并且减少对初始化的强烈依赖,允许更大的学习率,大幅提高训练速度。一般当你网络出现训练损失不下降的时候可以尝试;如果你的网络已经得到了不错的训练效果,添加
BN
层可能也不会给你带来显著的提高。
训练损失下降,验证损失不下降
上面分析完了训练损失不下降可能的原因,然后还有另一种情形就是训练损失下降,但是验证损失不下降,这种情况大家第一时间想到的应该是过拟合。
但是实际上还有可能是因为你的训练集和验证集分布差别较大,一般有可能是你的数据集未打乱导致的。
我们一般所说的过拟合的情形是,训练一开始训练损失和验证损失均开始下降,到中后期的时候,训练损失依然持续下降,但是验证损失不再下降,甚至有可能开始上升,不过需要注意的是,这些只是表象,如果这时,你用训练得到的模型来测试你的训练集,正确率应该是很高的,如果识别你的训练集的时候准确率都很低,那这完全不是过拟合!
所以过拟合的真正情况是,训练集准确率很高,验证集准确率很低!
所以,我们再回到讨论的标题,训练损失下降,验证损失不下降,除了过拟合,还有没有其他的可能性?
是有的,也就是当你的训练损失虽然在下降,但是训练集准确率依然很低(没有提升)的情况,这个时候对应训练过程的表象是,从最一开始虽然训练损失一直在下降,但是验证损失实际上没有下降过(或者微微下降一点点,然后又开始上升)。
这种明显异常的,检查代码最重要,大概率就是数据处理、解析输出时候哪里写错了;不过也有可能是网络过于不合理(根据我自己的实际经验得出)
那如果确实是过拟合,我们有哪些解决方式呢:
- 适当的dropout、正则化和降维;
- 适当降低模型的规模,如神经网络的层数,卷积的个数等;
- 获取更多的数据集。
补充:
之前训练的时候我发现从一开始我的验证损失就比训练损失小很多,当时我查资料得到的答案是:如果加了dropout或者正则化相关的操作,会导致验证损失比训练损失小,一开始我是没有仔细去思考,后来总觉得哪里不对劲。
理论上,验证损失比训练损失小一点点我觉得比较正常(因为正则化会降低精度,pytorch
的验证过程正则化不会生效),我这边是小了1~2个数量级,明显是不科学的,所以我就是开始调试看看到底是啥问题,最后发现原来计算验证损失的时候写了个bug。
train_loss和test_loss结果的一般性分析
- train loss 不断下降,test loss不断下降,说明网络仍在学习;
- train loss 不断下降,test loss趋于不变,说明网络过拟合;
- train loss 趋于不变,test loss不断下降,说明数据集100%有问题;
- train loss 趋于不变,test loss趋于不变,说明学习遇到瓶颈,需要减小学习率或批量数目;
- train loss 不断上升,test loss不断上升,说明网络结构设计不当,训练超参数设置不当,数据集经过清洗等问题。
曾经遇到问题的解决方式:
训练损失不下降:
调小batch_size,无效
剔除只有两个样本的一个类别,无效
添加
BN
层,有效
验证损失不下降:
- 首先我是遇到了我上面提到过的异常,也就是训练损失下降到很小,但是测试训练集的准确率低的离谱,理论上这应该是明显的异常,所以我仔细检查了一遍代码,只发现了一个验证损失计算方面的bug(也是上面补充的点),这个实际对模型的训练影响不大;
- 因为实在没有找到程序得异常之处,考虑是过拟合,先进行了模型的缩小,由6层卷积变成了4层卷积,结果对验证集损失的下降帮助不大,但是却解决了训练集准确率非常低的问题,对于训练集已经可以达到100%的准确率,那么问题就确实变成了过拟合。
- 既然确定是过拟合问题,进一步降低网络复杂度,然后增大
weight_decay
参数的值,有一定的效果,但是无法根本性解决问题(验证损失从1.1降低至0.55); - 解决过拟合的终极秘诀是增加数据集,本来我一开始是想把图片保持三通道输入到神经网络的,因为前面遇到各种问题所以我是先灰度化后输入的,现在前面的问题解决了,那我正好可以恢复到三通道,也是变相增加了数据集,经过测试,使得测试集准确率直接提升50%(验证损失从0.55降低至0.14)。
- 增加数据集,原来1800张图片,增加1400张,准确率直接又提升25%以上,验证损失从0.14降低至0.02。
补充1:
weight decay(权值衰减,相当于L2
正则化)使用的目的是防止过拟合。在损失函数中,weight decay是放在正则项(regularization)前面的一个系数,正则项一般指示模型的复杂度,所以weight decay的作用是调节模型复杂度对损失函数的影响,若weight decay很大,则复杂的模型损失函数的值也就大。
如果没有加weight_decay或者其值很小,这种情况下训练集和测试集loss相差会较大,随着训练进行,前者loss不断下降后者缓慢上升。
补充2:
上篇文章里记录了下关于net.train()
,net.eval()
的说明,也就是当声明train的时候,表示网络开始进行训练,声明eval
的时候,表示网络开始测试。 训练与测试,BN
和Dropout
有区别,也就是训练的时候BN
层和Dropout
生效,测试时不生效。
然后上一次的项目中,我在测试时,声明eval
与否,对测试结果好像没有什么影响,所以后面注释写了一句写不写都行,实际上是错的。
如果网络有使用BN
层和Dropout
,测试的时候最好加上net.eval()
,否则可能导致测试准确率不稳定且相比实际模型准确率低很多的情况。