注:本文先讲解理论部分,之后会用pytorch给出示例

神经网络是一种在很多用例中能够提供最优准确率的机器学习算法。但是,很多时候我们构建的神经网络的准确率可能无法令人满意,或者无法让我们在数据科学竞赛中拿到领先名次。所以,我们总是在寻求更好的方式来改善模型的性能。有很多技术可以帮助我们达到这个目标。本文将介绍这些技术,帮助大家构建更准确的神经网络。

过拟合

过拟合,典型的表现为训练集损失远远小于验证集损失。而欠拟合则表现为训练集损失大于验证集损失。

在这里插入图片描述
保证神经网络在测试集上运行良好的第一步就是验证神经网络没有过拟合。

我们要清楚远远大于的概念,如果训练集损失只比验证集损失多一点点的话,同等数量级(例如0.8与0.9)这种情况下并不是过拟合的表现。我们一般遇到的过拟合应该是0.8(训练集损失)与2.0(验证集损失)这种不在一个量级的损失比。

什么是过拟合呢?

当你的模型开始记录训练数据而不是从中学习的时候,就发生了过拟合。然后,当你的模型遇到之前没有见过的数据时,它就无法很好的运行。为了更好地理解,我们来看一个类比。

我们有一个记性特好的同学,假设一次数学考试马上就要来临了。你和这位擅长记忆的同学开始学习课本。这名同学记住课本中的每一个公式、问题以及问题的答案,然而你要比他来得聪明一些,所以你决定以直觉为基础、解决问题、学习这些公式是如何发挥作用的。考试来了,如果试卷中的问题是直接来源于课本的,那么可以想像那名记忆力超群的同学发挥得更好,但是,如果试题是涉及应用直观知识的全新问题,那么你将会做得更好,而你的朋友会惨败。

如何鉴别模型是否过拟合呢?

你仅仅需要交叉检查训练准确率和测试准确率。如果训练准确率远远高出了测试准确率,那么可以断定你的模型是过拟合了。你也可以在图中画出预测点来验证。下面是一些避免过拟合的技术:

  1. 数据正则化(L1 或 L2);
  2. Dropout:随机丢弃一些神经元之间的连接,强制神经网络寻找新的路径并泛化;

Dropout类似于bagging ensemble减少variance。也就是投通过投票来减少可变性。通常我们在全连接层部分使用dropout,在卷积层则不使用。但要声明,dropout并不适合所有的情况,请大家不要无脑上Dropout。

Dropout一般适合于全连接层部分,而卷积层由于其参数并不是很多,所以不需要dropout,加上的话对模型的泛化能力并没有太大的影响。如下图:
在这里插入图片描述
我们一般在网络的最开始和结束的时候使用全连接层,而hidden layers则是网络中的卷积层。所以一般情况,在全连接层部分,采用较大概率的dropout而在卷积层采用低概率或者不采用dropout。

  1. 早停(Early Stopping):促使神经网络训练早点停止,以减少在测试集中的误差。

超参数调节

在这里插入图片描述超参数是你必须给网络初始化的值,这些数值不能在训练的过程中学到。在卷积神经网络中,这些超参数包括:核大小、神经网络层数、激活函数、损失函数、所用的优化器(梯度下降、RMSprop)、批大小、训练的 epoch 数量等等。

每个神经网络都会有最佳超参数组合,这组参数能够得到最大的准确率。你也许会问,「有这么多超参数,我如何选择每个参数呢?

不幸的是,对每个神经网络而言,并没有确定最佳超参数组合的直接方法,所以通常都是通过反复试验得到的。但是也有一些关于上述超参数的最佳实践:

学习率:
选择最优学习率是很重要的,因为它决定了神经网络是否可以收敛到全局最小值。选择较高的学习率几乎从来不能到达全局最小值,因为你很可能跳过它。所以,你总是在全局最小值附近,但是从未收敛到全局最小值。选择较小的学习率有助于神经网络收敛到全局最小值,但是会花费很多时间。这样你必须用更多的时间来训练神经网络。较小的学习率也更可能使神经网络困在局部极小值里面,也就是说,神经网络会收敛到一个局部极小值,而且因为学习率比较小,它无法跳出局部极小值。所以,在设置学习率的时候你必须非常谨慎。

比如下图利用fastai中的lr_find()函数寻找合适的学习率,根据下方的学习率-损失曲线得到此时合适的学习率为1e-2
在这里插入图片描述如果想要了解更多,这里推荐一篇fastai首席设计师Sylvain Gugger的一篇博客:How Do You Find A Good Learning Rate以及相关的论文Cyclical Learning Rates for Training Neural Networks

