AlexNet 網絡詳解及Tensorflow實現源碼


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

1. 圖片數據處理

一張圖片是由一個個像素組成,每個像素的顏色常常用RGB、HSB、CYMK、RGBA等顏色值來表示,每個顏色值的取值范圍不一樣,但都代表了一個像素點數據信息。對圖片的數據處理過程中,RGB使用得最多,RGB表示紅綠藍三通道色,取值范圍為0~255,所以一個像素點可以把它看作是一個三維數組,即:array([[[0, 255, 255]]]),三個數值分布表示R、G、B(紅、綠、藍)的顏色值。比如下圖一張3*3大小的jpg格式的圖片:

2017-10-08-13-48-58

它的圖片經過Tensorflow解碼后,數據值輸出為

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值的轉換,為下一節的卷積提供數據支持。

2. 卷積神經網絡

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

2.1. 卷積層

卷積層是整個神經網絡中最重要的一層,該層最核心的部分為過濾器,或者稱為卷積核,卷積核有大小和深度兩個屬性,大小常用的有3X3、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百萬個參數,相比之下,卷積層的參數個數遠遠小於全鏈層,這就是為什么卷積網絡廣泛用於圖片識別上的原因。

對於卷積后的矩陣大小,有一個計算公式,如果使用了全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 \]

\[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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM