TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路

機器之心發表於2017-07-30

TensorFlow 已經成為了現在最流行的深度學習框架,相信很多對人工智慧和深度學習有興趣的人都躍躍欲試。對於初學者來說,TensorFlow 也是一個非常好的選擇,它有非常豐富的入門學習資料和龐大的開發者社群。近日,資料科學學習平臺 DataCamp 發表了一篇針對 TensorFlow 初學者的教程,從向量和張量的基本概念說起,一步步實現了一個分類交通標誌影象的神經網路。機器之心對本教程進行了編譯介紹。


深度學習是機器學習的一個子領域,包含了一系列受大腦的結構和功能啟發得到的演算法。

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路

TensorFlow 是谷歌開發的第二個機器學習框架,可用於設計、構建和訓練深度學習模型。你可以使用 TensorFlow 庫進行數值計算,這本身似乎並沒有什麼特別的,但這些計算是使用資料流圖完成的。在這些圖中,節點表示數學運算,而邊則表示資料——通常是多維的陣列或張量,在這些邊之間傳遞。

看到了吧?TensorFlow 的名字就源自神經網路在多維陣列或張量上執行的這種運算!它本質上就是張量的流。這就是目前你需要了解的關於張量的所有內容,但在接下來的章節中,我們還會更加深入!

這份 TensorFlow 入門教程將會以一種互動式的方式為你呈現如何進行深度學習:

  • 首先你將瞭解張量。
  • 然後,本教程將簡單介紹幾種在你的系統上安裝 TensorFlow 的方式,以便你能上手練習以及在你的工作空間中載入資料。
  • 之後,你將瞭解一些 TensorFlow 基礎知識:你會看到你可以輕鬆開始執行一些簡單計算。
  • 之後,你將開始進入實際工作:你將載入比利時交通標誌資料,然後使用簡單的統計和繪圖工具對其進行探索。
  • 在你的探索中,你將看到你需要運算元據,以便你能將其饋送給你的模型。所以你需要花時間調整你的圖片的大小,並將其轉換成灰度影象。
  • 接下來,你終於可以開始做你自己的神經網路模型了!你將一層層地構建起你的模型。
  • 一旦設定好了架構,你就可以使用它來迭代式地訓練你的模型,並且最終通過給它饋送一些測試資料來評估它。
  • 最後,你將得到一些用於未來進步的建議指導,以便你瞭解你能用你剛構建好的模型做什麼以及你該如何繼續使用 TensorFlow 進行學習。

你也可以在這裡下載本教程:https://github.com/Kacawi/datacamp-community/blob/master/TensorFlow%20Tutorial%20For%20Beginners/TensorFlow%20Tutorial%20For%20Beginners.ipynb

另外,你可能也會對下面三個課程感興趣:

  • Python 深度學習:https://www.datacamp.com/courses/deep-learning-in-python
  • DataCamp 的 Keras 教程:https://www.datacamp.com/community/tutorials/deep-learning-python
  • 使用 R 語言的 Keras 教程:https://www.datacamp.com/community/tutorials/keras-r-deep-learning

介紹張量

為了很好地瞭解張量,最好先了解一點線性代數和向量計算的知識。你在引言中已經讀到了,張量在 TensorFlow 中是作為多維資料陣列實現的,但為了完全理解張量及其在機器學習領域的應用,也許還是需要更多一些介紹。

平面向量(plane vector)

在你瞭解平面向量之前,我們先簡單澄清一下「向量」的概念。向量是特殊型別的矩陣(即數字構成的矩形陣列)。因為向量是有序的數字集合,所以它們往往被看作是列矩陣:它們只有一列和一定數量的行。換句話說,你也可以將向量看作是有一個方向的標量。

記住:標量是「5 米」或「60 米/秒」這樣的量,而向量則是「向北 5 米」或「向東 60 米/秒」這樣的量。這兩者之間的不同很顯然:向量有一個方向。但是,到目前為止你看到的這些例子與你在機器學習問題中實際操作的向量可能相差很大。這很正常;數學向量的長度是純數字:是絕對的。而方向則是相對的:它的度量是相對於某個參考方向,並且有弧度或度作單位。你通常假設方向是正的,並且從參考方向按逆時針方向旋轉。

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路

當然在視覺上你可以將向量表示成箭頭,如上圖所示。這意味著你可以將向量看作是有方向和長度的箭頭。方向又箭頭的頭表示,而長度則由箭頭的長度表示。

那麼什麼又是平面向量呢?

平面向量是最簡單的張量配置。它們就像是你上面看到的常規向量,唯一的不同是它們處在一個向量空間中。為了更好地理解這一點,讓我們從一個例子開始:你有一個 2×1 的向量。也就是說該向量屬於一次配對兩個數的實數集。或者換句話說,它們是一個二維空間的一部分。在這種情況,你可以使用箭頭或射線在座標 (x,y) 平面表示向量。

