1. 程式人生 > >tensorflow學習筆記:卷積神經網路最終筆記

tensorflow學習筆記:卷積神經網路最終筆記

  這已經是我的第四篇部落格學習卷積神經網路了。之前的文章分別是:

  1,Keras深度學習之卷積神經網路(CNN),這是開始學習Keras,瞭解到CNN,其實不懂的還是有點多,當然第一次筆記主要是給自己心中留下一個印象,知道什麼是卷積神經網路,當然主要是學習Keras,順便走一下CNN的過程。

  2,深入學習卷積神經網路(CNN)的原理知識,這次是對CNN進行深入的學習,對其原理知識認真學習,明白了神經網路如何識別影象,知道了卷積如何執行,池化如何計算,常用的卷積神經網路都有哪些等等。

  3,TensorFlow學習筆記——影象識別與卷積神經網路,這裡開始對TensorFlow進行完整的學習,以CNN為基礎開始對TensorFlow進行完整的學習,而這裡練習的是 LeNet網路。

  今天構建兩個完整卷積神經網路,一個簡單的MNIST熱個身,一個稍微複雜的Cifar,今天學習完,不再做卷積神經網路的相關筆記,我相信自己已經掌握了。

  卷積神經網路(Convolutional Neural Network  CNN)最初是為解決影象識別等問題設計的,當然其現在的應用不僅限於影象和視訊,也可用於時間序列訊號,比如音訊訊號,文字資料等。在早期的影象識別研究中,最大的挑戰是如何組織特徵,因為影象資料不像其他型別的資料那樣可以通過人工理解來提取特徵。在股票預測等模型中,我們可以從原始資料中提取過往的交易價格波動,市盈率,市淨率,盈利增長等金融因子,這既是特徵工程。但是在影象中,我們很難根據人為理解提取出有效而豐富的特徵。在深度學習出現之前,我們必須藉助 SIFT,HoG等演算法提取具有良好區分性的特徵,再集合 SVM 等機器學習演算法進行影象識別。如下圖所示,SIFT對一定程度內的縮放,平移,旋轉,視覺改變,亮度調整等畸變,都具有不變性,是當前最重要的影象特徵提取方法之一。可以說在之前只能依靠SIFT等特徵提取演算法才能勉強進行可靠地影象識別。

   然而SIFT這類演算法提取的特徵還是有侷限性的,在ImageNet ILSVRC 比賽的最好結果的錯誤率也有26%以上,而且常年難以產生突破。卷積神經網路提取的特徵則可以達到更好的效果,同時它不需要將特徵提取和分類訓練兩個過程分開,它在訓練時就自動提取了最有效的特徵。CNN作為一個深度學習架構被提出的最初訴求,是降低對影象資料預處理的要求,以及避免複雜的特徵工程。CNN可以直接使用影象的原始畫素作為輸入,而不必使用SIFT等演算法提取特徵,減輕了使用傳統演算法如SVM時必須做的大量重複,繁瑣的資料預處理工作。和SITF等演算法類似,CNN的最大特點在於卷積的權值共享結構,可以大幅減少神經網路的引數量,防止過擬合的同時又降低了神經網路模型的複雜度。CNN的權重共享其實也很像早期的延時神經網路(TDNN),只不過後者是在時間這一個維度上進行權值共享,降低了學習時間序列訊號的複雜度。

