1. 程式人生 > >關於SSD訓練時預設框輸出層輸出通道num_output的計算, 關於SSD訓練時預設框輸出層輸出通道num_output的計算

關於SSD訓練時預設框輸出層輸出通道num_output的計算, 關於SSD訓練時預設框輸出層輸出通道num_output的計算

關於SSD訓練時預設框輸出層輸出通道num_output的計算

2018年02月03日 21:00:40 閱讀數:2155

申明,本博文是為解決以下兩個問題而撰寫。

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中的內容是:

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.

博主水平有限,如有錯誤請多多指正,轉載請註明地址。謝謝!