從一個標準位置(其中向量的起點為 (0,0))的座標平面出發,你可以通過檢視該向量的第一行來推導 x 座標,同時你也可以在第二行找到 y 座標。當然,並不一定總是要維持這種標準位置,向量可以在平面內平行移動而不發生改變。

注:類似地,對於大小為 3×1 的向量,那就是在談論一個三維空間。你可以將該向量表示成一個三維圖形,帶有指向其向量速度位置的箭頭:它們被畫在標準的 x, y 和 z 軸上。

將這些向量表示到座標平面上是很好的,但本質上,這些向量可以用來執行運算,為了幫助做到這一點,你可以將你的向量表示成基礎或單位向量。

單位向量是指幅度為 1 的向量,通常用帶有「帽子」的小寫字母表示。如果你想將一個二維或三維向量表示成兩個或三個正交分量(比如 x 軸、y 軸和 z 軸)的和,單位向量可以非常方便。

而且比如當你考慮將一個向量表示成分量的和時,你會發現你在討論分量向量,其是兩個或三個向量,它們的和即是原來給出的向量。

提示:這個視訊用簡單的家用物品解釋了張量:https://www.youtube.com/watch?v=f5liqUk0ZTw

張量

平面向量以及餘向量(covector)和線性運算子(linear operator)這三者有一個共同點:它們都是張量的特定案例。你仍然記得前一節中如何將向量特徵化為帶有一個方向的標量。那麼,張量就是一種帶有幅度和多個方向的物理實體的一種數學表徵。

而且,就向你通過單個數字表示一個標量,3 個數字的序列表示一個三維空間中的向量一樣,三維空間中的張量可以通過具有 3R 個數字的陣列表示。

這裡的 R 表示張量的秩(rank):比如在一個三維空間中,一個第二秩張量(second-rank tensor)可以用 3^2=9 個數字表示。在一個 N 維空間中,標量仍然只需要一個數字,而向量則需要 N 個數字,張量則需要 N^R 個數字。這就是為什麼你常會聽到人們說標量是 0 秩的張量:因為沒有方向,你可以使用一個數字表示它。

記住這一點,就能輕鬆識別和區分標量、向量和張量了:標量可以用單個數字表示,向量是一個有序的數字集合,張量是一個數字的陣列。

張量的獨特之處在於分量和基向量的組合:基向量在參考系之間按一種方式變換,分量也只按這樣一種方式變換以保證分量和基向量之間的組合不變。

安裝 TensorFlow

現在你對 TensorFlow 已經有了一定程度的瞭解了,那就讓我們開始上手安裝這個庫吧。這裡需要說明一下,TensorFlow 提供了 Python、C++、Haskell、Java、Go、Rust 的 API,另外還有一個用於 R 語言的第三方軟體包 tensorflow.

提示:如果你想知道更多 R 語言的深度學習軟體包,可以參閱《keras: Deep Learning in R》:https://www.datacamp.com/community/tutorials/keras-r-deep-learning#gs.aLGxTIg 

在本教程中,你要下載可以用 Python 編寫你的深度學習專案的 TensorFlow 版本。在 TensorFlow 安裝頁面 https://www.tensorflow.org/install/,你可以看到一些使用 virtualenc、pip、Docker 和 lastly 安裝 TensorFlow 的最常見方式和最新說明,當然另外也有其它一些在你的個人計算機上安裝 TensorFlow 的方法。

注:如果你用的 Windows,你也可使用 Conda 安裝 TensorFlow。但是因為這種安裝方式是社群提供支援的,最好還是參考官方的安裝說明:https://www.tensorflow.org/install/install_windows

現在你應該已經完成了安裝,現在需要再次檢查你是否正確安裝了 TensorFlow,你可以將其匯入到別名 tf 之下的工作空間:

import tensorflow as tf

注:在上面的程式碼中使用的別名 tf 是一種慣例——使用這個別名既能讓你與其他在資料科學專案中使用 TensorFlow 的開發者保持一致,也有助於開源 TensorFlow 專案。

TensorFlow 入門:基礎知識

你將能寫一般的 TensorFlow 程式,你可以將其作為一個塊(chunk)執行;當你使用 Python 時,一眼看上去這或許有點矛盾。但是,如果你願意,你也可以使用 TensorFlow 的 Interactive Session,讓你可以對 TensorFlow 進行更互動式的操作。當你習慣了 IPython 時,這會非常方便。

對於這個教程,我們的重點是第二個選項:這將有助於你通過 TensorFlow 上手深度學習。但在你深入之前,先讓我們嘗試一些輕量級的,之後再進入重量級挑戰。

