第2篇 Fast AI深度学习课程——图像分类网络调优

在第一节课中,基于Dogs vs. Cats数据集,设置了一个ResNet34的网络,并通过学习速率选取方法,以及设置数据遍历次数为2,获得了一个准确率如下的网络:

Epochtrn_lossval_lossaccuracy
00.0520140.0283960.99
10.0497610.0287050.9885

本节将在上一节的基础上,通过若干参数的设定,提高所构造的分类网络的准确率。本节的主要内容有:

  • 数据修饰,包括训练数据修饰、测试数据修饰。
  • 随机梯度方法中的学习速率重置技巧。
  • 已有网络的参数微调。
  • 总结图片分类网络的通用构建步骤。
  • 实践通用步骤,以及csv格式的数据的训练。

训练数据修饰

由于我们要在已有的图片卷积网络的基础上构建新网络,而已有的图片卷积网络通常是在尺寸为224×224或是299×299的图片集上进行训练的,因此,Fast.AI会对输入图片进行裁剪,以使之满足网络输入层的要求。这一操作是通过在获取图片分类器时,传入参数tfms来实现的。如上节所示:

data = ImageClassfierData.from_paths(PATH, tfms=tfms_from_model(arch, sz))

事实上,裁剪后的图像可能会丢失关键特征区域(如猫或狗的脑袋),因此需要对输入图片进行额外的修饰,使得所构建的网络尽可能地“看全图片”。本节所采取的修饰如下:

tfms = tfms_from_model(resnet34, sz, aug_tfms=transforms_side_on, max_zoom=1.1)

其中transforms_side_on表明图片是从侧向拍摄,即是一般的摄影图像,区别于卫星图像;对这种图像会进行轻微平移、轻微旋转、左右翻转等变换。参数max_zoom指定了对图像进行缩放的幅度,即最大缩放不超过1.1倍。

由于输入图片被改变,由已有的卷积核所生成的结果应发生相应改变,因此设置precompute=False

通过数据修饰所获得准确率如下:

Epochtrn_lossval_lossaccuracy
00.0449650.0257170.992
10.0394760.0257230.9925

学习速率重置

在随机梯度下降算法中,我们选取某一点做出发点,然后针对损失函数逐步优化相应参数。由于开始时网络对最优解并无信息,因此会随机的选择参数值做为出发点。此时我们期望优化算法能够有效地进行梯度下降,从而尽快的找到最优解。但随着数据的投喂,网络对最优解也有了一定的认知,而此时的参数估计值也越来越接近最优解,我们则期望优化算法不要跨太大的步子,从而避免在最优解附近震荡。基于这样的理念,随机梯度下降算法将使用逐步减小学习速率(Learning Rate Annealing)的方法。

另一方面,我们期望最终训练出的网络,有稳定的泛化能力,即输入数据有微小变化时,其输出结果不至于变化的太离谱。这就要求训练所得的参数落在损失函数的较为平稳的区域。为达到这一目的,与逐步减小学习速率相反,我们会定期增大学习速率,以跳出底部尖锐(不稳定)的低损失函数区域。这就是随机梯度算法中的学习速率重置技巧(SGDR,Stochastic Gradient Descent with Restarts)。如下图所示。

图 1. 学习速率递降与重置
在Fast.AI中,上述功能通过如下代码实现:
learn.fit(1e-2, 3, cycle_len=1)

其中cycle_len指定了学习速率重置的周期,本例中即在遍历一次图片集后,重置学习速率。现在更新一下参数3的说明(原说明参见上一篇博文):3表示学习速率重置次数。使用语句learn.sched.plot_lr()可以画出学习速率的变化曲线:

图 2. 学习速率变化曲线
重新训练所得准确率如下:
Epochtrn_lossval_lossaccuracy
00.036020.0249770.989
10.036870.0226770.99
20.0397530.0222240.9925

网络参数微调

