Fast.ai 课程笔记: Dogs VS. Cats 实践

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/leayc/article/details/80753413

Fast.ai 的 Jeremy Howard 等人开发的 Deep Learning 课程,是我见过最贴合实践,同时又注重应用最新、最有效算法的入门课程。资源包括 fastai 库、视频、论坛和 一部分 Jupyter Notebook,视频在 USF (三藩大学)录制,实际上是 Jeremy 等人在 USF 做的一项数据科学学位课,所以授课期间会看到授课式的讲解和学生提问。

今年公开了第二期内容,内容大幅度更新,发布了基于 PyTorch 的 fastai 库,包含课程涉及的大量算法的实现。共十四讲,分两部分,第一部分讲 Deep Learning 的实践基础(Practical Deep Learning for Coders),第二部分是进阶内容(Cutting Edge Deep Learning for Coders),每讲90~120分钟,内容非常充实。Jeremy 建议有不懂的地方可以多加实践、多次观看视频,也可在课程论坛区提问。

要找工作了,发觉需要扎实补一下 Deep Learning 的实践基础,所以还是拿起这个课程来了。应该早点做的。

Lesson 1 是概述性的内容,加上 Lesson 2,以 Kaggle 的 Dogs VS. Cats 数据集为例,讲了做图像分类的基础内容。Notebook 里的内容是按视频时序的,并非一个 End-to-end 例子,只有训练和验证部分,刚上手的人可能不知道该怎么提交。

因此这里做一下简化和综合,感受感受 fastai 的强大威力。

数据集的划分和制作

首先要做的工作是把数据集分好。可以去 Kaggle 官网下载,也可用 Fast.ai 提供的文件:http://files.fast.ai/data/dogscats.zip 。后者已经将文件夹分好,前者则需要制作目录。因为在训练模型时数据的 mini-batch 是按照目录读进去的。

从上一期课程里扒出来了 Jupyter 下分数据的代码,供参考学习:

第一步,在当前文件夹下建一个 /data/redux 目录:

import os, sys
current_dir = os.getcwd()
LESSON_HOME_DIR = current_dir
DATA_HOME_DIR = current_dir+'/data/redux'

然后切换到这个目录下,新建一组文件夹。

当时的算法、硬件、框架还没有现在这么强,为了验证模型,还建立了一个 sample 目录,抽取一部分数据放进去。

%cd $DATA_HOME_DIR
%mkdir valid
%mkdir results
%mkdir -p sample/train
%mkdir -p sample/test
%mkdir -p sample/valid
%mkdir -p sample/results
%mkdir -p test/unknown

随机找一部分训练数据作为验证集:

%cd $DATA_HOME_DIR/train

g = glob('*.jpg')
shuf = np.random.permutation(g)
for i in range(2000): os.rename(shuf[i], DATA_HOME_DIR+'/valid/' + shuf[i])
## 随机取了两千张图片移动到 valid 目录下

在 sample 里做同样的操作:

from shutil import copyfile
g = glob('*.jpg')
shuf = np.random.permutation(g)
for i in range(200): copyfile(shuf[i], DATA_HOME_DIR+'/sample/train/' + shuf[i])

%cd $DATA_HOME_DIR/valid
g = glob('*.jpg')
shuf = np.random.permutation(g)
for i in range(50): copyfile(shuf[i], DATA_HOME_DIR+'/sample/valid/' + shuf[i])

接下来划分出文件夹:

#Divide cat/dog images into separate directories

%cd $DATA_HOME_DIR/sample/train
%mkdir cats
%mkdir dogs
%mv cat.*.jpg cats/
%mv dog.*.jpg dogs/

%cd $DATA_HOME_DIR/sample/valid
%mkdir cats
%mkdir dogs
%mv cat.*.jpg cats/
%mv dog.*.jpg dogs/

%cd $DATA_HOME_DIR/valid
%mkdir cats
%mkdir dogs
%mv cat.*.jpg cats/
%mv dog.*.jpg dogs/

%cd $DATA_HOME_DIR/train
%mkdir cats
%mkdir dogs
%mv cat.*.jpg cats/
%mv dog.*.jpg dogs/

%cd $DATA_HOME_DIR/test
%mv *.jpg unknown/

这样就实现了跟 http://files.fast.ai/data/dogscats.zip 一样的划分效果。

训练模型

准备工作:

%reload_ext autoreload
%autoreload 2
%matplotlib inline

from fastai.imports import *
from fastai.transforms import *
from fastai.conv_learner import *
from fastai.model import *
from fastai.dataset import *
from fastai.sgdr import *
from fastai.plots import *

PATH = "data/redux/"
sz=224

检查:

torch.cuda.is_available()

torch.backends.cudnn.enabled

cuda、cudnn 可用时,上面都会返回 True。

