AlexNet 網絡詳解及Tensorflow實現源碼

版權聲明:本文爲博主原創文章,未經博主容許不得轉載。html

1. 圖片數據處理

一張圖片是由一個個像素組成,每一個像素的顏色經常用RGB、HSB、CYMK、RGBA等顏色值來表示,每一個顏色值的取值範圍不同,但都表明了一個像素點數據信息。對圖片的數據處理過程當中,RGB使用得最多,RGB表示紅綠藍三通道色,取值範圍爲0~255,因此一個像素點能夠把它看做是一個三維數組,即:array([[[0, 255, 255]]]),三個數值分佈表示R、G、B(紅、綠、藍)的顏色值。好比下圖一張3*3大小的jpg格式的圖片:python

2017-10-08-13-48-58

它的圖片通過Tensorflow解碼後,數據值輸出爲git

image_path = 'images/image.jpg'
filename_queue = tf.train.string_input_producer(tf.train.match_filenames_once(image_path))
image_reader = tf.WholeFileReader()
_,image_file = image_reader.read(filename_queue)
image = tf.image.decode_jpeg(image_file)  # 若是是png格式的圖片,使用tf.image.decode_png()
sess.run(image)


--result

array([[[0, 0, 0], [255, 255, 255], [254, 0, 0]],
       [[0, 191, 0], [3, 108, 233], [0, 191, 0]],
       [[254, 0, 0], [255, 255, 255], [0, 0, 0]])

圖片的數據處理不只僅就是把RGB值轉換成運算須要的值,還包括調整圖片大小、圖片截取、圖片翻轉、圖片色彩調整,標註框、多線程處理圖片等等,在這裏就不一一介紹了,可是對於圖片的處理是進行卷積網絡的首要任務,你須要瞭解,並學會對圖片的相關操做。這裏只介紹RGB值的轉換,爲下一節的卷積提供數據支持。
github

2. 卷積神經網絡

卷積神經網絡(CNN)的基本架構一般包括卷積層,池化層,全鏈層三大層次,其中不一樣的層中可能還會包括一些非線性變化(RELU函數)、數據歸一化處理、dropoout等。咱們常據說的LeNet-五、AlexNet、VGG、ResNet等都是卷積神經網絡,並且都是由這些層組成,只是每一個網絡的層數不同,所達到的分類效果也不同。
算法

2.1. 卷積層

卷積層是整個神經網絡中最重要的一層,該層最核心的部分爲過濾器,或者稱爲卷積核,卷積核有大小和深度兩個屬性,大小經常使用的有3X三、5X5,也有11X11的卷積核,而深度通俗一點理解就是卷積核的個數。卷積核的大小和深度均由人工指定,而權重參數則在初始化的時候由程序隨機生成,並在後期訓練過程當中不斷優化這些權重值,以達到最好的分類效果。卷積的過程就是用這些權重值不斷的去乘這些圖片的RGB值,以提取圖片數據信息。下面的動圖完美地詮釋了卷積是怎麼發生的:
2017-10-08-15-05-31
上面黃色3X3大小不停移動的就是卷積核,綠色部分是5X5的輸入矩陣,粉色部分是卷積後的結果,稱做特徵值。從上面動圖看出,卷積不只提取了圖片信息,也能夠達到降維效果。若是但願卷積後的特徵值維度和原圖片一致,須要設置padding值(全零填充)爲SAME(若是爲VALID表示不填充),其中i爲輸入圖片,k爲卷積核大小,strides爲移動步長(移動步長>1也能夠達到降維的效果)。數組

tf.nn.conv2d(i, k,strides,padding='VALID')

在卷積層中,過濾器中的參數是共享的,即一個過濾器中的參數值在對全部圖片數據進行卷積過程當中保持不變,這樣卷積層的參數個數就和圖片大小無關,它只和過濾器的尺寸,深度,以及當前層節點的矩陣深度有關。好比,以手寫圖片爲例,輸入矩陣的維度是28X28X1,假設第一層卷積層使用的過濾器大小爲5X5,深度爲16,則該卷積層的參數個數爲5X5X1X16+16=416個,而若是使用500個隱藏節點的全鏈層會有1.5百萬個參數,相比之下,卷積層的參數個數遠遠小於全鏈層,這就是爲何卷積網絡普遍用於圖片識別上的緣由。markdown

對於卷積後的矩陣大小,有一個計算公式,若是使用了全0填充,則卷積後的矩陣大小爲:
\[out_{length}=\left \lceil in_{length}/stride_{length} \right \rceil\]網絡

\[out_{width}=\left \lceil in_{width}/stride_{width} \right \rceil\]
即輸出矩陣的長等於輸入矩陣長度除以長度方向上的步長,並向上取整數值;輸出矩陣的寬度等於輸入矩陣的寬度除以寬度方向上的步長,並向上取整數值。
若是不使用全0填充,則輸出矩陣的大小爲:
\[out_{length}=\left \lceil (in_{length}-filter_{length}+1)/stride_{length} \right \rceil\]session

\[out_{width}=\left \lceil (in_{width}-filter_{width}+1)/stride_{width} \right \rceil\]
卷積計算完成後,每每會加入一個修正線性單元ReLU函數,也就是把數據非線性化。爲何要把數據進行非線性化呢,這是由於非線性表明了輸入和輸出的關係是一條曲線而不是直線,曲線可以刻畫輸入中更爲複雜的變化。好比一個輸入值大部分時間都很穩定,但有可能會在某個時間點出現極值,可是經過ReLU函數之後,數據變得平滑,這樣以便對複雜的數據進行訓練。
ReLU是分段線性的,當輸入爲非負時,輸出將與輸入相同;而當輸入爲負時,輸出均爲0。它的優勢在於不受「梯度消失」的影響,且取值範圍爲[0,+∞];其缺點在於當使用了較大的學習速率時,易受達到飽和的神經元的影響。多線程

2017-10-09-15-39-32

2.2. 池化層

卷積層後通常會加入池化層,池化層能夠很是有效地縮小矩陣的尺寸,從而減小最後全鏈層中的參數,使用池化層既能夠加快計算速度也有防止過擬合問題的做用。
池化層也存在一個過濾器,可是過濾器對於輸入的數據的處理並非像卷積覈對輸入數據進行節點的加權和,而只是簡單的計算最大值或者平均值。過濾器的大小、是否全0填充、步長等也是由人工指定,而深度跟卷積核深度不同,卷積層使用過濾器是橫跨整個深度的,而池化層使用的過濾器隻影響一個深度上的節點,在計算過程當中,池化層過濾器不只要在長和寬兩個維度移動,還要在深度這個維度移動。使用最大值操做的池化層被稱之爲最大池化層,這種池化層使用得最多,使用平均值操做的池化層被稱之爲平均池化層,這種池化層的使用相對要少一點。
如下動圖能夠看到最大值池化層的計算過程:

2017-10-09-15-58-41

Tensorflow程序很容易就能夠實現最大值池化層的操做:

pool = tf.nn.max_pool(i, ksize=[1,3,3,1], stride=[1,2,2,1], padding='SAME')

# i爲輸入矩陣
# ksize爲過濾器尺寸,其中第一個和第四個值必須爲1,表示過濾器不能夠垮不一樣的輸入樣列和節點矩陣深度。中間的兩個值爲尺寸,常使用2*2或3*3。
# stride爲步長,第一個值和第四個值與ksize同樣
# padding爲全0填充,‘SAME’表示使用全0填充,‘VALID’表示不使用全0填充

2.3. 全鏈層

在KNN或線性分類中有對數據進行歸一化處理,而在神經網絡中,也會作數據歸一化的處理,緣由和以前的同樣,避免數據值大的節點對分類形成影響。歸一化的目標在於將輸入保持在一個可接受的範圍內。例如,將輸入歸一化到[0.0,1.0]區間內。在卷積神經網絡中,對數據歸一化的處理咱們有可能放在數據正式輸入到全鏈層以前或以後,或其餘地方,每一個網絡均可能不同。

全鏈層的做用就是進行正確的圖片分類,不一樣神經網絡的全鏈層層數不一樣,但做用確是相同的。輸入到全鏈層的神經元個數經過卷積層和池化層的處理後大大的減小了,好比以AlexNet爲例,一張227*227大小,顏色通道數爲3的圖片通過處理後,輸入到全鏈層的神經元個數有4096個,最後softmax的輸出,則能夠根據實際分類標籤數來定。

在全鏈層中,會使用dropout以隨機的去掉一些神經元,這樣可以比較有效地防止神經網絡的過擬合。相對於通常如線性模型使用正則的方法來防止模型過擬合,而在神經網絡中Dropout經過修改神經網絡自己結構來實現。對於某一層神經元,經過定義的機率來隨機刪除一些神經元,同時保持輸入層與輸出層神經元的我的不變,而後按照神經網絡的學習方法進行參數更新,下一次迭代中,從新隨機刪除一些神經元,直至訓練結束。
2017-10-09-16-57-39

3. AlexNet

AlexNet是2012年ILSVRC比賽的冠軍,它的出現直接打破了沉寂多年的圖片識別領域(在1998年出現LeNet-5網絡一直佔據圖片識別的領頭地位),給該領域帶來了新的契機,並一步步發展至今,甚至戰勝了人類的識別精確度,惋惜的是2017年的ILSVRC舉辦方宣佈從2018年起將取消該比賽,由於目前的神經網絡精確度已經達到跟高的程度了。但深度學習的步伐不會中止,人們將在其餘方面進行深刻的研究。

AlexNet是神經網絡之父Hinton的學生Alex Krizhevsky開發完成,它總共有8層,其中有5個卷積層,3個全鏈層,附上最經典的AlexNet網絡架構圖,以下。Alex在他的論文中寫到,他在處理圖片的時候使用了兩個GPU進行計算,所以,從圖中看出,在卷積過程當中他作了分組的處理,可是因爲硬件資源問題,咱們作的Alex網絡是使用一個CPU進行計算的,但原理和他的同樣,只是計算速度慢一點而已,對於大多數沒有性能優良的GPU的人來講,用咱們搭建好的網絡,徹底可使用家用臺式機進行訓練。
2017-10-09-17-25-04

Alex在論文中寫到他使用的輸入圖片大小爲224 X 224 X 3,但咱們使用的圖片尺寸爲227 X 227 X 3,這個沒有太大影響。AlexNet網絡分爲8層結構,前5層其實不徹底是卷積層,有些層還加入了池化層,並對數據進行標準化處理。下面簡要介紹一下每一層:

第一層

卷積核 深度 步長
11 * 11 96 4 * 4
池化層過濾器 步長
3 * 3 2 * 2

第一層包含了卷積層、標準化操做和池化層,其中卷積層和池化層的參數在上表已給出。在Tensorflow中,搭建的部分代碼程序爲:

# 1st Layer: Conv (w ReLu) -> Lrn -> Pool
conv1 = conv(X, 11, 11, 96, 4, 4, padding='VALID', name='conv1')
norm1 = lrn(conv1, 2, 2e-05, 0.75, name='norm1')
pool1 = max_pool(norm1, 3, 3, 2, 2, padding='VALID', name='pool1')

第二層

卷積核 深度 步長
5 * 5 256 1 * 1
池化層過濾器 步長
3 * 3 2 * 2

第二層實際也包含了卷積層、標準化操做和池化層,其中卷積層和池化層的參數在上表已給出。在Tensorflow中,搭建的部分代碼程序爲:

# 2nd Layer: Conv (w ReLu)  -> Lrn -> Pool with 2 groups
conv2 = conv(pool1, 5, 5, 256, 1, 1, groups=2, name='conv2')
norm2 = lrn(conv2, 2, 2e-05, 0.75, name='norm2')
pool2 = max_pool(norm2, 3, 3, 2, 2, padding='VALID', name='pool2')

第三層

卷積核 深度 步長
3 * 3 384 1 * 1

第三層僅有一個卷積層,卷積核的相關信息如上表,在Tensorflow中的部分代碼爲:

# 3rd Layer: Conv (w ReLu)
conv3 = conv(pool2, 3, 3, 384, 1, 1, name='conv3')

第四層

卷積核 深度 步長
3 * 3 384 1 * 1

第四層僅有一個卷積層,卷積核的相關信息如上表,該層與第三層很類似,只是把數據分紅了2組進行處理,在Tensorflow中的部分代碼爲:

# 4th Layer: Conv (w ReLu) splitted into two groups
conv4 = conv(conv3, 3, 3, 384, 1, 1, groups=2, name='conv4')

第五層

卷積核 深度 步長
3 * 3 256 1 * 1
池化層過濾器 步長
3 * 3 2 * 2

第五層是最後一層卷積層,包含一個卷積層和一個池化層,卷積核和池化層過濾器的相關信息如上表,該層仍然把數據分紅了2組進行處理,在Tensorflow中的部分代碼爲:

# 5th Layer: Conv (w ReLu) -> Pool splitted into two groups
conv5 = conv(conv4, 3, 3, 256, 1, 1, groups=2, name='conv5')
pool5 = max_pool(conv5, 3, 3, 2, 2, padding='VALID', name='pool5')

第六層

第六層是全鏈層,卷積層輸出的數據一共有4096個神經元,在進入第六層全鏈層後,首先作了數據的平滑處理,並隨機刪除了一些神經元,在Tensorflow中的部分代碼爲:

# 6th Layer: Flatten -> FC (w ReLu) -> Dropout
flattened = tf.reshape(pool5, [-1, 6*6*256])
fc6 = fc(flattened, 6*6*256, 4096, name='fc6')
dropout6 = dropout(fc6, self.KEEP_PROB)

第七層

第七層是全鏈層,也會作dropout處理,在Tensorflow中的部分代碼爲:

# 7th Layer: FC (w ReLu) -> Dropout
fc7 = fc(dropout6, 4096, 4096, name='fc7')
dropout7 = dropout(fc7, self.KEEP_PROB)

第八層

第八層是全鏈層,在最後softmax函數輸出的分類標籤是根據實際分類狀況來定義的,可能有2種,可能10種,可能120種等等,在Tensorflow中的部分代碼爲:

# 8th Layer: FC and return unscaled activations
self.fc8 = fc(dropout7, 4096, self.NUM_CLASSES, relu=False, name='fc8')

4. 用Tensorflow搭建完整的AlexNet

在搭建完整的AlexNet以前,須要作一些準備工做,以方便後期作訓練的時候觀測網絡的運行狀況。首先就是配置Tensorboard,Tensorboard是一款可視化工具,能夠用它來展示你的TensorFlow圖像,繪製圖像生成的定量指標圖,觀察loss函數的收斂狀況,網絡的精確度,以及附加數據等等,具體如何配置,網上也有不少講解,這裏就不詳細講述了;另外就是準備數據,imageNet官網上有不少圖片數據能夠供你們無償使用,官網地址:http://image-net.org/download-images 。網上還有不少無償使用的爬蟲能夠去爬取數據,總之,數據是訓練的根本,在網絡搭建好以前最好準備充分。準備好的數據放入當前訓練項目的根目錄下。

爲了讓各類需求的人可以複用AlexNet,咱們在Python類中定義了AlexNet,並把接口暴露出來,須要使用的人根據本身的狀況調用網絡,並輸入數據以及分類標籤個數等信息就能夠開始訓練數據了。要使用搭建好的網絡進行訓練,不只僅要利用網絡,更是須要網絡中的各項權重參數和偏置來達到更好的分類效果,目前,咱們使用的是別人已經訓練好的參數,全部的參數數據存放在bvlc_alexnet.npy這個文件中,下載地址爲:http://www.cs.toronto.edu/~guerzhoy/tf_alexnet/ ,下載後放入當前訓練項目的根目錄下便可。若是你有充分的時間和優越的硬件資源,你也能夠本身訓練參數,並把這些參數存儲起來供之後使用,可是該bvlc_alexnet.npy文件中的參數是imageNet訓練好了的,使用這些參數訓練的模型精確度比咱們以前訓練的要高。
在Tensorflow中,定義加載參數的程序代碼以下,默認的參數就是bvlc_alexnet.npy中存儲的權重和偏置值。

def load_initial_weights(self, session):

    """Load weights from file into network."""

    # Load the weights into memory
    weights_dict = np.load(self.WEIGHTS_PATH, encoding='bytes').item()

    # Loop over all layer names stored in the weights dict
    for op_name in weights_dict:

        # Check if layer should be trained from scratch
        if op_name not in self.SKIP_LAYER:

            with tf.variable_scope(op_name, reuse=True):

                # Assign weights/biases to their corresponding tf variable
                for data in weights_dict[op_name]:

                    # Biases
                    if len(data.shape) == 1:
                        var = tf.get_variable('biases', trainable=False)
                        session.run(var.assign(data))

                    # Weights
                    else:
                        var = tf.get_variable('weights', trainable=False)
                        session.run(var.assign(data))

在上一節講述AlexNet的架構的時,曾出現過數據分組處理,這裏用程序來描述一下在一個CPU狀況下,如何把數據進行分組處理。數據的分組處理都在卷積層中發生,所以首先一個卷積函數,因爲在第一層卷積沒有分組,因此在函數中須要作分組的判斷,若是沒有分組,輸入數據和權重直接作卷積運算;若是有分組,則把輸入數據和權重先劃分後作卷積運算,卷積結束後再用concat()合併起來,這就是分組的具體操做。

def conv(x, filter_height, filter_width, num_filters, stride_y, stride_x, name,padding='SAME', groups=1):
    """Create a convolution layer."""

    # Get number of input channels
    input_channels = int(x.get_shape()[-1])

    # Create lambda function for the convolution
    convolve = lambda i, k: tf.nn.conv2d(i, k,
                                         strides=[1, stride_y, stride_x, 1],
                                         padding=padding)

    with tf.variable_scope(name) as scope:
        # Create tf variables for the weights and biases of the conv layer
        weights = tf.get_variable('weights', shape=[filter_height,
                                                    filter_width,
                                                    input_channels/groups,
                                                    num_filters])
        biases = tf.get_variable('biases', shape=[num_filters])

    if groups == 1:
        conv = convolve(x, weights)

    # In the cases of multiple groups, split inputs & weights and
    else:
        # Split input and weights and convolve them separately
        input_groups = tf.split(axis=3, num_or_size_splits=groups, value=x)
        weight_groups = tf.split(axis=3, num_or_size_splits=groups,
                                 value=weights)
        output_groups = [convolve(i, k) for i, k in zip(input_groups, weight_groups)]

        # Concat the convolved output together again
        conv = tf.concat(axis=3, values=output_groups)

    # Add biases
    bias = tf.reshape(tf.nn.bias_add(conv, biases), tf.shape(conv))

    # Apply relu function
    relu = tf.nn.relu(bias, name=scope.name)

    return relu

對於AlexNet中池化層,全鏈層的代碼在alexnet.py已經所有定義好了,這裏就不一一列出來了。接着開始如何在Tensorflow中導入圖片,在圖片數據量大的狀況下,Tensorflow會建議把數據轉換成tfrecords文件,而後在導入到網絡中運算,這樣的好處是能夠加快計算速度,節約內存空間。但咱們沒有這樣作,由於在訓練網絡的時候咱們沒有發現轉換成tfrecords文件就明顯提升了計算速度,因此這裏直接把原生的圖片直接轉化成三維數據輸入到網絡中。這樣作代碼還要簡短一點,而圖片也是預先存儲在硬盤中,須要訓練的那一部分再從硬盤中讀取到內存中,並無浪費內存資源。

在Python類中定義圖片生成器,須要的參數有圖片URL,實際的標籤向量和標籤個數,batch_size等。首先打亂整個訓練集圖片的順序,由於圖片名多是按照某種規律來定義的,打亂圖片順序能夠幫助咱們更好的訓練網絡。完成這一步後就能夠把圖片從RGB色轉換成BRG三維數組。

class ImageDataGenerator(object):
    def __init__(self, images, labels, batch_size, num_classes, shuffle=True, buffer_size=1000):

        self.img_paths = images
        self.labels = labels
        self.num_classes = num_classes
        self.data_size = len(self.labels)
        self.pointer = 0

        # 打亂圖片順序
        if shuffle:
            self._shuffle_lists()
        
        self.img_paths = convert_to_tensor(self.img_paths, dtype=dtypes.string)
        self.labels = convert_to_tensor(self.labels, dtype=dtypes.int32)

        data = Dataset.from_tensor_slices((self.img_paths, self.labels))
        data = data.map(self._parse_function_train, num_threads=8,
                        output_buffer_size=100 * batch_size)

        data = data.batch(batch_size)

        self.data = data

    
    """打亂圖片順序"""
    def _shuffle_lists(self):
        path = self.img_paths
        labels = self.labels
        permutation = np.random.permutation(self.data_size)
        self.img_paths = []
        self.labels = []
        for i in permutation:
            self.img_paths.append(path[i])
            self.labels.append(labels[i])


    """把圖片生成三維數組,以及把標籤轉化爲向量"""
    def _parse_function_train(self, filename, label):
        one_hot = tf.one_hot(label, self.num_classes)
        img_string = tf.read_file(filename)
        img_decoded = tf.image.decode_png(img_string, channels=3)
        img_resized = tf.image.resize_images(img_decoded, [227, 227])
        img_centered = tf.subtract(img_resized, VGG_MEAN)
        img_bgr = img_centered[:, :, ::-1]
        
        return img_bgr, one_hot

網絡搭建完成,數據準備就緒,最後就是開始訓練了。因爲網絡和圖片生成器是能夠複用的,在訓練圖片的時候須要用戶根據本身的實際狀況編寫代碼調用網絡和圖片生成器模塊,同時定義好損失函數和優化器,以及須要在Tensorboard中觀測的各項指標等等操做。下面一節咱們將開始進行網絡訓練。

5. 用AlexNet識別貓狗圖片

5.1. 定義分類

如上一節講的,datagenerator.py(圖片轉換模塊)和alexnet.py(AlexNet網絡模塊)已經搭建好了,你在使用的時候無需作修改。如今你只須要根據本身的分類需求編寫精調代碼,如finetune.py中所示。
假設有3萬張貓狗圖片訓練集和3000張測試集,它們大小不一。咱們的目的是使用AlexNet正確的分類貓和狗兩種動物,所以,類別標籤個數只有2個,並用0表明貓,1表明狗。若是你須要分類其餘的動物或者物品,或者anything,你須要標註好圖片的實際標籤,定義好圖片Tensorboard存放的目錄,以及訓練好的模型和參數的存放目錄等等。就像這樣:

import os
import numpy as np
import tensorflow as tf
from alexnet import AlexNet
from datagenerator import ImageDataGenerator
from datetime import datetime
import glob
from tensorflow.contrib.data import Iterator

learning_rate = 1e-4                   # 學習率
num_epochs = 100                       # 代的個數
batch_size = 1024                      # 一次性處理的圖片張數
dropout_rate = 0.5                     # dropout的機率
num_classes = 2                        # 類別標籤
train_layers = ['fc8', 'fc7', 'fc6']   # 訓練層,即三個全鏈層
display_step = 20                      # 顯示間隔次數

filewriter_path = "./tmp/tensorboard"  # 存儲tensorboard文件
checkpoint_path = "./tmp/checkpoints"  # 訓練好的模型和參數存放目錄

if not os.path.isdir(checkpoint_path): #若是沒有存放模型的目錄,程序自動生成
    os.mkdir(checkpoint_path)

接着調用圖片生成器,來生成圖片數據,並初始化數據:

train_image_path = 'train/'  # 指定訓練集數據路徑(根據實際狀況指定訓練數據集的路徑)
test_image_cat_path = 'test/cat/'  # 指定測試集數據路徑(根據實際狀況指定測試數據集的路徑)
test_image_dog_path = 'test/dog/'

label_path = []
test_label = []

# 打開訓練數據集目錄,讀取所有圖片,生成圖片路徑列表
image_path = np.array(glob.glob(train_image_path + 'cat.*.jpg')).tolist()
image_path_dog = np.array(glob.glob(train_image_path + 'dog.*.jpg')).tolist()
image_path[len(image_path):len(image_path)] = image_path_dog
for i in range(len(image_path)):
    if 'dog' in image_path[i]:
        label_path.append(1)
    else:
        label_path.append(0)

# 打開測試數據集目錄,讀取所有圖片,生成圖片路徑列表
test_image = np.array(glob.glob(test_image_cat_path + '*.jpg')).tolist()
test_image_path_dog = np.array(glob.glob(test_image_dog_path + '*.jpg')).tolist()
test_image[len(test_image):len(test_image)] = test_image_path_dog
for i in range(len(test_image)):
    if i < 1500:
        test_label.append(0)
    else:
        test_label.append(1)

# 調用圖片生成器,把訓練集圖片轉換成三維數組
tr_data = ImageDataGenerator(
    images=image_path,
    labels=label_path,
    batch_size=batch_size,
    num_classes=num_classes)

# 調用圖片生成器,把測試集圖片轉換成三維數組
test_data = ImageDataGenerator(
    images=test_image,
    labels=test_label,
    batch_size=batch_size,
    num_classes=num_classes,
    shuffle=False)

# 定義迭代器
iterator = Iterator.from_structure(tr_data.data.output_types,
                                   tr_data.data.output_shapes)
# 定義每次迭代的數據
next_batch = iterator.get_next()

# 初始化數據
training_initalize = iterator.make_initializer(tr_data.data)
testing_initalize = iterator.make_initializer(test_data.data)

訓練數據準備好之後,讓數據經過AlexNet。

x = tf.placeholder(tf.float32, [batch_size, 227, 227, 3])
y = tf.placeholder(tf.float32, [batch_size, num_classes])
keep_prob = tf.placeholder(tf.float32) # dropout機率


# 圖片數據經過AlexNet網絡處理
model = AlexNet(x, keep_prob, num_classes, train_layers)

# 定義咱們須要訓練的全連層的變量列表
var_list = [v for v in tf.trainable_variables() if v.name.split('/')[0] in train_layers]


# 執行整個網絡圖
score = model.fc8

接着固然就是定義損失函數,優化器。整個網絡須要優化三層全鏈層的參數,同時在優化參數過程當中,使用的是梯度降低算法,而不是反向傳播算法。

# 損失函數
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=score, labels=y))

