【目标检测】SSD: Single Shot MultiBox Detector 模型fine-tune和网络架构

前言

博主在上一篇中提到了两种可能的改进方法。其中方法1,扩充类似数据集,详见Udacity Self-Driving 目标检测数据集简介与使用 ,由于一些原因,并未对此数据集做过多探索,一次简单训练下,mAP为64%左右,这还需要进一步探索。而方法2,说的是fine-tune已经训练好的SSD model,最近没来得及进行调参,初次实验效果有限,先把过程和原理记录下来,免得忘了,然后还会说下SSD的网络架构。

SSD模型fine-tune

CNN fine-tune

model fine-tune,就是模型微调的意思。举个例子说明一下:假如给你一个新的数据集做图片分类,这个数据集是关于汽车的,不过样本量很少,如果从零开始训练CNN(Alexnet或者VGG),容易过拟合。那怎么办呢?于是想到了迁移学习,用别人训练好的Imagenet模型来做,继承现有模型学到的特征“知识”,这样再训练的效果就会好一些。

SSD检测任务也适用fine-tune策略。之前博主介绍的SSD经典训练方法也算是一种fine-tune,为什么这么说,因为我们使用VGG_ILSVRC_16_layers_fc_reduced.caffemodel这个预训练模型,这其实是作者在Imagenet上训练的一个变种VGG,已经具备了一定的分类特征提取能力,我们继承这些“知识”,有利于模型的快速收敛。

其实,我们也可以直接去学习训练好的SSD的模型“知识”。比如,作者在COCO数据集上训练了一个SSD检测模型,这个模型对90类物体有较好的检测能力,现在我们只对其中的汽车和行人感兴趣,就只想检测这两类(COCO数据集当然也包含这两类),手头还有KITTI数据集等,现在就可以在这个模型的基础上去fine-tune,copy大部分有用的检测“知识”,并且专心学习两类物体特有的“知识”。

这里以COCO为例,介绍一下如何使用新数据集去fine-tune一个SSD COCO model,默认是SSD300x300。首先是下载07+12+COCO ,这个是作者用VOC数据集去微调COCO模型的范例,我们以此为蓝本进行修改,主要修改点是数据集以及预训练的模型。

下载压缩包,找到finetune_ssd_pascal.py文件,打开后可以看到和之前的ssd_pascal.py区别不是很大,重要的是这么一句:

pretrain_model = "models/VGGNet/VOC0712/SSD_300x300_coco/VGG_coco_SSD_300x300.caffemodel"

这就是预训练的模型,可以下载的,在作者主页找到COCO models: trainval35k,下载解压可得到VGG_coco_SSD_300x300_iter_400000.caffemodel ,再改个名字放到对应路径就行。接下来给复制一份脚本,命名为finetune_ssd_kitti.py ,然后修改成各种KITTI相关的名称和路径,类别数量也要改(可参考之前博文修改)。

下面运行命令开始训练看看:

 
  1. $ cd caffe

  2. $ python examples/ssd/finetune_ssd_kitti.py

发现有大bug,错误描述如下:

 
  1. Cannot copy param 0 weights from layer 'conv4_3_norm_mbox_conf'; shape mismatch. Source param shape is 324 512 3 3 (1492992); target param shape is 16 512 3 3 (73728). To learn this layer's parameters from scratch rather than copying from a saved net, rename the layer.

  2. *** Check failure stack trace: ***

  3. @ 0x7f0bddd2f5cd google::LogMessage::Fail()

  4. @ 0x7f0bddd31433 google::LogMessage::SendToLog()

  5. @ 0x7f0bddd2f15b google::LogMessage::Flush()

  6. @ 0x7f0bddd31e1e google::LogMessageFatal::~LogMessageFatal()

  7. @ 0x7f0bde5c34cb caffe::Net<>::CopyTrainedLayersFrom()

  8. @ 0x7f0bde5ca225 caffe::Net<>::CopyTrainedLayersFromBinaryProto()

  9. @ 0x7f0bde5ca2be caffe::Net<>::CopyTrainedLayersFrom()

  10. @ 0x40a849 CopyLayers()

  11. @ 0x40bca4 train()

  12. @ 0x4077c8 main

  13. @ 0x7f0bdc4c6830 __libc_start_main

  14. @ 0x408099 _start

  15. @ (nil) (unknown)

  16. Aborted (core dumped)