整个网络是在arch参数指定的架构的基础上搭建起来的。而原架构中的参数是在另外的数据集上训练得到的。比如在本节的示例中,采用的ResNet34卷积网络,其卷积核是在ImageNet数据集上训练的。若要使这些参数更契合Dog vs. Cat的数据,可以考虑进行现有模型的微调(Fine Tuning)。这一概念可通过下述代码实现:

learn.unfreeze()
lr=np.array([1e-4,1e-3,1e-2])
learn.fit(lr, 3, cycle_len=1, cycle_mult=2)

其中unfreeze()表示对ResNet34的卷积层参数解锁。而设置学习速率为向量lr,则是不同的隐含层采用不同的学习速率。其值的设定遵从如下理念:在深度神经网络中,越靠近输入层的隐含层学到的特征越通用化,比如第一隐含层可能只是通过卷积核获取了图像的边缘、形状信息等特征;而越靠近输出层的隐含层越贴近应用场景越,比如ImageNet数据集可能划分有上千类,那么最贴近输出层的隐含层就要想办法识别出足够划分上千类别的特征。对应于Dog vs. Cat问题,通用特征如边缘、特定形状等信息在这个问题中也可使用,因此获取这些信息的隐含层所需进行的微调幅度应该很小;另一方面,由于本例仅需分别两类,因此,ResNet34网络的靠近输出层的参数需要的微调幅度应该大些。基于这些考量,学习速率向量设定才如代码所示。

learn.fit()中,又出现了一个新参数cycle_mult=2,其表示学习速率每次重置时,遍历数据集的次数就翻倍。这是基于如下理念:在训练之初,由于所获得的信息有限,我们不指望一开始就能通过梯度下降进入泛化能力强的参数所在区域。这一阶段,我们较为频繁地改变学习速率,就能检验更大的参范围,尽快地找到理想的参数区域。但随着训练的进行,网络获得了越来越多的信息,优化算法基本限定在了理想参数所在的区域,此时我们希望能够做足够的训练以提高输出准确率,因此需要遍历多次数据集。cycle_mult的作用如下图所示。

图 3. cycle_mult=2时,学习速率变化曲线
此时所得准确率如下(最后3次,其中第7次遍历已经出现过拟合):
Epochtrn_lossval_lossaccuracy
40.029530.0215550.993
50.0241180.0198370.9935
60.0157680.0171750.993

测试数据修饰

正如训练数据需要修饰一样,由于网络输入层要求图片长宽一致,因此传入测试数据给网络时,也需裁剪,这样也会导致关键区域的缺失。此时需对测试数据进行修饰(TTA, Test Time Augmentation)。与在训练数据上所采用的策略不同,在此对测试数据随机进行4组变换,然后取对4组变换的检测结果的平均值。实现代码如下:

log_preds,y = learn.TTA()
probs = np.mean(np.exp(log_preds),0)

其中log_preds为预测的概率值的对数,因此在取平均时需要做指数运算。

图片分类网络的通用构建步骤

  1. 在指定训练数据集时,使数据修饰功能生效,同时设置precompute=True
  2. 使用函数lr_find()找到损失函数仍在明显降低的较大的学习速率。
  3. 遍历数据集1~2次,以固定的卷积核训练附加在已有模型后的全连接层。
  4. 更改precompute=False,设定cycle_len=1,遍历数据集2~3次,训练附加在已有模型后的全连接层。
  5. 解锁所有层,设置学习速率向量,设置规则如下:对与ImageNet中的图像相似的数据(如一般的摄影图片),一般设置前一速率为后一速率的1/10;对于卫星图像或医疗图像,一般设置前一速率为后一速率的1/3。重新调用lr_find()函数,找到合适的学习速率。设置cycle_mult=2,训练所有参数,直至出现overfitting现象。

在以CSV文件组织的数据集上实践通用步骤

为实践上述步骤,课程中选取Kaggle的狗类分辨比赛Dog Breed Challenge为例进行演示。
训练数据包含120类狗的10223张图片,其说明以CSV文件存储,格式如下:

图 4. CSV文件内容
其中id为文件名,最后一列为类名。

