1. 程式人生 > >VGG

VGG

大學 tensor puts 濾波器 資源 而且 word ati alexnet

前言  

  VGGNet是牛津大學計算機視覺組(VisualGeometry Group)和GoogleDeepMind公司的研究員一起研發的的深度卷積神經網絡。VGGNet探索了卷積神經網絡的深度與其性能之間的關系,通過反復堆疊3*3的小型卷積核和2*2的最大池化層,VGGNet成功地構築了16~19層深的卷積神經網絡。VGGNet相比之前state-of-the-art的網絡結構,錯誤率大幅下降,並取得了ILSVRC 2014比賽分類項目的第2名和定位項目的第1名。同時VGGNet的拓展性很強,遷移到其他圖片數據上的泛化性非常好。VGGNet的結構非常簡潔,整個網絡都使用了同樣大小的卷積核尺寸(3*3)和最大池化尺寸(2*2)。到目前為止,VGGNet依然經常被用來提取圖像特征。VGGNet訓練後的模型參數在其官方網站上開源了,可用來在特定的圖像分類任務上進行再訓練(相當於提供了非常好的初始化權重),因此被用在了很多地方。

VGG原理  

VGG16相比AlexNet的一個改進是采用連續的幾個3x3的卷積核代替AlexNet中的較大卷積核(11x11,7x7,5x5)。對於給定的感受野(與輸出有關的輸入圖片的局部大小),采用堆積的小卷積核是優於采用大的卷積核,因為多層非線性層可以增加網絡深度來保證學習更復雜的模式,而且代價還比較小(參數更少)。

  簡單來說,在VGG中,使用了3個3x3卷積核來代替7x7卷積核,使用了2個3x3卷積核來代替5*5卷積核,這樣做的主要目的是在保證具有相同感知野的條件下,提升了網絡的深度,在一定程度上提升了神經網絡的效果。

  比如,3個步長為1的3x3卷積核的一層層疊加作用可看成一個大小為7的感受野(其實就表示3個3x3連續卷積相當於一個7x7卷積),其參數總量為 3x(9xC^2) ,如果直接使用7x7卷積核,其參數總量為 49xC^2 ,這裏 C 指的是輸入和輸出的通道數。很明顯,27xC^2小於49xC^2,即減少了參數;而且3x3卷積核有利於更好地保持圖像性質。

這裏解釋一下為什麽使用2個3x3卷積核可以來代替5*5卷積核:

  5x5卷積看做一個小的全連接網絡在5x5區域滑動,我們可以先用一個3x3的卷積濾波器卷積,然後再用一個全連接層連接這個3x3卷積輸出,這個全連接層我們也可以看做一個3x3卷積層。這樣我們就可以用兩個3x3卷積級聯(疊加)起來代替一個 5x5卷積。

技術分享圖片

VGG網絡結構

下面是VGG網絡的結構(VGG16和VGG19都在):

技術分享圖片

  • VGG16包含了16個隱藏層(13個卷積層和3個全連接層),如上圖中的D列所示
  • VGG19包含了19個隱藏層(16個卷積層和3個全連接層),如上圖中的E列所示

VGG網絡的結構非常一致,從頭到尾全部使用的是3x3的卷積和2x2的max pooling

VGG-16

  假設這個小圖是我們的輸入圖像,尺寸是224×224×3,進行第一個卷積之後得到224×224×64的特征圖,接著還有一層224×224×64,得到這樣2個厚度為64的卷積層,意味著我們用64個過濾器進行了兩次卷積。正如我在前面提到的,這裏采用的都是大小為3×3,步幅為1的過濾器,並且都是采用same卷積,所以我就不再把所有的層都畫出來了,只用一串數字代表這些網絡。

  接下來創建一個池化層,池化層將輸入圖像進行壓縮,從224×224×64縮小到多少呢?沒錯,減少到112×112×64。然後又是若幹個卷積層,使用129個過濾器,以及一些same卷積,我們看看輸出什麽結果,112×112×128.然後進行池化,可以推導出池化後的結果是這樣(56×56×128)。接著再用256個相同的過濾器進行三次卷積操作,然後再池化,然後再卷積三次,再池化。如此進行幾輪操作後,將最後得到的7×7×512的特征圖進行全連接操作,得到4096個單元,然後進行softmax激活,輸出從1000個對象中識別的結果。

