AlexNet 網(wǎng)絡(luò)詳解及Tensorflow實(shí)現(xiàn)源碼

大數(shù)據(jù)

作者:行動(dòng)派Xdpie

1. 圖片數(shù)據(jù)處理

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

大數(shù)據(jù)

它的圖片經(jīng)過Tensorflow解碼后,數(shù)據(jù)值輸出為

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)--resultarray([[[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]])

圖片的數(shù)據(jù)處理不僅僅就是把RGB值轉(zhuǎn)換成運(yùn)算需要的值,還包括調(diào)整圖片大小、圖片截取、圖片翻轉(zhuǎn)、圖片色彩調(diào)整,標(biāo)注框、多線程處理圖片等等,在這里就不一一介紹了,但是對(duì)于圖片的處理是進(jìn)行卷積網(wǎng)絡(luò)的首要任務(wù),你需要了解,并學(xué)會(huì)對(duì)圖片的相關(guān)操作。這里只介紹RGB值的轉(zhuǎn)換,為下一節(jié)的卷積提供數(shù)據(jù)支持。

2. 卷積神經(jīng)網(wǎng)絡(luò)

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

2.1. 卷積層

卷積層是整個(gè)神經(jīng)網(wǎng)絡(luò)中最重要的一層,該層最核心的部分為過濾器,或者稱為卷積核,卷積核有大小和深度兩個(gè)屬性,大小常用的有3X3、5X5,也有11X11的卷積核,而深度通俗一點(diǎn)理解就是卷積核的個(gè)數(shù)。卷積核的大小和深度均由人工指定,而權(quán)重參數(shù)則在初始化的時(shí)候由程序隨機(jī)生成,并在后期訓(xùn)練過程中不斷優(yōu)化這些權(quán)重值,以達(dá)到最好的分類效果。卷積的過程就是用這些權(quán)重值不斷的去乘這些圖片的RGB值,以提取圖片數(shù)據(jù)信息。下面的動(dòng)圖完美地詮釋了卷積是怎么發(fā)生的:

大數(shù)據(jù)
上面黃色3X3大小不停移動(dòng)的就是卷積核,綠色部分是5X5的輸入矩陣,粉色部分是卷積后的結(jié)果,稱作特征值。從上面動(dòng)圖看出,卷積不僅提取了圖片信息,也可以達(dá)到降維效果。如果希望卷積后的特征值維度和原圖片一致,需要設(shè)置padding值(全零填充)為SAME(如果為VALID表示不填充),其中i為輸入圖片,k為卷積核大小,strides為移動(dòng)步長(zhǎng)(移動(dòng)步長(zhǎng)>1也可以達(dá)到降維的效果)。

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

在卷積層中,過濾器中的參數(shù)是共享的,即一個(gè)過濾器中的參數(shù)值在對(duì)所有圖片數(shù)據(jù)進(jìn)行卷積過程中保持不變,這樣卷積層的參數(shù)個(gè)數(shù)就和圖片大小無關(guān),它只和過濾器的尺寸,深度,以及當(dāng)前層節(jié)點(diǎn)的矩陣深度有關(guān)。比如,以手寫圖片為例,輸入矩陣的維度是28X28X1,假設(shè)第一層卷積層使用的過濾器大小為5X5,深度為16,則該卷積層的參數(shù)個(gè)數(shù)為5X5X1X16+16=416個(gè),而如果使用500個(gè)隱藏節(jié)點(diǎn)的全鏈層會(huì)有1.5百萬個(gè)參數(shù),相比之下,卷積層的參數(shù)個(gè)數(shù)遠(yuǎn)遠(yuǎn)小于全鏈層,這就是為什么卷積網(wǎng)絡(luò)廣泛用于圖片識(shí)別上的原因。

對(duì)于卷積后的矩陣大小,有一個(gè)計(jì)算公式,如果使用了全0填充,則卷積后的矩陣大小為:

大數(shù)據(jù)

即輸出矩陣的長(zhǎng)等于輸入矩陣長(zhǎng)度除以長(zhǎng)度方向上的步長(zhǎng),并向上取整數(shù)值;輸出矩陣的寬度等于輸入矩陣的寬度除以寬度方向上的步長(zhǎng),并向上取整數(shù)值。
如果不使用全0填充,則輸出矩陣的大小為:

大數(shù)據(jù)

卷積計(jì)算完成后,往往會(huì)加入一個(gè)修正線性單元ReLU函數(shù),也就是把數(shù)據(jù)非線性化。為什么要把數(shù)據(jù)進(jìn)行非線性化呢,這是因?yàn)榉蔷€性代表了輸入和輸出的關(guān)系是一條曲線而不是直線,曲線能夠刻畫輸入中更為復(fù)雜的變化。比如一個(gè)輸入值大部分時(shí)間都很穩(wěn)定,但有可能會(huì)在某個(gè)時(shí)間點(diǎn)出現(xiàn)極值,但是通過ReLU函數(shù)以后,數(shù)據(jù)變得平滑,這樣以便對(duì)復(fù)雜的數(shù)據(jù)進(jìn)行訓(xùn)練。
ReLU是分段線性的,當(dāng)輸入為非負(fù)時(shí),輸出將與輸入相同;而當(dāng)輸入為負(fù)時(shí),輸出均為0。它的優(yōu)點(diǎn)在于不受“梯度消失”的影響,且取值范圍為[0,+∞];其缺點(diǎn)在于當(dāng)使用了較大的學(xué)習(xí)速率時(shí),易受達(dá)到飽和的神經(jīng)元的影響。

