天天看點

經典卷積神經網絡(3)--VGGNet卷積網絡模型

相關介紹

VGGNet是牛津大學計算機視覺組(Visual Geometry Group)和Google DeepMind公司的研究員一起研發的的深度卷積神經網絡。

他們研究了卷積網絡深度在大規模的圖像識别環境下對準确性的影響。他們的主要貢獻是使用非常小的(3×3)卷積濾波器架構對網絡深度的增加進行了全面評估,這表明通過将深度推到16-19權重層可以實作對現有技術配置的顯著改進。這些發現是他們的ImageNet Challenge 2014送出的基礎,他們的團隊在定位和分類過程中分别獲得了第一名和第二名。VGG對于其他資料集泛化的很好,在其它資料集上取得了最好的結果。

1.VGGNet 探索的是神經網絡的深度(depth)與其性能之間的關系。

VGGNet論文中全部使用了3´3的卷積核和2´2的池化核,通過不斷加深網絡結構來進行性能對比,最終得出較好的深層網絡結構。牛津大學計算機視覺幾何組對11-19層的網絡都進行了相近的性能測試,根據網絡深度的不同以及是否使用LRN,VGGNet可以分為A-E6個級别.各級網絡結構如下圖所示:

經典卷積神經網絡(3)--VGGNet卷積網絡模型

盡管A-E對網絡逐漸加深了,但是網絡的參數量并沒有顯著增加,因為最後的3個全連層占據了大量的參數。在6個級别的VVGNet中,全連層都是相同的。

卷積層的參數共享和局部連接配接對降低參數數量做出了巨大貢獻,但是由于卷積操作和池化操作的運算過程比較複雜,是以訓練中比較耗時的部分是卷積層。

經典卷積神經網絡(3)--VGGNet卷積網絡模型

2.VGG結構全部都采用較小的卷積核(3×3,部分1×1)

在VGG出現之前的深度網絡,比如ZFNet或Overfeat普遍都采用了7×7和11×11的卷積核。

如何選擇卷積核的大小?越大越好還是越小越好?

答案是小而深,單獨較小的卷積核也是不好的,隻有堆疊很多小的卷積核,模型的性能才會提升。

為什麼選擇小而深的卷積核(即堆疊使用3×3的卷積核)

VGGNet相比AlexNet的一個改進是采用連續的幾個3x3的卷積核代替AlexNet中的較大卷積核(11x11,5x5)。

原因:1.兩個3´3的卷積層串聯相當于1個5´5的卷積層,即串聯後一個像素感受野大小為5´5。而3個3´3的卷積層串聯的效果則相當于1個7´7的卷積層。

2.除此之外,2個串聯的3´3的卷積層,擁有比1個5´5的卷積層更少的參數量.

3.最重要的是,2個3´3的卷積層擁有比1個5´5的卷積層更多的非線性變換(前者可以使用2次ReLU激活函數,而後者隻有1次),使得CNN對特征的學習能力更強。

如下圖

經典卷積神經網絡(3)--VGGNet卷積網絡模型

3. 在訓練與測試時進行資料增強處理(Multi-scale:多尺寸)

Multi-scale方法(多尺寸圖檔資料)

大緻過程:先将原始圖檔縮放到不同尺寸,然後再将得到的圖檔進行224* 224的随機裁剪

  初始對原始圖檔進行裁剪時,原始圖檔的最小邊不宜過小,這樣的話,裁剪到224x224的時候,就相當于幾乎覆寫了整個圖檔,這樣對原始圖檔進行不同的随機 裁剪得到的圖檔就基本上沒差别,就失去了增加資料集的意義,但同時也不宜過大,這樣的話,裁剪到的圖檔隻含有目标的一小部分,也不是很好。   針對上述裁剪的問題,提出的兩種解決辦法:

(1) 固定最小邊的尺寸為256

(2) 最小邊随機從[256,512]的确定範圍内進行抽樣,這樣原始圖檔尺寸不一,有利于訓練,這個方法叫做尺度抖動scal jittering,有利于訓練集增強。 訓練時運用大量的裁剪圖檔有利于提升識别精确率。