1,CNN在影象分類問題上有什麼優勢?

  這裡使用水果分類來分析一下SVM以及神經網路的劣勢。

  如果我們有一組水果的圖片,裡面有草莓,香蕉和橘子。在圖片尺寸較大的情況下,使用SVM分類的步驟是:

  • 人工提取特徵,比如說大小,形狀,重量,顏色等。
  • 根據上述特徵,把每一張圖片對映到空間中的一個點,空間的維度和特徵的數量相等。
  • 相同類別的物體具有類似的特徵,所以空間中標記為草莓的點肯定是聚在一起的,香蕉和橘子也是同理。這時候使用SVM演算法在空間中畫出各類點之間的分界線就完成了分類。

  在最後一步中,不使用SVM,使用別的分類器也是可以的,比如KNN,貝葉斯,甚至神經網路都是可以的。雖然不同演算法中效能會有差異,但是這裡我想說的就是在影象分類問題上的瓶頸並不在演算法的效能上,而是在特徵的提取上。

  區分草莓和橘子的特徵是容易提取的,那橘子和橙子呢?如果上述四個特徵不能很好的區分橘子和橙子,想要進一步提升演算法的效能怎麼辦?通常的做法是需要提取新的特徵。那麼新的特徵如何選擇呢?對於我這種水果盲來說,這個問題是具有一定難度的。

  除了橘子橙子問題,我們還有貓狗如何區分,狗品種如何識別等一系列問題。我想對於大部分人來說,狗狗品種的識別是非常有難度的。轉了一圈回來,突然發現,影象分類任務的瓶頸驚人出現在特徵選擇上。

  如果我們用神經網路直接對貓狗進行分類呢?這樣不就避開了特徵提取這一步了啊?假設輸入圖片大小為30*30,那麼設定900個輸入神經元,隱含層設定1000個神經元,輸出神經元個數對應需要的輸出數量不就好了嗎?甚至用SVM也可以這樣做,把一張30*30的圖看做900維空間中的一個點,代表貓的點和代表狗的點在這個900維的空間中必然是相聚於兩個簇,然後我們就可以使用SVM來劃出分界線了。

  但是這樣計算開銷就太大了,對於30*30的圖片我們也許可以這樣做,對於1000*1000的圖片我們這樣做的話就需要至少一百萬個隱層神經元,這樣我們就至少更新10^12個引數。而SVM的話,則相當於在一百萬維的空間中運行了。執行量將會大的難以估計。另外圖中並不是所有的資訊都和我們需要的。背景對我們的分類毫無價值,然而在這種一股腦全部拿來做輸入的情況下,背景也被當成了特徵進入了模型當中,準確度自然會有所下降。

  總之,如果不人工提取特徵,那麼計算量會非常大,精確度也無法保證。而人工提取特徵的方式又會在某些問題下難以進行,比如狗狗品種分類。

  而CNN通過它獨有的方式,成功的解決了這兩個問題。也就是說,CNN是一個可以自動提取特徵而卻待訓練引數相對不那麼多的神經網路,這就是CNN在影象分類任務中決定性的優勢。

2,為什麼要使用卷積層?

  和神經網路模型類似,CNN的設計靈感同樣來自於對神經細胞的研究。

    1981 年的諾貝爾醫學獎,頒發給了 David Hubel、TorstenWiesel,以及 
Roger Sperry。他們的主要貢獻,是發現了人的視覺系統的資訊處理是分級的。

    從低階的V1區提取邊緣特徵,再到V2區的形狀或者目標的部分等,再到更高層,
整個目標、目標的行為等。也就是說高層的特徵是低層特徵的組合,從低層到高層
的特徵表示越來越抽象,越來越能表現語義或者意圖。而抽象層面越高,存在的可
能猜測就越少,就越利於分類。

  值得一提的是,最低階的V1區需要提取邊緣特徵,而在上面提到的分類中,神經網路實際上是把30 * 30 的圖片按照 900 個畫素點處理的,那麼有沒有一種方法能夠神經網路像人一樣,按照邊緣來理解呢?

  卷積計算並不複雜,矩陣對應元素相乘的和就是卷積的結果,到了神經網路中會多出偏置b還有啟用函式,具體方法如下圖:

  圖片中展示的是由九個權重組成的矩陣 和圖片上九個畫素點組成矩陣 進行卷積過程。在偏置b為0,啟用函式使用ReLU的情況下,過程就像圖片右下角的公式一樣,對應元素乘積的和,再加上值為0的b,然後外套啟用函式得到輸出0。

  你可能會想到這部分的計算和普通的神經網路沒神經網路沒什麼差別,形式都是 f(wx + b)。那麼這麼處理和邊緣有什麼關係?多做幾次卷積就知道了。

  Filter 指的是權重組成矩陣,Input Layer中存的是圖片中的全部畫素。Convolutional Layer存的是Filter與圖片中所有3*3矩陣依次卷積後得到的結果。在輸出中我們可以看到兩個3,他們比其他的元素0都要大。是什麼決定了卷積結果的大小?觀察後發現,圖中參與卷積的部分1的排序和活動視窗中1的排列完全一樣時,輸出為3。而畫素的排列方式其實就是圖片中的形狀,這說明如果影象中的形狀和Filter中的形狀相似的話,輸出值就大,不像就小。因此,卷積的操作建立了神經網路與影象邊緣的聯絡。

  實際上CNN經過訓練之後,Filter中就是圖片的邊緣,角落之類的特徵。也即是說,卷積層是在自動提取圖片中的特徵。

  除此之外,卷積還有一種區域性連線的思想在裡面。他對圖片的處理方式是一塊一塊的,並不是所有畫素值一起處理。因此可以極大地降低引數值的總量。這裡我需要使用那張經典的圖片來說明卷積層是如何降低引數總量的:

  對於一張1000*1000的圖片來說,對他進行分類,至少需要10的12次方個引數。而如果對圖片使用卷積操作,每個神經元之和影象上的10*10的畫素連線的話,引數總量就變成了10的8次方。但是這樣的操作會導致一個問題,每個神經元只對圖片一部分的內容,那麼這個神經元學到的內容就不能應用到其他神經元上。比如說有這樣一個訓練集,同樣自私的貓出現在黑色神經元負責的區域中,但是測試集中,貓可能出現在圖片的任何位置。按照區域性連線的做法,其他區域的貓是無法被正確識別的。

   而為了讓出現在任何位置的貓都能夠被正確的識別,提出了權重共享。讓紅綠藍黑神經元中的引數全都一樣,這樣就可以使得模型的準確率不受物體位置的影響,看起來就像同一個Filter劃過了整個圖片。從提取特徵的角度上來講,一個Filter顯然不能滿足需求,因此需要生成多個不同的Filter來對圖片進行卷積。更棒的是,為了獲得平移不變性而使用的權重共享法,又以外的再一次降低了待訓練引數總數。

  就是使用100個權值共享的10*10 Filter來卷積,總引數也才10的4次方。也就是說,引數相較於普通的神經網路而言,總共下降了整整8個數量級,這種提升是誇張的。

  所以,卷積層的工作方式已經全部出來了,具體工作流程入下:

  藍色的部分代表輸入影象;綠色的部分代表輸出矩陣;周圍的虛線是padding操作,可以看做影象的一部分;下方不斷移動的陰影就是FIlter,其大小,數量,一次移動的距離都是可以自定義的;陰影至上方綠色的連線代表相乘再加之後的結果輸出到了輸出矩陣的那個位置。卷積層的折中操作方式,成功的模擬了生物視覺系統中的邊緣特徵提取部分。

  而CNN中對於分級結構的模擬,是通過卷積層的層層疊加實現的。AlexNet的論文中不止一次的提到,網路的深度對CNN效能的影響是顯著的。可以認為卷積層的不斷疊加會使得提取到的特徵越來越複雜,整個流程就像上述引用中提到的人類的視覺系統的工作方式一樣執行,最終完成對圖片的分類。

  那麼現在就可以很輕鬆的回答上面的問題了,使用卷積層是因為卷積層本質上是在自動提取圖片的特徵,而且在提取特徵的同時,極大的降低了網路總體待訓引數的總量。這兩個特徵使得CNN克服了特徵提取困難,待訓引數龐大的問題,成功制霸圖片分類問題。