大數(shù)據(jù)

2.2. 池化層

卷積層后一般會(huì)加入池化層,池化層可以非常有效地縮小矩陣的尺寸,從而減少最后全鏈層中的參數(shù),使用池化層既可以加快計(jì)算速度也有防止過擬合問題的作用。
池化層也存在一個(gè)過濾器,但是過濾器對(duì)于輸入的數(shù)據(jù)的處理并不是像卷積核對(duì)輸入數(shù)據(jù)進(jìn)行節(jié)點(diǎn)的加權(quán)和,而只是簡(jiǎn)單的計(jì)算最大值或者平均值。過濾器的大小、是否全0填充、步長(zhǎng)等也是由人工指定,而深度跟卷積核深度不一樣,卷積層使用過濾器是橫跨整個(gè)深度的,而池化層使用的過濾器只影響一個(gè)深度上的節(jié)點(diǎn),在計(jì)算過程中,池化層過濾器不僅要在長(zhǎng)和寬兩個(gè)維度移動(dòng),還要在深度這個(gè)維度移動(dòng)。使用最大值操作的池化層被稱之為最大池化層,這種池化層使用得最多,使用平均值操作的池化層被稱之為平均池化層,這種池化層的使用相對(duì)要少一點(diǎn)。
以下動(dòng)圖可以看到最大值池化層的計(jì)算過程:

大數(shù)據(jù)

Tensorflow程序很容易就可以實(shí)現(xiàn)最大值池化層的操作:

pool = tf.nn.max_pool(i, ksize=[1,3,3,1], stride=[1,2,2,1], padding='SAME')# i為輸入矩陣# ksize為過濾器尺寸,其中第一個(gè)和第四個(gè)值必須為1,表示過濾器不可以垮不同的輸入樣列和節(jié)點(diǎn)矩陣深度。中間的兩個(gè)值為尺寸,常使用2*2或3*3。# stride為步長(zhǎng),第一個(gè)值和第四個(gè)值與ksize一樣# padding為全0填充,‘SAME’表示使用全0填充,‘VALID’表示不使用全0填充

2.3. 全鏈層

在KNN或線性分類中有對(duì)數(shù)據(jù)進(jìn)行歸一化處理,而在神經(jīng)網(wǎng)絡(luò)中,也會(huì)做數(shù)據(jù)歸一化的處理,原因和之前的一樣,避免數(shù)據(jù)值大的節(jié)點(diǎn)對(duì)分類造成影響。歸一化的目標(biāo)在于將輸入保持在一個(gè)可接受的范圍內(nèi)。例如,將輸入歸一化到[0.0,1.0]區(qū)間內(nèi)。在卷積神經(jīng)網(wǎng)絡(luò)中,對(duì)數(shù)據(jù)歸一化的處理我們有可能放在數(shù)據(jù)正式輸入到全鏈層之前或之后,或其他地方,每個(gè)網(wǎng)絡(luò)都可能不一樣。

