关于SSD训练时默认框输出层输出通道num_output的计算
申明,本博文是为解决以下两个问题而撰写。
Check failed: num_priors_ * num_loc_classes_ * 4 == bottom[0]->channels() (264348 vs. 88116) Number of priors must match number of location predictions.
Check failed: num_priors_ * num_classes_ == bottom[1]->channels() (132174 vs. 99406) Number of priors must match number of confidence predictions.
我们针对小目标检测时受到此方面的困扰,在阅读源码后解决。特写下此博客,以供参考。具体源码,请仔细阅读:src/caffe/layers/multibox_loss_layer.cpp
输出通道主要涉及默认框产生层的置信输出mbox_conf和位置输出mbox_conf。对应prototxt文件中的应该是以下部分,我们以conv4_3为例讲解。
以上就是conv4_3层的在train.prototxt中的内容。我们主要讲解num_output=16是怎么计算来的,在原始SSD中conv4_3产生的每个特征图的中心点产生4个默认框,具体默认框的产生数量及方式请参看博主博文: 点击打开链接
layer { name: “conv4_3_norm_mbox_loc” type: “Convolution” bottom: “conv4_3_norm” top: “conv4_3_norm_mbox_loc” param { lr_mult: 1 decay_mult: 1 } param { lr_mult: 2 decay_mult: 0 } convolution_param { num_output: 16 pad: 1 kernel_size: 3 stride: 1 weight_filler { type: “xavier” } bias_filler { type: “constant” value: 0 } } }
这四个默认仍框分别对应x1,y1,x2,y2四个点,所以呢在产生位置损失时就会产生四个loc损失,所以一个中心点的所产生的4个默认框就有4*4=16个位置信息需要输出,这就是16的来源。具体在multibox_loss_layer.cpp中的定义是:
-
template <
typename Dtype>
-
void MultiBoxLossLayer<Dtype>::Reshape(
const
vector<Blob<Dtype>*>& bottom,
-
const
vector<Blob<Dtype>*>& top) {
-
LossLayer<Dtype>::Reshape(bottom, top);
-
num_ = bottom[
0]->num();
-
num_priors_ = bottom[
2]->height() /
4;
-
num_gt_ = bottom[
3]->height();
-
CHECK_EQ(bottom[
0]->num(), bottom[
1]->num());
-
CHECK_EQ(num_priors_ * loc_classes_ *
4, bottom[
0]->channels())
//num_priors_就是指每个中心点产生的默认框个数,位置个数计算。
-
<<
"Number of priors must match number of location predictions.";
-
CHECK_EQ(num_priors_ * num_classes_, bottom[
1]->channels())
//置信个数计算。
-
<<
"Number of priors must match number of confidence predictions.";
-
}
其他5个默认框提取特征层同样的计算方法。
对于置信输出通道,其在train.prototxt中的内容为:
置信conf的输出计算方法不同与位置loc,一个中心点会产生一个默认框,但是这个默认框到底是正样本还是负样本,这就涉及到正负的两个置信,如果是负的,那它就是背景。所以这需要看你的类别,在原始SSD检测VOC时,类别数为21,所以这里的num_output应该=(你的类别数+1)*中心点产生的默认框的个数,这里为21*4=64。它在multibox_loss_layer.cpp中的定义也在上面我们复制出来的代码中。
layer { name: "conv4_3_norm_mbox_conf" type: "Convolution" bottom: "conv4_3_norm" top: "conv4_3_norm_mbox_conf" param { lr_mult: 1 decay_mult: 1 } param { lr_mult: 2 decay_mult: 0 } convolution_param { num_output: 64 pad: 1 kernel_size: 3 stride: 1 weight_filler { type: "xavier" } bias_filler { type: "constant" value: 0 } } }
其他5个提取层也是如此计算。
注意的一点是以上是针对conv4_3的计算,而fc7、conv6_2、conv7_2,原始的SSD框架中这三层的每个中心点产生了6个默认框,例如fc7,他在train.prototxt中的内容是:
layer { name: "fc7_mbox_loc" type: "Convolution" bottom: "fc7" top: "fc7_mbox_loc" param { lr_mult: 1 decay_mult: 1 } param { lr_mult: 2 decay_mult: 0 } convolution_param { num_output: 24 pad: 1 kernel_size: 3 stride: 1 weight_filler { type: "xavier" } bias_filler { type: "constant" value: 0 } } }
这里我们只列举了位置loc,计算为4*6=24。置信输出就应该是21*6=126了,大家应该知道怎么计算了吧。
另外我们着重强调一点,关于flip参数的设置,这在我的博文关于ssd_pascal.py解读中有讲解:点击打开链接
他是设置翻转的,我们在关于默认框的产生一文中讲到,ssd默认产生一大一小两个正方形默认框,另外,每设置一个aspect_ratio则增加两个长方形默认框,然而在prior_box_layer.cpp文件中只有产生一个长方形默认框的计算公式如下:
-
// rest of priors
-
for (
int r =
0; r < aspect_ratios_.size(); ++r) {
-
float ar = aspect_ratios_[r];
-
if (
fabs(ar -
1.) <
1e-6) {
-
continue;
-
}
-
box_width = min_size_ *
sqrt(ar);
-
box_height = min_size_ /
sqrt(ar);
-
// xmin
-
top_data[idx++] = (center_x - box_width /
2.) / img_width;
-
// ymin
-
top_data[idx++] = (center_y - box_height /
2.) / img_height;
-
// xmax
-
top_data[idx++] = (center_x + box_width /
2.) / img_width;
-
// ymax
-
top_data[idx++] = (center_y + box_height /
2.) / img_height;
-
}
比如我们在conv4_3计算出的min_size=30,max_size=60,而该层的aspect_ratio=2,那么产生四个默认框,两个正方形,两个长方形。这里只计算了一个长方形框的大小,最后计算的纵横比为1:2,那么2:1的纵横比长方形哪里去了呢?就是靠我们的flip来计算了,当我们设置flip=True时一个aspect_ratio才会产生两个默认框,如果不设置或者为Flase那么就只产生一个长方形默认框。比如conv4_3产生的默认框在train.prototxt中的内容如下:
当然,这里是我们项目中的设置,我们这里只设置了min_size,因为我们只需要一个较小的正方形边框就可以了,并不需要较大的正方形边框,所以我们没有设置max_size参数,故每个默认框产生特征层只生成一个边长为min_size的正方形默认框,剔除边长为sqrt(min_size*max_size)的默认框。这里我们设置了一个aspect_ratio=2,所以每个中心点产生3个默认框。这里flip我吃了很大的亏。一直在报错。
layer { name: "conv4_3_norm_mbox_priorbox" //注意在卷积层conv4_3后有一个norm层做归一化。 type: "PriorBox" bottom: "conv4_3_norm" bottom: "data" top: "conv4_3_norm_mbox_priorbox" prior_box_param { min_size: 32 aspect_ratio: 2 flip: true //注意,如果没有flip参数,则aspect_ratio=2只能产生一个纵横比为1:2的默认框。 clip: false variance: 0.1 variance: 0.1 variance: 0.2 variance: 0.2 step: 8 offset: 0.5 } }
最后以上面的讲解延伸到我们最近研究的小人脸检测建构SFD,他最低层的默认框提取层是conv3_3,而并不是conv4_4,所以其在conv3_3后面也做了norm操作,另外他在conv3_3后面还添加了一个slice层,注意只在conv3_3后面添加,它在train.prototxt中的内容为:
该层的具体作用,博主还没有做仔细的解读,但是其中slice层导致了conv3_3的置信conf输出发生了变化,要在我们前面讲的基础上加上2,即我们在以上代码中所注释的部分。
layer { name: "conv3_3_norm_mbox_conf" type: "Convolution" bottom: "conv3_3_norm" top: "conv3_3_norm_mbox_conf" param { lr_mult: 1 decay_mult: 1 } param { lr_mult: 2 decay_mult: 0 } convolution_param { num_output: 8 //这里=类别数*该层每个中心点产生的默认框个数+2,这个2是由于以下添加的slice层的作用导致的。其他的如4_3、5_3、fc7、6_2、7_2这几层没有加slice层的则不需要+2。 pad: 1 kernel_size: 3 stride: 1 weight_filler { type: "xavier" } bias_filler { type: "constant" value: 0 } } } layer { name: "conv3_3_norm_mbox_conf_slice" type: "Slice" bottom: "conv3_3_norm_mbox_conf" top: "conv3_3_norm_mbox_conf1" top: "conv3_3_norm_mbox_conf2" top: "conv3_3_norm_mbox_conf3" top: "conv3_3_norm_mbox_conf4" slice_param { axis: 1 slice_point: 1 slice_point: 2 slice_point: 3 } } layer { name: "conv3_3_norm_mbox_conf_maxout" type: "Eltwise" bottom: "conv3_3_norm_mbox_conf1" bottom: "conv3_3_norm_mbox_conf2" bottom: "conv3_3_norm_mbox_conf3" top: "conv3_3_norm_mbox_conf_maxout" eltwise_param { operation: MAX } } layer { name: "conv3_3_norm_mbox_conf_out" type: "Concat" bottom: "conv3_3_norm_mbox_conf_maxout" bottom: "conv3_3_norm_mbox_conf4" top: "conv3_3_norm_mbox_conf_out" concat_param { axis: 1 } }
最后给大家附上有置信conf和位置loc输出通道设置错误引起的error:
1.由置信引起的:
Check failed: num_priors_ * num_classes_ == bottom[1]->channels() (132174 vs. 99406) Number of priors must match number of confidence predictions.
其中括号里的数字不必去纠结,这和你的数据有关。
2.由位置引起的:
Check failed: num_priors_ * num_loc_classes_ * 4 == bottom[0]->channels() (264348 vs. 88116) Number of priors must match number of location predictions.
博主水平有限,如有错误请多多指正,转载请注明地址。谢谢!