單尺度評估

測試圖像大小設定為單一固定的最小邊,訓練圖像使用兩種裁剪方式進行對比(是否進行尺寸抖動)

經典卷積神經網絡(3)--VGGNet卷積網絡模型

首先,我們注意到,使用局部響應歸一化(A-LRN網絡)對模型A沒有改善。是以,我們在較深的架構(B-E)中不采用歸一化。

第二,我們觀察到分類誤差随着ConvNet深度的增加而減小:從A中的11層到E中的19層。值得注意的是,盡管深度相同,配置C(包含三個1×1卷積層)比在整個網絡層中使用3×3卷積的配置D更差。這表明,雖然額外的非線性确實有幫助(C優于B),但也可以通過使用具有非平凡感受野(D比C好)的卷積濾波器來捕獲空間上下文。當深度達到19層時,我們架構的錯誤率飽和,但更深的模型可能有益于較大的資料集。 三,我們還将網絡B與具有5×5卷積層的淺層網絡進行了比較,淺層網絡可以通過用單個5×5卷積層替換B中每對3×3卷積層得到。測量的淺層網絡top-1錯誤率比網絡B的top-1錯誤率高7%,這證明了具有小濾波器的深層網絡優于具有較大濾波器的淺層網絡。

最後,訓練時的尺度抖動得到了與固定最小邊的圖像訓練相比更好的結果,即使在測試時使用單尺度。這證明了通過尺度抖動進行的訓練集增強确實有助于捕獲多尺度圖像統計。

多尺度評估

在單尺度上評估ConvNet模型後,我們現在評估測試時尺度抖動的影響。考慮到訓練和測試尺度之間的巨大差異會導緻性能下降,用固定S訓練的模型在三個測試圖像尺度上進行了評估。同時,訓練時的尺度抖動允許網絡在測試時應用于更廣的尺度範圍,是以用變量訓練的模型在更大的尺寸範圍上進行評估。表4中給出的結果表明,測試時的尺度抖動導緻了更好的性能

經典卷積神經網絡(3)--VGGNet卷積網絡模型

可以看到結果稍好于前者測試圖檔采用單一尺寸的效果。

最後将多個模型進行合并進一步得到了更好的效果,并在ILSVRC比賽上拿到了第二的成績。

經典卷積神經網絡(3)--VGGNet卷積網絡模型

新技術點:

①LRN層作用不大,還耗時,抛棄。

②網絡越深,效果越好。

③卷積核用較小的卷積核,比如3*3。

import tensorflow as tf
from datetime import datetime
import math
import time

batch_size = 12
num_batches = 100


# 定義卷積操作
def conv_op(input, name, kernel_h, kernel_w, num_out, step_h, step_w, para):
    # num_in是輸入的深度,這個參數被用來确定過濾器的輸入通道數
    num_in = input.get_shape()[-1].value

    with tf.name_scope(name) as scope:
        kernel = tf.get_variable(scope + "w", shape=[kernel_h, kernel_w, num_in, num_out],
                                 dtype=tf.float32,
                                 initializer=tf.contrib.layers.xavier_initializer_conv2d())
        conv = tf.nn.conv2d(input, kernel, (1, step_h, step_w, 1), padding="SAME")
        biases = tf.Variable(tf.constant(0.0, shape=[num_out], dtype=tf.float32),
                             trainable=True, name="b")
        # 計算relu後的激活值
        activation = tf.nn.relu(tf.nn.bias_add(conv, biases), name=scope)

        para += [kernel, biases]
        return activation


# 定義全連操作
def fc_op(input, name, num_out, para):
    # num_in為輸入單元的數量
    num_in = input.get_shape()[-1].value

    with tf.name_scope(name) as scope:
        weights = tf.get_variable(scope + "w", shape=[num_in, num_out], dtype=tf.float32,
                                  initializer=tf.contrib.layers.xavier_initializer())
        biases = tf.Variable(tf.constant(0.1, shape=[num_out], dtype=tf.float32), name="b")

        # tf.nn.relu_layer()函數會同時完成矩陣乘法以加和偏置項并計算relu激活值
        # 這是分步程式設計的良好替代
        activation = tf.nn.relu_layer(input, weights, biases)

        para += [weights, biases]
        return activation