全鏈層的作用就是進(jìn)行正確的圖片分類,不同神經(jīng)網(wǎng)絡(luò)的全鏈層層數(shù)不同,但作用確是相同的。輸入到全鏈層的神經(jīng)元個(gè)數(shù)通過卷積層和池化層的處理后大大的減少了,比如以AlexNet為例,一張227*227大小,顏色通道數(shù)為3的圖片經(jīng)過處理后,輸入到全鏈層的神經(jīng)元個(gè)數(shù)有4096個(gè),最后softmax的輸出,則可以根據(jù)實(shí)際分類標(biāo)簽數(shù)來定。

在全鏈層中,會(huì)使用dropout以隨機(jī)的去掉一些神經(jīng)元,這樣能夠比較有效地防止神經(jīng)網(wǎng)絡(luò)的過擬合。相對(duì)于一般如線性模型使用正則的方法來防止模型過擬合,而在神經(jīng)網(wǎng)絡(luò)中Dropout通過修改神經(jīng)網(wǎng)絡(luò)本身結(jié)構(gòu)來實(shí)現(xiàn)。對(duì)于某一層神經(jīng)元,通過定義的概率來隨機(jī)刪除一些神經(jīng)元,同時(shí)保持輸入層與輸出層神經(jīng)元的個(gè)人不變,然后按照神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)方法進(jìn)行參數(shù)更新,下一次迭代中,重新隨機(jī)刪除一些神經(jīng)元,直至訓(xùn)練結(jié)束。

大數(shù)據(jù)

3. AlexNet

AlexNet是2012年ILSVRC比賽的冠軍,它的出現(xiàn)直接打破了沉寂多年的圖片識(shí)別領(lǐng)域(在1998年出現(xiàn)LeNet-5網(wǎng)絡(luò)一直占據(jù)圖片識(shí)別的領(lǐng)頭地位),給該領(lǐng)域帶來了新的契機(jī),并一步步發(fā)展至今,甚至打敗了人類的識(shí)別精確度,可惜的是2017年的ILSVRC舉辦方宣布從2018年起將取消該比賽,因?yàn)槟壳暗纳窠?jīng)網(wǎng)絡(luò)精確度已經(jīng)達(dá)到跟高的程度了。但深度學(xué)習(xí)的步伐不會(huì)停止,人們將在其他方面進(jìn)行深入的研究。

AlexNet是神經(jīng)網(wǎng)絡(luò)之父Hinton的學(xué)生Alex Krizhevsky開發(fā)完成,它總共有8層,其中有5個(gè)卷積層,3個(gè)全鏈層,附上最經(jīng)典的AlexNet網(wǎng)絡(luò)架構(gòu)圖,如下。Alex在他的論文中寫到,他在處理圖片的時(shí)候使用了兩個(gè)GPU進(jìn)行計(jì)算,因此,從圖中看出,在卷積過程中他做了分組的處理,但是由于硬件資源問題,我們做的Alex網(wǎng)絡(luò)是使用一個(gè)CPU進(jìn)行計(jì)算的,但原理和他的一樣,只是計(jì)算速度慢一點(diǎn)而已,對(duì)于大多數(shù)沒有性能優(yōu)良的GPU的人來說,用我們搭建好的網(wǎng)絡(luò),完全可以使用家用臺(tái)式機(jī)進(jìn)行訓(xùn)練。

大數(shù)據(jù)

Alex在論文中寫到他使用的輸入圖片大小為224 X 224 X 3,但我們使用的圖片尺寸為227 X 227 X 3,這個(gè)沒有太大影響。AlexNet網(wǎng)絡(luò)分為8層結(jié)構(gòu),前5層其實(shí)不完全是卷積層,有些層還加入了池化層,并對(duì)數(shù)據(jù)進(jìn)行標(biāo)準(zhǔn)化處理。下面簡(jiǎn)要介紹一下每一層:

第一層

大數(shù)據(jù)

