轉載:Network In Network學習筆記

轉載原文1:http://blog.csdn.net/hjimce/article/details/50458190c++

轉載原文2:http://blog.csdn.net/mounty_fsc/article/details/51746111git

1、相關理論github

本篇博文主要講解2014年ICLR的一篇很是牛逼的paper:《Network In Network》,過去一年已經有了好幾百的引用量,這篇paper改進了傳統的CNN網絡,採用了少許的參數就鬆鬆擊敗了Alexnet網絡,Alexnet網絡參數大小是230M,採用這篇paper的算法才29M,減少了將近10倍啊。這篇paper提出的網絡結構,是對傳統CNN網絡的一種改進(這種文獻少之又少,因此感受頗有必要學習)。算法

傳統的卷積神經網絡通常來講是由:線性卷積層、池化層、全鏈接層堆疊起來的網絡。卷積層經過線性濾波器進行線性卷積運算,而後在接個非線性激活函數,最終生成特徵圖。以Relu激活函數爲例,特徵圖的計算公式爲:網絡

 

其中(i,j)表示圖片像素點的位置索引,xij表示咱們卷積窗口中的圖片塊,k則表示咱們要提取的特徵圖的索引。架構

通常來講,若是咱們要提取的一些潛在的特徵是線性可分的話,那麼對於線性的卷積運算來講這是足夠了。然而通常來講咱們所要提取的特徵通常是高度非線性的。在傳統的CNN中,也許咱們能夠用超完備的濾波器,來提取各類潛在的特徵。好比咱們要提取某個特徵,因而我就用了一大堆的濾波器,把全部可能的提取出來,這樣就能夠把我想要提取的特徵也覆蓋到,然而這樣存在一個缺點,那就是網絡太恐怖了,參數太多了。maven

 

這篇文章有兩個很重要的觀點:ide

  1. 1×1卷積的使用 
    文中提出使用mlpconv網絡層替代傳統的convolution層。mlp層其實是卷積加傳統的mlp(多層感知器),由於convolution是線性的,而mlp是非線性的,後者可以獲得更高的抽象,泛化能力更強。在跨通道(cross channel,cross feature map)狀況下,mlpconv等價於卷積層+1×1卷積層,因此此時mlpconv層也叫cccp層(cascaded cross channel parametric pooling)。函數

  2. CNN網絡中不使用FC層(全鏈接層) 
    文中提出使用Global Average Pooling取代最後的全鏈接層,由於全鏈接層參數多且易過擬合。作法即移除全鏈接層,在最後一層(文中使用mlpconv)層,後面加一層Average Pooling層。學習

以上兩點,之因此重要,在於,其在較大程度上減小了參數個數,確可以獲得一個較好的結果。而參數規模的減小,不只有利用網絡層數的加深(因爲參數過多,網絡規模過大,GPU顯存等不夠用而限制網絡層數的增長,從而限制模型的泛化能力),並且在訓練時間上也獲得改進。

2、MLP卷積層(文獻創新點1)

這個是文獻的大創新點,也就是提出了mlpconv層。Mlpconv層能夠當作是每一個卷積的局部感覺野中還包含了一個微型的多層網絡。其實在之前的卷積層中,咱們局部感覺野窗口的運算,能夠理解爲一個單層的網絡,以下圖所示:

線性卷積層

CNN層的計算公式以下:

 

然而如今不一樣了,咱們要採用多層的網絡,提升非線性,

單通道mlpconv層 

跨通道mlpconv層(cccp層) 

    • 由圖可知,mlpconv=convolution+mlp(圖中爲2層的mlp)。

    • 在caffe中實現上,mlpconv=convolution+1×1convolution+1×1convolution(2層的mlp)

因而mlpconv層的網絡結構圖以下:

 

Mlpconv層

從上面的圖能夠看到,說的簡單一點呢,利用多層mlp的微型網絡,對每一個局部感覺野的神經元進行更加複雜的運算,而之前的卷積層,局部感覺野的運算僅僅只是一個單層的神經網絡罷了。對於mlpconv層每張特徵圖的計算公式以下:

通常來講mlp是一個三層的網絡結構。
下面是一個單層的mlpconv網絡的caffe網絡結構文件,源碼來自於: https://gist.github.com/mavenlin/d802a5849de39225bcc6 :
layers {
  bottom: "data"
  top: "conv1"
  name: "conv1"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 96
    kernel_size: 11
    stride: 4
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.01
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "conv1"
  top: "conv1"
  name: "relu0"
  type: RELU
}
layers {
  bottom: "conv1"
  top: "cccp1"
  name: "cccp1"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 96
    kernel_size: 1
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.05
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "cccp1"
  top: "cccp1"
  name: "relu1"
  type: RELU
}
layers {
  bottom: "cccp1"
  top: "cccp2"
  name: "cccp2"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 96
    kernel_size: 1
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.05
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "cccp2"
  top: "cccp2"
  name: "relu2"
  type: RELU
}
3、全局均值池化(文獻創新點2)