3,為什麼要使用池化層?

   你可能會問,卷積層就已經把引數將了下來,還解決了特徵提取的問題,那還加一個池化層幹什麼呢?(可能池化層只是工程上想讓網路更深而做出的一個無奈之舉)

  就以最精彩出現的最大池化為例,來看看所謂的池化操作有多隨便吧。

   一個2*2 的最大池化操作如上圖,它做的就是把2*2視窗中的最大值存下來。所以綠色部分留下來了6;棕色部分是8;藍色部分是4。所以就很簡單,很隨便,但是這個操作有什麼優點呢?不知道,但是缺點卻顯而易見——損失了細節。為什麼損失細節也要做這一步呢?可能就是要壓縮矩陣,這樣可以在模型中多加幾層卷積層,用來提取更高維,更復雜的特徵。而從壓縮的角度來看,這一步可謂簡單有效,一個取最大值的操作,就讓矩陣大小變為四分之一。

  AlexNet的出現時2012年,那時候用到的是GTX580,3G視訊記憶體,文章中提到只用一塊GPU是不行的,因為視訊記憶體會爆,因此用了兩塊GPU並行進行這個任務。可能作者也是苦於總是爆視訊記憶體,而不得不加上池化層。就算這樣,還要用到兩塊GPU才成功訓練了整個網路。

  然而池化層的應用似乎帶來更多的便利之處。由於其只取最大值,忽視掉了其他影響較小的值,所以在當內容發生很小的變化的時候包括一些平移旋轉,CNN任然能夠穩定識別對應內容。也就是說池化層給模型帶來了一定程式上的不變性。

  而應不應該使用池化層還是一個正在討論的問題,有的網路用,有的網路不用。按照我的理解,在視訊記憶體夠用的情況下,不用池化層。這種丟失細節提升模型不變性的方法有點傷人傷己的意思。而且我們希望得到的模型並不是在不知道圖片變化了的情況下可以得到正確結果,我們希望的是模型可以認識到差異卻依然能做出正確的分類才對。

4,全連線層的作用

  在經過幾次卷積和池化的操作之後,卷積神經網路的最後一步是全連線層。這一步就和最最普通的神經網路沒有什麼區別。我認為這裡的神經網路就是充當一個分類器的作用,輸入時不同特徵的特徵值,輸出的是分類。其實可以在訓練好之後,把全連線層砍掉,把卷積部分的輸出當做是特徵,全連線層換成SVM或者別的分類器,重新訓練,也是可以取得良好效果的。