首先,在別名 tf 下匯入 tensorflow 庫,正如你在前一節看到的那樣。然後初始化兩個實際上是常量的變數。將一個 4 個數字的陣列傳遞到 constant( ) 函式。

注:你可能也可以傳入一個整數,但通常不會這樣,你會發現你操作的是陣列。正如引言中介紹的那樣,張量就是關於陣列!所以要確保你傳遞的是陣列。接下來,你可以使用 multiply() 來將你的兩個變數相乘。結果儲存在 result 變數中。最後,在 print() 函式的幫助下顯示 result 結果。

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路

注:你已經在上面的 DataCamp Light 程式碼塊中定義了常量,但你也可以使用另外兩種型別的值,即佔位符(placeholder)和變數(variable)。佔位符是指沒有分配的值,在你開始執行該 session 時會被初始化。正如其名,它只是張量的一個佔位符,在 session 執行時總是得到饋送。變數是指值可以變化的量。而前面你已經見過的常量的值則不會變化。

這幾行程式碼的結果是計算圖中的一個抽象張量。但是可能與你預期的相反,result 實際上沒有得到計算;它只是定義了模型,但沒有執行程式來計算結果。你可以在顯示輸出中看到,其中並沒有你希望看到的結果(比如 30)。這意味著 TensorFlow 的評估很懶惰!

但是,如果你確實想看到這個結果,你就必須以一種互動式會話的方式執行這段程式碼。做到這一點的方式有好幾個,下面展示了在 DataCamp Light 程式碼塊中的情況:

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路

注:你可以使用以下程式碼來啟動一個互動式 Session,執行 result,並在顯示輸出 output 之後再次自動關閉 Session。

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路

在上面的程式碼塊中,你剛剛定義了一個預設 Session,但要知道你也可以傳遞選項。比如說,你可以指定 config 引數,然後使用 ConfigProto 協議緩衝來為你的 session 增加配置選項。

比如說,如果你為你的 Session 增加了 config=tf.ConfigProto(log_device_placement=True),你就可以確保你錄入了運算分配到的 GPU 和 CPU 裝置。然後你就可以瞭解在該 session 中每個運算使用了哪個裝置。比如當你為裝置配置使用軟約束時,你也可使用下面的配置 session: config=tf.ConfigProto(allow_soft_placement=True).

現在你已經裝好了 TensorFlow,並且將其匯入到了你的工作空間,你也瞭解了操作這個軟體包的基礎知識。現在是時候調整一下方向,關注一下資料了。就像瞭解其它事情一樣,首先你要花時間探索和更好地瞭解你的資料,然後才能開始建模你的神經網路。

比利時交通標誌:背景

儘管交通是一個通常你們都知道的主題,但不妨也簡單瞭解一下這個資料集中的資料,看你在開始之前是否已經明白了一切。本質上,這一節將讓你快速瞭解你進一步學習本教程所需的領域知識。

當然,因為我是比利時人,所以用了比利時的交通標誌資料。這裡給出了一些有趣的軼事:

  • 比利時交通標誌通常為荷蘭語和法語。瞭解這一點當然不錯,但對於你要操作的資料集,這並不重要!
  • 比利時的交通標誌分成六大類:警告標誌、優先標誌、禁令標誌、強制性標誌、與停車和在路上等待相關的標誌、指示牌。
  • 在 2017 年 1 月 1 日,比利時移除了超過 30,000 塊交通標誌。它們都是與速度相關的優先標誌。
  • 談到移除,在比利時(而且可以擴充套件到整個歐盟),過多的交通標誌一直以來都是一個討論話題。

載入和探索資料

現在你已經得到了更多一些背景資訊,該下載資料了:http://btsd.ethz.ch/shareddata/。你應該下載 BelgiumTS for Classification (cropped images) 右邊的兩個壓縮包: BelgiumTSC_Training 和 BelgiumTSC_Testing。

提示:如果你已經下載了這些檔案或者將會在完成本教程後下載,那就看看你下載的資料的資料夾結構!你將看到測試和訓練資料資料夾包含了 61 個子資料夾,這是你將在本教程中使用的用於分類任務的 62 種型別的交通標誌。另外,你會發現這些檔案的副檔名是 .ppm,即 Portable Pixmap Format。你已經下載好了交通標誌圖片!

