作者:行動派Xdpie
1. 圖片數(shù)據(jù)處理
一張圖片是由一個個像素組成,每個像素的顏色常常用RGB、HSB、CYMK、RGBA等顏色值來表示,每個顏色值的取值范圍不一樣,但都代表了一個像素點數(shù)據(jù)信息。對圖片的數(shù)據(jù)處理過程中,RGB使用得最多,RGB表示紅綠藍三通道色,取值范圍為0~255,所以一個像素點可以把它看作是一個三維數(shù)組,即:array([[[0, 255, 255]]]),三個數(shù)值分布表示R、G、B(紅、綠、藍)的顏色值。比如下圖一張3*3大小的jpg格式的圖片:
它的圖片經(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)換成運算需要的值,還包括調(diào)整圖片大小、圖片截取、圖片翻轉(zhuǎn)、圖片色彩調(diào)整,標注框、多線程處理圖片等等,在這里就不一一介紹了,但是對于圖片的處理是進行卷積網(wǎng)絡(luò)的首要任務(wù),你需要了解,并學會對圖片的相關(guān)操作。這里只介紹RGB值的轉(zhuǎn)換,為下一節(jié)的卷積提供數(shù)據(jù)支持。
2. 卷積神經(jīng)網(wǎng)絡(luò)
卷積神經(jīng)網(wǎng)絡(luò)(CNN)的基本架構(gòu)通常包括卷積層,池化層,全鏈層三大層次,其中不同的層中可能還會包括一些非線性變化(RELU函數(shù))、數(shù)據(jù)歸一化處理、dropoout等。我們常聽說的LeNet-5、AlexNet、VGG、ResNet等都是卷積神經(jīng)網(wǎng)絡(luò),而且都是由這些層組成,只是每個網(wǎng)絡(luò)的層數(shù)不一樣,所達到的分類效果也不一樣。
2.1. 卷積層
卷積層是整個神經(jīng)網(wǎng)絡(luò)中最重要的一層,該層最核心的部分為過濾器,或者稱為卷積核,卷積核有大小和深度兩個屬性,大小常用的有3X3、5X5,也有11X11的卷積核,而深度通俗一點理解就是卷積核的個數(shù)。卷積核的大小和深度均由人工指定,而權(quán)重參數(shù)則在初始化的時候由程序隨機生成,并在后期訓(xùn)練過程中不斷優(yōu)化這些權(quán)重值,以達到最好的分類效果。卷積的過程就是用這些權(quán)重值不斷的去乘這些圖片的RGB值,以提取圖片數(shù)據(jù)信息。下面的動圖完美地詮釋了卷積是怎么發(fā)生的:
上面黃色3X3大小不停移動的就是卷積核,綠色部分是5X5的輸入矩陣,粉色部分是卷積后的結(jié)果,稱作特征值。從上面動圖看出,卷積不僅提取了圖片信息,也可以達到降維效果。如果希望卷積后的特征值維度和原圖片一致,需要設(shè)置padding值(全零填充)為SAME(如果為VALID表示不填充),其中i為輸入圖片,k為卷積核大小,strides為移動步長(移動步長>1也可以達到降維的效果)。
tf.nn.conv2d(i, k,strides,padding='VALID')
在卷積層中,過濾器中的參數(shù)是共享的,即一個過濾器中的參數(shù)值在對所有圖片數(shù)據(jù)進行卷積過程中保持不變,這樣卷積層的參數(shù)個數(shù)就和圖片大小無關(guān),它只和過濾器的尺寸,深度,以及當前層節(jié)點的矩陣深度有關(guān)。比如,以手寫圖片為例,輸入矩陣的維度是28X28X1,假設(shè)第一層卷積層使用的過濾器大小為5X5,深度為16,則該卷積層的參數(shù)個數(shù)為5X5X1X16+16=416個,而如果使用500個隱藏節(jié)點的全鏈層會有1.5百萬個參數(shù),相比之下,卷積層的參數(shù)個數(shù)遠遠小于全鏈層,這就是為什么卷積網(wǎng)絡(luò)廣泛用于圖片識別上的原因。
對于卷積后的矩陣大小,有一個計算公式,如果使用了全0填充,則卷積后的矩陣大小為:
即輸出矩陣的長等于輸入矩陣長度除以長度方向上的步長,并向上取整數(shù)值;輸出矩陣的寬度等于輸入矩陣的寬度除以寬度方向上的步長,并向上取整數(shù)值。
如果不使用全0填充,則輸出矩陣的大小為:
卷積計算完成后,往往會加入一個修正線性單元ReLU函數(shù),也就是把數(shù)據(jù)非線性化。為什么要把數(shù)據(jù)進行非線性化呢,這是因為非線性代表了輸入和輸出的關(guān)系是一條曲線而不是直線,曲線能夠刻畫輸入中更為復(fù)雜的變化。比如一個輸入值大部分時間都很穩(wěn)定,但有可能會在某個時間點出現(xiàn)極值,但是通過ReLU函數(shù)以后,數(shù)據(jù)變得平滑,這樣以便對復(fù)雜的數(shù)據(jù)進行訓(xùn)練。
ReLU是分段線性的,當輸入為非負時,輸出將與輸入相同;而當輸入為負時,輸出均為0。它的優(yōu)點在于不受“梯度消失”的影響,且取值范圍為[0,+∞];其缺點在于當使用了較大的學習速率時,易受達到飽和的神經(jīng)元的影響。
2.2. 池化層
卷積層后一般會加入池化層,池化層可以非常有效地縮小矩陣的尺寸,從而減少最后全鏈層中的參數(shù),使用池化層既可以加快計算速度也有防止過擬合問題的作用。
池化層也存在一個過濾器,但是過濾器對于輸入的數(shù)據(jù)的處理并不是像卷積核對輸入數(shù)據(jù)進行節(jié)點的加權(quán)和,而只是簡單的計算最大值或者平均值。過濾器的大小、是否全0填充、步長等也是由人工指定,而深度跟卷積核深度不一樣,卷積層使用過濾器是橫跨整個深度的,而池化層使用的過濾器只影響一個深度上的節(jié)點,在計算過程中,池化層過濾器不僅要在長和寬兩個維度移動,還要在深度這個維度移動。使用最大值操作的池化層被稱之為最大池化層,這種池化層使用得最多,使用平均值操作的池化層被稱之為平均池化層,這種池化層的使用相對要少一點。
以下動圖可以看到最大值池化層的計算過程:
Tensorflow程序很容易就可以實現(xiàn)最大值池化層的操作:
pool = tf.nn.max_pool(i, ksize=[1,3,3,1], stride=[1,2,2,1], padding='SAME')# i為輸入矩陣# ksize為過濾器尺寸,其中第一個和第四個值必須為1,表示過濾器不可以垮不同的輸入樣列和節(jié)點矩陣深度。中間的兩個值為尺寸,常使用2*2或3*3。# stride為步長,第一個值和第四個值與ksize一樣# padding為全0填充,‘SAME’表示使用全0填充,‘VALID’表示不使用全0填充
2.3. 全鏈層
在KNN或線性分類中有對數(shù)據(jù)進行歸一化處理,而在神經(jīng)網(wǎng)絡(luò)中,也會做數(shù)據(jù)歸一化的處理,原因和之前的一樣,避免數(shù)據(jù)值大的節(jié)點對分類造成影響。歸一化的目標在于將輸入保持在一個可接受的范圍內(nèi)。例如,將輸入歸一化到[0.0,1.0]區(qū)間內(nèi)。在卷積神經(jīng)網(wǎng)絡(luò)中,對數(shù)據(jù)歸一化的處理我們有可能放在數(shù)據(jù)正式輸入到全鏈層之前或之后,或其他地方,每個網(wǎng)絡(luò)都可能不一樣。
全鏈層的作用就是進行正確的圖片分類,不同神經(jīng)網(wǎng)絡(luò)的全鏈層層數(shù)不同,但作用確是相同的。輸入到全鏈層的神經(jīng)元個數(shù)通過卷積層和池化層的處理后大大的減少了,比如以AlexNet為例,一張227*227大小,顏色通道數(shù)為3的圖片經(jīng)過處理后,輸入到全鏈層的神經(jīng)元個數(shù)有4096個,最后softmax的輸出,則可以根據(jù)實際分類標簽數(shù)來定。
在全鏈層中,會使用dropout以隨機的去掉一些神經(jīng)元,這樣能夠比較有效地防止神經(jīng)網(wǎng)絡(luò)的過擬合。相對于一般如線性模型使用正則的方法來防止模型過擬合,而在神經(jīng)網(wǎng)絡(luò)中Dropout通過修改神經(jīng)網(wǎng)絡(luò)本身結(jié)構(gòu)來實現(xiàn)。對于某一層神經(jīng)元,通過定義的概率來隨機刪除一些神經(jīng)元,同時保持輸入層與輸出層神經(jīng)元的個人不變,然后按照神經(jīng)網(wǎng)絡(luò)的學習方法進行參數(shù)更新,下一次迭代中,重新隨機刪除一些神經(jīng)元,直至訓(xùn)練結(jié)束。
3. AlexNet
AlexNet是2012年ILSVRC比賽的冠軍,它的出現(xiàn)直接打破了沉寂多年的圖片識別領(lǐng)域(在1998年出現(xiàn)LeNet-5網(wǎng)絡(luò)一直占據(jù)圖片識別的領(lǐng)頭地位),給該領(lǐng)域帶來了新的契機,并一步步發(fā)展至今,甚至打敗了人類的識別精確度,可惜的是2017年的ILSVRC舉辦方宣布從2018年起將取消該比賽,因為目前的神經(jīng)網(wǎng)絡(luò)精確度已經(jīng)達到跟高的程度了。但深度學習的步伐不會停止,人們將在其他方面進行深入的研究。
AlexNet是神經(jīng)網(wǎng)絡(luò)之父Hinton的學生Alex Krizhevsky開發(fā)完成,它總共有8層,其中有5個卷積層,3個全鏈層,附上最經(jīng)典的AlexNet網(wǎng)絡(luò)架構(gòu)圖,如下。Alex在他的論文中寫到,他在處理圖片的時候使用了兩個GPU進行計算,因此,從圖中看出,在卷積過程中他做了分組的處理,但是由于硬件資源問題,我們做的Alex網(wǎng)絡(luò)是使用一個CPU進行計算的,但原理和他的一樣,只是計算速度慢一點而已,對于大多數(shù)沒有性能優(yōu)良的GPU的人來說,用我們搭建好的網(wǎng)絡(luò),完全可以使用家用臺式機進行訓(xùn)練。
Alex在論文中寫到他使用的輸入圖片大小為224 X 224 X 3,但我們使用的圖片尺寸為227 X 227 X 3,這個沒有太大影響。AlexNet網(wǎng)絡(luò)分為8層結(jié)構(gòu),前5層其實不完全是卷積層,有些層還加入了池化層,并對數(shù)據(jù)進行標準化處理。下面簡要介紹一下每一層:
第一層
池化層過濾器步長
3 * 32 * 2
第一層包含了卷積層、標準化操作和池化層,其中卷積層和池化層的參數(shù)在上表已給出。在Tensorflow中,搭建的部分代碼程序為:
# 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')
第二層
池化層過濾器步長
3 * 32 * 2
第二層實際也包含了卷積層、標準化操作和池化層,其中卷積層和池化層的參數(shù)在上表已給出。在Tensorflow中,搭建的部分代碼程序為:
# 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')
第三層
第三層僅有一個卷積層,卷積核的相關(guān)信息如上表,在Tensorflow中的部分代碼為:
# 3rd Layer: Conv (w ReLu)conv3 = conv(pool2, 3, 3, 384, 1, 1, name='conv3')
第四層
第四層僅有一個卷積層,卷積核的相關(guān)信息如上表,該層與第三層很相似,只是把數(shù)據(jù)分成了2組進行處理,在Tensorflow中的部分代碼為:
# 4th Layer: Conv (w ReLu) splitted into two groupsconv4 = conv(conv3, 3, 3, 384, 1, 1, groups=2, name='conv4')
第五層
第五層是最后一層卷積層,包含一個卷積層和一個池化層,卷積核和池化層過濾器的相關(guān)信息如上表,該層仍然把數(shù)據(jù)分成了2組進行處理,在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個神經(jīng)元,在進入第六層全鏈層后,首先做了數(shù)據(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)
第七層
第七層是全鏈層,也會做dropout處理,在Tensorflow中的部分代碼為:
# 7th Layer: FC (w ReLu) -> Dropoutfc7 = fc(dropout6, 4096, 4096, name='fc7')dropout7 = dropout(fc7, self.KEEP_PROB)
第八層
第八層是全鏈層,在最后softmax函數(shù)輸出的分類標簽是根據(jù)實際分類情況來定義的,可能有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之前,需要做一些準備工作,以方便后期做訓(xùn)練的時候觀測網(wǎng)絡(luò)的運行情況。首先就是配置Tensorboard,Tensorboard是一款可視化工具,可以用它來展現(xiàn)你的TensorFlow圖像,繪制圖像生成的定量指標圖,觀察loss函數(shù)的收斂情況,網(wǎng)絡(luò)的精確度,以及附加數(shù)據(jù)等等,具體如何配置,網(wǎng)上也有很多講解,這里就不詳細講述了;另外就是準備數(shù)據(jù),imageNet官網(wǎng)上有很多圖片數(shù)據(jù)可以供大家免費使用,官網(wǎng)地址:http://image-net.org/download-images?。網(wǎng)上還有很多免費使用的爬蟲可以去爬取數(shù)據(jù),總之,數(shù)據(jù)是訓(xùn)練的根本,在網(wǎng)絡(luò)搭建好之前最好準備充分。準備好的數(shù)據(jù)放入當前訓(xùn)練項目的根目錄下。
為了讓各種需求的人能夠復(fù)用AlexNet,我們在Python類中定義了AlexNet,并把接口暴露出來,需要使用的人根據(jù)自己的情況調(diào)用網(wǎng)絡(luò),并輸入數(shù)據(jù)以及分類標簽個數(shù)等信息就可以開始訓(xùn)練數(shù)據(jù)了。要使用搭建好的網(wǎng)絡(luò)進行訓(xùn)練,不僅僅要利用網(wǎng)絡(luò),更是需要網(wǎng)絡(luò)中的各項權(quán)重參數(shù)和偏置來達到更好的分類效果,目前,我們使用的是別人已經(jīng)訓(xùn)練好的參數(shù),所有的參數(shù)數(shù)據(jù)存放在bvlc_alexnet.npy這個文件中,下載地址為:http://www.cs.toronto.edu/~guerzhoy/tf_alexnet/?,下載后放入當前訓(xùn)練項目的根目錄下即可。如果你有充分的時間和優(yōu)越的硬件資源,你也可以自己訓(xùn)練參數(shù),并把這些參數(shù)存儲起來供以后使用,但是該bvlc_alexnet.npy文件中的參數(shù)是imageNet訓(xùn)練好了的,使用這些參數(shù)訓(xùn)練的模型精確度比我們之前訓(xùn)練的要高。
在Tensorflow中,定義加載參數(shù)的程序代碼如下,默認的參數(shù)就是bvlc_alexnet.npy中存儲的權(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)的時,曾出現(xiàn)過數(shù)據(jù)分組處理,這里用程序來描述一下在一個CPU情況下,如何把數(shù)據(jù)進行分組處理。數(shù)據(jù)的分組處理都在卷積層中發(fā)生,因此首先一個卷積函數(shù),由于在第一層卷積沒有分組,所以在函數(shù)中需要做分組的判斷,如果沒有分組,輸入數(shù)據(jù)和權(quán)重直接做卷積運算;如果有分組,則把輸入數(shù)據(jù)和權(quá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
對于AlexNet中池化層,全鏈層的代碼在alexnet.py已經(jīng)全部定義好了,這里就不一一列出來了。接著開始如何在Tensorflow中導(dǎo)入圖片,在圖片數(shù)據(jù)量大的情況下,Tensorflow會建議把數(shù)據(jù)轉(zhuǎn)換成tfrecords文件,然后在導(dǎo)入到網(wǎng)絡(luò)中運算,這樣的好處是可以加快計算速度,節(jié)約內(nèi)存空間。但我們沒有這樣做,因為在訓(xùn)練網(wǎng)絡(luò)的時候我們沒有發(fā)現(xiàn)轉(zhuǎn)換成tfrecords文件就明顯提高了計算速度,所以這里直接把原生的圖片直接轉(zhuǎn)化成三維數(shù)據(jù)輸入到網(wǎng)絡(luò)中。這樣做代碼還要簡短一點,而圖片也是預(yù)先存儲在硬盤中,需要訓(xùn)練的那一部分再從硬盤中讀取到內(nèi)存中,并沒有浪費內(nèi)存資源。
在Python類中定義圖片生成器,需要的參數(shù)有圖片URL,實際的標簽向量和標簽個數(shù),batch_size等。首先打亂整個訓(xùn)練集圖片的順序,因為圖片名可能是按照某種規(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ù)組,以及把標簽轉(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ù)準備就緒,最后就是開始訓(xùn)練了。由于網(wǎng)絡(luò)和圖片生成器是可以復(fù)用的,在訓(xùn)練圖片的時候需要用戶根據(jù)自己的實際情況編寫代碼調(diào)用網(wǎng)絡(luò)和圖片生成器模塊,同時定義好損失函數(shù)和優(yōu)化器,以及需要在Tensorboard中觀測的各項指標等等操作。下面一節(jié)我們將開始進行網(wǎng)絡(luò)訓(xùn)練。
5. 用AlexNet識別貓狗圖片
5.1. 定義分類
如上一節(jié)講的,datagenerator.py(圖片轉(zhuǎn)換模塊)和alexnet.py(AlexNet網(wǎng)絡(luò)模塊)已經(jīng)搭建好了,你在使用的時候無需做修改?,F(xiàn)在你只需要根據(jù)自己的分類需求編寫精調(diào)代碼,如finetune.py中所示。
假設(shè)有3萬張貓狗圖片訓(xùn)練集和3000張測試集,它們大小不一。我們的目的是使用AlexNet正確的分類貓和狗兩種動物,因此,類別標簽個數(shù)只有2個,并用0代表貓,1代表狗。如果你需要分類其他的動物或者物品,或者anything,你需要標注好圖片的實際標簽,定義好圖片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 # 學習率num_epochs = 100 # 代的個數(shù)batch_size = 1024 # 一次性處理的圖片張數(shù)dropout_rate = 0.5 # dropout的概率num_classes = 2 # 類別標簽train_layers = ['fc8', 'fc7', 'fc6'] # 訓(xùn)練層,即三個全鏈層display_step = 20 # 顯示間隔次數(shù)filewriter_path = "./tmp/tensorboard" # 存儲tensorboard文件checkpoint_path = "./tmp/checkpoints" # 訓(xùn)練好的模型和參數(shù)存放目錄if not os.path.isdir(checkpoint_path): #如果沒有存放模型的目錄,程序自動生成 os.mkdir(checkpoint_path)
接著調(diào)用圖片生成器,來生成圖片數(shù)據(jù),并初始化數(shù)據(jù):
train_image_path = 'train/' # 指定訓(xùn)練集數(shù)據(jù)路徑(根據(jù)實際情況指定訓(xùn)練數(shù)據(jù)集的路徑)test_image_cat_path = 'test/cat/' # 指定測試集數(shù)據(jù)路徑(根據(jù)實際情況指定測試數(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'))# 打開測試數(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,并把圖片對應(yīng)的實際標簽和路徑分別存入兩個新列表中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)# 遍歷測試集圖片URL,并把圖片路徑存入一個新列表中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)用圖片生成器,把測試集圖片轉(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ù)準備好以后,讓數(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í)行整個網(wǎng)絡(luò)圖score = model.fc8
接著當然就是定義損失函數(shù),優(yōu)化器。整個網(wǎng)絡(luò)需要優(yōu)化三層全鏈層的參數(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)化器,采用梯度下降算法進行優(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中觀測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)練完成后需要驗證模型的精確度,這個時候就得用上測試數(shù)據(jù)集了。
# 測試模型精確度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)練好的模型存儲起來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就搭建完成了。在準備好訓(xùn)練集和測試集數(shù)據(jù)后,下面我們開始訓(xùn)練網(wǎng)絡(luò)。
5.2. 訓(xùn)練網(wǎng)絡(luò)
我們總共訓(xùn)練了100代,使用CPU計算進行計算,在臺式機上跑了一天左右,完成了3萬張圖片的訓(xùn)練和3000張圖片的測試,網(wǎng)絡(luò)的識別精確度為71.25%,這個結(jié)果不是很好,可能與數(shù)據(jù)量少有關(guān)系。如果你有上十萬張的數(shù)據(jù)集,再增加訓(xùn)練次數(shù),相信你網(wǎng)絡(luò)的精度應(yīng)該比我們訓(xùn)練的還要好。下面看看網(wǎng)絡(luò)的計算圖,這是Tensorboard中記錄下的,通過該圖,你可以對整個網(wǎng)絡(luò)的架構(gòu)及運行一目了然。
5.3. 驗證
網(wǎng)絡(luò)訓(xùn)練好了以后,當然我們想迫不及待的試試我們網(wǎng)絡(luò)。首先我們還是得編寫自己的驗證代碼:
import tensorflow as tffrom alexnet import AlexNet # import訓(xùn)練好的網(wǎng)絡(luò)import matplotlib.pyplot as pltclass_name = ['cat', 'dog'] # 自定義貓狗標簽def test_image(path_image, num_class, weights_path='Default'): # 把新圖片進行轉(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中觀測分類結(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張貓狗圖片來進行驗證,有三張圖片識別錯誤(如下圖),驗證的精確度70%,效果不是很理想。但是如果你感興趣,你可以下載我們的代碼,用自己的訓(xùn)練集來試試,代碼地址為:https://github.com/stephen-v/tensorflow_alexnet_classify
- 蜜度索驥:以跨模態(tài)檢索技術(shù)助力“企宣”向上生長
- 長江存儲發(fā)布聲明:從無“借殼上市”意愿
- 泛微·數(shù)智大腦Xiaoe.AI正式發(fā)布,千人現(xiàn)場體驗數(shù)智化運營場景
- IDC:2024年第三季度北美IT分銷商收入增長至202億美元
- AI成為雙刃劍!凱捷調(diào)查:97%組織遭遇過GenAI漏洞攻擊
- openEuler開源五年樹立新里程碑,累計裝機量突破1000萬
- 創(chuàng)想 華彩新程!2024柯尼卡美能達媒體溝通會煥新增長之道
- 操作系統(tǒng)大會2024即將在京召開,見證openEuler發(fā)展新里程
- Gartner:AI引領(lǐng)歐洲IT支出激增,2025年將支出1.28萬億美元
- IDC:中國數(shù)字化轉(zhuǎn)型支出五年復(fù)合增長率約為15.6% 高于全球整體增速
- 2028年中國數(shù)字化轉(zhuǎn)型總體市場規(guī)模將超7300億美元
免責聲明:本網(wǎng)站內(nèi)容主要來自原創(chuàng)、合作伙伴供稿和第三方自媒體作者投稿,凡在本網(wǎng)站出現(xiàn)的信息,均僅供參考。本網(wǎng)站將盡力確保所提供信息的準確性及可靠性,但不保證有關(guān)資料的準確性及可靠性,讀者在使用前請進一步核實,并對任何自主決定的行為負責。本網(wǎng)站對有關(guān)資料所引致的錯誤、不確或遺漏,概不負任何法律責任。任何單位或個人認為本網(wǎng)站中的網(wǎng)頁或鏈接內(nèi)容可能涉嫌侵犯其知識產(chǎn)權(quán)或存在不實內(nèi)容時,應(yīng)及時向本網(wǎng)站提出書面權(quán)利通知或不實情況說明,并提供身份證明、權(quán)屬證明及詳細侵權(quán)或不實情況證明。本網(wǎng)站在收到上述法律文件后,將會依法盡快聯(lián)系相關(guān)文章源頭核實,溝通刪除相關(guān)內(nèi)容或斷開相關(guān)鏈接。