機器之心GitHub項目:從零開始用TensorFlow搭建卷積神經網路

機器之心基於 Ahmet Taspinar 的博文使用 TensorFlow 手動搭建卷積神經網路,並提供所有代碼和注釋的 Jupyter Notebook 文檔。我們將不僅描述訓練情況,同時還將提供各種背景知識和分析。所有的代碼和運行結果都已上傳至 Github,機器之心希望通過我們的試驗提供精確的代碼和運行經驗,我們將持續試驗這一類高質量的教程和代碼。

機器之心項目地址:github.com/jiqizhixin/M

本文的重點是實現,並不會從理論和概念上詳細解釋深度神經網路、卷積神經網路、最優化方法等基本內容。但是機器之心發過許多詳細解釋的入門文章或教程,因此,我們希望讀者能先了解以下基本概念和理論。當然,本文注重實現,即使對深度學習的基本演算法理解不那麼深同樣還是能實現本文所述的內容。

卷積神經網路:

  • 機器視角:長文揭秘圖像處理和卷積神經網路架構
  • 深度 | 從入門到精通:卷積神經網路初學者指南(附論文)
  • 專欄 | 卷積神經網路簡介
  • 重磅論文 | 解析深度卷積神經網路的14種設計模式(附下載)
  • 深度 | 從入門到精通:卷積神經網路初學者指南(附論文)
  • 深度 | 卷積神經網路架構詳解:它與神經網路有何不同?

TensorFlow 入門:

  • 入門級解讀:小白也能看懂的 TensorFlow 介紹
  • 教程 | 維度、廣播操作與可視化:如何高效使用 TensorFlow
  • 教程 | TensorFlow 從基礎到實戰:一步步教你創建交通標誌分類神經網路
  • 谷歌開放 GNMT 教程:如何使用 TensorFlow 構建自己的神經機器翻譯系統

優化方法:

  • 從淺層模型到深度模型:概覽機器學習優化演算法
  • 教程 | 聽說你了解深度學習最常用的學習演算法:Adam 優化演算法?
  • 深度解讀最流行的優化演算法:梯度下降
  • 從梯度下降到擬牛頓法:詳解訓練神經網路的五大學習演算法

首先是安裝TensorFlow,我們可以直接按照 TensorFlow 官方教程安裝。機器之心在 Jupyter Notebook 上運行和測試本文所有代碼,但是 TensorFlow 在 Windows 上只支持 Python 3.5x,而我們現在安裝的 Anaconda,支持的是 Python 3.6。所以如果需要在 Windows 上用 Jupyter Notebook 載入 TensorFlow,還需要另外一些操作。

TensorFlow 官方安裝教程:tensorflow.org/install/

現在假定我們已經安裝了最新的 Anaconda 4.4.0,如果希望在 Jupyter notebook 中導入 TensorFlow 需要以下步驟。

在 Anaconda Prompt(CMD 命令行中也行)中鍵入以下命令以創建名為 tensorflow 的 conda 環境:

conda create -n tensorflow python=3.5

然後再運行以下命令行激活 conda 環境:

activate tensorflow

運行後會變為「(tensorflow) C:Users用戶名>」,然後我們就可以繼續在該 conda 環境內安裝 TensorFlow(本文只使用 CPU 進行訓練,所以可以只安裝 CPU 版):

pip

install --ignore-installed --upgrade

storage.googleapis.com/

現在已經成功安裝了 TensorFlow,但是在 Jupyter Notebook 中並不能導入 TensorFlow,所以我們需要使用命令行在 TensorFlow 環境中安裝 Jupyter 和 Ipython:

conda install ipython conda install jupyter

最後,運行以下命令就能完成安裝,並在 Jupyter Notebook 中導入 TensorFlow:

ipython kernelspec install-self --user

TensorFlow 基礎

下面我們首先需要了解TensorFlow 的基本用法,這樣我們才能開始構建神經網路。本小節將從張量與圖、常數與變數還有佔位符等基本概念出發簡要介紹 TensorFlow,熟悉 TensorFlow 的讀者可以直接閱讀下一節。需要進一步了解 TensorFlow 的讀者最好可以閱讀谷歌TensorFlow 的文檔,當然也可以閱讀其他中文教程或書籍,例如《TensorFlow:實戰 Google

深度學習框架》和《TensorFlow 實戰》等。

TensorFlow 文檔地址:tensorflow.org/get_star

1.1 張量和圖

TensorFlow是一種採用數據流圖(data flow graphs),用於數值計算的開源軟體庫。其中 Tensor 代表傳遞的數據為張量(多維數組),Flow 代表使用計算圖進行運算。數據流圖用「結點」(nodes)和「邊」(edges)組成的有向圖來描述數學運算。「結點」一般用來表示施加的數學操作,但也可以表示數據輸入的起點和輸出的終點,或者是讀取/寫入持久變數(persistent variable)的終點。邊表示結點之間的輸入/輸出關係。這些數據邊可以傳送維度可動態調整的多維數據數組,即張量(tensor)。

下面代碼是使用計算圖的案例:

a = tf.constant(2, tf.int16)nb = tf.constant(4, tf.float32)nngraph = tf.Graph()nwith graph.as_default():n a = tf.Variable(8, tf.float32)n b = tf.Variable(tf.zeros([2,2], tf.float32))n nwith tf.Session(graph=graph) as session:n tf.global_variables_initializer().run()n print(f)n print(session.run(a))n print(session.run(b))nn#輸出:n>>> <tf.Variable Variable_2:0 shape=() dtype=int32_ref>n>>> 8n>>> [[ 0. 0.]n>>> [ 0. 0.]]n

在 Tensorflow 中,所有不同的變數和運算都是儲存在計算圖。所以在我們構建完模型所需要的圖之後,還需要打開一個會話(Session)來運行整個計算圖。在會話中,我們可以將所有計算分配到可用的 CPU 和 GPU 資源中。

如下所示代碼,我們聲明兩個常量 a 和 b,並且定義一個加法運算。但它並不會輸出計算結果,因為我們只是定義了一張圖,而沒有運行它:

  1. a=tf.constant([1,2],name="a")
  2. b=tf.constant([2,4],name="b")
  3. result = a+b
  4. print(result)

#輸出:Tensor("add:0", shape=(2,), dtype=int32)

下面的代碼才會輸出計算結果,因為我們需要創建一個會話才能管理 TensorFlow 運行時的所有資源。但計算完畢後需要關閉會話來幫助系統回收資源,不然就會出現資源泄漏的問題。下面提供了使用會話的兩種方式:

a=tf.constant([1,2,3,4])nb=tf.constant([1,2,3,4])nresult=a+bnsess=tf.Session()nprint(sess.run(result))nsess.closenn#輸出 [2 4 6 8]nnwith tf.Session() as sess:n a=tf.constant([1,2,3,4])n b=tf.constant([1,2,3,4])n result=a+bn print(sess.run(result))nn#輸出 [2 4 6 8]n

1.2 常量和變數

TensorFlow 中最基本的單位是常量(Constant)、變數(Variable)和佔位符(Placeholder)。常量定義後值和維度不可變,變數定義後值可變而維度不可變。在神經網路中,變數一般可作為儲存權重和其他信息的矩陣,而常量可作為儲存超參數或其他結構信息的變數。下面我們分別定義了常量與變數:

a = tf.constant(2, tf.int16)n b = tf.constant(4, tf.float32)n c = tf.constant(8, tf.float32)nn d = tf.Variable(2, tf.int16)n e = tf.Variable(4, tf.float32)n f = tf.Variable(8, tf.float32)nnnn g = tf.constant(np.zeros(shape=(2,2), dtype=np.float32))nnn h = tf.zeros([11], tf.int16)n i = tf.ones([2,2], tf.float32)n j = tf.zeros([1000,4,3], tf.float64)nn k = tf.Variable(tf.zeros([2,2], tf.float32))n l = tf.Variable(tf.zeros([5,6,5], tf.float32))n

在上面代碼中,我們分別聲明了不同的常量(tf.constant())和變數(tf.Variable()),其中

tf.float 和 tf.int 分別聲明了不同的浮點型和整數型數據。而 tf.ones() 和 tf.zeros() 分別產生全是

1、全是 0 的矩陣。我們注意到常量 g,它的聲明結合了 TensorFlow 和 Numpy,這也是可執行的。

w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))n