5,總結

  • 1,CNN之前的圖片分類演算法效能受制於特徵的提取以及龐大引數數量導致的計算困難。
  • 2,使用卷積來模擬人類視覺系統的工作方式,而這種方式極大的降低了神經網路的帶訓練引數數量。
  • 3,為了獲得平移不變形,使用了權重共享技術,該技術進一步降低了帶訓練引數數量
  • 4,卷積層實際上是在自動提取圖片特徵,解決了影象特徵提取這一難題。
  • 5,使用池化層的根部原因是降低計算量,而其帶來的不變形並不是我們需要的。不過在以模型準確率為綱的大背景下,繼續使用無可厚非。
  • 6,全連線層實質上是一個分類器。

6,卷積層的步驟

  一般的卷積神經網路由多個卷積層構成,每個卷積層中通常會進行如下幾個操作。

  (1)影象通過多個不同的卷積核的濾波,並加偏置(bias),提取出區域性特徵,每一個卷積核會映射出一個新的2D影象。

  (2)將前面卷積核的濾波輸出結果,進行非線性的啟用函式處理。目前最常見的是使用ReLU函式,而以前Sigmoid 函式用的比較多。

  (3)對啟用函式的結果再進行池化操作(即降取樣,比如將 2*2 的圖片降為 1*1 的圖片),目前一般是使用最大池化,保留最顯著的特徵,並提升模型的畸變容忍能力。

  這幾個步驟就構成了最常見的卷積層,當然也可以加上一個 LRN(Local Response Normalization,區域性響應歸一化層)層,目前非常流暢的Trick 還有 Batch Normalization等。

七,CNN在MNIST資料集上的影象分類

  程式碼如下:

# _*_coding:utf-8_*_
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets('MNIST_data/', one_hot=True)
sess = tf.InteractiveSession()


# 權重和偏置需要建立,我們這裡定義好初始化函式以便重複使用
# 這裡我們使用截斷的正態分佈噪聲,標準差設為0.1
def weight_varibale(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)


def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)


# 卷積層,池化層也是重複使用的,因此分別定義函式,以便後面重複使用
def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')


def max_pool_2x2(x):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1],
                          padding='SAME')


# 定義輸入的: x 特徵,y_ 真實的label
x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])
# 因為需要將1D 的輸入向量轉為2D的圖片結構,而且顏色只有一個通道
# 所以最終尺寸如下,當然-1代表樣本數量不固定,不用擔心
x_image = tf.reshape(x, [-1, 28, 28, 1])