Fast.ai 所自满的“三行代码训练模型”:

arch=resnet34
data = ImageClassifierData.from_paths(PATH, tfms=tfms_from_model(arch, sz), test_name='test/unknown')
learn = ConvLearner.pretrained(arch, data, precompute=True)
learn.fit(0.01, 3)

课程中 ImageClassifierData 一行的参数里并没有指定 test_name ,不指定是不会读取测试数据的,因为我们这里要提交结果所以要写上测试数据所在的目录。我这次运行显示的是:

Epoch ■■■■■■■■■■■■■■■■■■■■■■ 100% 3/3 [00:06<00:00, 2.22s/it]
epoch      trn_loss   val_loss   accuracy                      
    0      0.042787   0.038841   0.9895    
    1      0.038573   0.038824   0.989                         
    2      0.030729   0.036212   0.9905                        

[0.03621208056807518, 0.9905]

即训练了三个 epoch,在验证集上准确率为 99%。这个结果是好于第一次比赛时第一名的成绩的(98%+)。

注意现在我们使用了 resnet34 模型,只在最后一层做了 finetune 。我觉得可能训练得还不够,所以用 cyclical learning rate 再训两轮试试:

learn.fit(0.01, 2, cycle_len=1)

返回结果:

Epoch ■■■■■■■■■■■■■■■■■■■■■■ 100% 2/2 [00:04<00:00, 2.23s/it]
epoch      trn_loss   val_loss   accuracy                      
    0      0.024299   0.034596   0.992     
    1      0.026423   0.034668   0.9915                        

[0.03466804574429989, 0.9915]

好像没啥变化。

接下来放开所有层的权重并使用不同的 learning_rate 继续训练模型:

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

这三个 learning_rate 被高度封装的函数做了一些自适应,网络被分成了前中后三个部分,分别应用三个 lr ,因为通过对 CNN 的可视化研究,对训练好的网络来说,我们可能不希望过多改变距离输入更近的那些卷积层。

结果:

Epoch ■■■■■■■■■■■■■■■■■■■■■■ 100% 4/4 [05:56<00:00, 89.13s/it]
epoch      trn_loss   val_loss   accuracy                     
    0      0.038209   0.030199   0.994     
    1      0.029963   0.030769   0.9935                       
    2      0.026292   0.030465   0.9925                       
    3      0.024287   0.033463   0.9925                       

[0.03346334781497717, 0.9925]

好像……还是没啥变化。

Jeremy 简单介绍了一些数据增强的办法,但强调,不要无脑做增强,要看数据有什么特点。比如有些图比较扁,你就不能随意做 random_crop ,可能会裁出一些没有目标的样本,此时的增强数据等于是 artifact ,会把模型搞坏。

时间关系我没有做更多的增强,因为这则笔记是要搞通整个流程而非关注某一部分。

不过并不妨碍我做一个 TTA (Test Time Average):

log_preds, _ = learn.TTA(is_test=True)

出于精度保护的考虑,fastai 分类器输出的结果都是 log_preds ,需要做一下转换。TTA 的结果还要平均:

probs = np.mean(np.exp(log_preds), 0)

按提交要求,封装一个输出函数:

def submit_to_csv(probs):
    ids = np.arange(1, len(probs)+1)
    subm = np.stack([ids, probs], axis=1)
    np.savetxt('dogcat.csv', subm, fmt='%d,%.5f', header='id,label',comments='')

预测时不会打乱顺序,输出的结果是按文件名顺序排序的,对该问题来说文件 id 就是1 到 12500,所以直接生成一个序列就好啦。
注意线上评测基准不是准确率而是 log loss ,可以做一点数学上的分析稍微提高成绩。这里 Kaggle 的评测程序自动做了平滑处理,所以不用担心输出 0/1 时扣分(想起国内平台的各种坑啊)。不过 numpy 有剪裁函数 .clip(max=, min=) ,倒也方便。

此时已经拿到提交文件 dogcat.csv 了。可以下载下来,也可以加一行代码直接在 Notebook 里出现下载链接:

from IPython.display import FileLink
FileLink('dogcat.csv')

我一共提交了两次。

第一次是按上述操作得到的结果,public score 大概是0.068。Jeremy 提到增大输入图像的尺寸也是一种很有效的数据增强,因此我把最初的 sz 增加到了299并使用了 TTA,现在得到了0.061的分数,大概相当于前150的成绩。

整个过程非常简单,显卡也不要求顶级,显存占用最高的时候到了5.4G。Jeremy 真的非常厉害,讲课的内容也很简单实用,推荐还没有学过的人赶紧上手。
别花钱学那些水课了,Fast.ai 不要钱,全免费,质量还靠谱,干嘛不学这个!

猜你喜欢

转载自blog.csdn.net/leayc/article/details/80753413