第8篇 Fast AI深度学习课程——单目标识别与定位

一、前情回顾与课程展望

本系列课程的第一部分——神经网络入门与实践已结束,在该部分的1-7课中,我们通过图像分类网络(包括多类别分类)、时间序列处理、影评数据情感分析(包括构建语言模型、协同滤波)等实例,学习了Fast.AIAPI,熟悉了使用神经网络解决实际问题的流程,掌握了网络调参中的常用技巧,要点如下:

1. 迁移学习

要得到一个可用的模型,其实并不需要从零开始,可以利用已在其他数据集上训练得到的网络,提取新数据集上的特征;然后针对实际应用场景,添加若干附加层,设置合适的损失函数,训练添加层;然后微调已有模型。这种方法即为迁移学习。

2. 选择模型与损失函数
  • 若为固定尺寸的数据,则选用CNN;若为序列数据,则选用RNN
  • 对分类网络,若为单类别分类,则使用Softmax作为损失函数;若为多类别分类,则使用Sigmoid函数。
3. 过拟合解决方法
  • 增加数据;
  • 数据修饰;
  • 改进模型;
  • 正则化(如Dropout和正则项);
  • 减小模型规模。

在第一部分的基础上,本系列课程的第二部分将涉及如下内容:

1. 分类之外的CNN应用

包括目标定位、图像增强(超分辨、风格迁移)、GAN

2. 分类之外的NLP应用

包括机器翻译、注意力模型等。

3. 大数据集处理

包括大尺寸的图像、大样本的数据、大尺寸输出的处理。

本节的主要内容是:

  • 搬砖技巧与调试方法
    • VS Code中与代码查看相关的快捷键。
    • Jupyter Notebook中的调试方法。
  • 目标识别与标定
    • 数据预处理。
    • 如何在分类网络的基础上框定目标。

二、搬砖技巧和调试方法

1. pathlib

这是Python 3中的内置包,方便文件目录的操作。其将各个目录视为特定对象(在LinuxMac系统下,为PosixPath对象),并支持/连接目录的操作(重写了操作符/)。大部分和文件路径相关的包可接受pathlib定义的目录对象做参数;而cv2不支持,此时仅需通过str()进行转换即可。

2. VS Code中和代码查看相关的快捷键
  • ctrl(Mac: command)-shift-p: 打开VS Code的命令行终端。
  • ctrl(Mac: command)-t: 搜索变量的定义。
  • shift-F12: 搜索变量的引用。
  • F12: 由名称跳转到定义。
  • alt-left: 跳转到上一个浏览处(Mac下可能为ctrl--)。
3. Jupyter Notebook中的调试方法

若要使用断点调试,可使用pdb.set_trace()设置断点,然后使用pdb中的命令:

  • h: 显示帮助。
  • s: 进入函数定义。
  • n: 执行下一句。
  • c: 继续直至下一断点处。
  • u: 在调试栈中上溯,这样即可显示访问外层变量。
  • d: 在调试栈中下溯。
  • l: 显示代码上下文。
  • q: 结束。
  • 遇到和命令冲突的变量名,可使用p来进行打印。

使用ipython.core.debugger替代pdb,可使得调试窗口显得五颜六色的。
事实上,使用ipdb更方便,其命令与pdb兼容。

三、目标识别与标定

本节课中先讨论单一目标的问题。基本思路是:依据图片中面积最大的目标,对图片进行分类,同时针对目标框的四个角点,以L1范数构造损失函数。

1. 数据处理

数据集采用的是Pascal VOC,原网址经常崩溃,课程中提供了一个镜像网址。该数据集有20072012两个版本,后一个版本要比前一个的数据多一些。一般文献中经常将两个版本合并(注意合并过程中可能会存在图片重复或遗失的问题),本例中则只选用了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文件。

2. 构建分类网络

这与第一部分的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获取数据迭代器;获取的xcuda上的数据,该数据按照aug_tfms参数的设定进行了修饰;为生成标准正太分布的数据,还对x做了归一化;使用md.val_ds.denorm()获取反归一化的图像,才可用于显示。

图 1. crop_type=NO时的效果
3. 训练分类网络

找到合适的学习速率:

    lrf=learn.lr_find(1e-5,100)

由于开头和结尾处学习速率太过离谱时,损失函数值太大,导致无法整个损失函数曲线中观察到起伏,因此要跳过这些部分。而直接调用learn.sched.plot(),参数默认值为开头跳过10个,结尾跳过5个,这样会有问题,因此使用如下语句:

    learn.sched.plot(n_skip=5, n_skip_end=1)
图 2. 使用learn.sched.plot()的结果

采用lr=2E-2后,训练网络,解锁全部参数,设置lrs=[lr/5000,lr/500,lr/5],继续训练一轮,可以获得82%左右的准确率。考虑到大多图片中包含了不止一种的目标,这个准确率还是可以接受的。

4. 目标标定

现考虑确定目标框的四个角点。

  • 准备数据

    这一问题类似于第一部分中遥感图像的多类别分类。将角点信息整理成多类别分类所需的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个角点坐标的全连接层,这一操作是通过设置ConvLearnercustom_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范数,可更好的惩罚与目标值过大的偏差。

  • 训练网络

    按前述课程中的步骤训练网络,所得结果如下所示:

    图 3. 目标框定结果

一些有用的链接

几点需要注意的

  • 当描述一个屏幕大小为640x480时,通常表达的是横x纵;而当描述一个矩阵(numpy对象)为640x480时,表达的是行x列,即纵x横。这关系到使用目标标注的信息和绘图结果的对应。
  • 使用open cv预处理图片,要比使用torch visionPIL快很多。
  • 使用Matplotlib的面向对象的API,而尽量避免使用Matplotlib模仿Matlab的绘图接口。
  • 使用learn.save()保存网络模型时,保存路径为PATH/models/,可通过learn.get_model_path()查看。
  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值