神经网络架构:
并不存在能够在所有的测试集中带来高准确率的标准网络架构。你必须实验,尝试不同的架构,从实验结果进行推断,然后再尝试。我建议使用已经得到验证的架构,而不是构建自己的网络架构。例如:对于图像识别任务,有 VGG、Resnet、谷歌的 Inception 网络等。这些都是开源的,而且已经被证明具有较高的准确率。所以你可以把这些架构复制过来,然后根据自己的目的做一些调整。

优化器和损失函数:
这方面有很多可供选择。事实上,如果有必要,你可以自定义损失函数。常用的优化器有 RMSprop、随机梯度下降和 Adam。这些优化器貌似在很多用例中都可以起作用。如果你的任务是分类任务,那么常用的损失函数是类别交叉熵。如果你在执行回归任务,那么均方差是最常用的损失函数。你可以自由地使用这些优化器超参数进行试验,也可以使用不同的优化器和损失函数。

批大小和 epoch 次数:
同样,没有适用于所有用例的批大小和 epoch 次数的标准值。你必须进行试验,尝试不同的选择。在通常的实践中,批大小被设置为 8、16、32……epoch 次数则取决于开发者的偏好以及他/她所拥有的计算资源。

权重初始化
权重初始化相比于其他的trick来说在平常使用并不是很频繁。为什么呢?原因很简单,因为大部分人使用的模型都是预训练模型,使用的权重都是在大型数据集上训练好的模型,当然不需要自己去初始化权重了。只有没有预训练模型的领域会自己初始化权重,或者在模型中去初始化神经网络最后那几个全连接层的权重。那么大家喜欢用什么初始化权重算法?
当然是kaiming_normal或者xavier_normal。

相关论文:

Delving deep into rectifiers: Surpassing human-level performance on ImageNet classificationUnderstanding the difficulty of training deep feedforward neural networks

激活函数:
激活函数映射非线性函数输入和输出。激活函数是特别重要的,选择合适的激活函数有助于模型学习得更好。现在,整流线性单元(ReLU)是最广泛使用的激活函数,因为它解决了梯度消失的问题。更早时候,Sigmoid 和 Tanh 函数都是最常用的激活函数。但是它们都会遇到梯度消失的问题,即在反向传播中,梯度在到达初始层的过程中,值在变小,趋向于 0。这不利于神经网络向具有更深层的结构扩展。ReLU 克服了这个问题,因此也就可以允许神经网络扩展到更深的层。
在这里插入图片描述learning-rate学习率与batch-size批大小的关系
一般来说,越大的batch-size使用越大的学习率。

原理很简单,越大的batch-size意味着我们学习的时候,收敛方向的confidence越大,我们前进的方向更加坚定,而小的batch-size则显得比较杂乱,毫无规律性,因为相比批次大的时候,批次小的情况下无法照顾到更多的情况,所以需要小的学习率来保证不至于出错。

可以看下图损失Loss与学习率Lr的关系:

在这里插入图片描述当然我们也可以从上图中看出,当batchsize变大后,得到好的测试结果所能允许的lr范围在变小,也就是说,当batchsize很小时,比较容易找打一个合适的lr达到不错的结果,当batchsize变大后,可能需要精细地找一个合适的lr才能达到较好的结果,这也给实际的large batch分布式训练带来了困难。

所以说,在显存足够的条件下,最好采用较大的batch-size进行训练,找到合适的学习率后,可以加快收敛速度。另外,较大的batch-size可以避免batch normalization出现的一些小问题:

https://github.com/pytorch/pytorch/issues/4534

如何理解深度学习分布式训练中的large batch size与learning rate的关系?

差分学习率与迁移学习
有关迁移学习可以看这篇博客:迁移学习 Transfer Learning—通俗易懂地介绍(常见网络模型pytorch实现)

先说下迁移学习,迁移学习是一种很常见的深度学习技巧,我们利用很多预训练的经典模型直接去训练我们自己的任务。虽然说领域不同,但是在学习权重的广度方面,两个任务之间还是有联系的。
在这里插入图片描述由上图,我们拿来model A训练好的模型权重去训练我们自己的模型权重(Model B),其中,modelA可能是ImageNet的预训练权重,而ModelB则是我们自己想要用来识别猫和狗的预训练权重。

那么差分学习率和迁移学习有什么关系呢?我们直接拿来其他任务的训练权重,在进行optimize的时候,如何选择适当的学习率是一个很重要的问题。

