用TensorFlow和TensorBoard從零開始構建ConvNet(CNN)
更多深度文章,請關注:https://yq.aliyun.com/cloud
首先了解TensorBoard是什麼?解開你心中的疑惑!
在本教程中,我將介紹如何使用TensorFlow,從頭開始構建卷積神經網絡,並使用TensorBoard可視化我們的圖形及神經網絡性能。如果您不了解完全神經網絡的一些基礎知識,我強烈建議您首先看另一個教程關於TensorFlow。在這篇文章中,我也把卷積神經網絡的每個步驟講的都很仔細,所以你可以在文章中完全了解每個步驟發生了什麼。未來您可以看到每一層的卷積,並使用它們進行自己的網絡模型構建,所以我從頭開始構建該模型。我隻會強調主要的代碼,不過你可以在GitHub上下載源碼。
1.選擇數據集
一開始,我先要選擇圖像數據集。我決定用牛津大學的寵物數據集。我選擇了這個數據集的原因很簡單:標簽非常簡單,訓練數據也不錯,而且還有一些邊框。另一個我認為對於創建第一個模型是非常好的數據集是在Kaggle上發現的辛普森數據集。
2.選擇一個模型
第二步,必須決定我們的卷積神經網絡的模型。一些非常受歡迎的型號例如:GoogLeNet或VGG16,它們都具有多個卷積,可以用於檢測1000種數據集imagenet中的圖像。我決定一個更簡單的卷積網絡:
我們先簡要的分解這個模型,它以一個224x224x3的圖像開始,它根據前三個通道卷積到32個特征圖(Feature Map)。我們將這組32個特征圖集合到另外32個特征中。然後將其匯總到112x112x32圖像中,我們將卷入64個特征圖,然後再次進行二次,最後匯總為56x56x64。然後將這個最終合並的層的每個單元完全連接到一個512個的神經元中,然後基於類的數量最後放入softmax層。整個過程就是如此,如果你對上述有一些疑問,可以進一步與我交流。
3.處理和構建數據集:
首先,我們要加載我們的依賴項,其中包括我所調用的功能函數imFunctions
來處理圖像數據。
import imFunctions as imf
import tensorflow as tf
import scipy.ndimage
from scipy.misc import imsave
import matplotlib.pyplot as plt
import 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')
然後我們可以將圖像分成不同的文件夾,其中包括訓練文件夾和測試文件夾。sortImages
函數中的數字表示的是您想從訓練數據中分離出測試數據的百分比。
imf.sortImages(0.15)
然後,我們可以使用相應的一個熱矢量(one-hot)將我們的數據集構建成一個numpy數組,以表示我們的類。該函數將會表現您要讓神經網絡做那些事,由於我有限的GPU RAM(3GB),我選擇了一個非常小的數據集,僅僅試圖區分兩種狗:來自薩摩耶的ShibaInu。
train_x, train_y, test_x, test_y, classes, classLabels = imf.buildDataset()
4.如何卷積實現功能
現在我們有一個數據集可以使用,其次就是了解卷積如何工作。在跳入彩色卷積濾波器之前,讓我們來看一下灰度圖。讓我們製作一個應用四個不同特征圖的7x7濾鏡。TensorFlow的conv2d功能相當簡單,它包含四個變量:輸入,過濾器,步幅和填充。在TensorFlow官方網站上,他們描述的conv2d功能如下:
1.計算給定4-D輸入和濾波張量的2-D卷積。
2.給定一個形狀為[batch,in_height,in_width,in_channels]的輸入張量以及形狀為[filter_height,filter_width,in_channels,out_channels]的過濾器/內核張量。
由於我們正在使用灰度圖,所以in_channels是1,因為我們使用了四個過濾器,所以我們的out_channels將是4。我們將以下四個過濾器/內核應用到我們的一個圖像中:
讓我們看看這個過濾器如何處理我們的灰度圖像輸入:
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()
這將返回一個4d張量(1,224,224,4),我們可以使用它來可視化四個過濾器:
我們可以看到過濾器的內核卷積非常強大。為了打破它,我們的7x7內核一次跨越圖像像素的49個步長,然後將每個像素的值乘以內核值,然後將所有49個值加在一起,生成一個像素。
現在,本質上,大多數卷積神經網絡隻包括卷積和池化(pooling)。最常見的是3x3內核過濾器用於卷積。特別是步長為2×2,內核大小為2×2隻是基於內核中最大像素值來減少圖像大小的一種有效的方法。這是一個2x2內核的基本示例,在兩個維度上都具有步長2:
現在,對於兩個conv2d和最大池化,有兩個選項可用於填充:“VALID”:這將縮小輸入和“SAME”:這將通過在輸入邊緣添加來保持輸入大小。這是一個具有3x3內核的最大池的示例,步長為1x1以比較填充選項:
5.創建ConvNet
我們已經介紹了基礎知識,那麼讓我們開始構建我們的卷積神經網絡模型。我們可以從占位符開始。X將是我們的輸入占位符,我們將把圖像提供給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)
範圍(scope)對於在TensorBoard中的圖形可視化非常有用,因為它們將所有內容分組成一個可擴展對象。我們創建了第一套內核大小為3x3的過濾器,它采用三個通道並輸出32個過濾器。這意味著對於32個濾波器中的每一個,R、G和B通道將有3x3的內核權重。另外非常重要的一點是,我們的過濾器的權重值是使用截斷的正常值進行初始化的,所以我們有多個隨機過濾器,這意味著TensorFlow將適應我們的模型。
# CONVOLUTION 1 - 1
with 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結束時,我們通過應用relu來完成,通過將每個負數分配給零來作為閾值。然後我們將這32個特征集合到另外32個特征中。您可以看到第一個卷積層的輸出將作為conv2d的輸入。
# CONVOLUTION 1 - 2
with 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 1
with 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個特征通道轉換為64。
# CONVOLUTION 2 - 1
with 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 - 2
with 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 2
with 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)
接下來,我們創建一個512個神經元完全連接的神經網絡層,它將為56x56x64 pool2_1層的每個像素設置一個權重連接。這將是超過1億不同的權重值!為了計算我們的完全連接的網絡,我們必須將輸入轉化一維,然後我們可以乘以我們的權重並加上我們的偏差(Bias)。
with 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)
最後,我們的softmax與其相關的權重和偏差,最後是我們的輸出Y。
#FULLY CONNECTED 3 & SOFTMAX OUTPUT
with 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)
6.創建損失函數和優化器:
現在,我們可以開始開發我們的模型的訓練部分。首先,我們必須決定訓練批量大小;我自己的情況是不能使用超過10,因為GPU內存的緣故。那麼我們必須決定訓練次數,算法將分批地遍曆所有訓練數據的次數,最後是我們的學習速率α。
numEpochs = 400
batchSize = 10
alpha = 1e-5
然後,我們為我們的交叉熵,精度檢查器和反向傳播優化器創建了一些範圍。
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)
然後,我們可以創建我們的會話(session)並初始化所有的變量。
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
7. 準備TensorBoard
現在,我們想使用TensorBoard,這樣我們可以看到我們的分類器在做什麼。我們將創建plot模塊:一個用於我們的訓練集,一個用於我們的測試集。我們可以通過使用add_graph功能可視化我們的圖形網絡。我們將使用匯總標量(scalar)來衡量我們的損失值和準確度,並將我們的匯總合並在一起,所以我們隻需要調用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()
8.訓練模型
然後我們可以對我們的模型進行評估和訓練。我們不想在每個時間步驟中總結損失和準確性,因為這將大大減慢分類器的速度。所以相反,我們每五個步驟記錄一次。
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 step
step = i * steps + ii
acc = 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 TensorBoard
summary = 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 TensorBoard
test_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()
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})
9.可視化
當訓練的時候,我們來看看TensorBoard的結果,在終端上激活TensorBoard。
tensorboard --logdir="/tmp/cnn/"
然後我們可以將我們的網頁瀏覽器指向默認的TensorBoard地址https://0.0.0.0/6006。我們先看看我們的圖形化模型。正如你所看到的,通過使用範圍(scope),我們可視化了一個漂亮的幹淨版本的圖形。
10.性能測量
我們來看看我們準確度和損失的標量曆史:
你可能說我們有一個很嚴峻的問題。對於我們的培訓數據,分類器獲得了100%的準確性和0損失,但是我們的測試數據最多隻能達到80%,並且會有很大的損失。這是一個常見的現象原,因包括訓練數據不足或神經元太多。
我們可以通過調整、縮放和旋轉訓練數據以此來獲得更多的訓練數據,但是我認為更容易的方法是添加dropout功能到我們的池化層和完全連接層的輸出。這將使每個訓練步驟在每一層中隨機地完全刪除或退出一部分神經元。這將使我們的分類器一次隻訓練一小組的神經元,而全部的神經元。這使得專門神經元從事特定任務,而不是將所有神經元泛化在一起,同時進行某項工作。減少80%的卷積層和50%的完全連接的層可能會產生一些驚人的結果。
通過drop-out神經元,我們可以在測試數據上實現90%以下的性能,幾乎提高了10%!但也有一個缺點,分類器花了大約6倍的時間來訓練。
4.可視化進化過濾器
為了增加一些樂趣,每50個訓練步驟,我通過一個過濾器傳遞了一個圖像,並將過濾器的權重變化gif化。這出現了一些非常酷的效果和一些非常好的洞察例如:觀察如何卷積網絡的工作。以下是兩個過濾器conv1_2:
您可以看到加權初始化顯示了大量圖像,但權重隨著時間的推移而變化,他們更加專注於檢測某些邊緣。令我吃驚的是,我發現第一個卷積內核filter1_1幾乎沒有改變。為了進一步了解網絡,這裏是conv2_2,您可以看到它開始檢測更多的抽象的泛化功能。
總而言之,讓我印象深刻的是,我能夠使用少於400個訓練圖像訓練幾乎90%精度的模型。我相信有更多的訓練數據及更多的調整超參數,我可以取得更好的結果。
最後,重要的是要記住,在製作具有少量數據的分類器時,更容易的方法是采用已經在具有多個GPU(例如GoogLeNet或VGG16)的巨大數據集上進行訓練的模型和權重,並在最後一層用自己的類替換它們的類。那麼,這就要求所有的分類器都要做的是學習最後一層的權重,並使用預先存在的訓練過的權重。最後希望你能在本文中學到一些知識,have fun!
本文由北郵@愛可可-愛生活老師推薦,@阿裏雲雲棲社區組織翻譯。
文章原標題《Visualizing convolutional neural networks》
Justin Francis目前是加拿大阿爾伯塔大學的本科生。賈斯汀還在大學工程俱樂部“自主機器人車輛項目”(arvp.org)的軟件團隊中幫助實施和實驗了深入學習和強化學習算法
譯者:袁虎 審閱:坯子
文章為簡譯,更為詳細的內容,請查看原文
最後更新:2017-09-16 23:33:15