以上語句聲明一個2 行 3 列的變數矩陣,該變數的值服從標準差為 1 的正態分布,並隨機生成。TensorFlow 還有 tf.truncated_normal() 函數,即截斷正態分布隨機數,它只保留 [mean-2*stddev,mean+2*stddev] 範圍內的隨機數。

現在,我們可以應用變數來定義神經網路中的權重矩陣和偏置項向量:

weights = tf.Variable(tf.truncated_normal([256 * 256, 10]))nbiases = tf.Variable(tf.zeros([10]))nprint(weights.get_shape().as_list())nprint(biases.get_shape().as_list())n#輸出n>>>[65536, 10]n>>>[10]n

1.3 佔位符和 feed_dict

我們已經創建了各種形式的常量和變數,但 TensorFlow 同樣還支持佔位符。佔位符並沒有初始值,它只會分配必要的內存。在會話中,佔位符可以使用 feed_dict 饋送數據。

feed_dict是一個字典,在字典中需要給出每一個用到的佔位符的取值。在訓練神經網路時需要每次提供一個批量的訓練樣本,如果每次迭代選取的數據要通過常量表示,那麼TensorFlow 的計算圖會非常大。因為每增加一個常量,TensorFlow 都會在計算圖中增加一個結點。所以說擁有幾百萬次迭代的神經網路會擁有極其龐大的計算圖,而佔位符卻可以解決這一點,它只會擁有佔位符這一個結點。

下面一段代碼分別展示了使用常量和佔位符進行計算:

w1=tf.Variable(tf.random_normal([1,2],stddev=1,seed=1))nn#因為需要重複輸入x,而每建一個x就會生成一個結點,計算圖的效率會低。所以使用佔位符nx=tf.placeholder(tf.float32,shape=(1,2))nx1=tf.constant([[0.7,0.9]])nna=x+w1nb=x1+w1nnsess=tf.Session()nsess.run(tf.global_variables_initializer())nn#運行y時將佔位符填上,feed_dict為字典,變數名不可變ny_1=sess.run(a,feed_dict={x:[[0.7,0.9]]})ny_2=sess.run(b)nprint(y_1)nprint(y_2)nsess.closen

其中 y_1 的計算過程使用佔位符,而 y_2 的計算過程使用常量。

下面是使用佔位符的案例:

list_of_points1_ = [[1,2], [3,4], [5,6], [7,8]]nlist_of_points2_ = [[15,16], [13,14], [11,12], [9,10]]nnlist_of_points1 = np.array([np.array(elem).reshape(1,2) for elem in list_of_points1_])nlist_of_points2 = np.array([np.array(elem).reshape(1,2) for elem in list_of_points2_])nnngraph = tf.Graph()nnwith graph.as_default(): nn #我們使用 tf.placeholder() 創建佔位符 ,在 session.run() 過程中再投遞數據 n point1 = tf.placeholder(tf.float32, shape=(1, 2))n point2 = tf.placeholder(tf.float32, shape=(1, 2))nn def calculate_eucledian_distance(point1, point2):n difference = tf.subtract(point1, point2)n power2 = tf.pow(difference, tf.constant(2.0, shape=(1,2)))n add = tf.reduce_sum(power2)n eucledian_distance = tf.sqrt(add)n return eucledian_distancenn dist = calculate_eucledian_distance(point1, point2)nnnwith tf.Session(graph=graph) as session:n tf.global_variables_initializer().run() n for ii in range(len(list_of_points1)):n point1_ = list_of_points1[ii]n point2_ = list_of_points2[ii]nn #使用feed_dict將數據投入到[dist]中n feed_dict = {point1 : point1_, point2 : point2_}n distance = session.run([dist], feed_dict=feed_dict)n print("the distance between {} and {} -> {}".format(point1_, point2_, distance))nnn#輸出:n>>> the distance between [[1 2]] and [[15 16]] -> [19.79899]n>>> the distance between [[3 4]] and [[13 14]] -> [14.142136]n>>> the distance between [[5 6]] and [[11 12]] -> [8.485281]n>>> the distance between [[7 8]] and [[ 9 10]] -> [2.8284271]n

Ahmet

Taspinar 在第二部分就直接開始構建深度神經網路了,雖然我們在前一章增加了許多代碼段以幫助讀者了解 TensorFlow 的基本法則,但上面是遠遠不夠的。所以如果我們能先解析一部分神經網路代碼,那麼將有助於入門讀者鞏固以上的 TensorFlow 基本知識。下面,我們將先解析一段構建了三層全連接神經網路的代碼。

import tensorflow as tfnfrom numpy.random import RandomStatennnbatch_size=10nw1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))nw2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))nnn# None 可以根據batch 大小確定維度,在shape的一個維度上使用Nonenx=tf.placeholder(tf.float32,shape=(None,2))ny=tf.placeholder(tf.float32,shape=(None,1))nnn#激活函數使用ReLUna=tf.nn.relu(tf.matmul(x,w1))nyhat=tf.nn.relu(tf.matmul(a,w2))nnn#定義交叉熵為損失函數,訓練過程使用Adam演算法最小化交叉熵ncross_entropy=-tf.reduce_mean(y*tf.log(tf.clip_by_value(yhat,1e-10,1.0)))ntrain_step=tf.train.AdamOptimizer(0.001).minimize(cross_entropy)nnrdm=RandomState(1)ndata_size=516nnn#生成兩個特徵,共data_size個樣本nX=rdm.rand(data_size,2)n#定義規則給出樣本標籤,所有x1+x2<1的樣本認為是正樣本,其他為負樣本。Y,1為正樣本nY = [[int(x1+x2 < 1)] for (x1, x2) in X]nnwith tf.Session() as sess:n sess.run(tf.global_variables_initializer())n print(sess.run(w1))n print(sess.run(w2))n steps=11000n for i in range(steps):nnn #選定每一個批量讀取的首尾位置,確保在1個epoch內採樣訓練n start = i * batch_size % data_sizen end = min(start + batch_size,data_size)n sess.run(train_step,feed_dict={x:X[start:end],y:Y[start:end]})n if i % 1000 == 0:n training_loss= sess.run(cross_entropy,feed_dict={x:X,y:Y})n print("在迭代 %d 次後,訓練損失為 %g"%(i,training_loss))n

上面的代碼定義了一個簡單的三層全連接網路(輸入層、隱藏層和輸出層分別為

2、3 和 2 個神經元),隱藏層和輸出層的激活函數使用的是 ReLU 函數。該模型訓練的樣本總數為 512,每次迭代讀取的批量為 10。這個簡單的全連接網路以交叉熵為損失函數,並使用 Adam 優化演算法進行權重更新。

其中需要注意的幾個函數如:tf.nn.relu() 代表調用 ReLU 激活函數,tf.matmul() 為矩陣乘法等。tf.clip_by_value(yhat,1e-10,1.0) 這一語句代表的是截斷 yhat 的值,因為這一語句是嵌套在 tf.log() 函數內的,所以我們需要確保 yhat 的取值不會導致對數無窮大。

tf.train.AdamOptimizer(learning_rate).minimize(cost_function)是進行訓練的函數,其中我們採用的是 Adam 優化演算法更新權重,並且需要提供學習速率和損失函數這兩個參數。後面就是生成訓練數據,X=rdm.rand(512,2) 表示隨機生成 512 個樣本,每個樣本有兩個特徵值。最後就是迭代運行了,這裡我們計算出每一次迭代抽取數據的起始位置(start)和結束位(end),並且每一次抽取的數據量為前面我們定義的批量,如果一個epoch 最後剩餘的數據少於批量大小,那就只是用剩餘的數據進行訓練。最後兩句代碼是為了計算訓練損失并迭代一些次數後輸出訓練損失。這一部分代碼運行的結果如下:

TensorFlow 中的神經網路

2.1 簡介

上圖所描述的圖像識別流程需要包含以下幾步:

  • 輸入數據集,數據集分為訓練數據集和標註、測試數據集和標註(包括驗證數據集和標註)。測試和驗證集能賦值到 tf.constant() 中,而訓練集可以導入 tf.placeholder() 中,訓練集只有導入佔位符我們才能在隨機梯度下降中成批量地進行訓練。
  • 確定神經網路模型,該模型可以是簡單的一層全連接網路或 9 層、16 層的複雜卷積網路組成。
  • 網路定義的權重矩陣和偏置向量後需要執行初始化,每一層需要一個權重矩陣和一個偏置向量。
  • 構建損失函數,並計算訓練損失。模型會輸出一個預測向量,我們可以比較預測標籤和真實標籤並使用交叉熵函數和 softmax 回歸來確定損失值。訓練損失衡量預測值和真實值之間差距,並用於更新權重矩陣。
  • 優化器,優化器將使用計算的損失值和反向傳播演算法更新權重和偏置項參數。