一般地,我们设计的神经网络(如下图)一般分为三个部分,输入层,隐含层和输出层,随着层数的增加,神经网络学习到的特征越抽象。因此,下图中的卷积层和全连接层的学习率也应该设置的不一样,一般来说,卷积层设置的学习率应该更低一些,而全连接层的学习率可以适当提高。
在这里插入图片描述这就是差分学习率的意思,在不同的层设置不同的学习率,可以提高神经网络的训练效果,具体的介绍可以查看Transfer Learning using differential learning rates
在这里插入图片描述
余弦退火(cosine annealing)和热重启的随机梯度下降
余弦就是类似于余弦函数的曲线,退火就是下降,余弦退火就是学习率类似余弦函数慢慢下降。

热重启就是在学习的过程中,学习率慢慢下降然后突然再回弹(重启)然后继续慢慢下降。

两个结合起来就是下方的学习率变化图:
在这里插入图片描述更多详细的介绍可以查看知乎机器学习算法如何调参?这里有一份神经网络学习速率设置指南
以及相关论文SGDR: Stochastic Gradient Descent with Warm Restarts

多尺度训练
多尺度训练是一种直接有效的方法,通过输入不同尺度的图像数据集,因为神经网络卷积池化的特殊性,这样可以让神经网络充分地学习不同分辨率下图像的特征,可以提高机器学习的性能。

也可以用来处理过拟合效应,在图像数据集不是特别充足的情况下,可以先训练小尺寸图像,然后增大尺寸并再次训练相同模型,这样的思想在Yolo-v2的论文中也提到过:
在这里插入图片描述需要注意的是:多尺度训练并不是适合所有的深度学习应用,多尺度训练可以算是特殊的数据增强方法,在图像大小这一块做了调整。如果有可能最好利用可视化代码将多尺度后的图像近距离观察一下,看看多尺度会对图像的整体信息有没有影响,如果对图像信息有影响的话,这样直接训练的话会误导算法导致得不到应有的结果。

算法集成

如果单个神经网络不像你期待的那样准确,那么你可以创建一个神经网络集成,结合多个网络的预测能力。你可以选择不同的神经网络架构,在不同部分的数据集上训练它们,然后使用它们的集合预测能力在测试集上达到较高的准确率。

假设你在构建一个猫狗分类器,0 代表猫,1 代表狗。当组合不同的猫狗分类器时,基于单个分类器之间的皮尔逊相关系数,集成算法的准确率有了提升。让我们看一个例子,拿 3 个模型来衡量它们各自的准确率:

Ground Truth: 1111111111
Classifier 1: 1111111100 = 80% accuracy
Classifier 2: 1111111100 = 80% accuracy
Classifier 3: 1011111100 = 70% accuracy

3 个模型的皮尔逊相关系数很高。所以,集成它们并不会提升准确率。如果我们使用多数投票的方式来组合这三个模型,会得到下面的结果:

Ensemble Result: 1111111100 = 80% accuracy

正如你在上面所看到的,具有低皮尔逊相关系数的弱学习器的组合优于具有较高皮尔逊相关系数的学习器的组合。

难例挖掘 hard-negative-mining

在任何一个深度学习任务中,我们都会遇到一些比较“棘手”的数据,这些数据相比较于其他的普通数据更难识别,这种特比容易识别错误的例子就称为hard-negative。

举个栗子。比如Kaggle比赛中的一个识别遥感图像中船只的任务,使用的图像集是从一张大的遥感图中裁剪出来的,每张图的大小为768*768,在简单地对图像进行分类时(仅仅分类图像中有无船只),在validation中发现最容易识别出错的图如下:
在这里插入图片描述在观察到这些最难办的图像的特征后,我们可以针对这些难办的图像采取一些方法来解决这些问题。我们先用初始的正负样本(一般是正样本+与正样本同规模的负样本的一个子集)训练分类器, 然后再用训练出的分类器对样本进行分类, 把其中负样本中错误分类的那些样本(hard negative)放入负样本集合, 再继续训练分类器, 如此反复, 直到达到停止条件(比如分类器性能不再提升)。也就是不停滴将困难样本拿去训练,让分类器更好地学习到难以学习的特征,简单来说就是熟能生巧嘛。

在这里插入图片描述在 Fast-RCNN 中将与 groud-truth 的 IoU 在 [0.1, 0.5) 之间的图像标记为负例,[0, 0.1)的example 用于hard negative mining.在训练时一般输入为N=2张图片, 选择128个RoI,即每张图片64个RoI。对于每张图片, 按照1:3的比例来抽取RoI, 抽取正负样本的比例为1:3,要在负例中抽取48个。

相关的两篇文章可以看一下