傳統的卷積神經網絡卷積運算通常是出如今低層網絡。對於分類問題,最後一個卷積層的特徵圖經過量化而後與全鏈接層鏈接,最後在接一個softmax邏輯迴歸分類層。這種網絡結構,使得卷積層和傳統的神經網絡層鏈接在一塊兒。咱們能夠把卷積層看作是特徵提取器,而後獲得的特徵再用傳統的神經網絡進行分類。

然而,全鏈接層由於參數個數太多,每每容易出現過擬合的現象,致使網絡的泛化能力不盡人意。因而Hinton採用了Dropout的方法,來提升網絡的泛化能力。

本文提出採用全局均值池化的方法,替代傳統CNN中的全鏈接層。與傳統的全鏈接層不一樣,咱們對每一個特徵圖一整張圖片進行全局均值池化,這樣每張特徵圖均可以獲得一個輸出。這樣採用均值池化,連參數都省了,能夠大大減少網絡,避免過擬合,另外一方面它有一個特色,每張特徵圖至關於一個輸出特徵,而後這個特徵就表示了咱們輸出類的特徵。這樣若是咱們在作1000個分類任務的時候,咱們網絡在設計的時候,最後一層的特徵圖個數就要選擇1000,下面是《Network In Network》網絡的源碼,倒數一層的網絡相關參數:

layers {
  bottom: "cccp7"
  top: "cccp8"
  name: "cccp8-1024"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    #num_output: 1000
    num_output: 4
    kernel_size: 1
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.01
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}

全局均值池化層的相關參數以下:

layers {
  bottom: "cccp8"
  top: "pool4"
  name: "pool4"
  type: POOLING
  pooling_param {
    pool: AVE
    kernel_size: 6
    stride: 1
  }
}

由於在Alexnet網絡中,最後一個卷積層輸出的特徵圖大小恰好是6*6,因此咱們pooling的大小選擇6,方法選擇:AVE。

4、整體網絡架構

根據上面的做者對傳統CNN的兩個改進,利用其進行1000物體分類問題,因而做者最後設計了一個:4層的NIN+全局均值池化,網絡以下:

Caffe中4層網絡示意圖(ImageNet) 

說明:

1.方框爲網絡層,橢圓爲blob 
2.黃色pool4爲Average Pooling

caffe網絡數據數據以下(crop size=224)

Layer channels Filter size Filter stride Padding size Input size
conv1 96 11 4 - 224×224
cccp1 96 1 1 - 54×54
cccp2 96 1 1 - 54×54
pool1 96 3 2 - 54×54
conv2 256 5 1 2 27×27
cccp3 256 1 1 - 27×27
cccp4 256 1 1 - 27×27
pool2 256 3 2 - 27×27
conv3 384 3 1 1 13×13
cccp5 384 1 1 - 13×13
cccp6 384 1 1 - 13×13
pool3 384 3 2 - 13×13
conv4-1024 1024 3 1 1 6×6
cccp7-1024 1024 1 1 - 6×6
cccp8-1000 1000 1 1 - 6×6
pool4-ave 1000 6 1 - 6×6
accuracy 1000 - - - 1×1
    • 對於crop size = 227,則input size的變化爲227, 55, 27, 13, 6, 1。

 

我的總結:我的感受這篇文獻頗有價值,實現方式也很簡單,一開始我還覺得須要caffe的c++源碼來實現NIN網絡,結果發現實現NIN的源碼實現方式其實就是一個1*1的卷積核,實現卷積運算,因此實現起來至關容易,不須要本身寫源碼,只須要簡簡單單的把卷積核的大小變一下,而後最後一層的全鏈接層直接用avg pooling替換一下就ok了。我的評價:網絡淺顯易懂,簡單實現,卻能夠改進原來的網絡,提升精度,減少模型大小,因此是一篇很值得學習的文獻。後續即將講解另外幾篇2015年,也是對CNN網絡結構改進的牛逼文獻:《Spatial Transformer Networks》、《Striving For Simplicity:The All Convolutional Net》、《Stacked What-Where Auto-encoders》,敬請期待,畢竟這樣的文章勇於挑戰傳統的CNN結構,對其不知作出改進,因此咱們須要一篇一篇的學。

參考文獻:

一、《Network In Network》

