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 不要钱,全免费,质量还靠谱,干嘛不学这个!