2.2 載入數據

首先我們需要載入數據,載入的數據用來訓練和測試神經網路。在Ahmet Taspinar 的博客中,他用的是 MNIST 和 CIFAR-10 數據集。其中 MNIST 數據集包含 6 萬張手寫數字圖片,每一張圖片的大小都是 28 x 28 x 1(灰度圖)。而 CIFAR-10 數據集包含 6 萬張彩色(3

通道)圖片,每張圖片的大小為 32 x 32 x 3,該數據集有 10 種不同的物體(飛機、摩托車、鳥、貓、狗、青蛙、馬、羊和卡車)。

首先,讓我們定義一些函數,它們能幫助我們載入和預處理圖像數據。

def randomize(dataset, labels):n permutation = np.random.permutation(labels.shape[0])n shuffled_dataset = dataset[permutation, :, :]n shuffled_labels = labels[permutation]n return shuffled_dataset, shuffled_labelsnndef one_hot_encode(np_array):n return (np.arange(10) == np_array[:,None]).astype(np.float32)nndef reformat_data(dataset, labels, image_width, image_height, image_depth):n np_dataset_ = np.array([np.array(image_data).reshape(image_width, image_height, image_depth) for image_data in dataset])n np_labels_ = one_hot_encode(np.array(labels, dtype=np.float32))n np_dataset, np_labels = randomize(np_dataset_, np_labels_)n return np_dataset, np_labelsnndef flatten_tf_array(array):n shape = array.get_shape().as_list()n return tf.reshape(array, [shape[0], shape[1] * shape[2] * shape[3]])nndef accuracy(predictions, labels):n return (100.0 * np.sum(np.argmax(predictions, 1) == np.argmax(labels, 1)) / predictions.shape[0])def randomize(dataset, labels):n permutation = np.random.permutation(labels.shape[0])n shuffled_dataset = dataset[permutation, :, :]n shuffled_labels = labels[permutation]n return shuffled_dataset, shuffled_labelsnndef one_hot_encode(np_array):n return (np.arange(10) == np_array[:,None]).astype(np.float32)nndef reformat_data(dataset, labels, image_width, image_height, image_depth):n np_dataset_ = np.array([np.array(image_data).reshape(image_width, image_height, image_depth) for image_data in dataset])n np_labels_ = one_hot_encode(np.array(labels, dtype=np.float32))n np_dataset, np_labels = randomize(np_dataset_, np_labels_)n return np_dataset, np_labelsnndef flatten_tf_array(array):n shape = array.get_shape().as_list()n return tf.reshape(array, [shape[0], shape[1] * shape[2] * shape[3]])nndef accuracy(predictions, labels):n return (100.0 * np.sum(np.argmax(predictions, 1) == np.argmax(labels, 1)) / predictions.shape[0])n

圖像的標籤使用 one-hot 編碼,並且將數據載入到隨機數組中。在定義這些函數後,我們可以載入數據:

我們能從 Yann LeCun 的網站下載 MNIST 數據集,下載並解壓後就能使用 python-mnist 工具載入該數據集。

  • MNIST 數據集:yann.lecun.com/exdb/mni
  • python-mnist 工具:github.com/sorki/python
  • CIFAR-10 數據集:cs.toronto.edu/~kriz/ci

在Ahmet Taspinar 提供的上述代碼中,我們運行會出錯,因為「MNIST」並沒有定義,而我們機器之心在安裝完 python-mnist,並加上「from mnist import MNIST」語句後,仍然不能導入。所以我們可以修改以上代碼,使用 TensorFlow 官方教程中自帶的 MNIST 載入工具載入 MNIST。

如下所示,我們可以使用這種方法成功地導入 MNIST 數據集:

我們需要再次導入 CIFAR-10 數據集,這一段代碼也會出錯,原因是有變數沒有定義。下面代碼將導入數據集:

cifar10_folder = ./data/cifar10/ntrain_datasets = [data_batch_1, data_batch_2, data_batch_3, data_batch_4, data_batch_5, ]ntest_dataset = [test_batch]nnc10_image_height = 32nc10_image_width = 32nc10_image_depth = 3nc10_num_labels = 10nc10_image_size = 32 #Ahmet Taspinar的代碼缺少了這一語句nnnwith open(cifar10_folder + test_dataset[0], rb) as f0:n c10_test_dict = pickle.load(f0, encoding=bytes)nnc10_test_dataset, c10_test_labels = c10_test_dict[bdata], c10_test_dict[blabels]ntest_dataset_cifar10, test_labels_cifar10 = reformat_data(c10_test_dataset, c10_test_labels, c10_image_size, c10_image_size, c10_image_depth)nnc10_train_dataset, c10_train_labels = [], []nnfor train_dataset in train_datasets:n with open(cifar10_folder + train_dataset, rb) as f0:n c10_train_dict = pickle.load(f0, encoding=bytes)n c10_train_dataset_, c10_train_labels_ = c10_train_dict[bdata], c10_train_dict[blabels]nn c10_train_dataset.append(c10_train_dataset_)n c10_train_labels += c10_train_labels_nnc10_train_dataset = np.concatenate(c10_train_dataset, axis=0)ntrain_dataset_cifar10, train_labels_cifar10 = reformat_data(c10_train_dataset, c10_train_labels, c10_image_size, c10_image_size, c10_image_depth)ndel c10_train_datasetndel c10_train_labelsn

print("訓練集包含以下標籤: {}".format(np.unique(c10_train_dict[blabels])))

print(訓練集維度, train_dataset_cifar10.shape, train_labels_cifar10.shape)

print(測試集維度, test_dataset_cifar10.shape, test_labels_cifar10.shape)

在試驗中,我們需要注意放置數據集的地址。MNIST

可以自動檢測指定的目錄下是否有數據集,如果沒有就自動下載數據集至該目錄下。在上面的兩段代碼中,「./data/MNIST/」就代表著我們放置數據集的地址,它表示在

Python 根目錄下「data」文件夾下的「MNIST」文件夾內。CIFAR-10 同樣也是這樣,只不過它不會自動下載數據集。

2.3 創建簡單的多層全連接神經網路

Ahmet Taspinar 後面創建了一個單隱藏層全連接網路,不過我們還是報錯了。他在博客中給出了以下訓練準確度,我們看到該模型在 MNIST 數據集效果並不是很好。所以我們另外使用一個全連接神經網路來實現這一過程。

下面我們實現的神經網路共有三層,輸入層有784 個神經元,隱藏層與輸出層分別有 500 和 10 個神經元。這所以這樣設計是因為 MNIST 的像素為 28×28=784,所以每一個輸入神經元對應於一個灰度像素點。機器之心執行該模型得到的效果非常好,該模型在批量大小為 100,並使用學習率衰減的情況下迭代 10000 步能得到 98.34% 的測試集準確度,以下是該模型代碼:

