【深度學習系列】用PaddlePaddle和Tensorflow實現經典CNN網絡GoogLeNet

  前面講了LeNet、AlexNet和Vgg,這周來說講GoogLeNet。GoogLeNet是由google的Christian Szegedy等人在2014年的論文《Going Deeper with Convolutions》提出,其最大的亮點是提出一種叫Inception的結構,以此爲基礎構建GoogLeNet,並在當年的ImageNet分類和檢測任務中得到第一,ps:GoogLeNet的取名是爲了向YannLeCun的LeNet系列致敬。python

(本系列全部代碼均在github:https://github.com/huxiaoman7/PaddlePaddle_code)git


關於深度網絡的一些思考github

  在本系列最開始的幾篇文章咱們講到了卷積神經網絡,設計的網絡結構也很是簡單,屬於淺層神經網絡,如三層的卷積神經網絡等,可是在層數比較少的時候,有時候效果每每並無那麼好,在實驗過程當中發現,當咱們嘗試增長網絡的層數,或者增長每一層網絡的神經元個數的時候,對準確率有必定的提高,簡單的說就是增長網絡的深度與寬度,但這樣作有兩個明顯的缺點:api

  •  更深更寬的網絡意味着更多的參數,提升了模型的複雜度,從而大大增長過擬合的風險,尤爲在訓練數據不是那麼多或者某個label訓練數據不足的狀況下更容易發生;
  • 增長計算資源的消耗,實際狀況下,不論是由於數據稀疏仍是擴充的網絡結構利用不充分(好比不少權重接近0),都會致使大量計算的浪費。

  解決以上兩個問題的基本方法是將全鏈接或卷積鏈接改成稀疏鏈接。無論從生物的角度仍是機器學習的角度,稀疏性都有良好的表現,回想一下在講AlexNet這一節提出的Dropout網絡以及ReLU激活函數,其本質就是利用稀疏性提升模型泛化性(但須要計算的參數沒變少)。 
  簡單解釋下稀疏性,當整個特徵空間是非線性甚至不連續時:緩存

  •  學好局部空間的特徵集更能提高性能,相似於Maxout網絡中使用多個局部線性函數的組合來擬合非線性函數的思想;
  • 假設整個特徵空間由N個不連續局部特徵空間集合組成,任意一個樣本會被映射到這N個空間中並激活/不激活相應特徵維度,若是用C1表示某類樣本被激活的特徵維度集合,用C2表示另外一類樣本的特徵維度集合,當數據量不夠大時,要想增長特徵區分度並很好的區分兩類樣本,就要下降C1和C2的重合度(好比可用Jaccard距離衡量),即縮小C1和C2的大小,意味着相應的特徵維度集會變稀疏。

  不過尷尬的是,如今的計算機體系結構更善於稠密數據的計算,而在非均勻分佈的稀疏數據上的計算效率極差,好比稀疏性會致使的緩存miss率極高,因而須要一種方法既能發揮稀疏網絡的優點又能保證計算效率。好在前人作了大量實驗(如《On Two-Dimensional Sparse Matrix Partitioning: Models, Methods, and a Recipe》),發現對稀疏矩陣作聚類獲得相對稠密的子矩陣能夠大幅提升稀疏矩陣乘法性能,借鑑這個思想,做者提出Inception的結構。網絡

 

 圖1 Inception結構機器學習

  • 把不一樣大小卷積核抽象獲得的特徵空間看作子特徵空間,每一個子特徵空間都是稀疏的,把這些不一樣尺度特徵作融合,至關於獲得一個相對稠密的空間;
  • 採用1×一、3×三、5×5卷積核(不是必須的,也能夠是其餘大小),stride取1,利用padding能夠方便的作輸出特徵維度對齊;
  • 大量事實代表pooling層能有效提升卷積網絡的效果,因此加了一條max pooling路徑;
  • 這個結構符合直觀理解,視覺信息經過不一樣尺度的變換被聚合起來做爲下一階段的特徵,好比:人的高矮、胖瘦、青老信息被聚合後作下一步判斷。

這個網絡的最大問題是5×5卷積帶來了巨大計算負擔,例如,假設上層輸入爲:28×28×192:ide

  • 直接通過96個5×5卷積層(stride=1,padding=2)後,輸出爲:28×28×96,卷積層參數量爲:192×5×5×96=460800;
  • 借鑑NIN網絡(Network in Network,後續會講),在5×5卷積前使用32個1×1卷積核作維度縮減,變成28×28×32,以後通過96個5×5卷積層(stride=1,padding=2)後,輸出爲:28×28×96,但全部卷積層的參數量爲:192×1×1×32+32×5×5×96=82944,可見整個參數量是原來的1/5.5,且效果上沒有多少損失。 
    新網絡結構爲

 

 圖2 新Inception結構函數

 

 


 GoogLeNet網絡結構性能

  利用上述Inception模塊構建GoogLeNet,實驗代表Inception模塊出如今高層特徵抽象時會更加有效(我理解因爲其結構特色,更適合提取高階特徵,讓它提取低階特徵會致使特徵信息丟失),因此在低層依然使用傳統卷積層。整個網路結構以下:

 圖3 GoogLeNet網絡結構

 

 圖4 GoogLeNet詳細網絡結構示意圖

網絡說明:

  • 全部卷積層均使用ReLU激活函數,包括作了1×1卷積降維後的激活;
  • 移除全鏈接層,像NIN同樣使用Global Average Pooling,使得Top 1準確率提升0.6%,但因爲GAP與類別數目有關係,爲了方便你們作模型fine-tuning,最後加了一個全鏈接層;
  • 與前面的ResNet相似,實驗觀察到,相對淺層的神經網絡層對模型效果有較大的貢獻,訓練階段經過對Inception(4a、4d)增長兩個額外的分類器來加強反向傳播時的梯度信號,但最重要的仍是正則化做用,這一點在GoogLeNet v3中獲得實驗證明,並間接證明了GoogLeNet V2中BN的正則化做用,這兩個分類器的loss會以0.3的權重加在總體loss上,在模型inference階段,這兩個分類器會被去掉;
  • 用於降維的1×1卷積核個數爲128個;
  • 全鏈接層使用1024個神經元;
  • 使用丟棄機率爲0.7的Dropout層;

網絡結構詳細說明: 

  輸入數據爲224×224×3的RGB圖像,圖中"S"表明作same-padding,"V"表明不作。

  • C1卷積層:64個7×7卷積核(stride=2,padding=3),輸出爲:112×112×64;
  • P1抽樣層:64個3×3卷積核(stride=2),輸出爲56×56×64,其中:56=(112-3+1)/2+1
  • C2卷積層:192個3×3卷積核(stride=1,padding=1),輸出爲:56×56×192;
  • P2抽樣層:192個3×3卷積核(stride=2),輸出爲28×28×192,其中:28=(56-3+1)/2+1,接着數據被分出4個分支,進入Inception (3a)
  • Inception (3a):由4部分組成 
    • 64個1×1的卷積核,輸出爲28×28×64;
    • 96個1×1的卷積核作降維,輸出爲28×28×96,以後128個3×3卷積核(stride=1,padding=1),輸出爲:28×28×128
    • 16個1×1的卷積核作降維,輸出爲28×28×16,以後32個5×5卷積核(stride=1,padding=2),輸出爲:28×28×32
    • 192個3×3卷積核(stride=1,padding=1),輸出爲28×28×192,進行32個1×1卷積核,輸出爲:28×28×32 
      最後對4個分支的輸出作「深度」方向組合,獲得輸出28×28×256,接着數據被分出4個分支,進入Inception (3b);
  • Inception (3b):由4部分組成 
    • 128個1×1的卷積核,輸出爲28×28×128;
    • 128個1×1的卷積核作降維,輸出爲28×28×128,進行192個3×3卷積核(stride=1,padding=1),輸出爲:28×28×192
    • 32個1×1的卷積核作降維,輸出爲28×28×32,進行96個5×5卷積核(stride=1,padding=2),輸出爲:28×28×96
    • 256個3×3卷積核(stride=1,padding=1),輸出爲28×28×256,進行64個1×1卷積核,輸出爲:28×28×64 
      最後對4個分支的輸出作「深度」方向組合,獲得輸出28×28×480; 
      後面結構以此類推。

 


用PaddlePaddle實現GoogLeNet

  1.網絡結構 googlenet.py

  在PaddlePaddle的models下面,有關於GoogLeNet的實現代碼,你們能夠直接學習拿來跑一下:

  1 import paddle.v2 as paddle
  2 
  3 __all__ = ['googlenet']
  4 
  5 
  6 def inception(name, input, channels, filter1, filter3R, filter3, filter5R,
  7               filter5, proj):
  8     cov1 = paddle.layer.img_conv(
  9         name=name + '_1',
 10         input=input,
 11         filter_size=1,
 12         num_channels=channels,
 13         num_filters=filter1,
 14         stride=1,
 15         padding=0)
 16 
 17     cov3r = paddle.layer.img_conv(
 18         name=name + '_3r',
 19         input=input,
 20         filter_size=1,
 21         num_channels=channels,
 22         num_filters=filter3R,
 23         stride=1,
 24         padding=0)
 25     cov3 = paddle.layer.img_conv(
 26         name=name + '_3',
 27         input=cov3r,
 28         filter_size=3,
 29         num_filters=filter3,
 30         stride=1,
 31         padding=1)
 32 
 33     cov5r = paddle.layer.img_conv(
 34         name=name + '_5r',
 35         input=input,
 36         filter_size=1,
 37         num_channels=channels,
 38         num_filters=filter5R,
 39         stride=1,
 40         padding=0)
 41     cov5 = paddle.layer.img_conv(
 42         name=name + '_5',
 43         input=cov5r,
 44         filter_size=5,
 45         num_filters=filter5,
 46         stride=1,
 47         padding=2)
 48 
 49     pool1 = paddle.layer.img_pool(
 50         name=name + '_max',
 51         input=input,
 52         pool_size=3,
 53         num_channels=channels,
 54         stride=1,
 55         padding=1)
 56     covprj = paddle.layer.img_conv(
 57         name=name + '_proj',
 58         input=pool1,
 59         filter_size=1,
 60         num_filters=proj,
 61         stride=1,
 62         padding=0)
 63 
 64     cat = paddle.layer.concat(name=name, input=[cov1, cov3, cov5, covprj])
 65     return cat
 66 
 67 
 68 def googlenet(input, class_dim):
 69     # stage 1
 70     conv1 = paddle.layer.img_conv(
 71         name="conv1",
 72         input=input,
 73         filter_size=7,
 74         num_channels=3,
 75         num_filters=64,
 76         stride=2,
 77         padding=3)
 78     pool1 = paddle.layer.img_pool(
 79         name="pool1", input=conv1, pool_size=3, num_channels=64, stride=2)
 80 
 81     # stage 2
 82     conv2_1 = paddle.layer.img_conv(
 83         name="conv2_1",
 84         input=pool1,
 85         filter_size=1,
 86         num_filters=64,
 87         stride=1,
 88         padding=0)
 89     conv2_2 = paddle.layer.img_conv(
 90         name="conv2_2",
 91         input=conv2_1,
 92         filter_size=3,
 93         num_filters=192,
 94         stride=1,
 95         padding=1)
 96     pool2 = paddle.layer.img_pool(
 97         name="pool2", input=conv2_2, pool_size=3, num_channels=192, stride=2)
 98 
 99     # stage 3
100     ince3a = inception("ince3a", pool2, 192, 64, 96, 128, 16, 32, 32)
101     ince3b = inception("ince3b", ince3a, 256, 128, 128, 192, 32, 96, 64)
102     pool3 = paddle.layer.img_pool(
103         name="pool3", input=ince3b, num_channels=480, pool_size=3, stride=2)
104 
105     # stage 4
106     ince4a = inception("ince4a", pool3, 480, 192, 96, 208, 16, 48, 64)
107     ince4b = inception("ince4b", ince4a, 512, 160, 112, 224, 24, 64, 64)
108     ince4c = inception("ince4c", ince4b, 512, 128, 128, 256, 24, 64, 64)
109     ince4d = inception("ince4d", ince4c, 512, 112, 144, 288, 32, 64, 64)
110     ince4e = inception("ince4e", ince4d, 528, 256, 160, 320, 32, 128, 128)
111     pool4 = paddle.layer.img_pool(
112         name="pool4", input=ince4e, num_channels=832, pool_size=3, stride=2)
113 
114     # stage 5
115     ince5a = inception("ince5a", pool4, 832, 256, 160, 320, 32, 128, 128)
116     ince5b = inception("ince5b", ince5a, 832, 384, 192, 384, 48, 128, 128)
117     pool5 = paddle.layer.img_pool(
118         name="pool5",
119         input=ince5b,
120         num_channels=1024,
121         pool_size=7,
122         stride=7,
123         pool_type=paddle.pooling.Avg())
124     dropout = paddle.layer.addto(
125         input=pool5,
126         layer_attr=paddle.attr.Extra(drop_rate=0.4),
127         act=paddle.activation.Linear())
128 
129     out = paddle.layer.fc(
130         input=dropout, size=class_dim, act=paddle.activation.Softmax())
131 
132     # fc for output 1
133     pool_o1 = paddle.layer.img_pool(
134         name="pool_o1",
135         input=ince4a,
136         num_channels=512,
137         pool_size=5,
138         stride=3,
139         pool_type=paddle.pooling.Avg())
140     conv_o1 = paddle.layer.img_conv(
141         name="conv_o1",
142         input=pool_o1,
143         filter_size=1,
144         num_filters=128,
145         stride=1,
146         padding=0)
147     fc_o1 = paddle.layer.fc(
148         name="fc_o1",
149         input=conv_o1,
150         size=1024,
151         layer_attr=paddle.attr.Extra(drop_rate=0.7),
152         act=paddle.activation.Relu())
153     out1 = paddle.layer.fc(
154         input=fc_o1, size=class_dim, act=paddle.activation.Softmax())
155 
156     # fc for output 2
157     pool_o2 = paddle.layer.img_pool(
158         name="pool_o2",
159         input=ince4d,
160         num_channels=528,
161         pool_size=5,
162         stride=3,
163         pool_type=paddle.pooling.Avg())
164     conv_o2 = paddle.layer.img_conv(
165         name="conv_o2",
166         input=pool_o2,
167         filter_size=1,
168         num_filters=128,
169         stride=1,
170         padding=0)
171     fc_o2 = paddle.layer.fc(
172         name="fc_o2",
173         input=conv_o2,
174         size=1024,
175         layer_attr=paddle.attr.Extra(drop_rate=0.7),
176         act=paddle.activation.Relu())
177     out2 = paddle.layer.fc(
178         input=fc_o2, size=class_dim, act=paddle.activation.Softmax())
179 
180     return out, out1, out2

 

  2.訓練模型

  1 import gzip
  2 import paddle.v2.dataset.flowers as flowers
  3 import paddle.v2 as paddle
  4 import reader
  5 import vgg
  6 import resnet
  7 import alexnet
  8 import googlenet
  9 import argparse
 10 
 11 DATA_DIM = 3 * 224 * 224
 12 CLASS_DIM = 102
 13 BATCH_SIZE = 128
 14 
 15 
 16 def main():
 17     # parse the argument
 18     parser = argparse.ArgumentParser()
 19     parser.add_argument(
 20         'model',
 21         help='The model for image classification',
 22         choices=['alexnet', 'vgg13', 'vgg16', 'vgg19', 'resnet', 'googlenet'])
 23     args = parser.parse_args()
 24 
 25     # PaddlePaddle init
 26     paddle.init(use_gpu=True, trainer_count=7)
 27 
 28     image = paddle.layer.data(
 29         name="image", type=paddle.data_type.dense_vector(DATA_DIM))
 30     lbl = paddle.layer.data(
 31         name="label", type=paddle.data_type.integer_value(CLASS_DIM))
 32 
 33     extra_layers = None
 34     learning_rate = 0.01
 35     if args.model == 'alexnet':
 36         out = alexnet.alexnet(image, class_dim=CLASS_DIM)
 37     elif args.model == 'vgg13':
 38         out = vgg.vgg13(image, class_dim=CLASS_DIM)
 39     elif args.model == 'vgg16':
 40         out = vgg.vgg16(image, class_dim=CLASS_DIM)
 41     elif args.model == 'vgg19':
 42         out = vgg.vgg19(image, class_dim=CLASS_DIM)
 43     elif args.model == 'resnet':
 44         out = resnet.resnet_imagenet(image, class_dim=CLASS_DIM)
 45         learning_rate = 0.1
 46     elif args.model == 'googlenet':
 47         out, out1, out2 = googlenet.googlenet(image, class_dim=CLASS_DIM)
 48         loss1 = paddle.layer.cross_entropy_cost(
 49             input=out1, label=lbl, coeff=0.3)
 50         paddle.evaluator.classification_error(input=out1, label=lbl)
 51         loss2 = paddle.layer.cross_entropy_cost(
 52             input=out2, label=lbl, coeff=0.3)
 53         paddle.evaluator.classification_error(input=out2, label=lbl)
 54         extra_layers = [loss1, loss2]
 55 
 56     cost = paddle.layer.classification_cost(input=out, label=lbl)
 57 
 58     # Create parameters
 59     parameters = paddle.parameters.create(cost)
 60 
 61     # Create optimizer
 62     optimizer = paddle.optimizer.Momentum(
 63         momentum=0.9,
 64         regularization=paddle.optimizer.L2Regularization(rate=0.0005 *
 65                                                          BATCH_SIZE),
 66         learning_rate=learning_rate / BATCH_SIZE,
 67         learning_rate_decay_a=0.1,
 68         learning_rate_decay_b=128000 * 35,
 69         learning_rate_schedule="discexp", )
 70 
 71     train_reader = paddle.batch(
 72         paddle.reader.shuffle(
 73             flowers.train(),
 74             # To use other data, replace the above line with:
 75             # reader.train_reader('train.list'),
 76             buf_size=1000),
 77         batch_size=BATCH_SIZE)
 78     test_reader = paddle.batch(
 79         flowers.valid(),
 80         # To use other data, replace the above line with:
 81         # reader.test_reader('val.list'),
 82         batch_size=BATCH_SIZE)
 83 
 84     # Create trainer
 85     trainer = paddle.trainer.SGD(
 86         cost=cost,
 87         parameters=parameters,
 88         update_equation=optimizer,
 89         extra_layers=extra_layers)
 90 
 91     # End batch and end pass event handler
 92     def event_handler(event):
 93         if isinstance(event, paddle.event.EndIteration):
 94             if event.batch_id % 1 == 0:
 95                 print "\nPass %d, Batch %d, Cost %f, %s" % (
 96                     event.pass_id, event.batch_id, event.cost, event.metrics)
 97         if isinstance(event, paddle.event.EndPass):
 98             with gzip.open('params_pass_%d.tar.gz' % event.pass_id, 'w') as f:
 99                 trainer.save_parameter_to_tar(f)
100 
101             result = trainer.test(reader=test_reader)
102             print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics)
103 
104     trainer.train(
105         reader=train_reader, num_passes=200, event_handler=event_handler)
106 
107 
108 if __name__ == '__main__':
109     main()

  3.運行方式

