天天看點

hole algorithm增強卷積的視野

好久沒搬磚了,剛好元旦放假,跑實驗的同時,滿足一下自己搬磚的欲望^_^。

尊重原創,轉載請注明:http://blog.csdn.net/tangwei2014

deeplab發表在ICLR 2015上。論文下載下傳位址:Semantic Image Segmentation with Deep Convolutional Nets and Fully Connected CRFS.

  1. deeplab方法概述 

    deeplab方法分為兩步走,第一步仍然采用了FCN得到 coarse score map并插值到原圖像大小,然後第二步借用fully connected CRF對從FCN得到的分割結果進行細節上的refine。(有關FCN的内容介紹,可以參考我的前面得一篇部落格:http://blog.csdn.net/tangwei2014/article/details/46882257) 

    下面這張圖很清楚地展示了整個結構: 

    hole algorithm增強卷積的視野
    然後這張圖展示了CRF處理前後的效果對比,可以看出用了CRF以後,細節确實改善了很多: 
    hole algorithm增強卷積的視野
  2. deeplab對FCN更加優雅的處理方式 

    在第一步中,deeplab仍然采用了FCN來得到score map,并且也是在VGG網絡上進行fine-tuning。但是在得到score map的處理方式上,要比原FCN處理的優雅很多。 

    還記得CVPR 2015的FCN中是怎麼得到一個更加dense的score map的嗎? 是一張500x500的輸入圖像,直接在第一個卷積層上conv1_1來了一個100的大padding。最終在fc7層勉強得到一個16x16的score map。雖然處理上稍顯粗糙,但是畢竟人家是第一次将圖像分割在CNN上搞成end-to-end,并且在當時performance是state-of-the-art,也很了解。 

    deeplab摒棄了這種做法,取而代之的是對VGG的網絡結構上做了小改動:将VGG網絡的pool4和pool5層的stride由原來的2改為了1。就是這樣一個改動,使得vgg網絡總的stride由原來的32變成8,進而使得在輸入圖像為514x514,正常的padding時,fc7能得到67x67的score map, 要比FCN确實要dense很多很多。 

    但是這種改變網絡結果的做法也帶來了一個問題: stride改變以後,如果想繼續利用vgg model進行fine tuning,會導緻後面filter作用的區域發生改變,換句話說就是感受野發生變化。這個問題在下圖(a) (b)中通過花括号展現出來了:

  3. Hole算法 

    于是乎,作者想出了一招,來解決兩個看似有點沖突的問題: 

    既想利用已經訓練好的模型進行fine-tuning,又想改變網絡結構得到更加dense的score map. 

    這個解決辦法就是采用Hole算法。如下圖(a) (b)所示,在以往的卷積或者pooling中,一個filter中相鄰的權重作用在feature map上的位置都是實體上連續的。如下圖(c)所示,為了保證感受野不發生變化,某一層的stride由2變為1以後,後面的層需要采用hole算法,具體來講就是将連續的連接配接關系是根據hole size大小變成skip連接配接的(圖(c)為了顯示友善直接畫在本層上了)。不要被(c)中的padding為2吓着了,其實2個padding不會同時和一個filter相連。 

    pool4的stride由2變為1,則緊接着的conv5_1, conv5_2和conv5_3中hole size為2。接着pool5由2變為1, 則後面的fc6中hole size為4。 

    hole algorithm增強卷積的視野
  4. 代碼

主要是im2col(前傳)和col2im(反傳)中做了改動 (增加了hole_w, hole_h),這裡隻貼cpu的用于了解:

//forward
template <typename Dtype>
void im2col_cpu(const Dtype* data_im, 
    const int num, const int channels, const int height, const int width,
    const int kernel_h, const int kernel_w, const int pad_h, const int pad_w,
    const int stride_h, const int stride_w, const int hole_h, const int hole_w,
    Dtype* data_col) {
  // effective kernel if we expand the holes (trous)
  const int kernel_h_eff = kernel_h + (kernel_h - ) * (hole_h - );
  const int kernel_w_eff = kernel_w + (kernel_w - ) * (hole_w - );
  int height_col = (height +  * pad_h - kernel_h_eff) / stride_h + ;
  int width_col = (width +  * pad_w - kernel_w_eff) / stride_w + ;
  int channels_col = channels * kernel_h * kernel_w;
  for (int n = ; n < num; ++n) {
    for (int c = ; c < channels_col; ++c) {
      int w_offset = (c % kernel_w)  * hole_w;
      int h_offset = ((c / kernel_w) % kernel_h) * hole_h;
      int c_im = c / kernel_w / kernel_h;
      for (int h = ; h < height_col; ++h) {
        const int h_im = h * stride_h + h_offset - pad_h;
        for (int w = ; w < width_col; ++w) {
          const int w_im = w * stride_w + w_offset - pad_w;
          data_col[((n * channels_col + c) * height_col + h) * width_col + w] =
            (h_im >=  && h_im < height && w_im >=  && w_im < width) ?
            data_im[((n * channels + c_im) * height + h_im) * width + w_im] : 
            ; // zero-pad
        } //width_col
      } //height_col
    } //channels_col
  } //num
}

//backward
template <typename Dtype>
void col2im_cpu(const Dtype* data_col,
    const int num, const int channels, const int height, const int width,
    const int kernel_h, const int kernel_w, const int pad_h, const int pad_w,
    const int stride_h, const int stride_w, const int hole_h, const int hole_w,
    Dtype* data_im) {
  caffe_set(num * channels * height * width, Dtype(), data_im);
  const int kernel_h_eff = kernel_h + (kernel_h - ) * (hole_h - );
  const int kernel_w_eff = kernel_w + (kernel_w - ) * (hole_w - );
  int height_col = (height +  * pad_h - kernel_h_eff) / stride_h + ;
  int width_col = (width +  * pad_w - kernel_w_eff) / stride_w + ;
  int channels_col = channels * kernel_h * kernel_w;
  for (int n = ; n < num; ++n) {
    for (int c = ; c < channels_col; ++c) {
      int w_offset = (c % kernel_w)  * hole_w;
      int h_offset = ((c / kernel_w) % kernel_h) * hole_h;
      int c_im = c / kernel_w / kernel_h;
      for (int h = ; h < height_col; ++h) {
    const int h_im = h * stride_h + h_offset - pad_h;
        for (int w = ; w < width_col; ++w) {
          const int w_im = w * stride_w + w_offset - pad_w;
          if (h_im >=  && h_im < height && w_im >=  && w_im < width) {
            data_im[((n * channels + c_im) * height + h_im) * width + w_im] += 
              data_col[((n * channels_col + c) * height_col + h) * width_col + w];
          }
        }
      }
    }
  }
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

繼續閱讀