TensorFlow實戰——卷積神經網絡與圖像識別

TensorFlow實戰——卷積神經網絡與圖像識別

經典的圖像識別數據集

在圖像識別領域裏,經典數據集包括MNIST數據集、Cifar數據集和ImageNet數據集。
在處理上述這些數據集時,全連接神經網絡往往無法很好的處理圖像數據。因爲使用全連接神經網絡處理圖像的最大問題在於全連接層的參數太多。參數增多除了導致計算速度減慢,還很容易導致過擬合問題。

於是,卷積神經網絡CNN應運而來。

卷積神經網絡CNN

一個卷積神經網絡主要由以下五種結構組成:
1. 輸入層。輸入層是整個神經網絡的輸入,在處理圖像的卷積神經網絡中,它一般代表了一張圖片的像素矩陣。比如,三維矩陣就可以代表一張圖片。其中三維矩陣的長和寬代表了圖像的大小,而三維矩陣的深度代表了圖像的色彩通道。比如黑白圖片的深度爲1,而在RGB色彩模式下,圖像的深度爲3.
2. 卷積層。卷積層中每一個節點的輸入只是上一層神經網絡的一小塊,這個小塊常用的大小有3 × 3或者5 × 5。卷積層試圖將神經網絡中的每一小塊進行更加深入地分析從而得到抽象程度更高的特徵。一般來說,通過卷積層處理過的節點矩陣會變得更深。
3. 池化層(Pooling)。池化層神經網絡不會改變三維矩陣的深度,但是它可以縮小矩陣的大小。池化操作可以認爲是將一張分辨率較高的圖片轉化爲分辨率較低的圖片。通過池化層,可以進一步縮小最後全連接層中節點的個數,從而達到減少整個神經網絡中參數的目的。
4. 全連接層。在經過多輪卷積層和池化層的處理之後,在卷積神經網絡的最後一般會是由1到2個全連接層來給出最後的分類結果。經過幾輪卷積層和池化層的處理之後,可以認爲圖像中的信息已經被抽象成了信息含量更高的特徵。我們可以將卷積層和池化層看成自動圖像特徵提取的過程。在特徵提取完成之後,仍然需要使用全連接層來完成分類任務。
5. Softmax層。Softmax層主要用於分類問題。通過Softmax層,可以得到當前樣例屬於不同種類的概率分佈情況。

卷積神經網絡常用結構

卷積層

圖中顯示了卷積層神經網絡結構中最重要的部分,這個部分被稱之爲過濾器filter或者內核kernel。

過濾器可以將當前層神經網絡上的一個子節點矩陣轉化爲下一層神經網絡上的一個單位節點矩陣。單位節點矩陣指的是一個長和寬都爲1,但深度不限的節點矩陣。
在一個卷積層中,過濾器所處理的節點矩陣的長和寬都是由人工指定的,這個節點矩陣的尺寸也被稱之爲過濾器的尺寸。常用的過濾器尺寸有3×3或5×5.因爲過濾器處理的矩陣深度和當前層神經網絡節點矩陣的深度是一致的,所以雖然節點矩陣是三維的,但過濾器的尺寸只需要指定兩個維度。過濾器中另外一個需要人工指定的設置是處理得到的單位節點矩陣的深度,這個設置稱爲過濾器的深度。注意過濾器的尺寸指的是一個過濾器輸入節點矩陣的大小,而深度指的是輸出單位節點矩陣的深度。
過濾器的前向傳播過程就是通過圖中左側小矩陣中的節點計算出右側單位矩陣中節點的過程。一個具體的樣例如下:
通過過濾器將一個2×2×3的節點矩陣變化爲一個1×1×5的單位節點矩陣。一個過濾器的前向傳播過程和全連接層相似,它總共需要2×2×3×5+5=65個參數,其中最後的+5爲偏置項參數的個數。
假設使用wx,y,zi 來表示對於輸出單位節點矩陣中的第i個節點,過濾器輸入節點(x,y,z)的權重,使用bi 表示第i個輸出節點對應的偏置項參數,那麼單位矩陣中的第i個節點的取值g(i)爲:

g(i)=f(x=12y=12z=13ax,y,zwx,y,zi+bi)

其中ax,y,z 爲過濾器中節點(x,y,z)的取值,f爲激活函數。

在下圖中,展示了在3×3矩陣上使用2×2過濾器的卷積層前向傳播過程。在這個過程中,首先將這個過濾器用於左上角子矩陣,然後移動到左下角矩陣,再到右上角矩陣,最後到右下角矩陣。過濾器每移動一次,可以計算得到一個值,將這些數值拼接成一個新的矩陣,就完成了卷積層前向傳播的過程。