二、https://github.com/BVLC/caffe/wiki/Model-Zoo

三、https://gist.github.com/mavenlin/d802a5849de39225bcc6 

四、《Maxout Networks》

 

整個網絡結構:

name: "nin_work"
layers {
  top: "data"
  top: "label"
  name: "data"
  type: DATA
  data_param {
    source: "./data/nintrain"
    backend: LMDB
    batch_size: 64
  }
  transform_param {
    crop_size: 224
    mirror: true
    mean_file: "./data/net_mean.binaryproto"
  }
  include: { phase: TRAIN }
}
layers {
  top: "data"
  top: "label"
  name: "data"
  type: DATA
  data_param {
    source: "./data/nintest"
    backend: LMDB
    batch_size: 89
  }
  transform_param {
    crop_size: 224
    mirror: false
    mean_file: "./data/net_mean.binaryproto"
  }
  include: { phase: TEST }
}
layers {
  bottom: "data"
  top: "conv1"
  name: "conv1"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 96
    kernel_size: 11
    stride: 4
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.01
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "conv1"
  top: "conv1"
  name: "relu0"
  type: RELU
}
layers {
  bottom: "conv1"
  top: "cccp1"
  name: "cccp1"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 96
    kernel_size: 1
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.05
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "cccp1"
  top: "cccp1"
  name: "relu1"
  type: RELU
}
layers {
  bottom: "cccp1"
  top: "cccp2"
  name: "cccp2"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 96
    kernel_size: 1
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.05
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "cccp2"
  top: "cccp2"
  name: "relu2"
  type: RELU
}
layers {
  bottom: "cccp2"
  top: "pool0"
  name: "pool0"
  type: POOLING
  pooling_param {
    pool: MAX
    kernel_size: 3
    stride: 2
  }
}
layers {
  bottom: "pool0"
  top: "conv2"
  name: "conv2"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 256
    pad: 2
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.05
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "conv2"
  top: "conv2"
  name: "relu3"
  type: RELU
}
layers {
  bottom: "conv2"
  top: "cccp3"
  name: "cccp3"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 256
    kernel_size: 1
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.05
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "cccp3"
  top: "cccp3"
  name: "relu5"
  type: RELU
}
layers {
  bottom: "cccp3"
  top: "cccp4"
  name: "cccp4"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 256
    kernel_size: 1
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.05
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "cccp4"
  top: "cccp4"
  name: "relu6"
  type: RELU
}
layers {
  bottom: "cccp4"
  top: "pool2"
  name: "pool2"
  type: POOLING
  pooling_param {
    pool: MAX
    kernel_size: 3
    stride: 2
  }
}
layers {
  bottom: "pool2"
  top: "conv3"
  name: "conv3"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 384
    pad: 1
    kernel_size: 3
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.01
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "conv3"
  top: "conv3"
  name: "relu7"
  type: RELU
}
layers {
  bottom: "conv3"
  top: "cccp5"
  name: "cccp5"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 384
    kernel_size: 1
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.05
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "cccp5"
  top: "cccp5"
  name: "relu8"
  type: RELU
}
layers {
  bottom: "cccp5"
  top: "cccp6"
  name: "cccp6"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 384
    kernel_size: 1
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.05
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "cccp6"
  top: "cccp6"
  name: "relu9"
  type: RELU
}
layers {
  bottom: "cccp6"
  top: "pool3"
  name: "pool3"
  type: POOLING
  pooling_param {
    pool: MAX
    kernel_size: 3
    stride: 2
  }
}
layers {
  bottom: "pool3"
  top: "pool3"
  name: "drop"
  type: DROPOUT
  dropout_param {
    dropout_ratio: 0.5
  }
}
layers {
  bottom: "pool3"
  top: "conv4"
  name: "conv4-1024"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 1024
    pad: 1
    kernel_size: 3
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.05
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "conv4"
  top: "conv4"
  name: "relu10"
  type: RELU
}
layers {
  bottom: "conv4"
  top: "cccp7"
  name: "cccp7-1024"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 1024
    kernel_size: 1
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.05
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "cccp7"
  top: "cccp7"
  name: "relu11"
  type: RELU
}
layers {
  bottom: "cccp7"
  top: "cccp8"
  name: "cccp8-1024"
  type: CONVOLUTION
  blobs_lr: 1
  blobs_lr: 2
  weight_decay: 1
  weight_decay: 0
  convolution_param {
    num_output: 1000
    kernel_size: 1
    stride: 1
    weight_filler {
      type: "gaussian"
      mean: 0
      std: 0.01
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layers {
  bottom: "cccp8"
  top: "cccp8"
  name: "relu12"
  type: RELU
}
layers {
  bottom: "cccp8"
  top: "pool4"
  name: "pool4"
  type: POOLING
  pooling_param {
    pool: AVE
    kernel_size: 6
    stride: 1
  }
}
layers {
  name: "accuracy"
  type: ACCURACY
  bottom: "pool4"
  bottom: "label"
  top: "accuracy"
  include: { phase: TEST }
}
layers {
  bottom: "pool4"
  bottom: "label"
  name: "loss"
  type: SOFTMAX_LOSS
  include: { phase: TRAIN }
}

1×1卷積的做用

如下內容摘抄自:http://www.caffecn.cn/?/question/136

問:發現不少網絡使用了1X1卷積核,這能起到什麼做用呢?另外我一直以爲,1X1卷積核就是對輸入的一個比例縮放,由於1X1卷積核只有一個參數,這個核在輸入上滑動,就至關於給輸入數據乘以一個係數。不知道我理解的是否正確

答1: 
對於單通道的feature map和單個卷積核之間的卷積來講,題主的理解是對的,CNN裏的卷積大都是多通道的feature map和多通道的卷積核之間的操做(輸入的多通道的feature map和一組卷積核作卷積求和獲得一個輸出的feature map),若是使用1x1的卷積核,這個操做實現的就是多個feature map的線性組合,能夠實現feature map在通道個數上的變化。接在普通的卷積層的後面,配合激活函數,就能夠實現network in network的結構了(本內容做者僅受權給CaffeCN社區(caffecn.cn)使用,如需轉載請附上內容來源說明。)

答2: 
我來講說個人理解,我認爲1×1的卷積大概有兩個方面的做用吧: 
1. 實現跨通道的交互和信息整合 
2. 進行卷積核通道數的降維和升維

下面詳細解釋一下: 
1. 這一點孫琳鈞童鞋講的很清楚。1×1的卷積層(可能)引發人們的重視是在NIN的結構中,論文中林敏師兄的想法是利用MLP代替傳統的線性卷積核,從而提升網絡的表達能力。文中同時利用了跨通道pooling的角度解釋,認爲文中提出的MLP其實等價於在傳統卷積核後面接cccp層,從而實現多個feature map的線性組合,實現跨通道的信息整合。而cccp層是等價於1×1卷積的,所以細看NIN的caffe實現,就是在每一個傳統卷積層後面接了兩個cccp層(其實就是接了兩個1×1的卷積層)。 
2. 進行降維和升維引發人們重視的(可能)是在GoogLeNet裏。對於每個Inception模塊(以下圖),原始模塊是左圖,右圖中是加入了1×1卷積進行降維的。雖然左圖的卷積核都比較小,可是當輸入和輸出的通道數很大時,乘起來也會使得卷積核參數變的很大,而右圖加入1×1卷積後能夠下降輸入的通道數,卷積核參數、運算複雜度也就跟着降下來了。以GoogLeNet的3a模塊爲例,輸入的feature map是28×28×192,3a模塊中1×1卷積通道爲64,3×3卷積通道爲128,5×5卷積通道爲32,若是是左圖結構,那麼卷積核參數爲1×1×192×64+3×3×192×128+5×5×192×32,而右圖對3×3和5×5卷積層前分別加入了通道數爲96和16的1×1卷積層,這樣卷積核參數就變成了1×1×192×64+(1×1×192×96+3×3×96×128)+(1×1×192×16+5×5×16×32),參數大約減小到原來的三分之一。同時在並行pooling層後面加入1×1卷積層後也能夠下降輸出的feature map數量,左圖pooling後feature map是不變的,再加捲積層獲得的feature map,會使輸出的feature map擴大到416,若是每一個模塊都這樣,網絡的輸出會愈來愈大。而右圖在pooling後面加了通道爲32的1×1卷積,使得輸出的feature map數降到了256。GoogLeNet利用1×1的卷積降維後,獲得了更爲緊湊的網絡結構,雖然總共有22層,可是參數數量卻只是8層的AlexNet的十二分之一(固然也有很大一部分緣由是去掉了全鏈接層)。 

最近大熱的MSRA的ResNet一樣也利用了1×1卷積,而且是在3×3卷積層的先後都使用了,不只進行了降維,還進行了升維,使得卷積層的輸入和輸出的通道數都減少,參數數量進一步減小,以下圖的結構。(否則真不敢想象152層的網絡要怎麼跑起來TAT) 

[1]. https://gist.github.com/mavenlin/d802a5849de39225bcc6 
[2]. http://www.caffecn.cn/?/question/136

相關文章
相關標籤/搜索