技術分享圖片

  順便說一下,VGG-16的這個數字16,就是指在這個網絡中包含16個卷積層和全連接層。確實是個很大的網絡,總共包含約1.38億個參數,即便以現在的標準來看都算是非常大的網絡。但VGG-16的結構並不復雜,這點非常吸引人,而且這種網絡結構很規整,都是幾個卷積層後面跟著可以壓縮圖像大小的池化層,池化層縮小圖像的高度和寬度。同時,卷積層的過濾器數量變化存在一定的規律,由64翻倍變成128,再到256和512。作者可能認為512已經足夠大了,所以後面的層就不再翻倍了。無論如何,每一步都進行翻倍,或者說在每一組卷積層進行過濾器翻倍操作,正是設計此種網絡結構的另一個簡單原則。這種相對一致的網絡結構對研究者很有吸引力,而它的主要缺點是需要訓練的特征數量非常巨大。

VGG優缺點

VGG優點

VGGNet的結構非常簡潔,整個網絡都使用了同樣大小的卷積核尺寸(3x3)和最大池化尺寸(2x2)。

幾個小濾波器(3x3)卷積層的組合比一個大濾波器(5x5或7x7)卷積層好:

驗證了通過不斷加深網絡結構可以提升性能。

VGG缺點

VGG耗費更多計算資源,並且使用了更多的參數(這裏不是3x3卷積的鍋),導致更多的內存占用(140M)。其中絕大多數的參數都是來自於第一個全連接層。VGG可是有3個全連接層啊!

代碼實現,分別用原生tensorflow和slim實現

tensorflow

 1 import tensorflow as tf
 2 
 3 #VGGNet-16包含很多層卷積,我們先寫一個函數conv_op,用來創建卷積層並把本層的參數存入參數列表。
 4 def conv_op(input_op,name,kh,kw,n_out,dh,dw,p):
 5     n_in = input_op.get_shape()[-1].value
 6     with tf.name_scope(name) as scope:
 7         kernel = tf.get_variable(scope+w,shape=[kh,kw,n_in,n_out],dtype=tf.float32,initializer=tf.contrib.layers.xavier_initializer_conv2d())
 8         conv = tf.nn.conv2d(input_op,kernel,(1,dh,dw,1),padding=SAME)
 9         bias_init_val = tf.constant(0.0,shape=[n_out],dtype=tf.float32)