# 定義須要精調的每一層的梯度
gradients = tf.gradients(loss, var_list)
gradients = list(zip(gradients, var_list))

# 優化器,採用梯度降低算法進行優化
optimizer = tf.train.GradientDescentOptimizer(learning_rate)

# 須要精調的每一層都採用梯度降低算法優化參數
train_op = optimizer.apply_gradients(grads_and_vars=gradients)

# 定義網絡精確度
with tf.name_scope("accuracy"):
    correct_pred = tf.equal(tf.argmax(score, 1), tf.argmax(y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

# 如下幾步是須要在Tensorboard中觀測loss的收斂狀況和網絡的精確度而定義的
tf.summary.scalar('cross_entropy', loss)
tf.summary.scalar('accuracy', accuracy)
merged_summary = tf.summary.merge_all()
writer = tf.summary.FileWriter(filewriter_path)
saver = tf.train.Saver()

最後,訓練數據:

# 定義一代的迭代次數
train_batches_per_epoch = int(np.floor(tr_data.data_size / batch_size))
test_batches_per_epoch = int(np.floor(test_data.data_size / batch_size))

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

    # 把模型圖加入Tensorboard
    writer.add_graph(sess.graph)

    # 把訓練好的權重加入未訓練的網絡中
    model.load_initial_weights(sess)

    print("{} Start training...".format(datetime.now()))
    print("{} Open Tensorboard at --logdir {}".format(datetime.now(),
                                                      filewriter_path))

    # 總共訓練100代
    for epoch in range(num_epochs):
        sess.run(iterator.make_initializer(tr_data.data))
        print("{} Epoch number: {} start".format(datetime.now(), epoch + 1))

        # 開始訓練每一代,一代的次數爲train_batches_per_epoch的值
        for step in range(train_batches_per_epoch):
            img_batch, label_batch = sess.run(next_batch)
            sess.run(optimizer, feed_dict={x: img_batch,
                                           y: label_batch,
                                           keep_prob: dropout_rate})
            if step % display_step == 0:
                s = sess.run(merged_summary, feed_dict={x: img_batch,
                                                        y: label_batch,
                                                        keep_prob: 1.})

                writer.add_summary(s, epoch * train_batches_per_epoch + step)

訓練完成後須要驗證模型的精確度,這個時候就得用上測試數據集了。

# 測試模型精確度
print("{} Start validation".format(datetime.now()))
sess.run(testing_initalize)
test_acc = 0.
test_count = 0

for _ in range(test_batches_per_epoch):
    img_batch, label_batch = sess.run(next_batch)
    acc = sess.run(accuracy, feed_dict={x: img_batch, y: label_batch, keep_prob: 1.0})
    test_acc += acc
    test_count += 1

test_acc /= test_count

print("{} Validation Accuracy = {:.4f}".format(datetime.now(), test_acc))

最後把訓練好的模型持久化。

# 把訓練好的模型存儲起來
print("{} Saving checkpoint of model...".format(datetime.now()))

checkpoint_name = os.path.join(checkpoint_path,'model_epoch' + str(epoch + 1) + '.ckpt')
save_path = saver.save(sess, checkpoint_name)

print("{} Epoch number: {} end".format(datetime.now(), epoch + 1))

到此爲止,一個完整的AlexNet就搭建完成了。在準備好訓練集和測試集數據後,下面咱們開始訓練網絡。

5.2. 訓練網絡

咱們總共訓練了100代,使用CPU計算進行計算,在臺式機上跑了一天左右,完成了3萬張圖片的訓練和3000張圖片的測試,網絡的識別精確度爲71.25%,這個結果不是很好,可能與數據量少有關係。若是你有上十萬張的數據集,再增長訓練次數,相信你網絡的精度應該比咱們訓練的還要好。下面看看網絡的計算圖,這是Tensorboard中記錄下的,經過該圖,你能夠對整個網絡的架構及運行一目瞭然。

2017-10-16-13-54-41

5.3. 驗證

網絡訓練好了之後,固然咱們想火燒眉毛的試試咱們網絡。首先咱們仍是得編寫本身的驗證代碼:

import tensorflow as tf
from alexnet import AlexNet             # import訓練好的網絡
import matplotlib.pyplot as plt

class_name = ['cat', 'dog']             # 自定義貓狗標籤


def test_image(path_image, num_class, weights_path='Default'):
    # 把新圖片進行轉換
    img_string = tf.read_file(path_image)
    img_decoded = tf.image.decode_png(img_string, channels=3)
    img_resized = tf.image.resize_images(img_decoded, [227, 227])
    img_resized = tf.reshape(img_resized, shape=[1, 227, 227, 3])
    
    # 圖片經過AlexNet
    model = AlexNet(img_resized, 0.5, 2, skip_layer='', weights_path=weights_path)
    score = tf.nn.softmax(model.fc8)
    max = tf.arg_max(score, 1)
    saver = tf.train.Saver()

    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        saver.restore(sess, "./tmp/checkpoints/model_epoch10.ckpt") # 導入訓練好的參數
        # score = model.fc8
        print(sess.run(model.fc8))
        prob = sess.run(max)[0]

        # 在matplotlib中觀測分類結果
        plt.imshow(img_decoded.eval())
        plt.title("Class:" + class_name[prob])
        plt.show()


test_image('./test/20.jpg', num_class=2) # 輸入一張新圖片

在網上任意下載10張貓狗圖片來進行驗證,有三張圖片識別錯誤(以下圖),驗證的精確度70%,效果不是很理想。可是若是你感興趣,你能夠下載咱們的代碼,用本身的訓練集來試試,

源碼地址爲:https://github.com/stephen-v/tensorflow_alexnet_classify

2017-10-18-10-16-50

2017-10-18-10-18-37

2017-10-18-10-19-57

2017-10-18-10-21-22

2017-10-18-10-23-09

2017-10-18-10-27-53

2017-10-18-10-26-36

2017-10-18-10-29-58

2017-10-18-10-33-15
2017-10-18-10-38-02

做者:帥蟲哥 出處: http://www.cnblogs.com/vipyoumay/p/7686230.html

相關文章
相關標籤/搜索