池化層過濾器步長(zhǎng)

3 * 32 * 2

第一層包含了卷積層、標(biāo)準(zhǔn)化操作和池化層,其中卷積層和池化層的參數(shù)在上表已給出。在Tensorflow中,搭建的部分代碼程序?yàn)椋?/p>

# 1st Layer: Conv (w ReLu) -> Lrn -> Poolconv1 = 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')

第二層

大數(shù)據(jù)

池化層過濾器步長(zhǎng)

3 * 32 * 2

第二層實(shí)際也包含了卷積層、標(biāo)準(zhǔn)化操作和池化層,其中卷積層和池化層的參數(shù)在上表已給出。在Tensorflow中,搭建的部分代碼程序?yàn)椋?/p>

# 2nd Layer: Conv (w ReLu)  -> Lrn -> Pool with 2 groupsconv2 = 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')

第三層

大數(shù)據(jù)

第三層僅有一個(gè)卷積層,卷積核的相關(guān)信息如上表,在Tensorflow中的部分代碼為:

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

第四層

大數(shù)據(jù)

第四層僅有一個(gè)卷積層,卷積核的相關(guān)信息如上表,該層與第三層很相似,只是把數(shù)據(jù)分成了2組進(jìn)行處理,在Tensorflow中的部分代碼為:

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

第五層

大數(shù)據(jù)

第五層是最后一層卷積層,包含一個(gè)卷積層和一個(gè)池化層,卷積核和池化層過濾器的相關(guān)信息如上表,該層仍然把數(shù)據(jù)分成了2組進(jìn)行處理,在Tensorflow中的部分代碼為:

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

第六層

第六層是全鏈層,卷積層輸出的數(shù)據(jù)一共有4096個(gè)神經(jīng)元,在進(jìn)入第六層全鏈層后,首先做了數(shù)據(jù)的平滑處理,并隨機(jī)刪除了一些神經(jīng)元,在Tensorflow中的部分代碼為:

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

第七層

第七層是全鏈層,也會(huì)做dropout處理,在Tensorflow中的部分代碼為:

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

第八層

第八層是全鏈層,在最后softmax函數(shù)輸出的分類標(biāo)簽是根據(jù)實(shí)際分類情況來定義的,可能有2種,可能10種,可能120種等等,在Tensorflow中的部分代碼為:

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

4. 用Tensorflow搭建完整的AlexNet

在搭建完整的AlexNet之前,需要做一些準(zhǔn)備工作,以方便后期做訓(xùn)練的時(shí)候觀測(cè)網(wǎng)絡(luò)的運(yùn)行情況。首先就是配置Tensorboard,Tensorboard是一款可視化工具,可以用它來展現(xiàn)你的TensorFlow圖像,繪制圖像生成的定量指標(biāo)圖,觀察loss函數(shù)的收斂情況,網(wǎng)絡(luò)的精確度,以及附加數(shù)據(jù)等等,具體如何配置,網(wǎng)上也有很多講解,這里就不詳細(xì)講述了;另外就是準(zhǔn)備數(shù)據(jù),imageNet官網(wǎng)上有很多圖片數(shù)據(jù)可以供大家免費(fèi)使用,官網(wǎng)地址:http://image-net.org/download-images?。網(wǎng)上還有很多免費(fèi)使用的爬蟲可以去爬取數(shù)據(jù),總之,數(shù)據(jù)是訓(xùn)練的根本,在網(wǎng)絡(luò)搭建好之前最好準(zhǔn)備充分。準(zhǔn)備好的數(shù)據(jù)放入當(dāng)前訓(xùn)練項(xiàng)目的根目錄下。

