人們可以使用TensorFlow的所有高級工具如tf.contrib.learn和Keras,能夠用少量代碼輕易的建立一個卷積神經(jīng)網(wǎng)絡(luò)。但是通常在這種高級應(yīng)用中,你不能訪問代碼中的部分內(nèi)容,對深層次的原理缺乏理解。
在本教程中,我將介紹如何從零開始使用底層的TensorFlow構(gòu)建卷積神經(jīng)網(wǎng)絡(luò),并使用TensorBoard可視化我們的函數(shù)圖像和網(wǎng)絡(luò)性能。本教程需要你了解神經(jīng)網(wǎng)絡(luò)的一些基礎(chǔ)知識。在整篇文章中,我還將把卷積神經(jīng)網(wǎng)絡(luò)的每一步都分解為絕對的基礎(chǔ)知識,以便你可以充分理解圖中每一步發(fā)生的情況。通過從頭開始構(gòu)建這個模型,你可以輕松將圖形的不同方面可視化,這樣你就可以看到每個卷積層并使用它們進行自己的推論。我只會著重講代碼的重要的部分,想獲取要詳細代碼和注釋,請訪問下方鏈接。
https://github.com/wagonhelm/Visualizing-Convnets/blob/master/visualizingConvnets.ipynb
收集數(shù)據(jù)集
首先,我必須決定使用哪個圖像數(shù)據(jù)集。我決定使用牛津大學(xué)Visual Geometry Group的寵物數(shù)據(jù)集。我選擇這個數(shù)據(jù)集有幾個原因:這個數(shù)據(jù)集比較簡單,并且標記得很好,它有適當數(shù)量的訓(xùn)練數(shù)據(jù),如果我接下來想訓(xùn)練一個檢測模型,它也有可用包圍盒?;蛘吣阋部梢允褂梦以贙aggle上找到的Simpsons數(shù)據(jù)集,它包含大量可用來訓(xùn)練的簡單數(shù)據(jù)。
選擇一個模型
接下來,我要選擇卷積神經(jīng)網(wǎng)絡(luò)模型。比較流行的模型是GoogLeNet或VGG16,它們都具有多重卷積,可用于用于檢測ImageNet中1000種數(shù)據(jù)集的圖像。我決定建立一個簡單的四層卷積網(wǎng)絡(luò):
讓我們詳細講解這個模型的構(gòu)成,根據(jù)前三個通道被卷積到32個特征映射。我們接下來卷積這組32個特征映射組成另外32個特征。然后將其池化為一個112x112x32的圖像,然后我們卷積64特征映射兩次之后,最終池化為56x56x64。這個最后的池化層的每個單元然后被全連接到512個神經(jīng)元,然后根據(jù)類的數(shù)量接通softmax層。
處理和建立一個數(shù)據(jù)集
首先,讓我們開始加載我們的依賴項,其中包括用于處理圖像數(shù)據(jù)的函數(shù)imFunctions。
import imFunctions as imfimport tensorflow as tfimport scipy.ndimagefrom scipy.misc import imsaveimport matplotlib.pyplot as pltimport numpy as np
使用imFunctions下載和提取圖片。
imf.downloadImages('annotations.tar.gz', 19173078)imf.downloadImages('images.tar.gz', 791918971)imf.maybeExtract('annotations.tar.gz')imf.maybeExtract('images.tar.gz')
然后,我們可以將圖像分類到單獨的文件夾中,包括訓(xùn)練和測試文件夾。sortImages函數(shù)中的數(shù)字表示你想從訓(xùn)練數(shù)據(jù)中分離出測試數(shù)據(jù)的百分比。
imf.sortImages(0.15)
然后,我們可以將數(shù)據(jù)集構(gòu)建為一個numpy數(shù)組,其中對應(yīng)的獨熱向量表示我們的類。當構(gòu)建convnet時,這也將減少所有的訓(xùn)練和測試圖像中的圖像均值。這個函數(shù)會問你想要包含哪些類,由于我的GPU內(nèi)存有限(3GB),我選擇了一個非常小的數(shù)據(jù)集,試圖區(qū)分兩種狗:柴犬和薩摩耶。
train_x, train_y, test_x, test_y, classes, classLabels = imf.buildDataset()
卷積和匯集如何工作
現(xiàn)在我們有了一套數(shù)據(jù)集,讓我們稍微回顧一下,看看卷積的工作原理。在學(xué)習(xí)彩色卷積濾波器之前,讓我們看一下灰度圖。我們來做一個7×7的濾波器,應(yīng)用四個不同的特征映射。TensorFlow的conv2d功能相當簡單,并有四個變量:input,filter,strides和padding。在TensorFlow網(wǎng)站上,他們描述的conv2d功能如下:
計算給定四維輸入和濾波張量的二維卷積。
給定形狀的輸入張量[batch,in_height,in_width,in_channels]和形狀為[filter_height,filter_width,in_channels,out_channels]的濾波器/內(nèi)核張量。
由于我們正在處理的是灰度圖,因此in_channels是1,因為我們應(yīng)用了四個濾波器,所以out_channels是4.我們將以下四個濾波器/內(nèi)核應(yīng)用于我們的一個圖像(或者說batch為1):
讓我們看看濾波器如何影響我們的灰度圖像輸入。
gray = np.mean(image,-1)X = tf.placeholder(tf.float32, shape=(None, 224, 224, 1))conv = tf.nn.conv2d(X, filters, [1,1,1,1], padding="SAME")test = tf.Session()test.run(tf.global_variables_initializer())filteredImage = test.run(conv, feed_dict={X: gray.reshape(1,224,224,1)})tf.reset_default_graph()
這將返回一個(1,224,224,4)的4維張量,我們可以用它來可視化四個濾波器:
很明顯看到濾波器內(nèi)核的卷積非常強大。為了將其分解,我們的7×7內(nèi)核每次每步跨越圖像49個像素,然后將每個像素的值乘以每個內(nèi)核值,然后將所有49個值加在一起以構(gòu)成一個像素。(如果你仍然不理解圖像濾波的核心,訪問鏈接http://setosa.io/ev/image-kernels/)
本質(zhì)上,大多數(shù)卷積神經(jīng)網(wǎng)絡(luò)都是由卷積和池化構(gòu)成的。被用于卷積最常見的是3×3內(nèi)核濾波器。特別是,帶有2×2的步幅和2×2的池化核大小的最大池化是一種非常激進的方法,可以根據(jù)在內(nèi)核中的最大像素值縮小圖片尺寸。以下為它的示例。
現(xiàn)在,對于兩個conv2d和最大池化,有兩個選項可以選擇填充:“VALID”,這將縮小輸入和“SAME”,通過在輸入邊緣周圍添加零來保持輸入大小。這里是一個3×3內(nèi)核的最大池化的示例,使用1×1的步幅來比較填充選項:
創(chuàng)建ConvNet
現(xiàn)在我們已經(jīng)介紹了所有的基礎(chǔ)知識,可以開始構(gòu)建自己的卷積神經(jīng)網(wǎng)絡(luò)模型了。我們可以從占位符開始。X將是我們的輸入占位符,我們將把我們的圖像饋入,Y_是一組圖像的真實類別。
X = tf.placeholder(tf.float32, shape=(None, 224, 224, 3))Y_ = tf.placeholder(tf.float32, [None, classes])keepRate1 = tf.placeholder(tf.float32)keepRate2 = tf.placeholder(tf.float32)
我們將在一個范圍內(nèi)為每個過程創(chuàng)建所有部分。Scope對于在TensorBoard中可視化圖形是非常有用的,因為它們將所有東西都組合成一個可擴展的對象。我們創(chuàng)建了第一組內(nèi)核大小為3×3的濾波器,這個濾波器需要三個通道并輸出32個濾波器。這意味著對于32個濾波器中的每一個,R,G和B通道將會有3×3的內(nèi)核權(quán)重。我們的濾波器的權(quán)重值使用截尾正態(tài)分布初始化非常重要,所以我們有多個隨機濾波器,使TensorFlow適應(yīng)我們的模型。
# CONVOLUTION 1 - 1with tf.name_scope('conv1_1'):filter1_1 = tf.Variable(tf.truncated_normal([3, 3, 3, 32], dtype=tf.float32,stddev=1e-1), name='weights1_1')stride = [1,1,1,1]conv = tf.nn.conv2d(X, filter1_1, stride, padding='SAME')biases = tf.Variable(tf.constant(0.0, shape=[32], dtype=tf.float32),trainable=True, name='biases1_1')out = tf.nn.bias_add(conv, biases)conv1_1 = tf.nn.relu(out)
在第一次卷積conv1_1
結(jié)束時,我們使用relu進行處理,它將每個負數(shù)歸零。然后,我們將這32個特征卷積到另外的32個特征中。可以看到,conv2d
將輸入賦值為第一個卷積層的輸出。
# CONVOLUTION 1 - 2with tf.name_scope('conv1_2'):filter1_2 = tf.Variable(tf.truncated_normal([3, 3, 32, 32], dtype=tf.float32,stddev=1e-1), name='weights1_2')conv = tf.nn.conv2d(conv1_1, filter1_2, [1,1,1,1], padding='SAME')biases = tf.Variable(tf.constant(0.0, shape=[32], dtype=tf.float32),trainable=True, name='biases1_2')out = tf.nn.bias_add(conv, biases)conv1_2 = tf.nn.relu(out)
然后,我們把圖像縮小一半。
# POOL 1with tf.name_scope('pool1'):pool1_1 = tf.nn.max_pool(conv1_2,ksize=[1, 2, 2, 1],strides=[1, 2, 2, 1],padding='SAME',name='pool1_1')pool1_1_drop = tf.nn.dropout(pool1_1, keepRate1)
最后一部分涉及在池層上使用dropout(我們稍后詳細介紹)。然后我們再來兩次卷積,這樣就有64個特征和另一個池。請注意,第一個卷積必須將先前的32個特征通道轉(zhuǎn)換為64。
# CONVOLUTION 2 - 1with tf.name_scope('conv2_1'):filter2_1 = tf.Variable(tf.truncated_normal([3, 3, 32, 64], dtype=tf.float32,stddev=1e-1), name='weights2_1')conv = tf.nn.conv2d(pool1_1_drop, filter2_1, [1, 1, 1, 1], padding='SAME')biases = tf.Variable(tf.constant(0.0, shape=[64], dtype=tf.float32),trainable=True, name='biases2_1')out = tf.nn.bias_add(conv, biases)conv2_1 = tf.nn.relu(out)# CONVOLUTION 2 - 2with tf.name_scope('conv2_2'):filter2_2 = tf.Variable(tf.truncated_normal([3, 3, 64, 64], dtype=tf.float32,stddev=1e-1), name='weights2_2')conv = tf.nn.conv2d(conv2_1, filter2_2, [1, 1, 1, 1], padding='SAME')biases = tf.Variable(tf.constant(0.0, shape=[64], dtype=tf.float32),trainable=True, name='biases2_2')out = tf.nn.bias_add(conv, biases)conv2_2 = tf.nn.relu(out)# POOL 2with tf.name_scope('pool2'):pool2_1 = tf.nn.max_pool(conv2_2,ksize=[1, 2, 2, 1],strides=[1, 2, 2, 1],padding='SAME',name='pool2_1')pool2_1_drop = tf.nn.dropout(pool2_1, keepRate1)
接下來,我們創(chuàng)建一個512個神經(jīng)元的完全連接層,它將為我們的56x56x64的pool2_1層的每個像素建立一個權(quán)重連接。這是超過1億不同的權(quán)重值!為了計算我們完全連接網(wǎng)絡(luò),我們必須把輸入降至一維,然后乘以權(quán)重,再加上偏置。
#FULLY CONNECTED 1with tf.name_scope('fc1') as scope:shape = int(np.prod(pool2_1_drop.get_shape()[1:]))fc1w = tf.Variable(tf.truncated_normal([shape, 512], dtype=tf.float32,stddev=1e-1), name='weights3_1')fc1b = tf.Variable(tf.constant(1.0, shape=[512], dtype=tf.float32),trainable=True, name='biases3_1')pool2_flat = tf.reshape(pool2_1_drop, [-1, shape])out = tf.nn.bias_add(tf.matmul(pool2_flat, fc1w), fc1b)fc1 = tf.nn.relu(out)fc1_drop = tf.nn.dropout(fc1, keepRate2)
最后,我們得到帶有關(guān)聯(lián)權(quán)重和偏置的softmax,最后輸出Y.
#FULLY CONNECTED 3 & SOFTMAX OUTPUTwith tf.name_scope('softmax') as scope:fc2w = tf.Variable(tf.truncated_normal([512, classes], dtype=tf.float32,stddev=1e-1), name='weights3_2')fc2b = tf.Variable(tf.constant(1.0, shape=[classes], dtype=tf.float32),trainable=True, name='biases3_2')Ylogits = tf.nn.bias_add(tf.matmul(fc1_drop, fc2w), fc2b)Y = tf.nn.softmax(Ylogits)
創(chuàng)建損失和優(yōu)化
現(xiàn)在,我們可以開始開發(fā)我們模型的訓(xùn)練方面。首先,我們必須決定批處理大?。晃也荒苁褂贸^10個,因為GPU內(nèi)存不足。然后,我們必須決定訓(xùn)練次數(shù),即算法循環(huán)遍歷所有訓(xùn)練數(shù)據(jù)的次數(shù),最后決定我們的學(xué)習(xí)速率alpha。
numEpochs = 400batchSize = 10alpha = 1e-5
然后,我們?yōu)榻徊骒?,準確性檢查和反向傳播優(yōu)化創(chuàng)建范圍。
with tf.name_scope('cross_entropy'):cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=Ylogits, labels=Y_)loss = tf.reduce_mean(cross_entropy)with tf.name_scope('accuracy'):correct_prediction = tf.equal(tf.argmax(Y, 1), tf.argmax(Y_, 1))accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))with tf.name_scope('train'):train_step = tf.train.AdamOptimizer(learning_rate=alpha).minimize(loss)
然后,我們可以創(chuàng)建我們的會話并初始化所有的變量。
sess = tf.Session()init = tf.global_variables_initializer()sess.run(init)
為TensorBoard創(chuàng)建摘要
現(xiàn)在,我們也要使用TensorBoard,這樣我們可以看到我們的分類器的工作效果。我們將創(chuàng)建兩個圖:一個用于我們的訓(xùn)練集,一個用于我們的測試集。我們可以通過使用add_graph函數(shù)來顯示我們的圖形網(wǎng)絡(luò)。我們將使用摘要標量來衡量我們的總體損失和準確性,將我們的摘要合并到一起,這樣我們只需要調(diào)用write_op記錄我們的標量。
writer_1 = tf.summary.FileWriter("/tmp/cnn/train")writer_2 = tf.summary.FileWriter("/tmp/cnn/test")writer_1.add_graph(sess.graph)tf.summary.scalar('Loss', loss)tf.summary.scalar('Accuracy', accuracy)tf.summary.histogram("weights1_1", filter1_1)write_op = tf.summary.merge_all()
訓(xùn)練模型
然后我們可以編碼進行評估和訓(xùn)練。我不希望每步都記錄損失和準確性,因為這會大大減慢分類器的速度。所以,我們每五步記錄一次。
steps = int(train_x.shape[0]/batchSize)for i in range(numEpochs):accHist = []accHist2 = []train_x, train_y = imf.shuffle(train_x, train_y)for ii in range(steps):#Calculate our current stepstep = i * steps + ii#Feed forward batch of train images into graph and log accuracyacc = sess.run([accuracy], feed_dict={X: train_x[(ii*batchSize):((ii+1)*batchSize),:,:,:], Y_: train_y[(ii*batchSize):((ii+1)*batchSize)], keepRate1: 1, keepRate2: 1})accHist.append(acc)if step % 5 == 0:# Get Train Summary for one batch and add summary to TensorBoardsummary = sess.run(write_op, feed_dict={X: train_x[(ii*batchSize):((ii+1)*batchSize),:,:,:], Y_: train_y[(ii*batchSize):((ii+1)*batchSize)], keepRate1: 1, keepRate2: 1})writer_1.add_summary(summary, step)writer_1.flush()# Get Test Summary on random 10 test images and add summary to TensorBoardtest_x, test_y = imf.shuffle(test_x, test_y)summary = sess.run(write_op, feed_dict={X: test_x[0:10,:,:,:], Y_: test_y[0:10], keepRate1: 1, keepRate2: 1})writer_2.add_summary(summary, step)writer_2.flush()#Back propigate using adam optimizer to update weights and biases.sess.run(train_step, feed_dict={X: train_x[(ii*batchSize):((ii+1)*batchSize),:,:,:], Y_: train_y[(ii*batchSize):((ii+1)*batchSize)], keepRate1: 0.2, keepRate2: 0.5})print('Epoch number {} Training Accuracy: {}'.format(i+1, np.mean(accHist)))#Feed forward all test images into graph and log accuracyfor iii in range(int(test_x.shape[0]/batchSize)):acc = sess.run(accuracy, feed_dict={X: test_x[(iii*batchSize):((iii+1)*batchSize),:,:,:], Y_: test_y[(iii*batchSize):((iii+1)*batchSize)], keepRate1: 1, keepRate2: 1})accHist2.append(acc)print("Test Set Accuracy: {}".format(np.mean(accHist2)))
可視化圖表
在訓(xùn)練中,通過在終端中激活TensorBoard來檢查TensorBoard結(jié)果。
tensorboard --logdir="/tmp/cnn/"
然后,我們可以將我們的Web瀏覽器指向默認的TensorBoard地址http://0.0.0.0/6006。我們先來看看我們的圖表模型。
正如你所看到的,通過使用范圍,我們得到了完美的可視化的圖形。
測試性能
讓我們來看看準確性和損失的標量歷史記錄。
你可能發(fā)現(xiàn),這個圖反應(yīng)了一個很大的問題。我們的訓(xùn)練數(shù)據(jù),分類器獲得了100%的準確性和0損失,但是我們的測試數(shù)據(jù)最多只能達到80%的準確性,損失也很大。這是典型的過擬合現(xiàn)象,可能的原因包括沒有足夠的訓(xùn)練數(shù)據(jù)或神經(jīng)元過多。
我們可以通過調(diào)整,縮放和旋轉(zhuǎn)我們的訓(xùn)練數(shù)據(jù)來創(chuàng)建更多的訓(xùn)練數(shù)據(jù),但是更簡單的方法是添加dropout到池化和完全連接的層的輸出中。這將使每個訓(xùn)練步驟完全切割,在層中隨機丟棄一部分神經(jīng)元。這將迫使我們的分類器每次只訓(xùn)練一小組神經(jīng)元,而非整個集合。這讓神經(jīng)元專注于特定的任務(wù)。剔除80%的卷積層和50%完全連接層效果非常好。
通過減少神經(jīng)元,我們能夠達到90%的測試準確性,幾乎是10%的性能增長!但缺點是分類器花了大約6倍的時間來訓(xùn)練。
可視化進化濾波器
為了好玩,每50個訓(xùn)練步驟,我通過濾波器傳遞一個圖像,把濾波器的權(quán)重進化做成一個gif。它的效果很酷,并我們可以通過它對卷積網(wǎng)絡(luò)的工作方式有更好的認識。以下是兩個濾波器來自conv1_2:
你可以看到最初的權(quán)重初始化顯示了圖像很多細節(jié),但隨著時間的推移權(quán)重更新,他們變得更加專注于檢測某些邊緣。令我吃驚的是,我發(fā)現(xiàn)第一個卷積核filter1_1幾乎沒有變化。似乎初始權(quán)重初始化本身已經(jīng)足夠好了。我們繼續(xù)深入,在conv2_2中你可以看到它開始檢測更抽象和普遍的特征。
總而言之,使用少于400個訓(xùn)練圖像進行訓(xùn)練,訓(xùn)練后準確性幾乎可以達到90%,這給我留下了深刻的印象。我相信,如果有更多的訓(xùn)練數(shù)據(jù),更多的超參數(shù)調(diào)整,我可以取得更好的結(jié)果。
這篇文章總結(jié)了如何使用TensorFlow從零開始創(chuàng)建卷積神經(jīng)網(wǎng)絡(luò),以及如何從TensorBoard獲取推論以及如何使我們的濾波器可視化。重點記住,使用少量數(shù)據(jù)制作分類器時,更容易的方法取一個已經(jīng)使用多個GPU進行訓(xùn)練的大型數(shù)據(jù)集的模型和權(quán)重(如GoogLeNet或VGG16),并截斷最后一層并用他們自己的分類器替換它們。然后,分類器所要做的就是學(xué)習(xí)最后一層的權(quán)重,并使用預(yù)先存在的訓(xùn)練過的已有的濾波器權(quán)重。
本文為編譯文章,轉(zhuǎn)載請注明出處。
來源:atyun_com
來源網(wǎng)址:http://www.atyun.com/12659_使用tensorflow從零開始構(gòu)建卷積神經(jīng)網(wǎng)絡(luò)并可視化.html
聯(lián)系客服