# 第一個卷積層
W_conv1 = weight_varibale([5, 5, 1, 32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

# 第二個卷積層(區別第一個卷積,卷積核變為64,也就是說這一層會提取64個特徵)
W_conv2 = weight_varibale([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)

# 當經歷了兩次步長為2*2的最大池化,所以邊長已經只有1/4
# 所以圖片尺寸由28*28邊長7*7
W_fc1 = weight_varibale([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

# 為了防止過擬合,下面使用一個Dropout層
# Dropout層是通過一個placeholder傳入 keep_prob比率來控制的
# 在訓練時候,我們隨機丟掉一部分節點的資料來減輕過擬合
# 在預測時候,我們則保留全部資料來追求最好的預測效能
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

# 將Dropout層的輸出連線一個Softmax層,得到最後的概率輸出
W_fc2 = weight_varibale([1024, 10])
b_fc2 = bias_variable([10])
y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

# 定義損失函式cross_entropy,但是優化器使用Adam 並給予學習率1e-4
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv),
                                              reduction_indices=[1]))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

tf.global_variables_initializer().run()
for i in range(20000):
    batch = mnist.train.next_batch(50)
    if i % 100 == 0:
        train_accuracy = accuracy.eval(feed_dict={
            x: batch[0], y_: batch[1], keep_prob: 1.0
        })
        print("step %d, training accuracy %g" % (i, train_accuracy))
    train_step.run(feed_dict={x: batch[0], y_: batch[1],
                              keep_prob: 0.5})

print("test accuracy %g" % accuracy.eval(feed_dict={
    x: mnist.test.images,
    y_: mnist.test.labels,
    keep_prob: 1.0
}))

  結果如下:

step 0, training accuracy 0.1
step 100, training accuracy 0.9
step 200, training accuracy 0.86
step 300, training accuracy 0.92
step 400, training accuracy 0.92
step 500, training accuracy 0.94
step 600, training accuracy 0.98
step 700, training accuracy 0.96
......
step 7500, training accuracy 1
step 7600, training accuracy 0.98
... ...
step 19700, training accuracy 1
step 19800, training accuracy 1
step 19900, training accuracy 1
test accuracy 0.9929

  最後,這個CNN模型可以得到的準確率約為99.29%,基本可以滿足對手寫數字識別準確率的要求。相比之前MLP的2%的錯誤率,CNN的錯誤率下降了大約60%,這其中主要的效能提升都來自於更優秀的網路設計,即卷積網路對影象特徵的提取和抽象能力。依靠卷積核的權值共享,CNN的引數量並沒有爆炸,降低計算量的同時也減輕了過擬合,因此整個模型的效能有較大的提升。

  本文以CIFAR-10 為資料集,基於Tensorflow介紹CNN(卷積神經網路)影象分類模型的構建過程,著重分析了在建模過程中卷積層,池化層,扁平化層,全連線層,輸出層的運算機理,以及經過運算後圖像尺寸,資料維度等引數的變化情況。

八,CIFAR-10資料集介紹

  官網地址:http://www.cs.toronto.edu/~kriz/cifar.html

  Cifar資料集是一個影響力很大的影象分類資料集,Cifar資料集分為了Cifar-10和Cifar-100兩個問題,他們都是影象詞典專案(Visual Dictionary)中800萬張圖片的一個子集。

  CIFAR-10資料集由60000張彩色圖片構成,其中包括50000張訓練集圖片、10000張測試集圖片,每張圖片的shape為(32,32,3),即圖片尺寸為32*32,通道數為3;所有圖片可以被分為10類,包括:

飛機, 汽車, 鳥, 貓, 鹿, 狗, 青蛙, 馬, 船以及卡車。

  官網截圖如下所示:

  和MNIST資料集類似,Cifar-10 中的圖片大小都是固定的且每一張圖片中僅僅包含一個種類的實體。但是和MNIST相比,Cifar資料集最大的區別在於圖片由黑白變成的彩色,且分類的難度也相對更高。在Cifar-10資料集上,人工標註的正確率大概為94%,這比MNIST資料集上的人工表現要低很多。目前在Cifar-10資料集上最好的影象識別演算法正確率為95.59%,達到這個正確率的演算法使用了卷積神經網路。

  本次學習的目標是建立一個用於識別影象的相對較小的卷積神經網路,在這過程中,我們將會學到:

  1 著重建立一個規範的網路組織結構,訓練兵進行評估

  2 為建立更大規模更加複雜的模型提供一個範例

  選擇CIFAR-10是因為它的複雜程度足以用來檢驗TensorFlow中的大部分功能,並可將其擴充套件為更大的模型。與此同時由於模型較小所以訓練速度很快,比較適合用來測試新的想法,檢驗新的技術。

程式碼組織

  官網教程的程式碼位於tensorflow/models/image/cifar10/.

檔案作用
cifar10_input.py 讀取本地CIFAR-10的二進位制檔案格式的內容。
cifar10.py 建立CIFAR-10的模型。
cifar10_train.py 在CPU或GPU上訓練CIFAR-10的模型。
cifar10_multi_gpu_train.py 在多GPU上訓練CIFAR-10的模型。
cifar10_eval.py 評估CIFAR-10模型的預測效能。

  TensorFlow擅長訓練深度神經網路,被認定為是神經網路中最好用的庫之一。通過使用TensorFlow我們可以快速入門神經網路, 大大降低了深度學習(也就是深度神經網路)的開發成本和開發難度。 
  Tensorflow使用資料流圖進行數值計算,圖中的節點代表數學運算,圖中的邊代表在這些節點之間傳遞的多維陣列(張量)。在使用其構建模型時,先搭建好流圖結構——類似於鋪好管道,然後再載入資料——向管道中注水,讓資料在各節點計算、沿各管道流動;資料在流圖中計算、傳遞時採用多維陣列(張量)的形式,因此在Tensorflow中參與計算的均為陣列資料型別。 
  本文使用Tensorflow構建簡單的CNN影象多分類模型,其由3個卷積(含啟用函式)與池化層、1個扁平層、3個全連線層、1個輸出層構成,示意圖如下所示: 

 

  訓練自己的圖片(CNN):https://blog.csdn.net/Missayaaa/article/details/79119839

  該資料集的頁面:http://www.cs.toronto.edu/~kriz/cifar.html

  CIFAR-10和CIFAR-100是帶有標籤的資料集,都出自於規模更大的一個數據集,他有八千萬張小圖片(http://groups.csail.mit.edu/vision/TinyImages/。這個是一個大專案,你可以點選那個big map提交自己的標籤,可以幫助他們訓練讓計算機識別物體的模型)

資料的下載:

  • (共有三個版本:python,matlab,binary version 適用於C語言)
  • http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
  • http://www.cs.toronto.edu/~kriz/cifar-10-matlab.tar.gz
  • http://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz

 

九,CNN在Cifar-10資料集上的影象分類

  程式碼:

#_*_coding:utf-8_*_
# import cifar10
import cifar10_input
import tensorflow as tf
import numpy as np
import time


max_steps = 3000
batch_size = 128
data_dir = 'cifar10\CIFAR-10\cifar-10-batches-bin'


def variable_with_weight_loss(shape, stddev, w1):
    '''
    權重初始化函式
    :param shape: 卷積核引數,格式類似於[5, 5, 3, 32],代表卷積核尺寸(前兩個數字通道數和卷積核個數)
    :param stddev:  標準差
    :param w1:  L2正則化的權重引數
    :return: 返回帶有L2正則的初始化的權重引數
    '''
    # 截斷產生正態分佈,即產生正態分佈的值與均值的差值大於兩倍的標準差,那就重新寫
    var = tf.Variable(tf.truncated_normal(shape, stddev=stddev))
    if w1 is not None:
        # 給權重W加上L2 正則,並用W1 引數控制L2 Loss大小
        weight_loss = tf.multiply(tf.nn.l2_loss(var), w1, name='weight_loss')
        # 將weight_loss 存在一個名為 “losses” 的collection裡,後面會用到
        tf.add_to_collection('losses', weight_loss)
    return var

# 我們對影象進行資料增強的操作需要耗費大量CPU時間,因此distorted_inputs使用了16個
# 獨立的執行緒來加速任務,函式內部會產生執行緒池,在需要使用時會通過queue進行排程。
images_train, labels_train = cifar10_input.distorted_inputs(
    data_dir=data_dir, batch_size=batch_size
)

# 生成測試資料
images_test, labels_test = cifar10_input.inputs(eval_data=True,
                                                data_dir=data_dir,
                                                batch_size=batch_size)

# 資料中圖片的尺寸為24*24,即裁剪後的大小,而顏色的通道數則為3,代表RGB
image_holder = tf.placeholder(tf.float32, [batch_size, 24, 24, 3])
label_holder = tf.placeholder(tf.int32, [batch_size])

# 第一個卷積層
weight1 = variable_with_weight_loss(shape=[5, 5, 3, 64], stddev=5e-2,
                                    w1=0.0)
kernel1 = tf.nn.conv2d(image_holder, weight1, [1, 1, 1, 1],
                       padding='SAME')
bias1 = tf.Variable(tf.constant(0.0, shape=[64]))
conv1 = tf.nn.relu(tf.nn.bias_add(kernel1, bias1))
pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                       padding='SAME')
norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)

# 第二個卷積層,上一層卷積核數量為64,所以本層卷積核尺寸的第三個維度也需調整64
# 這裡bias值全部初始化為0.1,而不是0,最後調整了最大池化層和LRN層的順序,先LRN層處理
weight2 = variable_with_weight_loss(shape=[5, 5, 64, 64], stddev=5e-2, w1=0.0)
kernel2 = tf.nn.conv2d(norm1, weight2, [1, 1, 1, 1], padding='SAME')
bias2 = tf.Variable(tf.constant(0.1, shape=[64]))
conv2 = tf.nn.relu(tf.nn.bias_add(kernel2, bias2))
norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)
pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                       padding='SAME')