1 python train.py googlenet

  其中最後的googlenet是可選的網絡模型,輸入其餘的網絡模型,如alexnet、vgg三、vgg6等就能夠用不一樣的網絡結構來訓練了。

 

  


 用Tensorflow實現GoogLeNet

  tensorflow的實如今models裏有很是詳細的代碼,這裏就不所有貼出來了,你們能夠在models/research/slim/nets裏詳細看看,關於InceptionV1~InceptionV4的實現都有。

ps:這裏的slim不是tensorflow的contrib下的slim,是models下的slim,別弄混了,slim能夠理解爲Tensorflow的一個高階api,在構建這些複雜的網絡結構時,能夠直接調用slim封裝好的網絡結構就能夠了,而不須要從頭開始寫整個網絡結構。關於slim的詳細你們能夠在網上搜索,很是方便。

 

 


 總結

  其實GoogLeNet的最關鍵的一點就是提出了Inception結構,這有個什麼好處呢,原來你想要提升準確率,須要堆疊更深的層,增長神經元個數等,堆疊到必定層可能結果的準確率就提不上去了,由於參數更多了啊,模型更復雜,更容易過擬合了,可是在實驗中轉向了更稀疏可是更精密的結構一樣能夠達到很好的效果,說明咱們能夠照着這個思路走,繼續作,因此後面會有InceptionV2 ,V3,V4等,它表現的結果也很是好。給咱們傳統的經過堆疊層提升準確率的想法提供了一個新的思路。

相關文章
相關標籤/搜索