讓我們先把這些資料匯入到你的工作空間。讓我們先從出現在使用者定義函式(UDF)load_data() 下面的程式碼行開始:

  • 首先,設定你的 ROOT_PATH。這個路徑是帶有你的訓練資料和測試資料的目錄。
  • 接下來,你可以藉助 join() 函式為 ROOT_PATH 增加特定的路徑。你將這兩個特定的路徑儲存在 train_data_directory 和 test_data_directory 中
  • 之後,你可以呼叫 load_data() 函式,並將 train_data_directory 作為它的引數。
  • 現在 load_data() 啟動並自己開始收集 train_data_directory 下的所有子目錄;為此它藉助了一種被稱為列表推導式(list comprehension)的方法——這是一種構建列表的自然方法。基本上就是說:如果在 train_data_directory 中發現了一些東西,就雙重檢查這是否是一個目錄;如果是,就將其加入到你的列表中。注意:每個子目錄都代表了一個標籤。
  • 接下來,你必須迴圈遍歷這些子目錄。首先你要初始化兩個列表:labels 和 imanges。然後你要收集這些子目錄的路徑以及儲存在這些子目錄中的影象的檔名。之後,你可以使用 append() 函式來收集這兩個列表中的資料。

def load_data(data_directory):
    directories = [d for d in os.listdir(data_directory) 
                   if os.path.isdir(os.path.join(data_directory, d))]
    labels = []
    images = []
    for d in directories:
        label_directory = os.path.join(data_directory, d)
        file_names = [os.path.join(label_directory, f) 
                      for f in os.listdir(label_directory) 
                      if f.endswith(".ppm")]
        for f in file_names:
            images.append(skimage.data.imread(f))
            labels.append(int(d))
    return images, labels

ROOT_PATH = "/your/root/path"
train_data_directory = os.path.join(ROOT_PATH, "TrafficSigns/Training")
test_data_directory = os.path.join(ROOT_PATH, "TrafficSigns/Testing")

images, labels = load_data(train_data_directory)

注意在上面的程式碼塊中,訓練資料和測試資料分別位於名為 Training 和 Testing 的資料夾中,這兩個資料夾都是另一個目錄 TrafficSigns 的子目錄。在一個本地機器上,其路徑可能是這樣的:/Users/yourName/Downloads/TrafficSigns,之後帶有兩個子資料夾 Training 和 Testing。

交通標誌統計資料

載入好你的資料後,就可以做些資料檢查(data inspection)了!首先你可以使用 images 陣列的 ndim 和 size 屬性做一些相當簡單的分析:

注意 images 和 labels 變數都是列表,所以你可能需要使用 np.array() 將這些變數轉換成你工作空間中的陣列。這裡已經為你做好了!

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路

注:響應輸出的 images[0] 實際上是由陣列中的陣列表示的單個影象。一開始這可能看起來與直覺相反,但隨著你在機器學習或深度學習應用中對影象操作的進一步理解,你會習慣的。

接下來,你也可以簡單看看 labels,但現在你應該不會太過驚訝了:

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路

這些數字能讓你瞭解你的匯入有多成功以及你的資料的確切大小。大略看看,一切都是按照你預期的方式執行的,而且如果你考慮到你正在處理陣列中的陣列,那麼你會看到陣列的大小是相當大的。

提示:試試將以下屬性新增到你的陣列中,即使用 flags、itemsize 和 nbytes 屬性獲取關於記憶體佈局(memory layout)、一個陣列元素的位元組長度和總消耗位元組數的更多資訊。你可以在上面的 DataCamp Light 塊中的 IPython 控制檯中測試這些屬性!

接下來,你也可以看看這些交通標誌資料的分佈情況:

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路

幹得漂亮!現在讓我們仔細看看你做出的直方圖!

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路

你可以清楚地看到,所有型別的交通標誌都在該資料集中得到了平等地表示。這是後面你開始建模神經網路之前操作你的資料時所需要處理的東西。

大略來看,你可以看到該資料集中有的標籤的分量比其它標籤更重:標籤 22、32、38 和 61 顯然出類拔萃。現在記住這一點,在下一節你會更深入地瞭解!

視覺化交通標誌資料

前面的小型分析和檢查已經讓你對這些資料有所瞭解了,但是當你的資料基本上是由影象構成時,你應該通過視覺化的方式來探索你的資料。

我們先來看看一些隨機的交通標誌:

  • 首先,確保你在常用別名 plt 下匯入了 matplotlib 軟體包的 pyplot 模組。
  • 然後,你需要建立一個帶有 4 個隨機數字的列表。這些會被用於從 images 陣列中選擇你在前一節檢查過的交通標誌。在這個案例中,你會選擇 300、2250、3650 和 4000. 
  • 接下來,對於列表長度中的每個元素,即從 0 到 4,你要建立一個沒有軸的子圖(subplot)(這樣它們就不會關注所有東西,而只會單獨關注於影象!)。在這些子圖中,你將展示與索引 i 中數字相符的 images 陣列中的特定影象。在第一次迴圈中,你會把 300 傳遞給 images[],在第二輪傳遞 2250,依此類推。最後你需要調整子圖使它們之間具有足夠的寬度。
  • 最後使用 show() 函式展示你的圖表!

