1. 程式人生 > >TensorFlow實戰之實現AlexNet經典卷積神經網路

TensorFlow實戰之實現AlexNet經典卷積神經網路

本文根據最近學習TensorFlow書籍網路文章的情況,特將一些學習心得做了總結,詳情如下.如有不當之處,請各位大拿多多指點,在此謝過。

一、AlexNet模型及其基本原理闡述

1、關於AlexNet

            2012年,AlexKrizhevsky提出了深度卷積神經網路模型AlexNet,可以看作LeNet的一種更深更寬的版本。該模型包含了6億3000萬個連線,6000萬個引數和65萬個神經元,擁有5個卷積層,其中3個卷積層後面連線了最大池化層,最後還有3個全連線層。它將LeNet的思想得到更廣泛的傳播,把把CNN的基本原理應用到很深很寬的網路中。

2、AlexNet主要使用到的新技術點

      (1)成功使用RelU作為CNN的啟用函式,並驗證其效果在較深的網路超過Sigmoid函式,成功解決了Sigmoid函式在網路較深時梯度彌散問題。

        (2)訓練時使用Droupout隨機忽略一部分神經元,以避免出現模型過擬合。

        (3) 在CNN中使用重疊的最大池化。

        (4) 提出了LRN層,對區域性神經元的活動建立競爭機制,使得其中響應比較大的值變得相對更大,並抑制其他反饋較小的神經元,增強了模型的泛化能力。

        (5) 使用CUDA加速深度卷積網路的訓練,利用GPU強大的平行計算能力,處理神經網路訓練時大量的矩陣運算。

        (6) 資料增強,隨機地從256*256的原始影象中擷取224*224大小的區域(以及水平翻轉的映象),相當於增加了(256-224)^2=2048倍的資料量。

3、原理闡釋

       整個AlexNet包括8個需要訓練引數的層(不含LRN和池化層),前5層為卷積層,後3層為全連線層,如圖1所示。AlexNet最後一層是有1000類輸出的Softmax層用作分類。LRN層出現在第1個及第2個卷積層後,而最大池化層出現在兩個LRN層及最後一個卷積層後。RelU啟用函式則應用在這8層每一層的後面。

                                        

1AlexNet的網路結構

             AlexNet每層的超引數如圖1所示,其中輸入的圖片尺寸為224*224,第一個卷積層使用了較大的卷積核尺寸11*11,步長為4,有96個卷積核;然後是一個LRN層;再往後是一個3*3的最大池化層,步長為2。再往後的卷積核尺寸都比較小,基本都是5*5或3*3的大小,且步長都是1,即會掃描全影象所有畫素;而最大池化層依然保持為3*3,步長為2。

         這裡不難發現,前幾個卷積層裡面,雖然計算量很大,但引數量很小,基本都在1M左右甚至更小,只佔AlexNet總引數量很小一部分。這也是卷積層的價值所在:通過較小的引數量提取有效的特徵。

         如果前幾層直接使用全連線層,則引數量和計算量將會難以想象。儘管每一個卷積層只佔整個網路引數量的1%不到,但如果拋棄任何一個卷積層,都會迫使整個網路的分類能力大幅度下降。

             圖1中特殊的地方是卷積部分都是畫成上下兩塊,意思是說把這一層計算出來的featuremap分開,但是前一層用到的資料要看連線的虛線,如圖中input層之後的第一層第二層之間的虛線是分開的,是說二層上面的128map是由一層上面的48map計算的,下面同理;而第三層前面的虛線是完全交叉的,就是說每一個192map都是由前面的128+128=256map同時計算得到的。

              Alexnet有一個特殊的計算層,LRN層,做的事是對當前層的輸出結果做平滑處理,圖2所示:

        

               圖2

前後幾層(對應位置的點)對中間這一層做一下平滑約束,計算方法如下:

                

二、經典卷積神經網路AlexNet模型實現過程