為了讓各種需求的人能夠復(fù)用AlexNet,我們?cè)赑ython類中定義了AlexNet,并把接口暴露出來,需要使用的人根據(jù)自己的情況調(diào)用網(wǎng)絡(luò),并輸入數(shù)據(jù)以及分類標(biāo)簽個(gè)數(shù)等信息就可以開始訓(xùn)練數(shù)據(jù)了。要使用搭建好的網(wǎng)絡(luò)進(jìn)行訓(xùn)練,不僅僅要利用網(wǎng)絡(luò),更是需要網(wǎng)絡(luò)中的各項(xiàng)權(quán)重參數(shù)和偏置來達(dá)到更好的分類效果,目前,我們使用的是別人已經(jīng)訓(xùn)練好的參數(shù),所有的參數(shù)數(shù)據(jù)存放在bvlc_alexnet.npy這個(gè)文件中,下載地址為:http://www.cs.toronto.edu/~guerzhoy/tf_alexnet/?,下載后放入當(dāng)前訓(xùn)練項(xiàng)目的根目錄下即可。如果你有充分的時(shí)間和優(yōu)越的硬件資源,你也可以自己訓(xùn)練參數(shù),并把這些參數(shù)存儲(chǔ)起來供以后使用,但是該bvlc_alexnet.npy文件中的參數(shù)是imageNet訓(xùn)練好了的,使用這些參數(shù)訓(xùn)練的模型精確度比我們之前訓(xùn)練的要高。
在Tensorflow中,定義加載參數(shù)的程序代碼如下,默認(rèn)的參數(shù)就是bvlc_alexnet.npy中存儲(chǔ)的權(quán)重和偏置值。

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

在上一節(jié)講述AlexNet的架構(gòu)的時(shí),曾出現(xiàn)過數(shù)據(jù)分組處理,這里用程序來描述一下在一個(gè)CPU情況下,如何把數(shù)據(jù)進(jìn)行分組處理。數(shù)據(jù)的分組處理都在卷積層中發(fā)生,因此首先一個(gè)卷積函數(shù),由于在第一層卷積沒有分組,所以在函數(shù)中需要做分組的判斷,如果沒有分組,輸入數(shù)據(jù)和權(quán)重直接做卷積運(yùn)算;如果有分組,則把輸入數(shù)據(jù)和權(quán)重先劃分后做卷積運(yùn)算,卷積結(jié)束后再用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

對(duì)于AlexNet中池化層,全鏈層的代碼在alexnet.py已經(jīng)全部定義好了,這里就不一一列出來了。接著開始如何在Tensorflow中導(dǎo)入圖片,在圖片數(shù)據(jù)量大的情況下,Tensorflow會(huì)建議把數(shù)據(jù)轉(zhuǎn)換成tfrecords文件,然后在導(dǎo)入到網(wǎng)絡(luò)中運(yùn)算,這樣的好處是可以加快計(jì)算速度,節(jié)約內(nèi)存空間。但我們沒有這樣做,因?yàn)樵谟?xùn)練網(wǎng)絡(luò)的時(shí)候我們沒有發(fā)現(xiàn)轉(zhuǎn)換成tfrecords文件就明顯提高了計(jì)算速度,所以這里直接把原生的圖片直接轉(zhuǎn)化成三維數(shù)據(jù)輸入到網(wǎng)絡(luò)中。這樣做代碼還要簡(jiǎn)短一點(diǎn),而圖片也是預(yù)先存儲(chǔ)在硬盤中,需要訓(xùn)練的那一部分再?gòu)挠脖P中讀取到內(nèi)存中,并沒有浪費(fèi)內(nèi)存資源。

在Python類中定義圖片生成器,需要的參數(shù)有圖片URL,實(shí)際的標(biāo)簽向量和標(biāo)簽個(gè)數(shù),batch_size等。首先打亂整個(gè)訓(xùn)練集圖片的順序,因?yàn)閳D片名可能是按照某種規(guī)律來定義的,打亂圖片順序可以幫助我們更好的訓(xùn)練網(wǎng)絡(luò)。完成這一步后就可以把圖片從RGB色轉(zhuǎn)換成BRG三維數(shù)組。

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])    """把圖片生成三維數(shù)組,以及把標(biāo)簽轉(zhuǎn)化為向量"""    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

網(wǎng)絡(luò)搭建完成,數(shù)據(jù)準(zhǔn)備就緒,最后就是開始訓(xùn)練了。由于網(wǎng)絡(luò)和圖片生成器是可以復(fù)用的,在訓(xùn)練圖片的時(shí)候需要用戶根據(jù)自己的實(shí)際情況編寫代碼調(diào)用網(wǎng)絡(luò)和圖片生成器模塊,同時(shí)定義好損失函數(shù)和優(yōu)化器,以及需要在Tensorboard中觀測(cè)的各項(xiàng)指標(biāo)等等操作。下面一節(jié)我們將開始進(jìn)行網(wǎng)絡(luò)訓(xùn)練。