程式碼如下:

# Import the `pyplot` module of `matplotlib`
import matplotlib.pyplot as plt

# Determine the (random) indexes of the images that you want to see 
traffic_signs = [300, 2250, 3650, 4000]

# Fill out the subplots with the random images that you defined 
for i in range(len(traffic_signs)):
    plt.subplot(1, 4, i+1)
    plt.axis('off')
    plt.imshow(images[traffic_signs[i]])
    plt.subplots_adjust(wspace=0.5)

plt.show()

因為該資料集包含 62 個標籤,你大概也猜到了這些交通標誌彼此之間存在差異。

但你還注意到了什麼?仔細看看下面的影象:

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路


這四張影象的大小不一樣!

顯然你可以嘗試調整包含在 traffic_signs 列表中的數字,並對這個觀察進行更全面的跟蹤。但實際上,這是一個重要的觀察結果——當你開始操作你的資料以便將其輸入神經網路時,你會需要將這個觀察結果考慮進來。

讓我們通過輸出包含在這些子圖中的特定影象的形狀(shape)、最小值和最大值來確認圖片大小不同的假設。

下面的程式碼與前面用來建立上面的圖的程式碼非常相似,但不同的地方在於你將交替大小和影象,而不只是繪製接連的影象。

# Import `matplotlib`
import matplotlib.pyplot as plt

# Determine the (random) indexes of the images
traffic_signs = [300, 2250, 3650, 4000]

# Fill out the subplots with the random images and add shape, min and max values
for i in range(len(traffic_signs)):
    plt.subplot(1, 4, i+1)
    plt.axis('off')
    plt.imshow(images[traffic_signs[i]])
    plt.subplots_adjust(wspace=0.5)
    plt.show()
    print("shape: {0}, min: {1}, max: {2}".format(images[traffic_signs[i]].shape, 
                                                  images[traffic_signs[i]].min(), 
                                                  images[traffic_signs[i]].max()))

注:你如何在 shape: {0}, min: {1}, max: {2} 串上使用 format() 方法來填充你定義的引數 {0}、{1} 和 {2}。

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路


現在你已經看到了鬆散的影象,你可能還需要再看看你在資料探索的開始階段輸出的直方圖:你可以通過繪製所有 62 個類的整體情況以及屬於每個類的一張影象來輕鬆做到這一點。

# Import the `pyplot` module as `plt`
import matplotlib.pyplot as plt 

# Get the unique labels 
unique_labels = set(labels)

# Initialize the figure
plt.figure(figsize=(15, 15))

# Set a counter
i = 1

# For each unique label,
for label in unique_labels:
    # You pick the first image for each label
    image = images[labels.index(label)]
    # Define 64 subplots 
    plt.subplot(8, 8, i)
    # Don't include axes
    plt.axis('off')
    # Add a title to each subplot 
    plt.title("Label {0} ({1})".format(label, labels.count(label)))
    # Add 1 to the counter
    i += 1
    # And you plot this first image 
    plt.imshow(image)
    
# Show the plot
plt.show()

注:即使你定義了 64 個子圖,也並不是它們所有都會展示影象(因為總共只有 62 個標籤!)。再注意一下,你不需要包含任何軸以確保閱讀器的注意不會偏離主要主題:交通標誌!

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路

正如你基本上在上述直方圖中猜到的那樣,具有標籤 22、32、38 和 61 的交通標誌要多得多。上面的假設在這幅圖中得到了確認:你可以看到標籤 22 有 375 個例項,標籤 32 有 316 例項,標籤 38 有 285 個例項,標籤 61 有 282 個例項。

你可能要問一個有趣的問題:所有這些例項之間有什麼聯絡嗎——也許它們都是指示標誌?

讓我們仔細看看:你可以看到標籤 22 和 32 是禁令標誌,而標籤 38 和 61 則分別是指示標誌和優先標誌。這意味著這四者之間並沒有直接的聯絡,只是禁令標誌大量存在,佔到了該資料集的一半。

特徵提取

現在你已經全面探索了你的資料,是時候開始實戰了!讓我們簡要回顧一下你的發現,以確保你沒有忘記操作中的任何步驟。

  • 影象的大小是不同的;
  • 存在 62 個標籤或目標值(因為你的標籤始於 0,終於 61);
  • 交通標誌值的分佈相當不平衡;資料集中大量存在的標誌之間實際上並沒有任何聯絡。

現在你已經清楚瞭解了你需要改進的內容,你可以從加工你的資料開始,以使其可以用於神經網路或任何你想讓用來處理該資料的模型中——你將重新調整影象的大小,並將這些儲存在 images 陣列中的影象轉換成灰度影象。進行這種顏色轉換的主要原因是顏色在你正嘗試解決的這種分類問題上作用不大。但是對於檢測任務來說,顏色確實發揮了很大作用!所以在這些案例中,不需要進行這樣的轉換!