import tensorflow as tfnfrom tensorflow.examples.tutorials.mnist import input_datan nnn#載入MNIST數據集nmnist = input_data.read_data_sets("./data/MNIST/", one_hot=True)nnnINPUT_NODE = 784 nOUTPUT_NODE = 10 nLAYER1_NODE = 500 nnBATCH_SIZE = 100 n n nn# 模型相關的參數nLEARNING_RATE_BASE = 0.8 nLEARNING_RATE_DECAY = 0.99 nREGULARAZTION_RATE = 0.0001 nTRAINING_STEPS = 10000 nMOVING_AVERAGE_DECAY = 0.99 n nnndef inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):n # 使用滑動平均類n if avg_class == None:n layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)n return tf.matmul(layer1, weights2) + biases2nn else:nn layer1 = tf.nn.relu(tf.matmul(input_tensor, avg_class.average(weights1)) + avg_class.average(biases1))n return tf.matmul(layer1, avg_class.average(weights2)) + avg_class.average(biases2) nnndef train(mnist):n x = tf.placeholder(tf.float32, [None, INPUT_NODE], name=x-input)n y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name=y-input)nn # 生成隱藏層的參數。n weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=0.1))n biases1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))nn # 生成輸出層的參數。n weights2 = tf.Variable(tf.truncated_normal([LAYER1_NODE, OUTPUT_NODE], stddev=0.1))n biases2 = tf.Variable(tf.constant(0.1, shape=[OUTPUT_NODE]))nnn # 計算不含滑動平均類的前向傳播結果n y = inference(x, None, weights1, biases1, weights2, biases2)nnn # 定義訓練輪數及相關的滑動平均類 n global_step = tf.Variable(0, trainable=False)n variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)n variables_averages_op = variable_averages.apply(tf.trainable_variables())n average_y = inference(x, variable_averages, weights1, biases1, weights2, biases2)nn # 計算交叉熵及其平均值n cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))n cross_entropy_mean = tf.reduce_mean(cross_entropy)nn # 定義交叉熵損失函數加上正則項為模型損失函數n regularizer = tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)n regularaztion = regularizer(weights1) + regularizer(weights2)n loss = cross_entropy_mean + regularaztionnn # 設置指數衰減的學習率。n learning_rate = tf.train.exponential_decay(n LEARNING_RATE_BASE,n global_step,n mnist.train.num_examples / BATCH_SIZE,n LEARNING_RATE_DECAY,n staircase=True)nn # 隨機梯度下降優化器優化損失函數n train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)nn # 反向傳播更新參數和更新每一個參數的滑動平均值n with tf.control_dependencies([train_step, variables_averages_op]):n train_op = tf.no_op(name=train)nn # 計算準確度n correct_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1))n accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))nn # 初始化會話並開始訓練過程。n with tf.Session() as sess:n tf.global_variables_initializer().run()n validate_feed = {x: mnist.validation.images, y_: mnist.validation.labels}n test_feed = {x: mnist.test.images, y_: mnist.test.labels} nn # 循環地訓練神經網路。n for i in range(TRAINING_STEPS):n if i % 1000 == 0:n validate_acc = sess.run(accuracy, feed_dict=validate_feed)n print("After %d training step(s), validation accuracy using average model is %g " % (i, validate_acc))nn xs,ys=mnist.train.next_batch(BATCH_SIZE)n sess.run(train_op,feed_dict={x:xs,y_:ys})nn test_acc=sess.run(accuracy,feed_dict=test_feed)n print(("After %d training step(s), test accuracy using average model is %g" %(TRAINING_STEPS, test_acc)))n

該模型運行的結果如下:

在上面定義的整個計算圖中,我們先載入數據並定義權重矩陣和模型,然後在計算損失值並傳遞給優化器來優化權重。模型在迭代次數設定之內會一直循環地計算損失函數的梯度以更新權重。在上面的全連接神經網路中,我們使用梯度下降優化器來優化權重。然而,TensorFlow

中還有很多優化器,最常用的是 GradientDescentOptimizer、AdamOptimizer 和

AdaGradOptimizer。

下面我們就需要構建卷積神經網路了,不過在使用 TensorFlow 構建卷積網路之前,我們需要了解一下 TensorFlow 中的函數。

TensorFlow包含很多操作和函數,很多我們需要花費大量精力完成的過程可以直接調用已封裝的函數,比如說「logits = tf.matmul(tf_train_dataset, weights) + biases」可以由函數「logits = tf.nn.xw_plus_b(train_dataset, weights, biases)」代替。

還有很多函數可以讓構建不同層級的神經網路變得十分簡單。例如conv_2d() 和 fully_connected() 函數分別構建了卷積層和全連接層。通過這些函數,層級的數量、濾波器的大小/深度、激活函數的類型等都可以明確地作為一個參數。權重矩陣和偏置向量能自動創建,附加激活函數和dropout 正則化層同樣也能輕鬆構建。

如下所示為定義卷積層網路的代碼:

import tensorflow as tfn nw1 = tf.Variable(tf.truncated_normal([filter_size, filter_size, image_depth, filter_depth], stddev=0.1))nb1 = tf.Variable(tf.zeros([filter_depth]))nnlayer1_conv = tf.nn.conv2d(data, w1, [1, 1, 1, 1], padding=SAME)nlayer1_relu = tf.nn.relu(layer1_conv + b1)nlayer1_pool = tf.nn.max_pool(layer1_pool, [1, 2, 2, 1], [1, 2, 2, 1], padding=SAME)n

它們可以使用簡單的函數來替代上面的定義:

from tflearn.layers.conv import conv_2d, max_pool_2dnnlayer1_conv = conv_2d(data, filter_depth, filter_size, activation=relu)nlayer1_pool = max_pool_2d(layer1_conv_relu, 2, strides=2)n

正如我們前面所說的,我們並不需要定義權重、偏置和激活函數,特別是在定義多層神經網路的時候,這一點讓我們的代碼可以看起來十分整潔。

2.4 創建 LeNet5 卷積網路

LeNet5卷積網路架構最早是 Yann LeCun 提出來的,它是早期的一種卷積神經網路,並且可以用來識別手寫數字。雖然它在 MNIST 數據集上執行地非常好,但在其它高解析度和大數據集上性能有所降低。對於這些大數據集,像 AlexNet、VGGNet 或 ResNet 那樣的深度卷積網路才執行地十分優秀。

因為 LeNet5 只由 5 層網路,所以它是學習如何構建卷積網路的最佳起點。LeNet5 的架構如下:

LeNet5 包含 5 層網路:

  • 第一層:卷積層,該卷積層使用 Sigmoid 激活函數,並且在後面帶有平均池化層。
  • 第二層:卷積層,該卷積層使用 Sigmoid 激活函數,並且在後面帶有平均池化層。
  • 第三層:全連接層(使用 Sigmoid 激活函數)。
  • 第四層:全連接層(使用 Sigmoid 激活函數)。
  • 第五層:輸出層。

上面的 LeNet5 架構意味著我們需要構建 5 個權重和偏置項矩陣,我們模型的主體大概需要 12 行代碼完成(5 個神經網路層級、2 個池化層、4 個激活函數還有 1 個 flatten 層)。因為代碼比較多,所以我們最好在計算圖之外就定義好獨立的函數:

LENET5_BATCH_SIZE = 32nLENET5_PATCH_SIZE = 5nLENET5_PATCH_DEPTH_1 = 6nLENET5_PATCH_DEPTH_2 = 16nLENET5_NUM_HIDDEN_1 = 120nLENET5_NUM_HIDDEN_2 = 84nndef variables_lenet5(patch_size = LENET5_PATCH_SIZE, patch_depth1 = LENET5_PATCH_DEPTH_1, n patch_depth2 = LENET5_PATCH_DEPTH_2, n num_hidden1 = LENET5_NUM_HIDDEN_1, num_hidden2 = LENET5_NUM_HIDDEN_2,n image_depth = 1, num_labels = 10):n n w1 = tf.Variable(tf.truncated_normal([patch_size, patch_size, image_depth, patch_depth1], stddev=0.1))n b1 = tf.Variable(tf.zeros([patch_depth1]))nn w2 = tf.Variable(tf.truncated_normal([patch_size, patch_size, patch_depth1, patch_depth2], stddev=0.1))n b2 = tf.Variable(tf.constant(1.0, shape=[patch_depth2]))nn w3 = tf.Variable(tf.truncated_normal([5*5*patch_depth2, num_hidden1], stddev=0.1))n b3 = tf.Variable(tf.constant(1.0, shape = [num_hidden1]))nn w4 = tf.Variable(tf.truncated_normal([num_hidden1, num_hidden2], stddev=0.1))n b4 = tf.Variable(tf.constant(1.0, shape = [num_hidden2]))n n w5 = tf.Variable(tf.truncated_normal([num_hidden2, num_labels], stddev=0.1))n b5 = tf.Variable(tf.constant(1.0, shape = [num_labels]))n variables = {n w1: w1, w2: w2, w3: w3, w4: w4, w5: w5,n b1: b1, b2: b2, b3: b3, b4: b4, b5: b5n }n return variablesnndef model_lenet5(data, variables):n layer1_conv = tf.nn.conv2d(data, variables[w1], [1, 1, 1, 1], padding=SAME)n layer1_actv = tf.sigmoid(layer1_conv + variables[b1])n layer1_pool = tf.nn.avg_pool(layer1_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding=SAME)nn layer2_conv = tf.nn.conv2d(layer1_pool, variables[w2], [1, 1, 1, 1], padding=VALID)n layer2_actv = tf.sigmoid(layer2_conv + variables[b2])n layer2_pool = tf.nn.avg_pool(layer2_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding=SAME)nn flat_layer = flatten_tf_array(layer2_pool)n layer3_fccd = tf.matmul(flat_layer, variables[w3]) + variables[b3]n layer3_actv = tf.nn.sigmoid(layer3_fccd)n n layer4_fccd = tf.matmul(layer3_actv, variables[w4]) + variables[b4]n layer4_actv = tf.nn.sigmoid(layer4_fccd)n logits = tf.matmul(layer4_actv, variables[w5]) + variables[b5]n return logitsn

