关于SSD训练时默认框输出层输出通道num_output的计算, 关于SSD训练时默认框输出层输出通道num_output的计算

关于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为例讲解。

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
    }
  }
}
以上就是conv4_3层的在train.prototxt中的内容。我们主要讲解num_output=16是怎么计算来的,在原始SSD中conv4_3产生的每个特征图的中心点产生4个默认框,具体默认框的产生数量及方式请参看博主博文: 点击打开链接

这四个默认仍框分别对应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中的内容为:

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
    }
  }
}
置信conf的输出计算方法不同与位置loc,一个中心点会产生一个默认框,但是这个默认框到底是正样本还是负样本,这就涉及到正负的两个置信,如果是负的,那它就是背景。所以这需要看你的类别,在原始SSD检测VOC时,类别数为21,所以这里的num_output应该=(你的类别数+1)*中心点产生的默认框的个数,这里为21*4=64。它在multibox_loss_layer.cpp中的定义也在上面我们复制出来的代码中。

其他5个提取层也是如此计算。

注意的一点是以上是针对conv4_3的计算,而fc7、conv6_2、conv7_2,原始的SSD框架中这三层的每个中心点产生了6个默认框,例如fc7,他在train.prototxt中的内容是:

扫描二维码关注公众号,回复: 4265829 查看本文章

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中的内容如下:

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
  }
}
当然,这里是我们项目中的设置,我们这里只设置了min_size,因为我们只需要一个较小的正方形边框就可以了,并不需要较大的正方形边框,所以我们没有设置max_size参数,故每个默认框产生特征层只生成一个边长为min_size的正方形默认框,剔除边长为sqrt(min_size*max_size)的默认框。这里我们设置了一个aspect_ratio=2,所以每个中心点产生3个默认框。这里flip我吃了很大的亏。一直在报错。


最后以上面的讲解延伸到我们最近研究的小人脸检测建构SFD,他最低层的默认框提取层是conv3_3,而并不是conv4_4,所以其在conv3_3后面也做了norm操作,另外他在conv3_3后面还添加了一个slice层,注意只在conv3_3后面添加,它在train.prototxt中的内容为:

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
  }
}
该层的具体作用,博主还没有做仔细的解读,但是其中slice层导致了conv3_3的置信conf输出发生了变化,要在我们前面讲的基础上加上2,即我们在以上代码中所注释的部分。

最后给大家附上有置信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.

博主水平有限,如有错误请多多指正,转载请注明地址。谢谢!

猜你喜欢

转载自blog.csdn.net/m0_37192554/article/details/84143485