[17] P. Felzenszwalb, R. Girshick, D. McAllester, and D. Ramanan. Object detection with discriminatively trained part based models. TPAMI, 2010.
[37] K. Sung and T. Poggio. Example-based learning for viewbased human face detection. Technical Report A.I. Memo No. 1521, Massachussets Institute of Technology, 1994.

数据问题

缺乏数据
在使用了上述所有的技术以后,如果你的模型仍然没有在测试集上表现得更好一些,这可能是因为缺乏数据。在很多用例中训练数据的数量是有限的。如果你无法收集更多的数据,那么你可以采取数据增强方法。
在这里插入图片描述
如果你正在使用的是图像数据集,你可以通过剪切、翻转、随机裁剪等方法来增加新的图像。这可以为你正在训练的神经网络提供不同的样本。

数据集损坏
数据集的好坏是算法泛化好坏的一个非常重要的前提条件,我们通常在构建深度学习的任务中,所得到的数据集一般不会是非常完美的(不论是自己搭建还是利用他人的数据,在数据集量过大的时候,数据集中难免会有图像损坏或者错误的个例)。如果有损坏的图像,如果我们不知情,一般来说,在我们使用代码读取的时候就会报错,举个例子:

raise IOError("image file is truncated)

Kaggle的一个比赛中就存在这样的情况,训练的数据中有10w+的图像数据,但是存在10余张的图像内容缺失(图像大小明显小于其他正常图像)或者直接被损坏无法读取。这个时候就需要我们自己去手动编写代码将那些错误且无法参与训练的图像挑选出来。
在这里插入图片描述那么如果正确过滤这些图像呢?

  • 找到损坏图像的特点,例如无法读取,编写程序去除无法读取的图像
  • 找到内容缺失的图像,这样的图像大小往往比普通图像小一些,通过过滤文件大小小于某一阈值去除

当然,数据集损坏的形式还有很多,需要我们自己去发掘。在一开始得到数据集的时候最好不要直接开始训练,应该去仔细检查自己的数据集是否有问题,这样可以避免很多之后训练时需要的处理的麻烦。

Cross Validation 交叉验证

在李航的统计学方法中说到,交叉验证往往是对实际应用中数据不充足而采用的,基本目的就是重复使用数据。在平常中我们将所有的数据分为训练集和验证集就已经是简单的交叉验证了,可以称为1折交叉验证。注意,交叉验证和测试集没关系,测试集是用来衡量我们的算法标准的,不参与到交叉验证中来。

交叉验证只针对训练集和验证集。

交叉验证是Kaggle比赛中特别推崇的一种技巧,我们经常使用的是5-折(5-fold)交叉验证,将训练集分成5份,随机挑一份做验证集其余为训练集,循环5次,这种比较常见计算量也不是很大。还有一种叫做leave-one-out cross validation留一交叉验证,这种交叉验证就是n-折交叉,n表示数据集的容量,这种方法只适合数据量比较小的情况,计算量非常大的情况很少用到这种方法。

吴恩达有一节课The nuts and bolts of building applications using deep learning中也提到了。
在这里插入图片描述数据增强
数据集增强是一个老生常谈的话题了,我们为什么需要那么多数据?为什么需要数据增强技术?
可以看这篇文章来了解一下:深度学习为什么需要那么多的数据?

这里只简单说下我们常用的数据增强的Transform。大部分我们使用的图像增强技术一般是随机旋转,水平翻转,高斯模糊和尺度变化还有什么拉伸等blabla的操作。这些图像变化对大部分的任务是比较适合的,但针对特定的任务,存在某一簇特殊的图像增强技术可以达到比普通图像增强技术更好的效果。例如夜视图或者光照:
在这里插入图片描述
但也需要提个醒,并不是所有的图像增强都可以提升模型的泛化能力

而且有些图像增强技术会对原始图像造成损失从而导致神经网络学习到错误的信息,这点是我们比较容易忽视的问题,同样重要,相关内容可以查看fastai中的图像增强技术为什么相对比较好

TTA(Test Time Augmentation)
最初这个概念是在fastai课程中看到的,这个过程在训练阶段不会参与,是通过在验证和测试阶段进行的。具体过程是,对所要处理的图像进行几种随机的图像增强变化,然后对每种图像增强后的图像进行预测,对预测结果取平均值。

原理类似于模型平均,牺牲推断速度来实现推断精度的提升。
在这里插入图片描述
当然,这个技术也有好有坏,在我自己跑的卫星图数据集中采用TTA的精确度比不采用低了0.03个百分点。

训练神经网络真的很像炼丹,而我们所有使用的技巧tricks,也只是增加一些炼丹的成功率,而究竟炼丹能不能成功,最终还是要取决于我们设计的算法合不合理。

参考自:
Rohith Gandhi
OLDPAN

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