重調影象大小

為了解決不同影象大小的問題,你需要重調影象大小。你可以使用 skimage 或 Scikit-Image 庫輕鬆實現這一目標;Scikit-Image 是一個用於影象處理的演算法的集合。

在這個案例中,transform 模組用起來很方便,因為其提供了一個 resize() 函式;你將看到你會使用列表推導式(再一次!)來將每張影象的大小調整為 28×28 畫素。再一次,你將看到你實際構建列表的方式:對於每一張你在 images 陣列中找到的影象,你都可以執行從 skimage 庫借用的變換運算。最後,你將結果儲存在 images28 變數中:

# Import the `transform` module from `skimage`
from skimage import transform 

# Rescale the images in the `images` array
images28 = [transform.resize(image, (28, 28)) for image in images]

是不是很簡單?

注:影象現在是 4 維的:如果你將 images28 轉換成一個陣列,並且如果你將屬性 shape 連線到它,你就會看到輸出的結果表明 images28 的維度為 (4575, 28, 28, 3)。這些影象是 784 維的(因為你的影象是 28×28 畫素的)。

你可以藉助 traffic_signs 變數,通過複用上面你用過的程式碼來給出 4 張隨機影象的圖表,從而對大小調整操作進行檢查。只是不要忘記將所有的參考都從 images 改成 images28。

結果如下:

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路


注:因為你調整過大小了,所以你的 min 和 max 值都已經改變了;現在它們似乎全部都在同一個範圍內,這是一件非常好的事,因為你不再需要規範化你的資料了!

將影象轉換成灰度影象

正如本節介紹中提到的那樣,在嘗試解決分類問題時,影象的顏色的作用更小。所以你也需要麻煩一下,將影象轉換成灰度影象。但是注意,你也可以自己試試看如果不做這一步,對你的模型的最終結果有什麼影響。就像調整影象大小一樣,你也可以使用 Scikit-Image 庫來解決;在這個案例中,我們要在你需要的地方使用 color 模組中的 rgb2gray() 函式。這非常好用,而且很簡單!

但是,不要忘記將 image28 變數轉換回陣列,因為 rgb2gray() 函式並不使用陣列作為引數。

# Import `rgb2gray` from `skimage.color`
from skimage.color import rgb2gray

# Convert `images28` to an array
images28 = np.array(images28)

# Convert `images28` to grayscale
images28 = rgb2gray(images28)

再通過輸出一些影象的圖表來檢查一下你的灰度轉換;這裡,你可以再次複用並稍微調整一些程式碼來顯示調整後的影象:

import matplotlib.pyplot as plt

traffic_signs = [300, 2250, 3650, 4000]

for i in range(len(traffic_signs)):
    plt.subplot(1, 4, i+1)
    plt.axis('off')
    plt.imshow(images28[traffic_signs[i]], cmap="gray")
    plt.subplots_adjust(wspace=0.5)
    
# Show the plot
plt.show()

注:你確實必須指定顏色圖(即 cmap),並將其設定為 gray 以給出灰度影象的圖表。這是因為 imshow() 預設使用一種類似熱力圖的顏色圖。更多資訊參閱:https://stackoverflow.com/questions/39805697/skimage-why-does-rgb2gray-from-skimage-color-result-in-a-colored-image

提示:因為在本教程中你已經複用了這個函式很多次,所以你可以看看你是如何使其成為一個函式的 :)

這兩個步驟是非常基礎的;其它你可以在你的資料上嘗試的操作包括資料增強(旋轉、模糊、位移、改變亮度……)等。如果你願意,你也可以通過你傳送影象的方式來設定整個資料操作運算的流程。

使用 TensorFlow 做深度學習

現在你已經探索並操作好了你的資料,該使用 TensorFlow 軟體包來構建你的神經網路架構了!

建模神經網路

就像你可以用 Keras 做的那樣,現在是時候一層層構建你的神經網路了。

如果你還沒試過,就先將 tensorflow 匯入到慣例別名 tf 的工作空間中。然後你可以藉助 Graph() 對圖進行初始化。你可以使用這個函式來定義計算。注意如果只有這個圖,那你就不能計算任何東西,因為它不保留任何值。它只是定義你想在之後執行的運算。

在這個案例中,你可以使用 as_default() 設定一個預設背景,該函式會返回一個背景管理器,可使這個特定圖成為預設的圖。如果你想在這同一個流程中建立多個圖,你也可以使用這種方法:使用這個函式,如果你不明確建立一個新圖,那你就為所有將被加入的運算設定了一個全域性預設圖。