5. 用AlexNet識(shí)別貓狗圖片

5.1. 定義分類

如上一節(jié)講的,datagenerator.py(圖片轉(zhuǎn)換模塊)和alexnet.py(AlexNet網(wǎng)絡(luò)模塊)已經(jīng)搭建好了,你在使用的時(shí)候無需做修改?,F(xiàn)在你只需要根據(jù)自己的分類需求編寫精調(diào)代碼,如finetune.py中所示。
假設(shè)有3萬張貓狗圖片訓(xùn)練集和3000張測(cè)試集,它們大小不一。我們的目的是使用AlexNet正確的分類貓和狗兩種動(dòng)物,因此,類別標(biāo)簽個(gè)數(shù)只有2個(gè),并用0代表貓,1代表狗。如果你需要分類其他的動(dòng)物或者物品,或者anything,你需要標(biāo)注好圖片的實(shí)際標(biāo)簽,定義好圖片Tensorboard存放的目錄,以及訓(xùn)練好的模型和參數(shù)的存放目錄等等。就像這樣:

import osimport numpy as npimport tensorflow as tffrom alexnet import AlexNetfrom datagenerator import ImageDataGeneratorfrom datetime import datetimeimport globfrom tensorflow.contrib.data import Iteratorlearning_rate = 1e-4                   # 學(xué)習(xí)率num_epochs = 100                       # 代的個(gè)數(shù)batch_size = 1024                      # 一次性處理的圖片張數(shù)dropout_rate = 0.5                     # dropout的概率num_classes = 2                        # 類別標(biāo)簽train_layers = ['fc8', 'fc7', 'fc6']   # 訓(xùn)練層,即三個(gè)全鏈層display_step = 20                      # 顯示間隔次數(shù)filewriter_path = "./tmp/tensorboard"  # 存儲(chǔ)tensorboard文件checkpoint_path = "./tmp/checkpoints"  # 訓(xùn)練好的模型和參數(shù)存放目錄if not os.path.isdir(checkpoint_path): #如果沒有存放模型的目錄,程序自動(dòng)生成    os.mkdir(checkpoint_path)

接著調(diào)用圖片生成器,來生成圖片數(shù)據(jù),并初始化數(shù)據(jù):

train_image_path = 'train/'  # 指定訓(xùn)練集數(shù)據(jù)路徑(根據(jù)實(shí)際情況指定訓(xùn)練數(shù)據(jù)集的路徑)test_image_cat_path = 'test/cat/'  # 指定測(cè)試集數(shù)據(jù)路徑(根據(jù)實(shí)際情況指定測(cè)試數(shù)據(jù)集的路徑)test_image_dog_path = 'test/dog/'# 打開訓(xùn)練數(shù)據(jù)集目錄,讀取全部圖片,生成圖片路徑列表image_filenames_cat = np.array(glob.glob(train_image_path + 'cat.*.jpg'))image_filenames_dog = np.array(glob.glob(train_image_path + 'dog.*.jpg'))# 打開測(cè)試數(shù)據(jù)集目錄,讀取全部圖片,生成圖片路徑列表test_image_filenames_cat = np.array(glob.glob(test_image_cat_path + '*.jpg'))test_image_filenames_dog = np.array(glob.glob(test_image_dog_path + '*.jpg'))image_path = []label_path = []test_image = []test_label = []# 遍歷訓(xùn)練集圖片URL,并把圖片對(duì)應(yīng)的實(shí)際標(biāo)簽和路徑分別存入兩個(gè)新列表中for catitem in image_filenames_cat:    image_path.append(catitem)    label_path.append(0)for dogitem in image_filenames_dog:    image_path.append(dogitem)    label_path.append(1)# 遍歷測(cè)試集圖片URL,并把圖片路徑存入一個(gè)新列表中for catitem in test_image_filenames_cat:    test_image.append(catitem)    test_label.append(0)for dogitem in test_image_filenames_cat:    test_image.append(dogitem)    test_label.append(1)# 調(diào)用圖片生成器,把訓(xùn)練集圖片轉(zhuǎn)換成三維數(shù)組tr_data = ImageDataGenerator(    images=image_path,    labels=label_path,    batch_size=batch_size,    num_classes=num_classes)# 調(diào)用圖片生成器,把測(cè)試集圖片轉(zhuǎn)換成三維數(shù)組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)# 定義每次迭代的數(shù)據(jù)next_batch = iterator.get_next()# 初始化數(shù)據(jù)training_initalize = iterator.make_initializer(tr_data.data)testing_initalize = iterator.make_initializer(test_data.data)