1、簡要說明

         因為使用ImageNet資料集訓練一個完整的AlexNet非常耗時,這裡AlexNet的實現將不涉及實際資料的訓練,但會建立一個完整的AlexNet卷積神經網路,然後對它每個batch的前饋計算(forward)和反饋計算(backward)的速度進行測試。下面使用隨機圖片資料來計算每輪前饋、反饋的平均耗時。當然,讀者也可以自行下載ImageNet資料完成訓練並測試。

2、實現過程

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

#這裡總共測試100個batch的資料。
batch_size=32
num_batches=100
#定義一個用來顯示網路每一層結構的函式print_activations,展示每一個卷積層或池化層輸出的tensor尺寸。
def print_activations(t):
    print(t.op.name, ' ', t.get_shape().as_list())

#設計AlexNet網路結構。
#設定inference函式,用於接受images作為輸入,返回最後一層pool5(第5個池化層)及parameters(AlexnNet中所有需要訓練的模型引數)
#該函式包括多個卷積層和池化層。
def inference(images):
    parameters = []
    # 第1個卷積層
    with tf.name_scope('conv1') as scope:
        kernel = tf.Variable(tf.truncated_normal([11, 11, 3, 64], dtype=tf.float32,
                                                 stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(images, kernel, [1, 4, 4, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[64], dtype=tf.float32),
                             trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv1 = tf.nn.relu(bias, name=scope)
        print_activations(conv1)
        parameters += [kernel, biases]

  # 新增LRN層和最大池化層
    lrn1 = tf.nn.lrn(conv1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75, name='lrn1')
    pool1 = tf.nn.max_pool(lrn1,
                           ksize=[1, 3, 3, 1],
                           strides=[1, 2, 2, 1],
                           padding='VALID',
                           name='pool1')
    print_activations(pool1)

  # 設計第2個卷積層
    with tf.name_scope('conv2') as scope:
        kernel = tf.Variable(tf.truncated_normal([5, 5, 64, 192], dtype=tf.float32,
                                                 stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(pool1, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[192], dtype=tf.float32),
                             trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv2 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
    print_activations(conv2)

  # 對第2個卷積層的輸出進行處理,同樣也是先做LRN處理再做最大化池處理。
    lrn2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75, name='lrn2')
    pool2 = tf.nn.max_pool(lrn2,
                           ksize=[1, 3, 3, 1],
                           strides=[1, 2, 2, 1],
                           padding='VALID',
                           name='pool2')
    print_activations(pool2)

  # 設計第3個卷積層
    with tf.name_scope('conv3') as scope:
        kernel = tf.Variable(tf.truncated_normal([3, 3, 192, 384],
                                                 dtype=tf.float32,
                                                 stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(pool2, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[384], dtype=tf.float32),
                             trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv3 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
        print_activations(conv3)

  # 設計第4個卷積層
    with tf.name_scope('conv4') as scope:
        kernel = tf.Variable(tf.truncated_normal([3, 3, 384, 256],
                                                 dtype=tf.float32,
                                                 stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(conv3, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32),
                             trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv4 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
        print_activations(conv4)

  # 設計第5個卷積層
    with tf.name_scope('conv5') as scope:
        kernel = tf.Variable(tf.truncated_normal([3, 3, 256, 256],
                                                 dtype=tf.float32,
                                                 stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(conv4, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32),
                             trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv5 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
        print_activations(conv5)

  # 最大池化層
    pool5 = tf.nn.max_pool(conv5,
                           ksize=[1, 3, 3, 1],
                           strides=[1, 2, 2, 1],
                           padding='VALID',
                           name='pool5')
    print_activations(pool5)

    return pool5, parameters

#構建函式time_tensorflow_run,用來評估AlexNet每輪計算時間。
def time_tensorflow_run(session, target, info_string):
    num_steps_burn_in = 10
    total_duration = 0.0
    total_duration_squared = 0.0
    for i in range(num_batches + num_steps_burn_in):
        start_time = time.time()
        _ = session.run(target)
        duration = time.time() - start_time
        if i >= num_steps_burn_in:
            if not i % 10:
                print ('%s: step %d, duration = %.3f' %
                       (datetime.now(), i - num_steps_burn_in, duration))
            total_duration += duration
            total_duration_squared += duration * duration
    mn = total_duration / num_batches
    vr = total_duration_squared / num_batches - mn * mn
    sd = math.sqrt(vr)
    print ('%s: %s across %d steps, %.3f +/- %.3f sec / batch' %
           (datetime.now(), info_string, num_batches, mn, sd))

#主函式
def run_benchmark():

    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))

   
        pool5, parameters = inference(images)

    
        init = tf.global_variables_initializer()

    
        config = tf.ConfigProto()
        config.gpu_options.allocator_type = 'BFC'
        sess = tf.Session(config=config)
        sess.run(init)

   
        time_tensorflow_run(sess, pool5, "Forward")

    
        objective = tf.nn.l2_loss(pool5)
   
        grad = tf.gradients(objective, parameters)
    # Run the backward benchmark.
        time_tensorflow_run(sess, grad, "Forward-backward")

#執行主函式
run_benchmark()

3、執行結果分析

          這裡有三部分如下:

             首先是AlexNet的網路結構   

conv1   [32, 56, 56, 64]
pool1   [32, 27, 27, 64]
conv2   [32, 27, 27, 192]
pool2   [32, 13, 13, 192]
conv3   [32, 13, 13, 384]
conv4   [32, 13, 13, 256]
conv5   [32, 13, 13, 256]
pool5   [32, 6, 6, 256]

              其次是Forward執行的時間。

2018-02-26 08:08:01.966903: step 0, duration = 0.914
2018-02-26 08:08:11.376824: step 10, duration = 0.939
2018-02-26 08:08:21.075799: step 20, duration = 0.953
2018-02-26 08:08:30.983637: step 30, duration = 0.930
2018-02-26 08:08:40.616086: step 40, duration = 0.938
2018-02-26 08:08:50.259619: step 50, duration = 0.965
2018-02-26 08:09:01.280123: step 60, duration = 1.128
2018-02-26 08:09:11.993487: step 70, duration = 0.998
2018-02-26 08:09:22.223815: step 80, duration = 0.935
2018-02-26 08:09:31.741528: step 90, duration = 0.921
2018-02-26 08:09:40.085934: Forward across 100 steps, 0.990 +/- 0.082 sec / batch

               最後是backward執行的時間。

2018-02-26 08:10:19.714161: step 0, duration = 3.387
2018-02-26 08:10:55.765137: step 10, duration = 3.495
2018-02-26 08:11:32.451839: step 20, duration = 4.189
2018-02-26 08:12:07.982546: step 30, duration = 3.369
2018-02-26 08:12:43.531404: step 40, duration = 3.415
2018-02-26 08:13:18.980045: step 50, duration = 3.470
2018-02-26 08:13:54.535575: step 60, duration = 3.371
2018-02-26 08:14:29.413705: step 70, duration = 3.655
2018-02-26 08:15:06.147061: step 80, duration = 3.583
2018-02-26 08:15:43.403758: step 90, duration = 3.921
2018-02-26 08:16:16.511215: Forward-backward across 100 steps, 3.602 +/- 0.237 sec / batch

三、小結

            AlexNet為卷積神經網路和深度學習正名,為復興神經網路做出了很多貢獻,當然,超大型的資料集ImageNet也給深度學習帶來了很大貢獻。訓練深度卷積神經網路,必須要有一個類似於ImageNet這樣超大型資料集才能避免出現過擬合現象,從而更能體現深度學習的優勢所在。因此,傳統機器學習模型更適合一個小型資料集,在面對大型資料集時,需要更大學習容量的模型,那就是深度學習模型。

            深度學習的引數不一定比傳統機器學習模型多,尤其是在卷積層使用的引數量一般都比較少,但其抽取特徵的能力非常強悍,這是CNN之所以有效的原因。

   參考資料   主要參考資料《TensorFlow實戰》(黃文堅  唐源 著)(電子工業出版社)。