# 全連線層
reshape = tf.reshape(pool2, [batch_size, -1])
dim = reshape.get_shape()[1].value
weight3 = variable_with_weight_loss(shape=[dim, 384], stddev=0.04, w1=0.004)
bias3 = tf.Variable(tf.constant(0.1, shape=[384]))
local3 = tf.nn.relu(tf.matmul(reshape, weight3) + bias3)

# 全連線層2,和之前很像,只不過其隱含節點數下降了一半,只有192個,其他超引數不變
weight4 = variable_with_weight_loss(shape=[384, 192], stddev=0.04, w1=0.004)
bias4 = tf.Variable(tf.constant(0.1, shape=[192]))
local4 = tf.nn.relu(tf.matmul(local3, weight4) + bias4)

# 最後一層,先建立weight,其正態分佈標準差為上一個隱含層的節點數的倒數,並且不計入L2正則
weight5 = variable_with_weight_loss(shape=[192, 10], stddev=1/192.0, w1=0.0)
bias5 = tf.Variable(tf.constant(0.0, shape=[10]))
logits = tf.add(tf.matmul(local4, weight5), bias5)

  上面程式碼就完成了整個網路inference的部分。梳理整個網路結構可以得到如下表,從上到下,依次是整個卷積神經網路從輸入到輸出的流程。可以觀察到,其實設計CNN主要就是安排卷積層,池化層,全連線層的分佈和順序,以及其中超引數的設計,Trick的使用等。設計效能良好的CNN一定有規律可循的,但是要想針對某個問題設計最合適的網路結構,是需要大量實踐摸索的。

   完成了模型 inference 部分的構建,接下來計算 CNN的 loss。這裡依然使用 cross_entropy,主要注意的是這裡我們把softmax的計算和 cross_entropy_loss的計算合併在了一起。

#_*_coding:utf-8_*_
# import cifar10
import cifar10_input
import tensorflow as tf
import numpy as np
import time