# 定義前向傳播的計算過程,input參數的大小為224x224x3,也就是輸入的模拟圖檔資料
def inference_op(input, keep_prob):
    parameters = []

    # 第一段卷積,輸出大小為112x112x64(省略了第一個batch_size參數)
    conv1_1 = conv_op(input, name="conv1_1", kernel_h=3, kernel_w=3, num_out=64,
                      step_h=1, step_w=1, para=parameters)
    conv1_2 = conv_op(conv1_1, name="conv1_2", kernel_h=3, kernel_w=3, num_out=64,
                      step_h=1, step_w=1, para=parameters)
    pool1 = tf.nn.max_pool(conv1_2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1],
                           padding="SAME", name="pool1")
    print(pool1.op.name, ' ', pool1.get_shape().as_list())

    # 第二段卷積,輸出大小為56x56x128(省略了第一個batch_size參數)
    conv2_1 = conv_op(pool1, name="conv2_1", kernel_h=3, kernel_w=3, num_out=128,
                      step_h=1, step_w=1, para=parameters)
    conv2_2 = conv_op(conv2_1, name="conv2_2", kernel_h=3, kernel_w=3, num_out=128,
                      step_h=1, step_w=1, para=parameters)
    pool2 = tf.nn.max_pool(conv2_2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1],
                           padding="SAME", name="pool2")
    print(pool2.op.name, ' ', pool2.get_shape().as_list())

    # 第三段卷積,輸出大小為28x28x256(省略了第一個batch_size參數)
    conv3_1 = conv_op(pool2, name="conv3_1", kernel_h=3, kernel_w=3, num_out=256,
                      step_h=1, step_w=1, para=parameters)
    conv3_2 = conv_op(conv3_1, name="conv3_2", kernel_h=3, kernel_w=3, num_out=256,
                      step_h=1, step_w=1, para=parameters)
    conv3_3 = conv_op(conv3_2, name="conv3_3", kernel_h=3, kernel_w=3, num_out=256,
                      step_h=1, step_w=1, para=parameters)
    pool3 = tf.nn.max_pool(conv3_3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1],
                           padding="SAME", name="pool3")
    print(pool2.op.name, ' ', pool2.get_shape().as_list())

    # 第四段卷積,輸出大小為14x14x512(省略了第一個batch_size參數)
    conv4_1 = conv_op(pool3, name="conv4_1", kernel_h=3, kernel_w=3, num_out=512,
                      step_h=1, step_w=1, para=parameters)
    conv4_2 = conv_op(conv4_1, name="conv4_2", kernel_h=3, kernel_w=3, num_out=512,
                      step_h=1, step_w=1, para=parameters)
    conv4_3 = conv_op(conv4_2, name="conv4_3", kernel_h=3, kernel_w=3, num_out=512,
                      step_h=1, step_w=1, para=parameters)
    pool4 = tf.nn.max_pool(conv4_3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1],
                           padding="SAME", name="pool4")
    print(pool4.op.name, ' ', pool4.get_shape().as_list())

    # 第五段卷積,輸出大小為7x7x512(省略了第一個batch_size參數)
    conv5_1 = conv_op(pool4, name="conv5_1", kernel_h=3, kernel_w=3, num_out=512,
                      step_h=1, step_w=1, para=parameters)
    conv5_2 = conv_op(conv5_1, name="conv5_2", kernel_h=3, kernel_w=3, num_out=512,
                      step_h=1, step_w=1, para=parameters)
    conv5_3 = conv_op(conv5_2, name="conv5_3", kernel_h=3, kernel_w=3, num_out=512,
                      step_h=1, step_w=1, para=parameters)
    pool5 = tf.nn.max_pool(conv5_3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1],
                           padding="SAME", name="pool5")
    print(pool5.op.name, ' ', pool5.get_shape().as_list())

    # pool5的結果彙總為一個向量的形式
    pool_shape = pool5.get_shape().as_list()
    flattened_shape = pool_shape[1] * pool_shape[2] * pool_shape[3]
    reshped = tf.reshape(pool5, [-1, flattened_shape], name="reshped")

    # 第一個全連層
    fc_6 = fc_op(reshped, name="fc6", num_out=4096, para=parameters)
    fc_6_drop = tf.nn.dropout(fc_6, keep_prob, name="fc6_drop")

    # 第二個全連層
    fc_7 = fc_op(fc_6_drop, name="fc7", num_out=4096, para=parameters)
    fc_7_drop = tf.nn.dropout(fc_7, keep_prob, name="fc7_drop")

    # 第三個全連層及softmax層
    fc_8 = fc_op(fc_7_drop, name="fc8", num_out=1000, para=parameters)
    softmax = tf.nn.softmax(fc_8)

    # predictions模拟了通過argmax得到預測結果
    predictions = tf.argmax(softmax, 1)
    return predictions, softmax, fc_8, parameters