當過濾器的大小不爲1×1時,卷積層前向傳播得到的矩陣的尺寸要小於當前層矩陣的尺寸。爲了避免尺寸的變化,可以在當前層矩陣的邊界上加入全0填充。這樣可以使得卷積層前向傳播結果矩陣的大小和當前層矩陣保持一致。下圖中顯示了使用全0填充後卷積層前向傳播過程示意圖。

除了使用全0填充,還可以通過設置過濾器移動的步長來調整結果矩陣的大小。下圖中顯示了當移動步長爲2且使用全0填充時,卷積層前向傳播的結果。

下面的公式給出了在同時使用全0填充時結果矩陣的大小。

如果不使用全0填充,下面公式給出了結果矩陣的大小。

在卷積神經網絡中,每一個卷積層中使用的過濾器中的參數都是一樣的。這是卷積神經網絡一個非常重要的性質。比如,輸入層矩陣的維度是32×32×3,第一層卷積層使用尺寸爲5×5,深度爲16的過濾器,那麼這個卷積層的參數個數爲5×5×3×16+16=1216個。
共享每一個卷積層中過濾器中的參數可以巨幅減少神經網絡上的參數。卷積層的參數個數要遠遠小於全連接層,而且卷積層的參數個數和圖片的大小無關,它只和過濾器的尺寸、深度以及當前層節點矩陣的深度有關,這使得卷積神經網絡可以很好的擴展到更大的圖像數據上。
下面的程序實現了一個卷積層的前向傳播過程
#通過tf.get_variable的方式創建過濾器的權重變量和偏置項變量。上面介紹了卷積層的參數個數只和過濾器的尺寸、深度以及當前層節點矩陣的深度有關,所以這裏聲明的參數變量是一個四維矩陣,前面兩個維度代表了過濾器的尺寸,第三個維度表示當前層的深度,第四個維度表示過濾器的深度。
filter_weight = tf.get_variable('weights', [5, 5, 3, 16], initializer=tf.truncated_normal_initializer(stddev=0.1))
#和卷積層的權重類似,當前層矩陣上不同位置的偏置項也是共享的,所以總共有下一層深度個不同的偏置項。
biases = tf.get_variable('biases', [16], initializer=tf.constant_initializer(0.1))

#tf.nn.conv2d提供了一個非常方便的函數來實現卷積層前向傳播的算法。這個函數的第一個輸入爲當前層的節點矩陣。注意這個矩陣是一個四維矩陣,後面三個維度對應一個節點矩陣,第一維對應一個輸入batch。比如在輸入層,input[0,:,:,:]表示第一張圖片,input[1,:,:,:]表示第二張圖片,以此類推。tf.nn.conv2d第二個參數提供了卷積層的權重,第三個參數爲不同維度上的步長。雖然第三個參數提供的是一個長度爲4的數組,但是第一維和最後一維的數字要求一定是1。這是因爲卷積層的步長只對矩陣的長和寬有效。最後一個參數是填充的方法,TensorFlow提供了SAME或VALID兩種選擇,其中SAME表示添加全0填充,VALID表示不添加
conv = tf.nn.conv2d(input, filter_weight, strides=[1, 1, 1, 1], padding='SAME')
#tf.nn.bias_add提供了一個方便的函數給每一個節點加上偏置項。注意這裏不能直接使用加法,因爲矩陣上不同位置上的節點都需要加上同樣的偏置項。
bias = tf.nn.bias_add(conv, biases)
actived_conv = tf.nn.relu(bias)#將計算結果通過ReLU激活函數完成去線性化

池化層

池化層可以非常有效的縮小矩陣的尺寸,從而減少最後全連接層中的參數。使用池化層既可以加快計算速度也有防止過擬合問題的作用。
池化層前向傳播的過程也是通過移動一個類似過濾器的結構完成的。不過池化層過濾器中的計算不是節點的加權和,而是採用更加簡單的最大值或者平均值運算。使用最大值操作的池化層被稱之爲最大池化層max pooling,這是被使用得最多的池化層結構。使用平均值操作的池化層被稱之爲平均池化層average pooling。
池化層的過濾器也需要人工設定過濾器的尺寸、是否使用全0填充以及過濾器移動的步長等設置。唯一的區別在於卷積層使用的過濾器是橫跨整個深度的,而池化層使用的過濾器隻影響一個深度上的節點。所以池化層的過濾器除了在長和寬兩個維度移動之外,它還需要在深度這個維度移動。