max_steps = 3000   #訓練輪數(每一輪一個batch參與訓練)
batch_size = 128   # batch 大小
data_dir = 'cifar10\CIFAR-10\cifar-10-batches-bin' # 資料目錄


def variable_with_weight_loss(shape, stddev, w1):
    '''
    權重初始化函式
    :param shape: 卷積核引數,格式類似於[5, 5, 3, 32],代表卷積核尺寸(前兩個數字通道數和卷積核個數)
    :param stddev:  標準差
    :param w1:  L2正則化的權重引數
    :return: 返回帶有L2正則的初始化的權重引數
    '''
    # 截斷產生正態分佈,即產生正態分佈的值與均值的差值大於兩倍的標準差,那就重新寫
    var = tf.Variable(tf.truncated_normal(shape, stddev=stddev))
    if w1 is not None:
        # 給權重W加上L2 正則,並用W1 引數控制L2 Loss大小
        weight_loss = tf.multiply(tf.nn.l2_loss(var), w1, name='weight_loss')
        # 將weight_loss 存在一個名為 “losses” 的collection裡,後面會用到
        tf.add_to_collection('losses', weight_loss)
    return var

def loss(logits, labels):
    # 型別轉換為 tf.int64
    labels = tf.cast(labels, tf.int64)
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
        logits=logits, labels=labels,
        name='cross_entropy_per_example'
    )
    # 計算一個batch中交叉熵的均值
    cross_entropy_mean = tf.reduce_mean(cross_entropy,
                                        name='cross_entropy')
    # 將交叉熵存在 losses的collection
    tf.add_to_collection('losses', cross_entropy_mean)
    return tf.add_n(tf.get_collection('losses'), name='total_loss')

# 我們對影象進行資料增強的操作需要耗費大量CPU時間,因此distorted_inputs使用了16個
# 獨立的執行緒來加速任務,函式內部會產生執行緒池,在需要使用時會通過queue進行排程。
# images_train, labels_train = cifar10_input.distorted_inputs(
#     data_dir=data_dir, batch_size=batch_size
# )
images_train, labels_train = cifar10_input.distorted_inputs(
    batch_size=batch_size
)

# 生成測試資料,每次執行都會生成一個 batch_size 的數量的測試樣本
# images_test, labels_test = cifar10_input.inputs(eval_data=True,
#                                                 data_dir=data_dir,
#                                                 batch_size=batch_size)

images_test, labels_test = cifar10_input.inputs(eval_data=True,
                                                batch_size=batch_size)
# 資料中圖片的尺寸為24*24,即裁剪後的大小,而顏色的通道數則為3,代表RGB
image_holder = tf.placeholder(tf.float32, [batch_size, 24, 24, 3])
label_holder = tf.placeholder(tf.int32, [batch_size])

# 第一個卷積層
# 第一層權重初始化,產生64個三通道(RGB圖片),尺寸為5*5的卷積核,不帶L2正則(w1=0)
weight1 = variable_with_weight_loss(shape=[5, 5, 3, 64], stddev=5e-2,
                                    w1=0.0)
# 對輸出原始影象進行卷積操作,步長為[1, 1, 1, 1]即將每一個畫素點都計算到
kernel1 = tf.nn.conv2d(image_holder, weight1, [1, 1, 1, 1],
                       padding='SAME')
# 定義第一層的偏置引數,由於有64個卷積核,這裡有偏置尺寸為64
bias1 = tf.Variable(tf.constant(0.0, shape=[64]))
# 卷積結果加偏置後採用relu啟用
conv1 = tf.nn.relu(tf.nn.bias_add(kernel1, bias1))
# 第一層的池化操作,使用尺寸為3*3,步長為2*2 的池化層進行操作
# 這裡的ksize和strides 第一個和第四個數字一般都是1
pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                       padding='SAME')
# 用LRN對結果進行處理,使用比較大的值變得更大,比較小的值變得更小,模仿神經系統的側抑制機制
norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)

# 第二個卷積層,上一層卷積核數量為64,所以本層卷積核尺寸的第三個維度也需調整64
# 這裡bias值全部初始化為0.1,而不是0,最後調整了最大池化層和LRN層的順序,先LRN層處理
weight2 = variable_with_weight_loss(shape=[5, 5, 64, 64], stddev=5e-2, w1=0.0)
kernel2 = tf.nn.conv2d(norm1, weight2, [1, 1, 1, 1], padding='SAME')
bias2 = tf.Variable(tf.constant(0.1, shape=[64]))
conv2 = tf.nn.relu(tf.nn.bias_add(kernel2, bias2))
norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)
pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                       padding='SAME')

