本系列课程的第一部分——神经网络入门与实践已结束,在该部分的1
-7
课中,我们通过图像分类网络(包括多类别分类)、时间序列处理、影评数据情感分析(包括构建语言模型、协同滤波)等实例,学习了Fast.AI
的API
,熟悉了使用神经网络解决实际问题的流程,掌握了网络调参中的常用技巧,要点如下:
要得到一个可用的模型,其实并不需要从零开始,可以利用已在其他数据集上训练得到的网络,提取新数据集上的特征;然后针对实际应用场景,添加若干附加层,设置合适的损失函数,训练添加层;然后微调已有模型。这种方法即为迁移学习。
CNN
;若为序列数据,则选用RNN
。Softmax
作为损失函数;若为多类别分类,则使用Sigmoid
函数。Dropout
和正则项);在第一部分的基础上,本系列课程的第二部分将涉及如下内容:
CNN
应用包括目标定位、图像增强(超分辨、风格迁移)、GAN
。
NLP
应用包括机器翻译、注意力模型等。
包括大尺寸的图像、大样本的数据、大尺寸输出的处理。
本节的主要内容是:
VS Code
中与代码查看相关的快捷键。Jupyter Notebook
中的调试方法。pathlib
这是Python 3
中的内置包,方便文件目录的操作。其将各个目录视为特定对象(在Linux
和Mac
系统下,为PosixPath
对象),并支持/
连接目录的操作(重写了操作符/
)。大部分和文件路径相关的包可接受pathlib
定义的目录对象做参数;而cv2
不支持,此时仅需通过str()
进行转换即可。
VS Code
中和代码查看相关的快捷键VS Code
的命令行终端。Jupyter Notebook
中的调试方法若要使用断点调试,可使用pdb.set_trace()
设置断点,然后使用pdb
中的命令:
使用ipython.core.debugger
替代pdb
,可使得调试窗口显得五颜六色的。
事实上,使用ipdb
更方便,其命令与pdb
兼容。
本节课中先讨论单一目标的问题。基本思路是:依据图片中面积最大的目标,对图片进行分类,同时针对目标框的四个角点,以L1
范数构造损失函数。
数据集采用的是Pascal VOC
,原网址经常崩溃,课程中提供了一个镜像网址。该数据集有2007
和2012
两个版本,后一个版本要比前一个的数据多一些。一般文献中经常将两个版本合并(注意合并过程中可能会存在图片重复或遗失的问题),本例中则只选用了2007
版。
Pascal VOC
中关于图片的分类与目标的标定的信息,是通过xml
文件给出的。后续有人整理成了json
格式。课程中给的链接下载不到(科学上网也不行),一个替代地址是Kaggle
上的链接。
以pascal_train2007.json
为例说明信息文件的组织结构。该文件中包含如下4个字段:
images
: 存储了图片的文件名,文件id
,以及长和宽:
{'file_name': '000012.jpg', 'height': 333, 'width': 500, 'id': 12}
{'file_name': '000017.jpg', 'height': 364, 'width': 480, 'id': 17}
annotations
: 存储了图片id
,图中目标的分类(编号)、目标框的起始位置、长和宽以及面积,目标的分割(一组多边形的角点),是否为群目标,等(这个等省略的是ignore
字段)。一个目标为一条记录,一张图可能有多条记录。
{'segmentation': [[155, 96, 155, 270, 351, 270, 351, 96]],
'area': 34104, 'iscrowd': 0, 'image_id': 12, 'bbox': [155, 96, 196, 174]'category_id': 7, 'id': 1, 'ignore': 0},
{'segmentation': [[184, 61, 184, 199, 279, 199, 279, 61]],
'area': 13110, 'iscrowd': 0, 'image_id': 17, 'bbox': [184, 61, 95, 138],'category_id': 15, 'id': 2, 'ignore': 0}
categories
: 存储了父类,类别名称,类别编号。
{'supercategory': 'none', 'id': 1, 'name': 'aeroplane'}
{'supercategory': 'none', 'id': 2, 'name': 'bicycle'}
type
: 仅有一个单词—instances
。
训练数据集的图像共有2501
张,目标共有7844
个。
然后提取每张图片中面积最大目标的信息,并将图片名-类别存储为CSV
文件。
这与第一部分的Lesson 1
中的流程大致相同:
tfms = tfms_from_model(f_model, sz, aug_tfms=transforms_side_on, crop_type=CropType.NO)
md = ImageClassifierData.from_csv(PATH, JPEGS, CSV, tfms=tfms, bs=bs)
learn = ConvLearner.pretrained(f_model, md, metrics=[accuracy])
learn.opt_fn = optim.Adam
其中网络模型f_model
选为resnet34
;图片sz
设为224
;图片裁剪crop_type
设为CropType.NO
,表示将图片不按比例缩放为正方形,不进行裁剪。Fast.AI
中默认对图片进行的操作是:将图像的窄边缩放至224
,在长边方向进行裁剪—在训练阶段随机裁剪,验证阶段使用中心裁剪。
此步骤结束后绘制图片时,使用如下语句:
x,y=next(iter(md.val_dl))
show_img(md.val_ds.denorm(to_np(x))[0])
要点如下:使用md.val_dl
获取数据迭代器;获取的x
为cuda
上的数据,该数据按照aug_tfms
参数的设定进行了修饰;为生成标准正太分布的数据,还对x
做了归一化;使用md.val_ds.denorm()
获取反归一化的图像,才可用于显示。
找到合适的学习速率:
lrf=learn.lr_find(1e-5,100)
由于开头和结尾处学习速率太过离谱时,损失函数值太大,导致无法整个损失函数曲线中观察到起伏,因此要跳过这些部分。而直接调用learn.sched.plot()
,参数默认值为开头跳过10
个,结尾跳过5
个,这样会有问题,因此使用如下语句:
learn.sched.plot(n_skip=5, n_skip_end=1)
采用lr=2E-2
后,训练网络,解锁全部参数,设置lrs=[lr/5000,lr/500,lr/5]
,继续训练一轮,可以获得82%
左右的准确率。考虑到大多图片中包含了不止一种的目标,这个准确率还是可以接受的。
现考虑确定目标框的四个角点。
准备数据
这一问题类似于第一部分中遥感图像的多类别分类。将角点信息整理成多类别分类所需的CSV
文件格式—每个图片对应着一个以空格分隔的"类别组"(在此即为角点坐标值):
'fn,bbox\n',
'000012.jpg,96 155 269 350\n',
'000017.jpg,77 89 335 402\n',
接着构建合适的数据对象。不同于遥感图像的多类别分类,此处输出值为连续值,而且需要根据分类网络所做的预处理做相应调整(图像被缩放后,目标框的坐标自然也会随之变化):
tfms = tfms_from_model(f_model, sz, crop_type=CropType.NO, tfm_y=TfmType.COORD) md = ImageClassifierData.from_csv(PATH, JPEGS, BB_CSV, tfms=tfms, continuous=True)
其中tfm_y=TfmType.COORD
告知网络要对四个角点的坐标值做变换,continuous=True
告知网络这个是回归问题,而非分类问题。
另外,如果需要数据修饰,对图片所做的变换,也要指明参数tfm_y=TfmType.COORD
:
tfm_y = TfmType.COORD
augs = [RandomFlip(tfm_y=tfm_y),
RandomRotate(3, p=0.5, tfm_y=tfm_y),
RandomLighting(0.05,0.05, tfm_y=tfm_y)]
tfms = tfms_from_model(f_model, sz, crop_type=CropType.NO,
tfm_y=tfm_y, aug_tfms=augs)
构建网络
在resnet34
的基础上,添加输出4
个角点坐标的全连接层,这一操作是通过设置ConvLearner
的custom_head
参数完成的(即设置自定义的末尾层)。
head_reg4 = nn.Sequential(Flatten(), nn.Linear(25088,4)) learn = ConvLearner.pretrained(f_model, md, custom_head=head_reg4) learn.opt_fn = optim.Adam learn.crit = nn.L1Loss()
损失函数采用L1
范数,可更好的惩罚与目标值过大的偏差。
训练网络
按前述课程中的步骤训练网络,所得结果如下所示:
json
格式提供了目标分类与标定的信息。640x480
时,通常表达的是横x纵
;而当描述一个矩阵(numpy
对象)为640x480
时,表达的是行x列
,即纵x横
。这关系到使用目标标注的信息和绘图结果的对应。open cv
预处理图片,要比使用torch vision
或PIL
快很多。Matplotlib
的面向对象的API
,而尽量避免使用Matplotlib
模仿Matlab
的绘图接口。learn.save()
保存网络模型时,保存路径为PATH/models/
,可通过learn.get_model_path()
查看。