通過上面獨立定義的變數和模型,我們可以一點點調整數據流圖而不像前面的全連接網路那樣。

#parameters determining the model sizenimage_size = mnist_image_sizennum_labels = mnist_num_labelsnn#the datasetsntrain_dataset = mnist_train_datasetntrain_labels = mnist_train_labelsntest_dataset = mnist_test_datasetntest_labels = mnist_test_labelsnn#number of iterations and learning ratennum_steps = 10001ndisplay_step = 1000nlearning_rate = 0.001nngraph = tf.Graph()nwith graph.as_default():n #1) First we put the input data in a tensorflow friendly form. n tf_train_dataset = tf.placeholder(tf.float32, shape=(batch_size, image_width, image_height, image_depth))n tf_train_labels = tf.placeholder(tf.float32, shape = (batch_size, num_labels))n tf_test_dataset = tf.constant(test_dataset, tf.float32)nn #2) Then, the weight matrices and bias vectors are initializedn <strong>variables = variables_lenet5(image_depth = image_depth, num_labels = num_labels)</strong>nn #3. The model used to calculate the logits (predicted labels)n <strong>model = model_lenet5</strong>n <strong>logits = model(tf_train_dataset, variables)</strong>nn #4. then we compute the softmax cross entropy between the logits and the (actual) labelsn loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=tf_train_labels))n n #5. The optimizer is used to calculate the gradients of the loss function n optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)nn # Predictions for the training, validation, and test data.n train_prediction = tf.nn.softmax(logits)n test_prediction = tf.nn.softmax(model(tf_test_dataset, variables))nwith tf.Session(graph=graph) as session:n tf.global_variables_initializer().run()n print(Initialized with learning_rate, learning_rate)n for step in range(num_steps):nn #Since we are using stochastic gradient descent, we are selecting small batches from the training dataset,n #and training the convolutional neural network each time with a batch. n offset = (step * batch_size) % (train_labels.shape[0] - batch_size)n batch_data = train_dataset[offset:(offset + batch_size), :, :, :]n batch_labels = train_labels[offset:(offset + batch_size), :]n feed_dict = {tf_train_dataset : batch_data, tf_train_labels : batch_labels}n _, l, predictions = session.run([optimizer, loss, train_prediction], feed_dict=feed_dict)n n if step % display_step == 0:n train_accuracy = accuracy(predictions, batch_labels)n test_accuracy = accuracy(test_prediction.eval(), test_labels)n message = "step {:04d} : loss is {:06.2f}, accuracy on training set {:02.2f} %, accuracy on test set {:02.2f} %".format(step, l, train_accuracy, test_accuracy)n print(message)n

我們看到Ahmet Taspinar 構建的 LeNet5 網路要比他所訓練的全連接網路在 MNIST 數據集上有更好的性能。但是在我們所訓練的全連接神經網路中,因為使用了 ReLU、學習率指數衰減、滑動平均類和正則化等機制,我們的準確度達到了 98% 以上。

2.5 超參數如何影響一層網路的輸出尺寸

一般來說,確實是層級越多神經網路的性能就越好。我們可以添加更多的層級、更改激活函數和池化層、改變學習率並查看每一步對性能的影響。因為層級 i 的輸出是層級 i+1 的輸入,所以我們需要知道第 i 層神經網路的超參數如何影響其輸出尺寸。

為了理解這一點我們需要討論一下 conv2d() 函數。

該函數有四個參數:

  • 輸入圖像,即一個四維張量 [batch size, image_width, image_height, image_depth]
  • 權重矩陣,即一個四維張量 [filter_size, filter_size, image_depth, filter_depth]
  • 每一個維度的步幅數
  • Padding (= SAME / VALID)

這四個參數決定了輸出圖像的尺寸。前面兩個參數都是四維張量,其包括了批量輸入圖像的信息和卷積濾波器的權值。第三個參數為卷積的步幅(stride),即卷積濾波器在4 個維度中的每一次移動的距離。四個中間的第一個維度代表著圖像的批量數,這個維度肯定每次只能移動一張圖片。最後一個維度為圖片深度(即色彩通道數,1 代表灰度圖片,而 3 代表 RGB 圖片),因為我們通常並不想跳過任何一個通道,所以這一個值也通常為 1。第二個和第三個維度代表 X 和 Y 方向(圖片寬度和高度)的步幅。如果我們希望能應用步幅參數,我們需要設定每個維度的移動步幅。例如設定步幅為 1,那麼步幅參數就需要設定為 [1, 1, 1, 1],如果我們希望在圖像上移動的步幅設定為 2,步幅參數為 [1, 2, 2, 1]。

最後一個參數表明TensorFlow 是否需要使用 0 來填補圖像周邊,這樣以確保圖像輸出尺寸在步幅參數設定為 1 的情況下保持不變。通過設置 padding = SAME,圖像會只使用 0 來填補周邊(輸出尺寸不變),而 padding = VALID則不會使用 0。在下圖中,我們將看到兩個使用卷積濾波器在圖像上掃描的案例,其中濾波器的大小為5 x 5、圖像的大小為 28 x 28。左邊的 Padding 參數設置為SAME,並且最後四行/列的信息也會包含在輸出圖像中。而右邊padding 設置為 VALID,最後四行/列是不包括在輸出圖像內的。

沒有 padding 的圖片,最後四個像素點是無法包含在內的,因為卷積濾波器已經移動到了圖片的邊緣。這就意味著輸入 28 x 28 尺寸的圖片,輸出尺寸只有 24 x 24。如果 padding = SAME,那麼輸出尺寸就是 28 x 28。

如果我們輸入圖片尺寸是 28 x 28、濾波器尺寸為 5 x 5,步幅分別設置為 1 到 4,那麼就能得到下表

對於任意給定的步幅 S、濾波器尺寸 K、圖像尺寸 W、padding 尺寸 P,輸出的圖像尺寸可以總結上表的規則如下:

2.6 調整 LeNet5 架構

LeNet5 架構在原論文中使用的是 Sigmoid 激活函數和平均池化。然而如今神經網路使用 ReLU 激活函數更為常見。所以我們可以修改一下 LeNet5 架構,並看看是否能獲得性能上的提升,我們可以稱這種修改的架構為類 LeNet5 架構。

最大的不同是我們使用ReLU 激活函數代替 Sigmoid 激活函數。除了激活函數意外,我們還修改了優化器,因為我們可以看到不同優化器對識別準確度的影響。在這裡,機器之心在CIFAR-10 上使用該修正的 LeNet 進行了訓練,詳細代碼如下。機器之心訓練的準確度並不高,可能是學習率、批量數或者其他設置有些問題,也可能是 LeNet對於三通道的圖太簡單了。該運行結果展現在機器之心該項目的 Github 中,感興趣的讀者可以進一步修正該模型以期望達到更好的效果。