# 全連線層
# 將上面的輸出結果展平,-1代表不確定多大
reshape = tf.reshape(pool2, [batch_size, -1])
# 得到資料扁平化後的長度
dim = reshape.get_shape()[1].value
# 建立一個隱含節點數為384的全連線層
weight3 = variable_with_weight_loss(shape=[dim, 384], stddev=0.04, w1=0.004)
bias3 = tf.Variable(tf.constant(0.1, shape=[384]))
local3 = tf.nn.relu(tf.matmul(reshape, weight3) + bias3)

# 全連線層2,和之前很像,只不過其隱含節點數下降了一半,只有192個,其他超引數不變
weight4 = variable_with_weight_loss(shape=[384, 192], stddev=0.04, w1=0.004)
bias4 = tf.Variable(tf.constant(0.1, shape=[192]))
local4 = tf.nn.relu(tf.matmul(local3, weight4) + bias4)

# 最後一層,先建立weight,其正態分佈標準差為上一個隱含層的節點數的倒數,並且不計入L2正則
weight5 = variable_with_weight_loss(shape=[192, 10], stddev=1/192.0, w1=0.0)
bias5 = tf.Variable(tf.constant(0.0, shape=[10]))
# 注意這裡,直接是網路的原始輸出(即wx+b 的形式),沒有加softmax啟用
logits = tf.add(tf.matmul(local4, weight5), bias5)


# 將logits節點和label_holder傳入loss函式獲得最終的loss
loss = loss(logits, label_holder)
#優化器依然選擇 adam Optimizer 學習速率設定為1e-3
train_op = tf.train.AdamOptimizer(1e-3).minimize(loss)
# 關於tf.nn.in_top_k函式的用法見http://blog.csdn.net/uestc_c2_403/article/details/73187915
# tf.nn.in_top_k會返回一個[batch_size, classes(類別數)]大小的布林型張量,記錄是否判斷正確
top_k_op = tf.nn.in_top_k(logits, label_holder, 1)

# 建立預設的session,接著初始化全部模型引數
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()

# 啟動圖片資料增強的執行緒佇列,這裡一共使用了16個執行緒來進行加速
# 注意:如果不啟動執行緒,後續的inference及訓練的操作都是無法開始的
tf.train.start_queue_runners()

for step in range(max_steps):
    start_time = time.time()
    # 獲得一個batch的訓練資料
    image_batch, label_batch = sess.run([images_train, labels_train])
    # 執行訓練過程並獲得一個batch的total_loss
    _, loss_value = sess.run([train_op, loss],
                             feed_dict={image_holder: image_batch,
                                        label_holder: label_batch})
    # 記錄跑一個batch所耗費的時間
    duration = time.time() - start_time

    # 每10個batch輸出資訊
    if step % 10 == 0:
        # 計算每秒能跑多少個樣本
        examples_per_sec = batch_size / duration
        # 計算每個batch需要耗費的時間
        sec_per_batch = float(duration)

        format_str = ('step %d, loss=%.2f(%.1f examples/sec; %.3f sec/batch)')
        print(format_str%(step, loss_value, examples_per_sec, sec_per_batch))


num_examples = 10000
import math
num_iter = int(math.ceil(num_examples / batch_size))
true_count = 0
total_sample_count = num_iter * batch_size
step = 0
while step < num_iter:
    image_batch, label_batch = sess.run([images_test, labels_test])
    predictions = sess.run([top_k_op], feed_dict={
        image_holder: image_batch, label_holder: label_batch
    })
    true_count += np.sum(predictions)
    step += 1

precision = true_count / total_sample_count
print('precsion @ 1 = %.3f' %precision)

  部分結果如下:

step 0, loss=4.68(136.3 examples/sec; 0.939 sec/batch)
step 10, loss=3.66(187.9 examples/sec; 0.681 sec/batch)
step 20, loss=3.13(175.1 examples/sec; 0.731 sec/batch)
step 30, loss=2.83(181.5 examples/sec; 0.705 sec/batch)
step 40, loss=2.47(177.5 examples/sec; 0.721 sec/batch)
step 50, loss=2.25(185.2 examples/sec; 0.691 sec/batch)
step 60, loss=2.18(196.3 examples/sec; 0.652 sec/batch)
step 70, loss=2.12(191.3 examples/sec; 0.669 sec/batch)
step 80, loss=1.94(187.4 examples/sec; 0.683 sec/batch)
... ...
step 160, loss=1.86(206.4 examples/sec; 0.620 sec/batch)
step 170, loss=1.94(207.4 examples/sec; 0.617 sec/batch)
step 180, loss=1.85(209.8 examples/sec; 0.610 sec/batch)
... ...
step 310, loss=1.60(183.1 examples/sec; 0.699 sec/batch)
step 320, loss=1.57(193.3 examples/sec; 0.662 sec/batch)

  

 

 

參考文獻:https://blog.csdn.net/BaiHuaXi