10         biases = tf.Variable(bias_init_val,trainable=True,name=b)
11         z = tf.nn.bias_add(conv,biases)
12         activation = tf.nn.relu(z,name=scope)
13         p += [kernel,biases]
14         return activation
15 
16 def fc_op(input_op,name,n_out,p):
17     n_in = input_op.get_shape()[-1].value
18     with tf.name_scope(name) as scope:
19         kernel = tf.get_variable(scope + "w", shape=[n_in, n_out], dtype=tf.float32,initializer=tf.contrib.layers.xavier_initializer())
20         biases = tf.Variable(tf.constant(0.1, shape=[n_out], dtype=tf.float32), name=b)
21         activation = tf.nn.relu_layer(input_op, kernel, biases, name=scope)
22         p += [kernel, biases]
23         return activation
24 
25 def mpool_op(input_op, name, kh, kw, dh, dw):
26     return tf.nn.max_pool(input_op, ksize=[1, kh, kw, 1], strides=[1, dh, dw, 1], padding=SAME, name=name)
27 
28 
29 def inference_op(input_op,keep_prob):
30     p = []
31     conv1_1 = conv_op(input_op,name=conv1_1,kh=3,kw=3,n_out=64,dh=1,dw=1,p=p)
32     conv1_2 = conv_op(conv1_1, name="conv1_2", kh=3, kw=3, n_out=64, dh=1, dw=1, p=p)
33     pool1 = mpool_op(conv1_2, name="pool1", kh=2, kw=2, dw=2, dh=2)
34 
35     conv2_1 = conv_op(pool1, name="conv2_1", kh=3, kw=3, n_out=128, dh=1, dw=1, p=p)
36     conv2_2 = conv_op(conv2_1, name="conv2_2", kh=3, kw=3, n_out=128, dh=1, dw=1, p=p)
37     pool2 = mpool_op(conv2_2, name="pool2", kh=2, kw=2, dw=2, dh=2)
38 
39     conv3_1 = conv_op(pool2, name="conv3_1", kh=3, kw=3, n_out=256, dh=1, dw=1, p=p)
40     conv3_2 = conv_op(conv3_1, name="conv3_2", kh=3, kw=3, n_out=256, dh=1, dw=1, p=p)
41     conv3_3 = conv_op(conv3_2, name="conv3_3", kh=3, kw=3, n_out=256, dh=1, dw=1, p=p)
42     pool3 = mpool_op(conv3_3, name="pool3", kh=2, kw=2, dw=2, dh=2)
43 
44     conv4_1 = conv_op(pool3, name="conv4_1", kh=3, kw=3, n_out=512, dh=1, dw=1, p=p)
45     conv4_2 = conv_op(conv4_1, name="conv4_2", kh=3, kw=3, n_out=512, dh=1, dw=1, p=p)
46     conv4_3 = conv_op(conv4_2, name="conv4_3", kh=3, kw=3, n_out=512, dh=1, dw=1, p=p)
47     pool4 = mpool_op(conv4_3, name="pool4", kh=2, kw=2, dw=2, dh=2)
48 
49     conv5_1 = conv_op(pool4, name="conv5_1", kh=3, kw=3, n_out=512, dh=1, dw=1, p=p)
50     conv5_2 = conv_op(conv5_1, name="conv5_2", kh=3, kw=3, n_out=512, dh=1, dw=1, p=p)
51     conv5_3 = conv_op(conv5_2, name="conv5_3", kh=3, kw=3, n_out=512, dh=1, dw=1, p=p)
52     pool5 = mpool_op(conv5_3, name="pool5", kh=2, kw=2, dw=2, dh=2)
53 
54     shp = pool5.get_shape()
55     flattened_shape = shp[1].value * shp[2].value * shp[3].value
56     resh1 = tf.reshape(pool5, [-1, flattened_shape], name="resh1")
57 
58     fc6 = fc_op(resh1, name="fc6", n_out=4096, p=p)
59     fc6_drop = tf.nn.dropout(fc6, keep_prob, name="fc6_drop")
60 
61     fc7 = fc_op(fc6_drop, name="fc7", n_out=4096, p=p)
62     fc7_drop = tf.nn.dropout(fc7, keep_prob, name="fc7_drop")
63 
64     fc8 = fc_op(fc7_drop, name="fc8", n_out=1000, p=p)
65     softmax = tf.nn.softmax(fc8)
66     predictions = tf.argmax(softmax, 1)
67     return predictions, softmax, fc8, p

slim實現

 1 def vgg16(inputs):
 2       with slim.arg_scope([slim.conv2d, slim.fully_connected],
 3                 activation_fn=tf.nn.relu,
 4                 weights_initializer=tf.truncated_normal_initializer(0.0, 0.01),
 5                 weights_regularizer=slim.l2_regularizer(0.0005)):
 6         net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope=conv1)
 7         net = slim.max_pool2d(net, [2, 2], scope=pool1)
 8         net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope=conv2)
 9         net = slim.max_pool2d(net, [2, 2], scope=pool2)
10         net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope=conv3)
11         net = slim.max_pool2d(net, [2, 2], scope=pool3)
12         net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope=conv4)
13         net = slim.max_pool2d(net, [2, 2], scope=pool4)
14         net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope=conv5)
15         net = slim.max_pool2d(net, [2, 2], scope=pool5)
16          net = slim.flatten(net)
17         net = slim.fully_connected(net, 4096, scope=fc6)
18         net = slim.dropout(net, 0.5, scope=dropout6)
19         net = slim.fully_connected(net, 4096, scope=fc7)
20         net = slim.dropout(net, 0.5, scope=dropout7)
21         net = slim.fully_connected(net, 1000, activation_fn=None, scope=fc8)
22       return net

VGG