下面的TensorFlow程序實現了最大池化層的前向傳播算法

#tf.nn.max_pool實現了最大池化層的前向傳播過程,它的參數和tf.nn.conv2d函數類似。ksize提供了過濾器的尺寸,strides提供了步長信息,padding提供了是否使用全0填充。
pool = tf.nn.max_pool(actived_conv, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME')

在tfnn.max_pool函數中,首先需要傳入當前層的節點矩陣,這個矩陣是一個四維矩陣,格式和tf.nn.conv2d函數中的第一個參數一致。第二個參數爲過濾器尺寸。雖然給出的是一個長度爲4的一維數組,但這個數組的第一個和最後一個數必須是1.這意味着池化層的過濾器是不可以跨不同輸入樣例或者節點矩陣深度的。在實際應用中使用得最多的池化層過濾器尺寸爲[1,2,2,1]或者[1,3,3,1]。
TensorFlow還提供了tf.nn.avg_pool來實現平均池化層,調用格式和tf.nn.max_pool函數是一致的。

經典卷積網絡模型

LeNet-5模型


第一層,卷積層
這一層的輸入就是原始的圖像像素,LeNet-5模型接收的輸入層大小爲32×32×1.第一個卷積層過濾器尺寸爲5×5,深度爲6,不使用全0填充,步長爲1。
第二層,池化層
這一層的輸入爲第一層的輸出,是一個28×28×6的節點矩陣。過濾器大小爲2×2,長和寬的步長均爲2.
第三層,卷積層
本層的輸入矩陣大小爲14×14×6,使用的過濾器大小爲5×5,深度爲16,不使用全0填充,步長爲1.
第四層,池化層
本層的輸入矩陣大小爲10×10×16,採用的過濾器大小爲2×2,步長爲2.
第五層,全連接層
本層的輸入矩陣大小爲5×5×16,在LeNet5模型的論文中將這一層稱爲卷積層,但因爲過濾器的大小就是5×5,所以和全連接層沒有區別。
第六層,全連接層
本層的輸入節點個數爲120個,輸出節點個數爲84個。
第七層,全連接層
本層的輸入節點個數爲84個,輸出節點個數爲10個。

通過TensorFlow訓練卷積神經網絡的過程和第五章中介紹的訓練全連接神經網絡是完全一樣的。唯一的區別在於因爲卷積神經網絡的輸入層爲一個三維矩陣,所以需要調整一下輸入數據的格式:

x = tf.plackholder(tf.float32, [BATCH_SIZE, mnist_inference.IMAGE_SIZE, mnist_inference.IMAGE_SIZE, mnist_inference.NUM_CHANNELS], name='x-input')#第一維表示一個batch中樣例的個數,第二維和第三維表示圖片的尺寸。第四維表示圖片的深度,對於RBG格式的圖片,深度爲5
#類似的將輸入的訓練數據格式調整爲一個四維矩陣,並將這個調整後的數據傳入sess.run過程
reshaped_xs = np.reshape(BATCH_SIZE, mnist_inference.IMAGE_SIZE, mnist_inference.IMAGE_SIZE, mnist_inference.NUM_CHANNELS))

調整完輸入格式後,修改mnist_inference.py程序如下。

# -*- coding: utf-8 -*-
import tensorflow as tf