LENET5_LIKE_BATCH_SIZE = 32nLENET5_LIKE_FILTER_SIZE = 5nLENET5_LIKE_FILTER_DEPTH = 16nLENET5_LIKE_NUM_HIDDEN = 120nnndef variables_lenet5_like(filter_size = LENET5_LIKE_FILTER_SIZE, n filter_depth = LENET5_LIKE_FILTER_DEPTH, n num_hidden = LENET5_LIKE_NUM_HIDDEN,n image_width = 32, image_height = 32, image_depth = 3, num_labels = 10):nn w1 = tf.Variable(tf.truncated_normal([filter_size, filter_size, image_depth, filter_depth], stddev=0.1))n b1 = tf.Variable(tf.zeros([filter_depth]))nn w2 = tf.Variable(tf.truncated_normal([filter_size, filter_size, filter_depth, filter_depth], stddev=0.1))n b2 = tf.Variable(tf.constant(1.0, shape=[filter_depth]))nn w3 = tf.Variable(tf.truncated_normal([(image_width // 4)*(image_height // 4)*filter_depth , num_hidden], stddev=0.1))n b3 = tf.Variable(tf.constant(1.0, shape = [num_hidden]))nn w4 = tf.Variable(tf.truncated_normal([num_hidden, num_hidden], stddev=0.1))n b4 = tf.Variable(tf.constant(1.0, shape = [num_hidden]))nn w5 = tf.Variable(tf.truncated_normal([num_hidden, num_labels], stddev=0.1))n b5 = tf.Variable(tf.constant(1.0, shape = [num_labels]))n variables = {n w1: w1, w2: w2, w3: w3, w4: w4, w5: w5,n b1: b1, b2: b2, b3: b3, b4: b4, b5: b5n }n return variablesn nndef model_lenet5_like(data, variables):n layer1_conv = tf.nn.conv2d(data, variables[w1], [1, 1, 1, 1], padding=SAME)n layer1_actv = tf.nn.relu(layer1_conv + variables[b1])n layer1_pool = tf.nn.avg_pool(layer1_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding=SAME)nn layer2_conv = tf.nn.conv2d(layer1_pool, variables[w2], [1, 1, 1, 1], padding=SAME)n layer2_actv = tf.nn.relu(layer2_conv + variables[b2])n layer2_pool = tf.nn.avg_pool(layer2_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding=SAME)nn flat_layer = flatten_tf_array(layer2_pool)n layer3_fccd = tf.matmul(flat_layer, variables[w3]) + variables[b3]n layer3_actv = tf.nn.relu(layer3_fccd)n layer3_drop = tf.nn.dropout(layer3_actv, 0.5)nn layer4_fccd = tf.matmul(layer3_actv, variables[w4]) + variables[b4]n layer4_actv = tf.nn.relu(layer4_fccd)n layer4_drop = tf.nn.dropout(layer4_actv, 0.5)nn logits = tf.matmul(layer4_actv, variables[w5]) + variables[b5]n return logitsnnnum_steps = 10001ndisplay_step = 1000nlearning_rate = 0.001nbatch_size = 16nn#定義數據的基本信息,傳入變數nimage_width = 32nimage_height = 32nimage_depth = 3nnum_labels = 10nntest_dataset = test_dataset_cifar10ntest_labels = test_labels_cifar10ntrain_dataset = train_dataset_cifar10ntrain_labels = train_labels_cifar10nngraph = tf.Graph()nnwith graph.as_default():nn #1 首先使用佔位符定義數據變數的維度n tf_train_dataset = tf.placeholder(tf.float32, shape=(batch_size, image_width, image_height, image_depth))n tf_train_labels = tf.placeholder(tf.float32, shape = (batch_size, num_labels))n tf_test_dataset = tf.constant(test_dataset, tf.float32)nn #2 然後初始化權重矩陣和偏置向量n variables = variables_lenet5_like(image_width = image_width, image_height=image_height, image_depth = image_depth, num_labels = num_labels)nn #3 使用模型計算分類n logits = model_lenet5_like(tf_train_dataset, variables)nn #4 使用帶softmax的交叉熵函數計算預測標籤和真實標籤之間的損失函數n loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=tf_train_labels))nn #5 採用Adam優化演算法優化上一步定義的損失函數,給定學習率n optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(lossn n# 執行預測推斷n train_prediction = tf.nn.softmax(logits)n test_prediction = tf.nn.softmax(model_lenet5_like(tf_test_dataset, variables))nnwith tf.Session(graph=graph) as session:nn #初始化全部變數n tf.global_variables_initializer().run()n print(Initialized with learning_rate, learning_rate)n for step in range(num_steps):n offset = (step * batch_size) % (train_labels.shape[0] - batch_size)n batch_data = train_dataset[offset:(offset + batch_size), :, :, :]n batch_labels = train_labels[offset:(offset + batch_size), :]nn #在每一次批量中,獲取當前的訓練數據,並傳入feed_dict以饋送到佔位符中n feed_dict = {tf_train_dataset : batch_data, tf_train_labels : batch_labels}n _, l, predictions = session.run([optimizer, loss, train_prediction], feed_dict=feed_dict)n train_accuracy = accuracy(predictions, batch_labels)nn if step % display_step == 0:n test_accuracy = accuracy(test_prediction.eval(), test_labels)n message = "step {:04d} : loss is {:06.2f}, accuracy on training set {:02.2f} %, accuracy on test set {:02.2f} %".format(step, l, train_accuracy, test_accuracy)n print(message)n

2.7 學習率和優化器的影響

我們可以在下圖看到這些 CNN 在 MNIST 和 CIFAR-10 數據集上的性能。

上圖展示了模型在兩個測試集上的準確度和迭代次數,其代表的模型從左至右分別為全連接神經網路、LeNet5 和 改進後的 LeNet5。不過由於 MNIST 太簡單,全連接網路也能做得挺好。不過在 CIFAR-10 數據集中,全連接網路的性能明顯下降了不少。

上圖展示了三種神經網路在 CIFAR-10 數據集上使用不同的優化器而得出的性能。可能 L2 正則化和指數衰減學習率能進一步提高模型的性能,不過要獲得更大的提升,我們需要使用深度神經網路。

TensorFlow 中的深度神經網路

LeNet5 由兩個卷積層加上三個全連接層組成,因此它是一種淺層神經網路。下面我們將了解其它卷積神經網路,它們的層級更多,所以可以稱為深度神經網路。下面介紹的深度卷積神經網路我們並沒有根據Ahmet Taspinar 提供的代碼進行實踐,因為我們暫時安裝的是 TensorFlow 的 CPU 版,而使用 CPU 訓練前面的 LeNet 就已經十分吃力了,所以我們暫時沒有實現這幾個深度 CNN。我們將會在後面實現它們,並將修改的代碼上傳到機器之心的Github 中。

卷積神經網路最出名的就是2012 年所提出的 AlexNet、2013 年的 7 層 ZF-Net 和 2014 年提出的 16 層 VGGNet。到了 2015 年,谷歌通過 Inception 模塊開發出 22 層的卷積神經網路(GoogLeNet),而微軟亞洲研究院創造出了 152 層的卷積神經網路:ResNet。下面,我們將學習如何使用 TensofFlow 構建 AlexNet 和 VGGNet16。

3.1 AlexNet

AlexNet是由 Alex Krizhevsky 和 Geoffrey Hinton 等人提出來的,雖然相對於現在的卷積神經網路來說它的架構十分簡單,但當時它是十分成功的一個模型。它贏得了當年的 ImageNet

挑戰賽,並開啟了深度學習和 AI 的變革。下面是 AlexNet 的基本架構:

AlexNet 包含 5 個卷積層(帶有 ReLU 激活函數)、3 個最大池化層、3 個全連接層和兩個 dropout 層。該神經網路的架構概覽如下:

  • 層級 0:規格為 224 x 224 x 3 的輸入圖片。
  • 層級 1:帶有 96 個濾波器(filter_depth_1 = 96)的卷積層,濾波器的尺寸為 11 x 11(filter_size_1 = 11)、步幅為 4。該層的神經網路使用 ReLU 激活函數,並且後面帶有最大池化層和局部響應歸一化層。
  • 層級 2:帶有 256 個濾波器(filter_depth_2 = 256)的卷積層,濾波器的尺寸為 5 x 5(filter_size_2 = 5)、步幅為 1。該層的神經網路使用 ReLU 激活函數,並且後面帶有最大池化層和局部響應歸一化層。
  • 層級 3:帶有 384 個濾波器(filter_depth_3 = 384)的卷積層,濾波器的尺寸為 3 x 3(filter_size_3 = 3)、步幅為 1。該層的神經網路使用 ReLU 激活函數。
  • 層級 4 和層級 3 的結構是一樣的。
  • 層級 5:帶有 256 個濾波器(filter_depth_4 = 256)的卷積層,濾波器的尺寸為 3 x 3(filter_size_4 = 3)、步幅為 1。該層的神經網路使用 ReLU 激活函數。
  • 層級 6-8:這幾個卷積層每一個後面跟著一個全連接層,每一層有 4096 個神經元。在原論文中,他們是為了 1000 個類別的分類,當我們這邊並不需要這麼多。

注意AlexNet 或其他深度 CNN 並不能使用 MNIST 或者 CIFAR-10 數據集,因為這些圖片的解析度太小。正如我們所看到的,池化層(或者步幅為 2 的卷積層)減少了兩倍的圖像大小。AlexNet 有 3 個最大池化層和一個步幅為 4 的卷積層,這就意味著原圖片會被縮小很多倍,而 MNIST 數據集的圖像尺寸太小而不能進行著一系列操作。

因此,我們需要載入有更高像素圖像的數據集,最好是和原論文一樣採用 224 x 224 x 3。aka oxflower17 數據集可能是比較理想的數據集,它含有 17 種花的圖片,並且像素正好是我們所需要的:

ox17_image_width = 224nox17_image_height = 224nox17_image_depth = 3nox17_num_labels = 17nnimport tflearn.datasets.oxflower17 as oxflower17ntrain_dataset_, train_labels_ = oxflower17.load_data(one_hot=True)ntrain_dataset_ox17, train_labels_ox17 = train_dataset_[:1000,:,:,:], train_labels_[:1000,:]ntest_dataset_ox17, test_labels_ox17 = train_dataset_[1000:,:,:,:], train_labels_[1000:,:]nnprint(Training set, train_dataset_ox17.shape, train_labels_ox17.shape)nprint(Test set, test_dataset_ox17.shape, test_labels_ox17.shape)n

下面,我們可以定義 AlexNet 中的權重矩陣和不同的層級。正如我們前面所看到的,我們需要定義很多權重矩陣和偏置向量,並且它們還需要和每一層的濾波器尺寸保持一致。

ALEX_PATCH_DEPTH_1, ALEX_PATCH_DEPTH_2, ALEX_PATCH_DEPTH_3, ALEX_PATCH_DEPTH_4 = 96, 256, 384, 256nALEX_PATCH_SIZE_1, ALEX_PATCH_SIZE_2, ALEX_PATCH_SIZE_3, ALEX_PATCH_SIZE_4 = 11, 5, 3, 3nALEX_NUM_HIDDEN_1, ALEX_NUM_HIDDEN_2 = 4096, 4096nnndef variables_alexnet(patch_size1 = ALEX_PATCH_SIZE_1, patch_size2 = ALEX_PATCH_SIZE_2, n patch_size3 = ALEX_PATCH_SIZE_3, patch_size4 = ALEX_PATCH_SIZE_4, n patch_depth1 = ALEX_PATCH_DEPTH_1, patch_depth2 = ALEX_PATCH_DEPTH_2, n patch_depth3 = ALEX_PATCH_DEPTH_3, patch_depth4 = ALEX_PATCH_DEPTH_4, n num_hidden1 = ALEX_NUM_HIDDEN_1, num_hidden2 = ALEX_NUM_HIDDEN_2,n image_width = 224, image_height = 224, image_depth = 3, num_labels = 17):n n w1 = tf.Variable(tf.truncated_normal([patch_size1, patch_size1, image_depth, patch_depth1], stddev=0.1))n b1 = tf.Variable(tf.zeros([patch_depth1]))nn w2 = tf.Variable(tf.truncated_normal([patch_size2, patch_size2, patch_depth1, patch_depth2], stddev=0.1))n b2 = tf.Variable(tf.constant(1.0, shape=[patch_depth2]))nn w3 = tf.Variable(tf.truncated_normal([patch_size3, patch_size3, patch_depth2, patch_depth3], stddev=0.1))n b3 = tf.Variable(tf.zeros([patch_depth3]))nn w4 = tf.Variable(tf.truncated_normal([patch_size4, patch_size4, patch_depth3, patch_depth3], stddev=0.1))n b4 = tf.Variable(tf.constant(1.0, shape=[patch_depth3]))n n w5 = tf.Variable(tf.truncated_normal([patch_size4, patch_size4, patch_depth3, patch_depth3], stddev=0.1))n b5 = tf.Variable(tf.zeros([patch_depth3]))n n pool_reductions = 3n conv_reductions = 2n no_reductions = pool_reductions + conv_reductionsn w6 = tf.Variable(tf.truncated_normal([(image_width // 2**no_reductions)*(image_height // 2**no_reductions)*patch_depth3, num_hidden1], stddev=0.1))n b6 = tf.Variable(tf.constant(1.0, shape = [num_hidden1]))nn w7 = tf.Variable(tf.truncated_normal([num_hidden1, num_hidden2], stddev=0.1))n b7 = tf.Variable(tf.constant(1.0, shape = [num_hidden2]))n n w8 = tf.Variable(tf.truncated_normal([num_hidden2, num_labels], stddev=0.1))n b8 = tf.Variable(tf.constant(1.0, shape = [num_labels]))n n variables = {n w1: w1, w2: w2, w3: w3, w4: w4, w5: w5, w6: w6, w7: w7, w8: w8, n b1: b1, b2: b2, b3: b3, b4: b4, b5: b5, b6: b6, b7: b7, b8: b8n }n return variablesnnndef model_alexnet(data, variables):n layer1_conv = tf.nn.conv2d(data, variables[w1], [1, 4, 4, 1], padding=SAME)n layer1_relu = tf.nn.relu(layer1_conv + variables[b1])n layer1_pool = tf.nn.max_pool(layer1_relu, [1, 3, 3, 1], [1, 2, 2, 1], padding=SAME)n layer1_norm = tf.nn.local_response_normalization(layer1_pool)n n layer2_conv = tf.nn.conv2d(layer1_norm, variables[w2], [1, 1, 1, 1], padding=SAME)n layer2_relu = tf.nn.relu(layer2_conv + variables[b2])n layer2_pool = tf.nn.max_pool(layer2_relu, [1, 3, 3, 1], [1, 2, 2, 1], padding=SAME)n layer2_norm = tf.nn.local_response_normalization(layer2_pool)n n layer3_conv = tf.nn.conv2d(layer2_norm, variables[w3], [1, 1, 1, 1], padding=SAME)n layer3_relu = tf.nn.relu(layer3_conv + variables[b3])n n layer4_conv = tf.nn.conv2d(layer3_relu, variables[w4], [1, 1, 1, 1], padding=SAME)n layer4_relu = tf.nn.relu(layer4_conv + variables[b4])n n layer5_conv = tf.nn.conv2d(layer4_relu, variables[w5], [1, 1, 1, 1], padding=SAME)n layer5_relu = tf.nn.relu(layer5_conv + variables[b5])n layer5_pool = tf.nn.max_pool(layer4_relu, [1, 3, 3, 1], [1, 2, 2, 1], padding=SAME)n layer5_norm = tf.nn.local_response_normalization(layer5_pool)n n flat_layer = flatten_tf_array(layer5_norm)n layer6_fccd = tf.matmul(flat_layer, variables[w6]) + variables[b6]n layer6_tanh = tf.tanh(layer6_fccd)n layer6_drop = tf.nn.dropout(layer6_tanh, 0.5)n n layer7_fccd = tf.matmul(layer6_drop, variables[w7]) + variables[b7]n layer7_tanh = tf.tanh(layer7_fccd)n layer7_drop = tf.nn.dropout(layer7_tanh, 0.5)n n logits = tf.matmul(layer7_drop, variables[w8]) + variables[b8]n return logitsn

3.2 VGGNet-16

VGGNet 比 AlexNet 擁有的層級更多(16-19 層),但是每一層的設計都簡單了許多,所有層的濾波器大小都是 3 x 3、步幅都是 1,而所有的最大池化層的步幅都是 2。所以它雖然是一種深度 CNN,但結構比較簡單。

VGGNet 有 16 層或 19 層兩種配置,如下所示,這兩種配置的不同之處在於它在第二個、第三個和第四個最大池化層後面到底是採用三個卷積層還是四個卷積層。

#The VGGNET Neural Network nVGG16_PATCH_SIZE_1, VGG16_PATCH_SIZE_2, VGG16_PATCH_SIZE_3, VGG16_PATCH_SIZE_4 = 3, 3, 3, 3nVGG16_PATCH_DEPTH_1, VGG16_PATCH_DEPTH_2, VGG16_PATCH_DEPTH_3, VGG16_PATCH_DEPTH_4 = 64, 128, 256, 512nVGG16_NUM_HIDDEN_1, VGG16_NUM_HIDDEN_2 = 4096, 1000nndef variables_vggnet16(patch_size1 = VGG16_PATCH_SIZE_1, patch_size2 = VGG16_PATCH_SIZE_2, n patch_size3 = VGG16_PATCH_SIZE_3, patch_size4 = VGG16_PATCH_SIZE_4, n patch_depth1 = VGG16_PATCH_DEPTH_1, patch_depth2 = VGG16_PATCH_DEPTH_2, n patch_depth3 = VGG16_PATCH_DEPTH_3, patch_depth4 = VGG16_PATCH_DEPTH_4,n num_hidden1 = VGG16_NUM_HIDDEN_1, num_hidden2 = VGG16_NUM_HIDDEN_2,n image_width = 224, image_height = 224, image_depth = 3, num_labels = 17):n n w1 = tf.Variable(tf.truncated_normal([patch_size1, patch_size1, image_depth, patch_depth1], stddev=0.1))n b1 = tf.Variable(tf.zeros([patch_depth1]))n w2 = tf.Variable(tf.truncated_normal([patch_size1, patch_size1, patch_depth1, patch_depth1], stddev=0.1))n b2 = tf.Variable(tf.constant(1.0, shape=[patch_depth1]))nn w3 = tf.Variable(tf.truncated_normal([patch_size2, patch_size2, patch_depth1, patch_depth2], stddev=0.1))n b3 = tf.Variable(tf.constant(1.0, shape = [patch_depth2]))n w4 = tf.Variable(tf.truncated_normal([patch_size2, patch_size2, patch_depth2, patch_depth2], stddev=0.1))n b4 = tf.Variable(tf.constant(1.0, shape = [patch_depth2]))n n w5 = tf.Variable(tf.truncated_normal([patch_size3, patch_size3, patch_depth2, patch_depth3], stddev=0.1))n b5 = tf.Variable(tf.constant(1.0, shape = [patch_depth3]))n w6 = tf.Variable(tf.truncated_normal([patch_size3, patch_size3, patch_depth3, patch_depth3], stddev=0.1))n b6 = tf.Variable(tf.constant(1.0, shape = [patch_depth3]))n w7 = tf.Variable(tf.truncated_normal([patch_size3, patch_size3, patch_depth3, patch_depth3], stddev=0.1))n b7 = tf.Variable(tf.constant(1.0, shape=[patch_depth3]))nn w8 = tf.Variable(tf.truncated_normal([patch_size4, patch_size4, patch_depth3, patch_depth4], stddev=0.1))n b8 = tf.Variable(tf.constant(1.0, shape = [patch_depth4]))n w9 = tf.Variable(tf.truncated_normal([patch_size4, patch_size4, patch_depth4, patch_depth4], stddev=0.1))n b9 = tf.Variable(tf.constant(1.0, shape = [patch_depth4]))n w10 = tf.Variable(tf.truncated_normal([patch_size4, patch_size4, patch_depth4, patch_depth4], stddev=0.1))n b10 = tf.Variable(tf.constant(1.0, shape = [patch_depth4]))n n w11 = tf.Variable(tf.truncated_normal([patch_size4, patch_size4, patch_depth4, patch_depth4], stddev=0.1))n b11 = tf.Variable(tf.constant(1.0, shape = [patch_depth4]))n w12 = tf.Variable(tf.truncated_normal([patch_size4, patch_size4, patch_depth4, patch_depth4], stddev=0.1))n b12 = tf.Variable(tf.constant(1.0, shape=[patch_depth4]))n w13 = tf.Variable(tf.truncated_normal([patch_size4, patch_size4, patch_depth4, patch_depth4], stddev=0.1))n b13 = tf.Variable(tf.constant(1.0, shape = [patch_depth4]))n n no_pooling_layers = 5nn w14 = tf.Variable(tf.truncated_normal([(image_width // (2**no_pooling_layers))*(image_height // (2**no_pooling_layers))*patch_depth4 , num_hidden1], stddev=0.1))n b14 = tf.Variable(tf.constant(1.0, shape = [num_hidden1]))n n w15 = tf.Variable(tf.truncated_normal([num_hidden1, num_hidden2], stddev=0.1))n b15 = tf.Variable(tf.constant(1.0, shape = [num_hidden2]))n n w16 = tf.Variable(tf.truncated_normal([num_hidden2, num_labels], stddev=0.1))n b16 = tf.Variable(tf.constant(1.0, shape = [num_labels]))n variables = {n w1: w1, w2: w2, w3: w3, w4: w4, w5: w5, w6: w6, w7: w7, w8: w8, w9: w9, w10: w10, n w11: w11, w12: w12, w13: w13, w14: w14, w15: w15, w16: w16, n b1: b1, b2: b2, b3: b3, b4: b4, b5: b5, b6: b6, b7: b7, b8: b8, b9: b9, b10: b10, n b11: b11, b12: b12, b13: b13, b14: b14, b15: b15, b16: b16n }n return variablesnndef model_vggnet16(data, variables):n layer1_conv = tf.nn.conv2d(data, variables[w1], [1, 1, 1, 1], padding=SAME)n layer1_actv = tf.nn.relu(layer1_conv + variables[b1])n layer2_conv = tf.nn.conv2d(layer1_actv, variables[w2], [1, 1, 1, 1], padding=SAME)n layer2_actv = tf.nn.relu(layer2_conv + variables[b2])n layer2_pool = tf.nn.max_pool(layer2_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding=SAME)nn layer3_conv = tf.nn.conv2d(layer2_pool, variables[w3], [1, 1, 1, 1], padding=SAME)n layer3_actv = tf.nn.relu(layer3_conv + variables[b3]) n layer4_conv = tf.nn.conv2d(layer3_actv, variables[w4], [1, 1, 1, 1], padding=SAME)n layer4_actv = tf.nn.relu(layer4_conv + variables[b4])n layer4_pool = tf.nn.max_pool(layer4_pool, [1, 2, 2, 1], [1, 2, 2, 1], padding=SAME)nn layer5_conv = tf.nn.conv2d(layer4_pool, variables[w5], [1, 1, 1, 1], padding=SAME)n layer5_actv = tf.nn.relu(layer5_conv + variables[b5])n layer6_conv = tf.nn.conv2d(layer5_actv, variables[w6], [1, 1, 1, 1], padding=SAME)n layer6_actv = tf.nn.relu(layer6_conv + variables[b6])n layer7_conv = tf.nn.conv2d(layer6_actv, variables[w7], [1, 1, 1, 1], padding=SAME)n layer7_actv = tf.nn.relu(layer7_conv + variables[b7])n layer7_pool = tf.nn.max_pool(layer7_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding=SAME)nn layer8_conv = tf.nn.conv2d(layer7_pool, variables[w8], [1, 1, 1, 1], padding=SAME)n layer8_actv = tf.nn.relu(layer8_conv + variables[b8])n layer9_conv = tf.nn.conv2d(layer8_actv, variables[w9], [1, 1, 1, 1], padding=SAME)n layer9_actv = tf.nn.relu(layer9_conv + variables[b9])n layer10_conv = tf.nn.conv2d(layer9_actv, variables[w10], [1, 1, 1, 1], padding=SAME)n layer10_actv = tf.nn.relu(layer10_conv + variables[b10])n layer10_pool = tf.nn.max_pool(layer10_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding=SAME)nn layer11_conv = tf.nn.conv2d(layer10_pool, variables[w11], [1, 1, 1, 1], padding=SAME)n layer11_actv = tf.nn.relu(layer11_conv + variables[b11])n layer12_conv = tf.nn.conv2d(layer11_actv, variables[w12], [1, 1, 1, 1], padding=SAME)n layer12_actv = tf.nn.relu(layer12_conv + variables[b12])n layer13_conv = tf.nn.conv2d(layer12_actv, variables[w13], [1, 1, 1, 1], padding=SAME)n layer13_actv = tf.nn.relu(layer13_conv + variables[b13])n layer13_pool = tf.nn.max_pool(layer13_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding=SAME)n n flat_layer = flatten_tf_array(layer13_pool)n layer14_fccd = tf.matmul(flat_layer, variables[w14]) + variables[b14]n layer14_actv = tf.nn.relu(layer14_fccd)n layer14_drop = tf.nn.dropout(layer14_actv, 0.5)n n layer15_fccd = tf.matmul(layer14_drop, variables[w15]) + variables[b15]n layer15_actv = tf.nn.relu(layer15_fccd)n layer15_drop = tf.nn.dropout(layer15_actv, 0.5)n n logits = tf.matmul(layer15_drop, variables[w16]) + variables[b16]n return logitsn

上面已經為大家介紹了卷積神經網路,我們從TensorFlow 的安裝與基礎概念、簡單的全連接神經網路、數據的下載與導入、在 MNIST 上訓練全連接神經網路、在 CIFAR-10 上訓練經過修正的 LeNet 還有深度卷積神經網路等方面向大家介紹了神經網路,機器之心本文所有實驗的代碼、結果以及代碼注釋都將在 Github 上開放,這也是機器之心第一次試驗性地向大家介紹教程以及實現。我們希望在為讀者提供教程的同時也提供實際操作的經驗,希望能為大家學習該教程起到積極的作用。

參考博客:ataspinar.com/2017/08/1


推薦閱讀:

Node快閃:五分鐘建立一個免費的github頁面
一點微小的工作

TAG:TensorFlow | GitHub | 人工智能算法 |