訓(xùn)練數(shù)據(jù)準(zhǔn)備好以后,讓數(shù)據(jù)通過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概率# 圖片數(shù)據(jù)通過AlexNet網(wǎng)絡(luò)處理model = AlexNet(x, keep_prob, num_classes, train_layers)# 定義我們需要訓(xùn)練的全連層的變量列表var_list = [v for v in tf.trainable_variables() if v.name.split('/')[0] in train_layers]# 執(zhí)行整個(gè)網(wǎng)絡(luò)圖score = model.fc8

接著當(dāng)然就是定義損失函數(shù),優(yōu)化器。整個(gè)網(wǎng)絡(luò)需要優(yōu)化三層全鏈層的參數(shù),同時(shí)在優(yōu)化參數(shù)過程中,使用的是梯度下降算法,而不是反向傳播算法。

# 損失函數(shù)loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=score, labels=y))# 定義需要精調(diào)的每一層的梯度gradients = tf.gradients(loss, var_list)gradients = list(zip(gradients, var_list))# 優(yōu)化器,采用梯度下降算法進(jìn)行優(yōu)化optimizer = tf.train.GradientDescentOptimizer(learning_rate)# 需要精調(diào)的每一層都采用梯度下降算法優(yōu)化參數(shù)train_op = optimizer.apply_gradients(grads_and_vars=gradients)# 定義網(wǎng)絡(luò)精確度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中觀測(cè)loss的收斂情況和網(wǎng)絡(luò)的精確度而定義的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()

最后,訓(xùn)練數(shù)據(jù):

# 定義一代的迭代次數(shù)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)    # 把訓(xùn)練好的權(quán)重加入未訓(xùn)練的網(wǎng)絡(luò)中    model.load_initial_weights(sess)    print("{} Start training...".format(datetime.now()))    print("{} Open Tensorboard at --logdir {}".format(datetime.now(),                                                      filewriter_path))    # 總共訓(xùn)練100代    for epoch in range(num_epochs):        sess.run(iterator.make_initializer(tr_data.data))        print("{} Epoch number: {} start".format(datetime.now(), epoch + 1))        # 開始訓(xùn)練每一代,一代的次數(shù)為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)

訓(xùn)練完成后需要驗(yàn)證模型的精確度,這個(gè)時(shí)候就得用上測(cè)試數(shù)據(jù)集了。

# 測(cè)試模型精確度print("{} Start validation".format(datetime.now()))sess.run(testing_initalize)test_acc = 0.test_count = 0for _ 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 += 1test_acc /= test_countprint("{} Validation Accuracy = {:.4f}".format(datetime.now(), test_acc))

最后把訓(xùn)練好的模型持久化。

# 把訓(xùn)練好的模型存儲(chǔ)起來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))

到此為止,一個(gè)完整的AlexNet就搭建完成了。在準(zhǔn)備好訓(xùn)練集和測(cè)試集數(shù)據(jù)后,下面我們開始訓(xùn)練網(wǎng)絡(luò)。

5.2. 訓(xùn)練網(wǎng)絡(luò)