这个问题困扰了几天,后来才知道,COCO模型有90类,而本次训练的文件只有3类,conf层没有办法共享权值。解决方法就是把conf层改个名字,跳过这些层的fine-tune,意味着这部分层的参数需要从零学起。至于loc层则可以不改名,因为位置坐标和类别无关,所有类别均可共享。那么,注意到该脚本下有两个一模一样的CreateMultiBoxHead函数:

 
  1. mbox_layers = CreateMultiBoxHead(net, data_layer='data', from_layers=mbox_source_layers,

  2. use_batchnorm=use_batchnorm, min_sizes=min_sizes, max_sizes=max_sizes,

  3. aspect_ratios=aspect_ratios, steps=steps, normalizations=normalizations,

  4. num_classes=num_classes, share_location=share_location, flip=flip, clip=clip,

  5. prior_variance=prior_variance, kernel_size=3, pad=1, lr_mult=lr_mult)

现在,仅需要在这两个函数中添加参数conf_postfix='_kitti'就可以把conf层的名字了,其实就是在所有’conf’字符后面添加了’kitti’字样,新函数就变成了:

 
  1. mbox_layers = CreateMultiBoxHead(net, data_layer='data', from_layers=mbox_source_layers,

  2. use_batchnorm=use_batchnorm, min_sizes=min_sizes, max_sizes=max_sizes,

  3. aspect_ratios=aspect_ratios, steps=steps, normalizations=normalizations,

  4. num_classes=num_classes, share_location=share_location, flip=flip, clip=clip,

  5. prior_variance=prior_variance, kernel_size=3, pad=1, lr_mult=lr_mult, conf_postfix='_kitti')

然后再运行命令,就可以正常开始训练了。博主所用脚本可以参考一下:finetune_ssd_kitti.py

一开始使用默认solver训练了一次,120000迭代,mAP勉强达到63%,感觉还是不够看的。后来觉着默认solver中的学习率策略可能不太对,如果使用大的学习率(比如0.001)训练太久的话,原有caffemodel的基础权重可能会被破坏的比较厉害,那效果就会打折扣,因此有大神建议博主可以每隔10000次迭代降低学习率为一半,就是暂时没空尝试,如果有效果再更新吧。

SSD网络架构

VGGNet-SSD网络结构图

如果只是训练想VGGNet的SSD,并不用太关心SSD的网络结构,毕竟有python脚本可用。可是如果要想进一步修改网络,比如把基础网络换成Mobilenet,Resnet等,或者是修改多层融合检测层,那就需要简单了解SSD的基本结构。

SSD论文给的图是这样的,大致知道了结构,细节还是看不懂。

这里写图片描述

当然,有人会把prototxt放进这个Netscope 中,得到可视化的结构,这是最直接的方法了,为了方便表示,训练网络train.prototxt的架构用如下简图表示(新标签页中打开可看到大图)。

这里写图片描述

说明一下,为了简便,没有完整反映出6个bottom输入层;图中的“*”号表示省略写法,比如第一行的 *_perm展开后就表示conv4_3_norm_mbox_loc_perm,其他以此类推;然后mbox_priorbox层均有有第二个bottom输入,即data层,不过这里没有画出来。

下面以VGGNet-SSD为例,结合prototxt源文件说一下网络结构。

数据层

