第二节课中,讲述了提高图像分类网络准确率的若干手段,如数据修饰、学习速率重置、参数微调等。本节课将介绍卷积神经网络的基本架构,多标签图像分类,并演示如何将结果提交到Kaggle上。主要内容如下:
- 如何悄没声儿地把自己挂在Kaggle竞赛的leader board上。
- 如何对单一图像进行分类。
- 卷积网络基本结构。
- 多标签卫星遥感图像分类。
向Kaggle提交结果
1. 生成测试集的各类别的概率
log_preds, y = learn.TTA(is_test=True)
probs = np.exp(log_preds)
其中is_test=True
使得网络在测试集而非验证集上进行输出;出于精度考虑,Fast.AI提供的是概率的对数,因此,最终的结果需要通过exp()
函数获得。
2. 生成Kaggle所要求格式的数据
# 生成pandas数据对象
ds = pd.DataFrame(probs)
# 按每个类别名,给每列添加表头
ds.columns = data.classes
# 增加id列并设为文件id
ds.insert(0, 'id', [o[5:-4] for o in data.test_ds.fnames])
# 写入gzip压缩文件
ds.to_csv('subm.gz', compression='gzip', index=False)
由于每个文件的文件名形如test/0042d6bf3e5f3700865886db32689436.jpg
,因此要去除开头的5个字母和结尾的4个字母,才能得到文件id:0042d6bf3e5f3700865886db32689436
。
3. 提交至Kaggle
若使用kaggle-cli,则使用如下格式的语句
$ kg submit <submission-file> -u <username> -p <password> -c <competition> -m "<message>"
若使用Kaggle api,则使用如下格式的语句
kaggle competitions submit -c dog-breed-identification -f submission.csv -m "Message"
若使用浏览器,则直接在Late Submission
页面中提交即可。
若使用社交账号注册Kaggle,可能大家会对使用kaggle-cli时需要填写的用户名和账号感到迷茫,下面的链接是这种情况的解决方法。
这个是使用kaggle API的方法,还有这个。
利用训练所得的模型对单一文件进行分类
假设文件名变量为img_file
,则使用如下语句进行分类:
# 获取对训练数据集和验证数据集所做的变换
trn_tfms, val_tfms = tfms_from_model(arch, sz)
# 以对验证数据集的变换处理图片
im = val_tfms(Image.open(img_file)
# 将处理后的图片数据包装成图像序列形式,即增加一个维度表明待处理的是图片的序列
preds = learn.predict_array(im[None])
# 获取分类结果
np.argmax(preds)
卷积网络基本结构
课程中利用Excel
给出了一个卷积网络的通用结构在MNIST
手写数字识别上的简化示例。(这种可视化方法真是厉害了)
1. 卷积核与非线性激活函数
卷积核就是图像处理中的对应的概念。通过卷积操作,可以提取如边缘、形状等图像信息。卷积层后会连接一个非线性激活层,课程中以ReLU
(Rectified Linear Unit
)为例。ReLU会使得卷积输出的负数部分变为0。所用示例为MNIST
手写体数字7,首先经过归一化后,其像素值限定在了0~1区间。卷积核则使用垂直方向和水平方向的边缘检测算子为示意,经过ReLU后,将过滤同一边缘在卷积后图像会出现明暗两条线的现象。经过上述操作,所得效果如下:
2. 池化(Pooling)层
经过卷积后的图像,尺寸并未比原图小多少,反而因为多种卷积核的存在,一幅图像会生成多种卷积特征。如果不做处理,这就意味着紧邻的隐含层的连接参数会非常多,一方面会降低计算效率,另一方面会加剧过拟合现象。针对这一问题,网络引入了池化层。其基本理念是:对某一小块区域,按某种策略选择一个数值值描述这一块区域的特征。常用的策略有取平均、选最大值等。课程中将图像划分成2×2的区域,然后采用每一区域的最大值描述其特征。经过上述操作,所得结果如下:
3. 全连接层
最靠近输出层的即是全连接层,也就是常规的神经网络层:对上一层产生的激活值进行线性加权。最后需要根据应用场景,采用sigmoid
或softmax
等非线性函数,产生所需要的输出。
多标签卫星遥感图像分类
首先下载Planet数据集,解压后放置在data
目录下,其结构为
models tmp train-jpg test-jpg train_v2.csv
其中train_v2.csv文件的结构如下,每一个文件的标签里都可能包含多类。
选取网络结构为resnet34
,生成验证数据集val_idxs = get_cv_idxs(n)
,利用ImageClassifierData
生成所需数据,注意此时数据修饰方式为transforms_top_down
。这种方式会对图像随机的进行以下八种变换中的一种:旋转0、90、180、270度,每种角度下还会进行翻转。
由Fast.AI获取的数据data
对象,包含val_ds
、train_ds
、test_ds
等字段,其中ds
表示data set
,即数据集合。而data
对象还提供另一种访问形式data loader
,对应的字段为val_dl
、train_dl
、test_dl
,通过这些字段,可以用迭代器的形式产生变换后的块数据,如x, y = next(iter(data.val_dl))
,则y
的大小为torch.Size([64, 17])
,其中64
就是数据块的大小,17
为类别数,每一行的y
则是0-1
类别标识:
获取数据后,考虑到每次训练都需要对数据进行放缩,为节省这部分时间,可事先将数据放缩至合适尺寸并存储。
data = data.resize(int(sz*1.3), 'tmp')
接下来就是按照上一节所叙述的通用步骤进行网络训练:查找合适的学习速率;初步训练网络;解锁所有层,并用差异化的学习速率训练网络;锁定卷积层,并放大图像尺寸训练网络;解锁所有层训练网络。最终可得f2=0.930
的结果。
其中所示用的评判指标是在构建分类器时以参数metrics
传入ConvLearner.pretrained()
中的。
一些备注
- 先对pretrained=True进行训练,再对pretrained=False进行训练的理念:载入的参数已经包含了部分可用的图像信息,而后面附加的全连接层的参数则是随机初始化的。如果直接对所有参数进行训练,则有可能会污染已经训练好的参数。因此,我们期望附加层先获取一些有用信息,因此采用这种策略。
- 多标签分类中使用
sigmoid
做非线性激活函数的理念:之所以不选softmax
,是由于softmax
输出的向量的各值求和为1,与实际的多标签0-1表示不符。 - Fβ F β 指标: Fβ=(β2+1)P⋅Rβ2P+R F β = ( β 2 + 1 ) P ⋅ R β 2 P + R ,其中 P P 为查准率,即被判定为某一类的数据样本中,确实是该类别的比例;为查全率,即某一类的数据样本中,被判别为该类的比例。
一些有用的链接
- 卷积网络可视化视频:需科学上网。
- 卷积网络可视化。
- 课程wiki : 本节课程的一些相关资源,包括课程笔记、课上提到的博客地址等。
- scikit-learn关于 Fβ F β 的介绍。