首先需要从训练数据中分割出验证集:

val_idxs = get_cv_idxs(n)

其中n为数据样本数,get_cv_indx()默认会挑出20%的数据索引。

1. 设置参数,构建网络
tfms = tfms_from_model(arch, sz, 
          aug_tfms=transforms_side_on, max_zoom=1.1)
data = ImageClassifierData.from_csv(PATH, 'train', 
          f'{PATH}labels.csv', test_name='test', 
          val_idxs=val_idxs, suffix='.jpg', tfms=tfms, bs=bs)

其中from_csv()指定了数据组织形式是csv文件;'train'为训练数据集所在目录,'f'{PATH}labels.csv''为csv文件,val_idxs为验证数据的索引,suffix为图片文件扩展名。出于显存的考虑,bz设置为24(不同于课程中的值,这也导致了后续训练参数的变化)。

2. 使用lr_find()确定学习速率

本例数据与猫狗分类的的数据类似,因此学习速率直接设定为0.01。

3. 遍历网络1~2次,以固定卷积核训练全连接层
learn = ConvLearner.pretrained(arch, data, precompute=True)
learn.fit(1e-2, 3)

所得准确率如下

Epochtrn_lossval_lossaccuracy
00.6166650.326650.893346
10.3924340.2780940.905088
20.292070.2629630.913405

课程中设定遍历次数为5,因为其bz=16。而在本博文bz=24的情况下,5次遍历大概率会过拟合。后续参数不同于课程中所设的原因也是如此,不再赘述。

4. 更改precompute=False,设定cycle_len=1,重新训练
learn.precompute = False
learn.fit(1e-2, 3, cycle_len=1)

所得准确率为:

Epochtrn_lossval_lossaccuracy
00.3053280.2497810.919765
10.2888350.2533770.923679
20.2614830.2556040.91683
5. 解锁所有参数,设置学习速率向量,设置cycle_mult=2,训练所有参数
learn.unfreeze()
lr=np.array([1e-4,1e-3,1e-2])
learn.fit(lr, 3, cycle_len=1, cycle_mult=2

所得准确率为:

Epochtrn_lossval_lossaccuracy
00.6153310.4334250.866928
10.8997870.6558090.799413
20.2645830.4026610.886497
30.9517140.9644350.729941
40.4197520.6181640.822407
50.1728570.4661430.871331
60.1043040.4439730.876223

其实效果不是太好。因此课程中其实并未用这一步骤。主要原因是本例所用数据集应该是ImageNet数据集的子集,因此由ImageNet数据集训练所得的网络参数更为有效。而若根据本数据集进行微调,反而会使网络性能下降。这也从另一方面说明了数据集的重要性。

6. 额外的尝试

课程中实际采用了另一种策略:若数据集为图像,则从小尺寸的图像开始训练,然后转到大尺寸图像继续训练,是一种有效避免过拟合的手段。代码如下:

learn.set_data(get_data(299, bs))
learn.freeze()
learn.fit(1e-2, 3, cycle_len=1)

所得准确率如下:

Epochtrn_lossval_lossaccuracy
00.2652640.2384930.921233
10.2948650.2457320.920744
20.2413060.2350980.925636

上述结果中训练数据集的损失值和验证集的损失值已经非常接近,再训练会出现过拟合现象,因此,博主训到这儿就结束了。课程中没有这个现象,因此还会设置cycle_mut=2,继续训一轮。

最后采用测试集数据修饰,代码如下:

log_preds, y = learn.TTA()
probs = np.mean(np.exp(log_preds), 0)
accuracy_np(probs,y)
metrics.log_loss(y, probs)

得到在测试集上的分类准确度为0.928,所得对数损失函数值为0.219。而课程中得到的值为0.941,0.1998。

一些备注

  • 存储模型参数:learn.save(filename),会被存储在{PATH}/tmp/{arch}/models目录下。
  • 解锁某层之后的系数:learn.unfreeze_to(n)

一些有用的链接

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值