接下來,你就可以將運算加入到你的圖中了。在 Keras 中,你需要先構建你的模型,然後在編譯它,你要定義一個損失函式、一個優化器和一個指標。而使用 TensorFlow,你一步就能完成上述操作:

  • 首先,你要為輸入和標籤定義佔位符,因為你尚未輸入「真實」資料。記住佔位符是未分配的值,可以在你執行它時被 session 初始化。所以當你執行該 session 結束時,這些佔位符會獲取你在 run() 函式中傳遞的資料集中的值。
  • 然後構建你的網路。首先使用 flatten() 函式展平輸入,其會給你一個形狀為 [None, 784] 的陣列,而不是 [None, 28, 28]——這是你的灰度影象的形狀。
  • 在你展平了輸入之後,構建一個全連線層,其可以生成大小為 [None, 62] 的 logits。logits 是執行在早期層未縮放的輸出上的函式,其使用相對比例來了解單位是否是線性的。
  • 構建出多層感知器後,就可以定義損失函式了。損失函式的選擇取決於當前的任務:在這個案例中,你可以使用 sparse_softmax_cross_entropy_with_logits(),其可以計算 logits 和標籤之間的稀疏 softmax 交叉熵。換句話說,它測量的是離散分類任務中的概率誤差,在這些任務中,類別是互斥的。這意味著每一個條目(entry)都是一個單獨的類別。在這種情況下,一個交通標誌只會有一個標籤。記住這一點,迴歸(regression)被用於預測連續值,而分類(classification)則被用於預測離散值或資料點的類別。你可以使用 reduce_mean() 來包裹這個函式,它可以計算一個張量的維度上各個元素的均值。
  • 你也需要定義一個訓練優化器;最流行的優化演算法包括隨機梯度下降(SGD)、ADAM 和 RMSprop。取決於你選擇的演算法,你需要調節特定的引數,比如學習率或動量。在這個案例中,你要選擇 ADAM,並將其學習率定義為 0.001。
  • 最後,你可以在進入訓練之前初始化要執行的運算。

現在你已經用 TensorFlow 創造出了你的第一個神經網路!

# Import `tensorflow` 
import tensorflow as tf 

# Initialize placeholders 
x = tf.placeholder(dtype = tf.float32, shape = [None, 28, 28])
y = tf.placeholder(dtype = tf.int32, shape = [None])

# Flatten the input data
images_flat = tf.contrib.layers.flatten(x)

# Fully connected layer 
logits = tf.contrib.layers.fully_connected(images_flat, 62, tf.nn.relu)

# Define a loss function
loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(labels = y, 
                                                                    logits = logits))
# Define an optimizer 
train_op = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)

# Convert logits to label indexes
correct_pred = tf.argmax(logits, 1)

# Define an accuracy metric
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

如果你想,你可以顯示輸出大部分變數的值以快速瞭解和檢查你剛剛編寫的東西:

print("images_flat: ", images_flat)
print("logits: ", logits)
print("loss: ", loss)
print("predicted_labels: ", correct_pred)

提示:如果看到了「module 'pandas' has no attribute 'computation'」這樣的錯誤提示,可以通過在命令列執行 pip install --upgrade dask 來升級 dask 軟體包。參見這個帖子瞭解更多:https://stackoverflow.com/questions/43833081/attributeerror-module-object-has-no-attribute-computation

執行神經網路

現在你已經一層層地構建好了你的模型,可以跑起來了!要做到這一點,首先你需要使用 Session() 初始化一個 session,你可以將你在之前一節定義的圖 graph 傳遞給該函式。接下來,將同樣在前一節定義的初始化運算 init 變數傳遞給 run(),並通過該函式執行該 session。

接下來,你可以使用這些初始化的 session 來啟動 epoch 或訓練迴圈。在這個案例中,你可以選擇 201,因為你需要註冊最新的 loss_value;在訓練迴圈中,使用你在前一節定義的訓練優化器和損失(或準確度)指標,執行這個 session。你也可以傳遞一個 feed_dict 引數,你可以使用它將資料傳遞給模型。每 10 個 epoch 之後,你會獲得一個日誌,能給你提供更多有關模型的損失或成本的資訊。

正如你在 TensorFlow 基礎知識一節看到的那樣,我們不需要人工關閉 session;TensorFlow 會自動完成。但是,如果你想嘗試不同的設定,當你將你的 session 定義為 sess 時,你可能將需要使用 sess.close(),就像在下列程式碼塊中一樣:

tf.set_random_seed(1234)
sess = tf.Session()

sess.run(tf.global_variables_initializer())

for i in range(201):
        print('EPOCH', i)
        _, accuracy_val = sess.run([train_op, accuracy], feed_dict={x: images28, y: labels})
        if i % 10 == 0:
            print("Loss: ", loss)
        print('DONE WITH EPOCH')