我們總共訓(xùn)練了100代,使用CPU計(jì)算進(jìn)行計(jì)算,在臺(tái)式機(jī)上跑了一天左右,完成了3萬張圖片的訓(xùn)練和3000張圖片的測(cè)試,網(wǎng)絡(luò)的識(shí)別精確度為71.25%,這個(gè)結(jié)果不是很好,可能與數(shù)據(jù)量少有關(guān)系。如果你有上十萬張的數(shù)據(jù)集,再增加訓(xùn)練次數(shù),相信你網(wǎng)絡(luò)的精度應(yīng)該比我們訓(xùn)練的還要好。下面看看網(wǎng)絡(luò)的計(jì)算圖,這是Tensorboard中記錄下的,通過該圖,你可以對(duì)整個(gè)網(wǎng)絡(luò)的架構(gòu)及運(yùn)行一目了然。

大數(shù)據(jù)

5.3. 驗(yàn)證

網(wǎng)絡(luò)訓(xùn)練好了以后,當(dāng)然我們想迫不及待的試試我們網(wǎng)絡(luò)。首先我們還是得編寫自己的驗(yàn)證代碼:

import tensorflow as tffrom alexnet import AlexNet             # import訓(xùn)練好的網(wǎng)絡(luò)import matplotlib.pyplot as pltclass_name = ['cat', 'dog']             # 自定義貓狗標(biāo)簽def test_image(path_image, num_class, weights_path='Default'):    # 把新圖片進(jìn)行轉(zhuǎn)換    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") # 導(dǎo)入訓(xùn)練好的參數(shù)        # score = model.fc8        print(sess.run(model.fc8))        prob = sess.run(max)[0]        # 在matplotlib中觀測(cè)分類結(jié)果        plt.imshow(img_decoded.eval())        plt.title("Class:" + class_name[prob])        plt.show()test_image('./test/20.jpg', num_class=2) # 輸入一張新圖片

在網(wǎng)上任意下載10張貓狗圖片來進(jìn)行驗(yàn)證,有三張圖片識(shí)別錯(cuò)誤(如下圖),驗(yàn)證的精確度70%,效果不是很理想。但是如果你感興趣,你可以下載我們的代碼,用自己的訓(xùn)練集來試試,代碼地址為:https://github.com/stephen-v/tensorflow_alexnet_classify

大數(shù)據(jù)

大數(shù)據(jù)

大數(shù)據(jù)

大數(shù)據(jù)

大數(shù)據(jù)

大數(shù)據(jù)

大數(shù)據(jù)

大數(shù)據(jù)

大數(shù)據(jù)

免責(zé)聲明:本網(wǎng)站內(nèi)容主要來自原創(chuàng)、合作伙伴供稿和第三方自媒體作者投稿,凡在本網(wǎng)站出現(xiàn)的信息,均僅供參考。本網(wǎng)站將盡力確保所提供信息的準(zhǔn)確性及可靠性,但不保證有關(guān)資料的準(zhǔn)確性及可靠性,讀者在使用前請(qǐng)進(jìn)一步核實(shí),并對(duì)任何自主決定的行為負(fù)責(zé)。本網(wǎng)站對(duì)有關(guān)資料所引致的錯(cuò)誤、不確或遺漏,概不負(fù)任何法律責(zé)任。任何單位或個(gè)人認(rèn)為本網(wǎng)站中的網(wǎng)頁(yè)或鏈接內(nèi)容可能涉嫌侵犯其知識(shí)產(chǎn)權(quán)或存在不實(shí)內(nèi)容時(shí),應(yīng)及時(shí)向本網(wǎng)站提出書面權(quán)利通知或不實(shí)情況說明,并提供身份證明、權(quán)屬證明及詳細(xì)侵權(quán)或不實(shí)情況證明。本網(wǎng)站在收到上述法律文件后,將會(huì)依法盡快聯(lián)系相關(guān)文章源頭核實(shí),溝通刪除相關(guān)內(nèi)容或斷開相關(guān)鏈接。

2017-10-20
AlexNet 網(wǎng)絡(luò)詳解及Tensorflow實(shí)現(xiàn)源碼
作者:行動(dòng)派Xdpie 1 圖片數(shù)據(jù)處理 一張圖片是由一個(gè)個(gè)像素組成,每個(gè)像素的顏色常常用RGB、HSB、CYMK、RGBA等顏色值來表示,每個(gè)顏色值的取值范

長(zhǎng)按掃碼 閱讀全文