with tf.Graph().as_default():
    # 建立模拟的圖檔資料
    image_size = 224
    images = tf.Variable(tf.random_normal([batch_size, image_size, image_size, 3],
                                          dtype=tf.float32, stddev=1e-1))

    # Dropout的keep_prob會根據前向傳播或者反向傳播而有所不同,在前向傳播時,
    # keep_prob=1.0,在反向傳播時keep_prob=0.5
    keep_prob = tf.placeholder(tf.float32)

    # 為目前計算圖添加前向傳播過程
    predictions, softmax, fc_8, parameters = inference_op(images, keep_prob)

    init_op = tf.global_variables_initializer()

    # 使用BFC算法确定GPU記憶體最佳配置設定政策
    config = tf.ConfigProto()
    config.gpu_options.allocator_type = "BFC"
    with tf.Session(config=config) as sess:
        sess.run(init_op)
        num_steps_burn_in = 10

        total_dura = 0.0
        total_dura_squared = 0.0

        back_total_dura = 0.0
        back_total_dura_squared = 0.0

        # 運作前向傳播的測試過程
        for i in range(num_batches + num_steps_burn_in):
            start_time = time.time()
            _ = sess.run(predictions, feed_dict={keep_prob: 1.0})
            duration = time.time() - start_time
            if i >= num_steps_burn_in:
                if i % 10 == 0:
                    print("%s: step %d, duration = %.3f" %
                          (datetime.now(), i - num_steps_burn_in, duration))
                total_dura += duration
                total_dura_squared += duration * duration
        average_time = total_dura / num_batches

        # 列印前向傳播的運算時間資訊
        print("%s: Forward across %d steps, %.3f +/- %.3f sec / batch" %
              (datetime.now(), num_batches, average_time,
               math.sqrt(total_dura_squared / num_batches - average_time * average_time)))

        # 定義求解梯度的操作
        grad = tf.gradients(tf.nn.l2_loss(fc_8), parameters)

        # 運作反向傳播測試過程
        for i in range(num_batches + num_steps_burn_in):
            start_time = time.time()
            _ = sess.run(grad, feed_dict={keep_prob: 0.5})
            duration = time.time() - start_time
            if i >= num_steps_burn_in:
                if i % 10 == 0:
                    print("%s: step %d, duration = %.3f" %
                          (datetime.now(), i - num_steps_burn_in, duration))
                back_total_dura += duration
                back_total_dura_squared += duration * duration
        back_avg_t = back_total_dura / num_batches

        # 列印反向傳播的運算時間資訊
        print("%s: Forward-backward across %d steps, %.3f +/- %.3f sec / batch" %
              (datetime.now(), num_batches, back_avg_t,
               math.sqrt(back_total_dura_squared / num_batches - back_avg_t * back_avg_t)))
           

繼續閱讀