記住,你也可以執行下面這段程式碼,但這段程式碼會在之後立即關閉 session,就像你在本教程的引言中看到的那樣:

tf.set_random_seed(1234)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for i in range(201):
        _, loss_value = sess.run([train_op, loss], feed_dict={x: images28, y: labels})
        if i % 10 == 0:
            print("Loss: ", loss)

注:你使用了 global_variables_initializer(),因為不提倡使用 initialize_all_variables() 函式。

現在你已經成功訓練了你的模型!並不是很難吧?

評估你的神經網路

訓練完了並不是結束;你還需要對你的神經網路進行評估。在這個案例中,你可以通過選取隨機 10 張影象並比較預測標籤和真實標籤來簡單瞭解你的模型的表現。

你首先可以顯示輸出它們,但為什麼不使用 matplotlib 來得到交通標誌本身的圖表並進行視覺比較呢?

# Import `matplotlib`
import matplotlib.pyplot as plt
import random

# Pick 10 random images
sample_indexes = random.sample(range(len(images28)), 10)
sample_images = [images28[i] for i in sample_indexes]
sample_labels = [labels[i] for i in sample_indexes]

# Run the "correct_pred" operation
predicted = sess.run([correct_pred], feed_dict={x: sample_images})[0]
                        
# Print the real and predicted labels
print(sample_labels)
print(predicted)

# Display the predictions and the ground truth visually.
fig = plt.figure(figsize=(10, 10))
for i in range(len(sample_images)):
    truth = sample_labels[i]
    prediction = predicted[i]
    plt.subplot(5, 2,1+i)
    plt.axis('off')
    color='green' if truth == prediction else 'red'
    plt.text(40, 10, "Truth:        {0}\nPrediction: {1}".format(truth, prediction), 
             fontsize=12, color=color)
    plt.imshow(sample_images[i],  cmap="gray")

plt.show()

TensorFlow從基礎到實戰:一步步教你建立交通標誌分類神經網路


但是,僅僅檢視隨機影象並不能讓你瞭解你的模型的表現究竟如何。所以你需要載入測試資料。

# Import `skimage`
from skimage import transform

# Load the test data
test_images, test_labels = load_data(test_data_directory)

# Transform the images to 28 by 28 pixels
test_images28 = [transform.resize(image, (28, 28)) for image in test_images]

# Convert to grayscale
from skimage.color import rgb2gray
test_images28 = rgb2gray(np.array(test_images28))

# Run predictions against the full test set.
predicted = sess.run([correct_pred], feed_dict={x: test_images28})[0]

# Calculate correct matches 
match_count = sum([int(y == y_) for y, y_ in zip(test_labels, predicted)])

# Calculate the accuracy
accuracy = match_count / len(test_labels)

# Print the accuracy
print("Accuracy: {:.3f}".format(accuracy))

注:你要使用 load_data() 函式,你在本教程開始時定義了這個函式。

記住使用 sess.close() 關閉 session,以防你沒有使用 with tf.Session() as sess: 來啟動你的 TensorFlow session。

接下來看什麼?

如果你想繼續使用這個教程中的資料集和模型,可以嘗試下面的東西:

  • 在你的資料上應用正則化 LDA,之後再將其應用於你的模型。這個建議來自於一篇原始論文:http://btsd.ethz.ch/shareddata/publications/Mathias-IJCNN-2013.pdf,是由收集並分析這個資料集的研究者寫的。
  • 正如在本教程中說的那樣,你也可以在這些交通標誌資料上執行一些其它的資料增強操作。此外,你也可以嘗試進一步調整這個網路——你目前創造的這個網路還相當簡單。
  • 早停(early stopping):在你訓練神經網路時,要一直關注訓練和測試誤差。當兩個誤差都下降又突然升高時要停止訓練——這是神經網路開始過擬合訓練資料的徵兆。

一定要看 Nishant Shukla 寫的書《Machine Learning With TensorFlow》:https://www.manning.com/books/machine-learning-with-tensorflow

提示:另外也看看 TensorFlow Playground(http://playground.tensorflow.org)和 TensorBoard(https://www.tensorflow.org/get_started/summaries_and_tensorboard)。

如果你想繼續處理影象,肯定不能錯過 DataCamp 的 scikit-learn 教程:https://www.datacamp.com/community/tutorials/machine-learning-python,在這個教程中你將瞭解如何用 PCA、K-均值和支援向量機(SVM)來處理 MNIST 資料集。你也可以看看其它使用了比利時交通標誌資料集的教程,比如:https://github.com/waleedka/traffic-signs-tensorflow/blob/master/notebook1.ipynb

原文地址:https://www.datacamp.com/community/tutorials/tensorflow-tutorial

相關文章