INPUT_NODE = 784
OUTPUT_NODE = 10
IMAGE_SIZE = 28
NUM_CHANNELS = 1
NUM_LABELS = 10
#第一層卷積層的尺寸和深度
CONV1_DEEP = 32
CONV1_SIZE = 5
#第二層卷積層的尺寸和深度
CONV2_DEPP = 64
CONV2_SIZE = 5
#全連接層的節點個數
FC_SIZE = 512
#定義卷積神經網絡的前向傳播過程,這裏的新的參數train用於區分訓練過程和測試過程。在這個程序中將用到dropout方法,dropout可以進一步提升模型可靠性並且防止過擬合,dropout過程只在訓練時使用。
def inference(input_tensor, train, regularizer):
    with tf.variable_scope('layer1-conv1'):
        conv1_weights = tf.get_variable("weight", [CONV1_SIZE, CONV1_SIZE, NUM_CHANNELS, CONV1_DEEP], initializer=tf.truncated_normal_initializer(stddev=0.1))
        conv1_biases = tf.get_variable("bias", [CONV1_DEEP], initializer=tf.constant_initializer(0.0))
        conv1 = tf.nn.conv2d(input_tensor, conv1_weights, strides=[1, 1, 1, 1], padding='SAME')
        relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_biases))

    with tf.name_scope('layer2-pool1'):
        pool1 = tf.nn.max_pool(relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

    with tf.variable_scope('layer3-conv2'):
        conv2_weights = tf.get_variable("weight", [CONV2_SIZE, CONV2_SIZE, CONV1_DEEP, CONV2_DEEP], initializer=tf.truncated_normal_initializer(stddev=0.1))
        conv2_biases = tf.get_variable("bias", [CONV2_DEEP], initializer=tf.constant_initializer(0.0))
        conv2 = tf.nn.conv2d(pool1, conv2_weights, strides=[1, 1, 1, 1], padding="SAME")
        relu2 = tf.nn.relu(tf.nn.bias_add(conv2, covn2_biases)

    with  tf.name_scope('layer4-pool2'):
        pool2 = tf.nn.max_pool(relu2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
    #將第四層池化層的輸出轉化爲第五層全連接層的輸入格式。第四層的輸出爲7×7×64的矩陣,然而第五層全連接層需要的輸入格式爲向量,所以在這裏需要將這個7×7×64的矩陣拉直成一個向量。pool2.get_shape函數可以得到第四層輸出矩陣的維度而不需要手工計算。注意因爲每一層神經網絡的輸入輸出都爲一個batch的矩陣,所以這裏得到的維度也包含了一個batch中數據的個數。
    pool_shape = pool2.get_shape().as_list()#把得到的維度變成列表形式
    #計算將矩陣拉直成向量之後的長度,這個長度就是矩陣長寬及深度的乘積,注意這裏的pool_shape[0]爲一個batch中數據的個數
    nodes = pool_shape[1] * pool_shape[2] * pool_shape[3]
    #通過tf.reshape函數將第四層的輸出變成一個batch的向量
    reshaped = tf.reshape(pool2, [pool_shape[0], nodes])
    #接下來是第五層全連接層的變量並實現前向傳播過程。這一層和之前全連接網絡基本一致,唯一的區別就是引入了dropout概念。dropout在訓練時會隨機將部分節點的輸出改爲0,可以避免過擬合問題,從而使得模型在測試數據上的效果更好。dropout一般只在全連接層而不是卷積層或者池化層使用。
    with tf.variable_scope('layer5-fc1'):
        fc1_weights = tf.get_variable("weight", [nodes, FC_SIZE], initializer=tf.truncated_normal_initializer(stddev=0.1))
        #只有全連接層的權重需要加入正則化
        if regularizer != None:
            tf.add_to_collection('losses', regularizer(fc1_weights))
        fc1_biases = tf.get_variable("bias", [FC_SIZE], initializer=tf.constant_initializer(0.1))
        fc1 = tf.nn.relu(tf.matmul(reshaped, fc1_weights) + fc1_biases)
        if train:
            fc1 = tf.nn.dropout(tc1, 0.5)

    with tf.variable_scope('layer6-fc2'):
        fc2_weights = tf.get_variable("weight", [FC_SIZE, NUM_LABELS], initializer=tf.truncated_normal_initializer(stddev=0.1))
        if regularizer != None:
            tf.add_to_collection('losses', regularizer(fc2_weights))
        fc2_biases = tf.get_variable("bias", [NUM_LABELS], initializer=tf.constant_initializer(0.1))
        logit = tf.matmul(fc1, fc2_weights) + fc2_biases

    return logit

同樣的,可以修改全連接網絡中的mnist_eval程序的輸入部分,就可以測試卷積神經網絡在MNIST數據集上的正確率,最終結果大約是99.4%,比全連接的98.4%要好得多。
然而一種卷積神經網絡架構不能解決所有問題,比如LeNet5模型就無法很好的處理類似ImageNet這樣比較大的圖像數據集,下面的正則表達式公式總結了一些經典的用於圖片分類問題的卷積神經網絡架構:
輸入層 ->(卷積層+->池化層?)+ -> 全連接層+
卷積層+ 表示一層或者多層卷積層,大部分卷積神經網絡中一般最多連續使用三層卷積層。“池化層?”表示沒有或者一層池化層。池化層雖然可以起到減少參數防止過擬合問題,但是在部分論文中也發現可以直接通過調整卷積層步長來完成,所以有些卷積神經網絡中沒有池化層(但我目前沒有找到這種)。在多輪卷積層和池化層之後,卷積神經網絡在輸出之前一般會經過1-2個全連接層。
在過濾器的深度上,大部分卷積神經網絡都採用逐層遞增的方式。池化層的配置相對簡單,用的最多的是最大池化層。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章