这里的数据层类型是AnnotatedData,是作者自己写的。里面有很多复杂的设置,并不能完全看懂,所以也不好随意修改,主要注意下lmdb的source,batch_size以及label_map_file即可。

 
  1. # 数据层

  2. name: "VGG_VOC0712_SSD_300x300_train"

  3. layer {

  4. name: "data"

  5. type: "AnnotatedData"

  6. top: "data"

  7. top: "label"

  8. include {

  9. phase: TRAIN

  10. }

  11. transform_param {

  12. mirror: true

  13. mean_value: 104

  14. mean_value: 117

  15. mean_value: 123

  16. resize_param {

  17. prob: 1

  18. resize_mode: WARP

  19. height: 300

  20. width: 300

  21. interp_mode: LINEAR

  22. interp_mode: AREA

  23. interp_mode: NEAREST

  24. interp_mode: CUBIC

  25. interp_mode: LANCZOS4

  26. }

  27. emit_constraint {

  28. emit_type: CENTER

  29. }

  30. distort_param {

  31. brightness_prob: 0.5

  32. brightness_delta: 32

  33. contrast_prob: 0.5

  34. contrast_lower: 0.5

  35. contrast_upper: 1.5

  36. hue_prob: 0.5

  37. hue_delta: 18

  38. saturation_prob: 0.5

  39. saturation_lower: 0.5

  40. saturation_upper: 1.5

  41. random_order_prob: 0.0

  42. }

  43. expand_param {

  44. prob: 0.5

  45. max_expand_ratio: 4.0

  46. }

  47. }

  48. data_param {

  49. source: "examples/VOC0712/VOC0712_trainval_lmdb"

  50. batch_size: 32

  51. backend: LMDB

  52. }

  53. annotated_data_param {

  54. batch_sampler {

  55. max_sample: 1

  56. max_trials: 1

  57. }

  58. batch_sampler {

  59. sampler {

  60. min_scale: 0.3

  61. max_scale: 1.0

  62. min_aspect_ratio: 0.5

  63. max_aspect_ratio: 2.0

  64. }

  65. sample_constraint {

  66. min_jaccard_overlap: 0.1

  67. }

  68. max_sample: 1

  69. max_trials: 50

  70. }

  71. batch_sampler {

  72. sampler {

  73. min_scale: 0.3

  74. max_scale: 1.0

  75. min_aspect_ratio: 0.5

  76. max_aspect_ratio: 2.0

  77. }

  78. sample_constraint {

  79. min_jaccard_overlap: 0.3

  80. }

  81. max_sample: 1

  82. max_trials: 50

  83. }

  84. batch_sampler {

  85. sampler {

  86. min_scale: 0.3

  87. max_scale: 1.0

  88. min_aspect_ratio: 0.5

  89. max_aspect_ratio: 2.0

  90. }

  91. sample_constraint {

  92. min_jaccard_overlap: 0.5

  93. }

  94. max_sample: 1

  95. max_trials: 50

  96. }

  97. batch_sampler {

  98. sampler {

  99. min_scale: 0.3

  100. max_scale: 1.0

  101. min_aspect_ratio: 0.5

  102. max_aspect_ratio: 2.0

  103. }

  104. sample_constraint {

  105. min_jaccard_overlap: 0.7

  106. }

  107. max_sample: 1

  108. max_trials: 50

  109. }

  110. batch_sampler {

  111. sampler {

  112. min_scale: 0.3

  113. max_scale: 1.0

  114. min_aspect_ratio: 0.5

  115. max_aspect_ratio: 2.0

  116. }

  117. sample_constraint {

  118. min_jaccard_overlap: 0.9

  119. }

  120. max_sample: 1

  121. max_trials: 50

  122. }

  123. batch_sampler {

  124. sampler {

  125. min_scale: 0.3

  126. max_scale: 1.0

  127. min_aspect_ratio: 0.5

  128. max_aspect_ratio: 2.0

  129. }

  130. sample_constraint {

  131. max_jaccard_overlap: 1.0

  132. }

  133. max_sample: 1

  134. max_trials: 50

  135. }

  136. label_map_file: "data/VOC0712/labelmap_voc.prototxt"

  137. }

  138. }

特征提取网络

特征提取网络由两部分构成,VGGNet和新增的8个卷积层,要点有三处。

第一,这个VGGNet不同于原版,是修改过的,全名叫VGG_ILSVRC_16_layers_fc_reduced是作者另一篇论文ParseNet: Looking Wider to See Better的成果,然后被用到了SSD中。

第二,为什么要新增这么8个卷积层?因为在300x300输入下,conv4_3的分辨率为38x38,而到fc7层,也还有19x19,那就必须新增一些卷积层,以产生更多低分辨率的层,方便进行多层特征融合。最终作者选择了6个卷积层作为bottom,分辨率如下图所示,然后依次接上若干检测层。

卷积层 分辨率
conv4_3_norm 38x38
f7 19x19
conv6_2 10x10
conv7_2 5x5
conv8_2 3x3
conv9_2 1x1

第三,只有conv4_3加入了norm,这是为什么呢?原因仍然在论文中ParseNet中,大概是说,因为conv4_3的scale(尺度?)和其它层相比不同,才要加入norm使其均衡一些。

这里写图片描述

 
  1. # 特征提取网络

  2. layer {

  3. name: "conv1_1"

  4. type: "Convolution"

  5. bottom: "data"

  6. top: "conv1_1"

  7. param {

  8. lr_mult: 1

  9. decay_mult: 1

  10. }

  11. param {

  12. lr_mult: 2

  13. decay_mult: 0

  14. }

  15. convolution_param {

  16. num_output: 64

  17. pad: 1

  18. kernel_size: 3

  19. weight_filler {

  20. type: "xavier"

  21. }

  22. bias_filler {

  23. type: "constant"

  24. value: 0

  25. }

  26. }

  27. }

  28. #

  29. # 省略很多层

  30. #

  31. layer {

  32. name: "fc7"

  33. type: "Convolution"

  34. bottom: "fc6"

  35. top: "fc7"

  36. param {

  37. lr_mult: 1

  38. decay_mult: 1

  39. }

  40. param {

  41. lr_mult: 2

  42. decay_mult: 0

  43. }

  44. convolution_param {

  45. num_output: 1024

  46. kernel_size: 1

  47. weight_filler {

  48. type: "xavier"

  49. }

  50. bias_filler {

  51. type: "constant"

  52. value: 0

  53. }

  54. }

  55. }

  56. layer {

  57. name: "relu7"

  58. type: "ReLU"

  59. bottom: "fc7"

  60. top: "fc7"

  61. }

  62.  
  63. # 到此是VGG网络层,从conv1到fc7

  64.  
  65. layer {

  66. name: "conv6_1"

  67. type: "Convolution"

  68. bottom: "fc7"

  69. top: "conv6_1"

  70. param {

  71. lr_mult: 1

  72. decay_mult: 1

  73. }

  74. param {

  75. lr_mult: 2

  76. decay_mult: 0

  77. }

  78. convolution_param {

  79. num_output: 256

  80. pad: 0

  81. kernel_size: 1

  82. stride: 1

  83. weight_filler {

  84. type: "xavier"

  85. }

  86. bias_filler {

  87. type: "constant"

  88. value: 0

  89. }

  90. }

  91. }

  92. #

  93. # 省略很多层

  94. #

  95. layer {

  96. name: "conv9_2_relu"

  97. type: "ReLU"

  98. bottom: "conv9_2"

  99. top: "conv9_2"

  100.  
  101. # 到此是新增的8层卷积层,从conv6_1到conv9_2

多层融合检测网络

这一部分,作者选择了6个层作为bottom,在其上连接了一系列的检测相关层,这里以f7层为例,看看它作为bottom,连接了那些层。首先是loc相关层,包括fc7_mbox_loc,fc7_mbox_loc_perm和fc7_mbox_loc_flat。

 
  1. # mbox_loc层,预测box的坐标,其中24=6(default box数量)x4(四个坐标)

  2. layer {

  3. name: "fc7_mbox_loc"

  4. type: "Convolution"

  5. bottom: "fc7"

  6. top: "fc7_mbox_loc"

  7. param {

  8. lr_mult: 1

  9. decay_mult: 1

  10. }

  11. param {

  12. lr_mult: 2

  13. decay_mult: 0

  14. }

  15. convolution_param {

  16. num_output: 24

  17. pad: 1

  18. kernel_size: 3

  19. stride: 1

  20. weight_filler {

  21. type: "xavier"

  22. }

  23. bias_filler {

  24. type: "constant"

  25. value: 0

  26. }

  27. }

  28. }

  29. # mbox_loc_perm层,将上一层产生的mbox_loc重新排序

  30. layer {

  31. name: "fc7_mbox_loc_perm"

  32. type: "Permute"

  33. bottom: "fc7_mbox_loc"

  34. top: "fc7_mbox_loc_perm"

  35. permute_param {

  36. order: 0

  37. order: 2

  38. order: 3

  39. order: 1

  40. }

  41. }

  42. # mbox_loc_flat层,将perm层展平(例如将7x7展成1x49),方便拼接

  43. layer {

  44. name: "fc7_mbox_loc_flat"

  45. type: "Flatten"

  46. bottom: "fc7_mbox_loc_perm"

  47. top: "fc7_mbox_loc_flat"

  48. flatten_param {

  49. axis: 1

  50. }

  51. }

然后是conf相关层,包括fc7_mbox_conf,fc7_mbox_conf_perm和fc7_mbox_conf_flat。

 
  1. # mbox_conf层,预测box的类别置信度,126=6(default box数量)x21(总类别数)

  2. layer {

  3. name: "fc7_mbox_conf"

  4. type: "Convolution"

  5. bottom: "fc7"

  6. top: "fc7_mbox_conf"

  7. param {

  8. lr_mult: 1

  9. decay_mult: 1

  10. }

  11. param {

  12. lr_mult: 2

  13. decay_mult: 0

  14. }

  15. convolution_param {

  16. num_output: 126

  17. pad: 1

  18. kernel_size: 3

  19. stride: 1

  20. weight_filler {

  21. type: "xavier"

  22. }

  23. bias_filler {

  24. type: "constant"

  25. value: 0

  26. }

  27. }

  28. }

  29. # box_conf_perm层,将上一层产生的mbox_conf重新排序

  30. layer {

  31. name: "fc7_mbox_conf_perm"

  32. type: "Permute"

  33. bottom: "fc7_mbox_conf"

  34. top: "fc7_mbox_conf_perm"

  35. permute_param {

  36. order: 0

  37. order: 2

  38. order: 3

  39. order: 1

  40. }

  41. }

  42. # mbox_conf_flat层,将perm层展平,方便拼接

  43. layer {

  44. name: "fc7_mbox_conf_flat"

  45. type: "Flatten"

  46. bottom: "fc7_mbox_conf_perm"

  47. top: "fc7_mbox_conf_flat"

  48. flatten_param {

  49. axis: 1

  50. }

  51. }

最后是priorbox层,就只有fc7_mbox_priorbox

 
  1. # mbox_priorbox层,根据标注信息,经过转换,在cell中产生box真实值

  2. layer {

  3. name: "fc7_mbox_priorbox"

  4. type: "PriorBox"

  5. bottom: "fc7"

  6. bottom: "data"

  7. top: "fc7_mbox_priorbox"

  8. prior_box_param {

  9. min_size: 60.0

  10. max_size: 111.0 # priorbox的上下界为60~111

  11. aspect_ratio: 2

  12. aspect_ratio: 3 # 共同定义default box数量为6

  13. flip: true

  14. clip: false

  15. variance: 0.1

  16. variance: 0.1

  17. variance: 0.2

  18. variance: 0.2

  19. step: 16 # 不同层的值不同

  20. offset: 0.5

  21. }

  22. }

下面做一个表格统计一下多层融合检测层的相关重要参数:

bottom层 conv4_3 fc7 conv6_2 conv7_2 conv8_2 conv9_2
分辨率 38x38 19x19 10x10 5x5 3x3 1x1
default box数量 4 6 6 6 4 4
loc层num_output 16 24 24 24 16 16
conf层num_output 84 126 126 126 84 84
priorbox层size 30~60 60~111 111~162 162~213 213~264 264~315
priorbox层step 8 16 32 64 100 300

Concat层

这里需要添加3个concat层,分别拼接所有的loc层,conf层以及priorbox层。

 
  1. # mbox_loc层,拼接6个loc_flat层

  2. layer {

  3. name: "mbox_loc"

  4. type: "Concat"

  5. bottom: "conv4_3_norm_mbox_loc_flat"

  6. bottom: "fc7_mbox_loc_flat"

  7. bottom: "conv6_2_mbox_loc_flat"

  8. bottom: "conv7_2_mbox_loc_flat"

  9. bottom: "conv8_2_mbox_loc_flat"

  10. bottom: "conv9_2_mbox_loc_flat"

  11. top: "mbox_loc"

  12. concat_param {

  13. axis: 1

  14. }

  15. }

  16. # mbox_conf层,拼接6个conf_flat层

  17. layer {

  18. name: "mbox_conf"

  19. type: "Concat"

  20. bottom: "conv4_3_norm_mbox_conf_flat"

  21. bottom: "fc7_mbox_conf_flat"

  22. bottom: "conv6_2_mbox_conf_flat"

  23. bottom: "conv7_2_mbox_conf_flat"

  24. bottom: "conv8_2_mbox_conf_flat"

  25. bottom: "conv9_2_mbox_conf_flat"

  26. top: "mbox_conf"

  27. concat_param {

  28. axis: 1

  29. }

  30. }

  31. # mbox_priorbox层,拼接6个mbox_priorbox层

  32. layer {

  33. name: "mbox_priorbox"

  34. type: "Concat"

  35. bottom: "conv4_3_norm_mbox_priorbox"

  36. bottom: "fc7_mbox_priorbox"

  37. bottom: "conv6_2_mbox_priorbox"

  38. bottom: "conv7_2_mbox_priorbox"

  39. bottom: "conv8_2_mbox_priorbox"

  40. bottom: "conv9_2_mbox_priorbox"

  41. top: "mbox_priorbox"

  42. concat_param {

  43. axis: 2

  44. }

  45. }

损失层

损失层类型是MultiBoxLoss,这也是作者自己写的,在smooth_L1损失层基础上修改。损失层的bottom是三个concat层以及data层中的label,参数一般不需要改,注意其中的num_classes即可。

 
  1. # 损失层

  2. layer {

  3. name: "mbox_loss"

  4. type: "MultiBoxLoss"

  5. bottom: "mbox_loc"

  6. bottom: "mbox_conf"

  7. bottom: "mbox_priorbox"

  8. bottom: "label"

  9. top: "mbox_loss"

  10. include {

  11. phase: TRAIN

  12. }

  13. propagate_down: true

  14. propagate_down: true

  15. propagate_down: false

  16. propagate_down: false

  17. loss_param {

  18. normalization: VALID

  19. }

  20. multibox_loss_param {

  21. loc_loss_type: SMOOTH_L1

  22. conf_loss_type: SOFTMAX

  23. loc_weight: 1.0

  24. num_classes: 21

  25. share_location: true

  26. match_type: PER_PREDICTION

  27. overlap_threshold: 0.5

  28. use_prior_for_matching: true

  29. background_label_id: 0

  30. use_difficult_gt: true

  31. neg_pos_ratio: 3.0

  32. neg_overlap: 0.5

  33. code_type: CENTER_SIZE

  34. ignore_cross_boundary_bbox: false

  35. mining_type: MAX_NEGATIVE

  36. }

  37. }

至此,SSD的训练网络train.prototxt基本介绍完毕,那测试网络test.prototxt和部署网络deploy.prototxt就很容易理解了,只需要保持特征检测网络和多层融合检测网络不变,copy修改其他部分就好了。

了解SSD网络架构,可以方便我们对网络进行修改,比如说训练一个最近热门的Mobilenet-SSD就是蛮好的的应用(自己构造网络尽量遵照VGGNet-SSD的范例)。

你可能感兴趣的:(【目标检测】SSD: Single Shot MultiBox Detector 模型fine-tune和网络架构)