# 四、CNN 實戰
以前面提到的`5×5`輸入矩陣為例,CNN 的輸入層由 25 個神經元(`5×5`)組成,其任務是獲取與每個像素對應的輸入值并將其轉移到下一層。
在多層網絡中,輸入層中所有神經元的輸出將連接到隱藏層(完全連接層)中的每個神經元。然而,在 CNN 網絡中,上面定義的連接方案和我們要描述的卷積層是顯著不同的。正如您可能猜到的,這是層的主要類型:在 CNN 中使用這些層中的一個或多個是必不可少的。
在卷積層中,每個神經元連接到輸入區域的某個區域,稱為感受野。例如,使用`3×3`內核濾波器,每個神經元將具有偏置并且 9 個權重(`3×3`)連接到單個感受野。為了有效地識別圖像,我們需要將各種不同的內核過濾器應用于相同的感受野,因為每個過濾器應該識別來自圖像的不同特征。識別相同特征的神經元集定義了單個特征映射。
下圖顯示了運行中的 CNN 架構:`28×28`輸入圖像將由一個由`28x28x32`特征映射組成的卷積層進行分析。該圖還顯示了一個感受野和一個`3×3`內核過濾器:

圖 5:CNN 正在運行中
CNN 可以由級聯連接的若干卷積層組成。每個卷積層的輸出是一組特征映射(每個都由單個內核過濾器生成)。這些矩陣中的每一個都定義了將由下一層使用的新輸入。
通常,在 CNN 中,每個神經元產生高達激活閾值的輸出,該激活閾值與輸入成比例并且不受限制。
CNN 還使用位于卷積層之后的池化層。池化層將卷積區域劃分為子區域。然后,池化層選擇單個代表值(最大池化或平均池化)以減少后續層的計算時間并增加特征相對于其空間位置的穩健性。卷積網絡的最后一層通常是完全連接的網絡,具有用于輸出層的 softmax 激活函數。在接下來的幾節中,將詳細分析最重要的 CNN 的架構。
# LeNet5
LeNet5 CNN 架構由 Yann LeCun 于 1998 年發明,是第一個 CNN。它是一個多層前饋網絡,專門用于對手寫數字進行分類。它被用于 LeCun 的實驗,由七層組成,包含可訓練的權重。 LeNet5 架構如下所示:

圖 6:LeNet5 網絡
LeNet5 架構由三個卷積層和兩個交替序列池化層組成。最后兩層對應于傳統的完全連接的神經網絡,即完全連接的層,后面是輸出層。輸出層的主要功能是計算輸入向量和參數向量之間的歐幾里德距離。輸出函數識別輸入模式和我們模型的測量值之間的差異。輸出保持最小,以實現最佳模型。因此,完全連接的層被配置為使得輸入模式和我們的模型的測量之間的差異最小化。雖然它在 MNIST 數據集上表現良好,但是在具有更高分辨率和更多類別的更多圖像的數據集上表現下降。
### 注意
有關 LeNet 系列模型的基本參考,請參見[此鏈接](http://yann.lecun.com/exdb/lenet/index.html)。
# 逐步實現 LeNet-5
在本節中,我們將學習如何構建 LeNet-5 架構來對 MNIST 數據集中的圖像進行分類。下圖顯示了數據如何在前兩個卷積層中流動:使用濾波器權重在第一個卷積層中處理輸入圖像。這導致 32 個新圖像,一個用于卷積層中的每個濾波器。圖像也通過合并操作進行下采樣,因此圖像分辨率從`28×28`降低到`14×14`。然后在第二卷積層中處理這 32 個較小的圖像。我們需要為這 32 個圖像中的每一個再次使用濾波器權重,并且我們需要該層的每個輸出通道的濾波器權重。通過合并操作再次對圖像進行下采樣,使得圖像分辨率從`14×14`減小到`7×7`。此卷積層的特征總數為 64。

圖 7:前兩個卷積層的數據流
通過(`3×3`)第三卷積層再次過濾 64 個結果圖像。沒有對該層應用池操作。第三卷積層的輸出是`128×7×7`像素圖像。然后將這些圖像展平為單個向量,長度為`4×4×128 = 2048`,其用作完全連接層的輸入。
LeNet-5 的輸出層由 625 個神經元作為輸入(即完全連接層的輸出)和 10 個神經元作為輸出,用于確定圖像的類別,該數字在圖片。

圖 8:最后三個卷積層的數據流
卷積濾波器最初是隨機選擇的。輸入圖像的預測類和實際類之間的差異被稱為成本函數,并且這使我們的網絡超出訓練數據。然后,優化器會自動通過 CNN 傳播此成本函數,并更新過濾器權重以改善分類誤差。這反復進行數千次,直到分類誤差足夠低。
現在讓我們詳細看看如何編寫我們的第一個 CNN。讓我們首先導入我們實現所需的 TensorFlow 庫:
```py
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
```
設置以下參數。它們表示在訓練階段(`128`)和測試階段(`256`)使用的樣本數量:
```py
batch_size = 128
test_size = 256
```
當我們定義以下參數時,該值為`28`,因為 MNIST 圖像的高度和寬度為`28`像素:
```py
img_size = 28
```
對于類的數量,值`10`意味著我們將為每個 0 到 9 位數設置一個類:
```py
num_classes = 10
```
為輸入圖像定義占位符變量`X`。該張量的數據類型設置為`float32`,形狀設置為`[None, img_size, img_size, 1]`,其中`None`表示張量可以保存任意數量的圖像:
```py
X = tf.placeholder("float", [None, img_size, img_size, 1])
```
然后我們為占位符變量`X`中的輸入圖像正確關聯的標簽設置另一個占位符變量`Y`。此占位符變量的形狀為`[None, num_classes]`,這意味著它可以包含任意數量的標簽。每個標簽都是長度為`num_classes`的向量,在這種情況下為`10`:
```py
Y = tf.placeholder("float", [None, num_classes])
```
我們收集`MNIST`數據,這些數據將被復制到數據文件夾中:
```py
mnist = input_data.read_data_sets("MNIST-data", one_hot=True)
```
我們構建訓練數據集(`trX`,`trY`)和測試網絡(`teX`,`teY)`:
```py
trX, trY, teX, teY = mnist.train.images, \
mnist.train.labels, \
mnist.test.images, \
mnist.test.labels
```
必須重新整形`trX`和`teX`圖像集以匹配輸入形狀:
```py
trX = trX.reshape(-1, img_size, img_size, 1)
teX = teX.reshape(-1, img_size, img_size, 1)
```
我們現在開始定義網絡的`weights`。
`init_weights`函數在提供的形狀中構建新變量,并使用隨機值初始化網絡權重:
```py
def init_weights(shape):
return tf.Variable(tf.random_normal(shape, stddev=0.01))
```
第一卷積層的每個神經元被卷積為輸入張量的小子集,尺寸為`3×3×1`。值`32`只是我們為第一層考慮的特征圖的數量。然后定義權重`w`:
```py
w = init_weights([3, 3, 1, 32])
```
然后輸入的數量增加到`32`,這意味著第二卷積層中的每個神經元被卷積到第一卷積層的`3×3×32`個神經元。`w2`權重如下:
```py
w2 = init_weights([3, 3, 32, 64])
```
值`64`表示獲得的輸出特征的數量。第三個卷積層被卷積為前一層的`3x3x64`個神經元,而`128`是結果特征。
```py
w3 = init_weights([3, 3, 64, 128])
```
第四層完全連接并接收`128x4x4`輸入,而輸出等于`625`:
```py
w4 = init_weights([128 * 4 * 4, 625])
```
輸出層接收`625`輸入,輸出是類的數量:
```py
w_o = init_weights([625, num_classes])
```
請注意,這些初始化實際上并未在此時完成。它們僅在 TensorFlow 圖中定義。
```py
p_keep_conv = tf.placeholder("float")
p_keep_hidden = tf.placeholder("float")
```
是時候定義網絡模型了。就像網絡的權重定義一樣,它將是一個函數。它接收`X`張量,權重張量和丟棄參數作為卷積和完全連接層的輸入:
```py
def model(X, w, w2, w3, w4, w_o, p_keep_conv, p_keep_hidden):
```
`tf.nn.conv2d()`執行 TensorFlow 操作進行卷積。請注意,所有尺寸的`strides`都設置為 1。實際上,第一步和最后一步必須始終為 1,因為第一步是圖像編號,最后一步是輸入通道。`padding`參數設置為`'SAME'`,這意味著輸入圖像用零填充,因此輸出的大小相同:
```py
conv1 = tf.nn.conv2d(X, w,strides=[1, 1, 1, 1],\
padding='SAME')
```
然后我們將`conv1`層傳遞給 ReLU 層。它為每個輸入像素`x`計算`max(x, 0)`函數,為公式添加一些非線性,并允許我們學習更復雜的函數:
```py
conv1_a = tf.nn.relu(conv1)
```
然后由`tf.nn.max_pool`運算符合并生成的層:
```py
conv1 = tf.nn.max_pool(conv1_a, ksize=[1, 2, 2, 1]\
,strides=[1, 2, 2, 1],\
padding='SAME')
```
這是一個`2×2`最大池,這意味著我們正在檢查`2×2`窗口并在每個窗口中選擇最大值。然后我們將 2 個像素移動到下一個窗口。我們嘗試通過`tf.nn.dropout()`函數減少過擬合,我們傳遞`conv1`層和`p_keep_conv`概率值:
```py
conv1 = tf.nn.dropout(conv1, p_keep_conv)
```
如您所見,接下來的兩個卷積層`conv2`和`conv3`的定義方式與`conv1`相同:
```py
conv2 = tf.nn.conv2d(conv1, w2,\
strides=[1, 1, 1, 1],\
padding='SAME')
conv2_a = tf.nn.relu(conv2)
conv2 = tf.nn.max_pool(conv2, ksize=[1, 2, 2, 1],\
strides=[1, 2, 2, 1],\
padding='SAME')
conv2 = tf.nn.dropout(conv2, p_keep_conv)
conv3=tf.nn.conv2d(conv2, w3,\
strides=[1, 1, 1, 1]\
,padding='SAME')
conv3 = tf.nn.relu(conv3)
```
完全連接的層將添加到網絡中。第一個`FC_layer`的輸入是前一個卷積的卷積層:
```py
FC_layer = tf.nn.max_pool(conv3, ksize=[1, 2, 2, 1],\
strides=[1, 2, 2, 1],\
padding='SAME')
FC_layer = tf.reshape(FC_layer,\
[-1, w4.get_shape().as_list()[0]])
```
`dropout`函數再次用于減少過擬合:
```py
FC_layer = tf.nn.dropout(FC_layer, p_keep_conv)
```
輸出層接收`FC_layer`和`w4`權重張量作為輸入。應用 ReLU 和丟棄運算符:
```py
output_layer = tf.nn.relu(tf.matmul(FC_layer, w4))
output_layer = tf.nn.dropout(output_layer, p_keep_hidden)
```
結果是一個長度為 10 的向量。這用于確定圖像所屬的 10 個輸入類中的哪一個:
```py
result = tf.matmul(output_layer, w_o)
return result
```
交叉熵是我們在此分類器中使用的表現指標。交叉熵是一個連續的函數,它總是正的,如果預測的輸出與期望的輸出完全匹配,則等于零。因此,這種優化的目標是通過改變網絡層中的變量來最小化交叉熵,使其盡可能接近零。 TensorFlow 具有用于計算交叉熵的內置函數。請注意,該函數在內部計算 softmax,因此我們必須直接使用`py_x`的輸出:
```py
py_x = model(X, w, w2, w3, w4, w_o, p_keep_conv, p_keep_hidden)
Y_ = tf.nn.softmax_cross_entropy_with_logits_v2\
(labels=Y,logits=py_x)
```
現在我們已經為每個分類圖像定義了交叉熵,我們可以衡量模型在每個圖像上的表現。我們需要一個標量值來使用交叉熵來優化網絡變量,因此我們只需要對所有分類圖像求平均交叉熵:
```py
cost = tf.reduce_mean(Y_)
```
為了最小化評估的`cost`,我們必須定義一個優化器。在這種情況下,我們將使用`RMSPropOptimizer`,它是 GD 的高級形式。`RMSPropOptimizer`實現了 RMSProp 算法,這是一種未發表的自適應學習率方法,由 Geoff Hinton 在他的 [Coursera 課程的第 6 講](http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf)中提出。
### 注意
您可以在[此鏈接](https://www.coursera.org/learn/neural-networks)找到 Geoff Hinton 的課程。
`RMSPropOptimizer`還將學習率除以梯度平方的指數衰減均值。 Hinton 建議將衰減參數設置為`0.9`,而學習率的良好默認值為`0.001`:
```py
optimizer = tf.train.RMSPropOptimizer(0.001, 0.9).minimize(cost)
```
基本上,通用 SGD 算法存在一個問題,即學習率必須以`1 / T`(其中`T`是迭代次數)進行縮放以實現收斂。 RMSProp 嘗試通過自動調整步長來解決這個問題,以使步長與梯度相同。隨著平均梯度變小,SGD 更新中的系數變得更大以進行補償。
### 注意
[有關此算法的有趣參考可在此處找到](http://www.cs.toronto.edu/%7Etijmen/csc321/slides/lecture_slides_lec6.pdf)。
最后,我們定義`predict_op`,它是模式輸出中尺寸最大值的索引:
```py
predict_op = tf.argmax(py_x, 1)
```
請注意,此時不執行優化。什么都沒有計算,因為我們只是將優化器對象添加到 TensorFlow 圖中以便以后執行。
我們現在來定義網絡的運行會話。訓練集中有 55,000 個圖像,因此使用所有這些圖像計算模型的梯度需要很長時間。因此,我們將在優化器的每次迭代中使用一小批圖像。如果您的計算機崩潰或由于 RAM 耗盡而變得非常慢,那么您可以減少此數量,但您可能需要執行更多優化迭代。
現在我們可以繼續實現 TensorFlow 會話:
```py
with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(100):
```
我們得到了一批訓練樣例,`training_batch`張量現在包含圖像的子集和相應的標簽:
```py
training_batch = zip(range(0, len(trX), batch_size),\
range(batch_size, \
len(trX)+1, \
batch_size))
```
將批量放入`feed_dict`中,并在圖中為占位符變量指定相應的名稱。我們現在可以使用這批訓練數據運行優化器。 TensorFlow 將饋送中的變量分配給占位符變量,然后運行優化程序:
```py
for start, end in training_batch:
sess.run(optimizer, feed_dict={X: trX[start:end],\
Y: trY[start:end],\
p_keep_conv: 0.8,\
p_keep_hidden: 0.5})
```
同時,我們得到了打亂的一批測試樣本:
```py
test_indices = np.arange(len(teX))
np.random.shuffle(test_indices)
test_indices = test_indices[0:test_size]
```
對于每次迭代,我們顯示批次的評估`accuracy`:
```py
print(i, np.mean(np.argmax(teY[test_indices], axis=1) ==\
sess.run\
(predict_op,\
feed_dict={X: teX[test_indices],\
Y: teY[test_indices], \
p_keep_conv: 1.0,\
p_keep_hidden: 1.0})))
```
根據所使用的計算資源,訓練網絡可能需要幾個小時。我機器上的結果如下:
```py
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Successfully extracted to train-images-idx3-ubyte.mnist 9912422 bytes.
Loading ata/train-images-idx3-ubyte.mnist
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
Successfully extracted to train-labels-idx1-ubyte.mnist 28881 bytes.
Loading ata/train-labels-idx1-ubyte.mnist
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Successfully extracted to t10k-images-idx3-ubyte.mnist 1648877 bytes.
Loading ata/t10k-images-idx3-ubyte.mnist
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Successfully extracted to t10k-labels-idx1-ubyte.mnist 4542 bytes.
Loading ata/t10k-labels-idx1-ubyte.mnist
(0, 0.95703125)
(1, 0.98046875)
(2, 0.9921875)
(3, 0.99609375)
(4, 0.99609375)
(5, 0.98828125)
(6, 0.99609375)
(7, 0.99609375)
(8, 0.98828125)
(9, 0.98046875)
(10, 0.99609375)
.
.
.
..
.
(90, 1.0)
(91, 0.9921875)
(92, 0.9921875)
(93, 0.99609375)
(94, 1.0)
(95, 0.98828125)
(96, 0.98828125)
(97, 0.99609375)
(98, 1.0)
(99, 0.99609375)
```
經過 10,000 次迭代后, 模型的準確率為 99.60%,這還不錯!
## AlexNet
AlexNet 神經網絡是首批實現巨大成功的 CNN 之一。作為 2012 年 ILSVRC 的獲勝者,這個神經網絡是第一個使用 LeNet-5 網絡之前定義的神經網絡的標準結構在 ImageNet 等非常復雜的數據集上獲得良好結果。
### 注意
ImageNet 項目是一個大型視覺數據庫,設計用于視覺對象識別軟件研究。截至 2016 年,ImageNet 手工標注了超過一千萬個圖像 URL,以指示圖像中的對象。在至少一百萬個圖像中,還提供了邊界框。第三方圖像 URL 的標注數據庫可直接從 ImageNet 免費獲得。
AlexNet 的架構如下圖所示:

圖 9:AlexNet 網絡
在 AlexNet 架構中,有八層具有可訓練參數:一系列五個連續卷積層,后面是三個完全連接的層。每個卷積層之后是 ReLU 層,并且可選地還有最大池層,尤其是在網絡的開始處,以便減少網絡占用的空間量。
所有池層都有`3x3`擴展區域,步長率為 2:這意味著您始終使用重疊池。這是因為與沒有重疊的普通池相比,這種類型的池提供了稍好的網絡表現。在網絡的開始,在池化層和下一個卷積層之間,總是使用幾個 LRN 標準化層:經過一些測試,可以看出它們傾向于減少網絡誤差。
前兩個完全連接的層擁有 4,096 個神經元,而最后一個擁有 1,000 個單元,對應于 ImageNet 數據集中的類數。考慮到完全連接層中的大量連接,在每對完全連接的層之間添加了比率為 0.5 的丟棄層,即,每次忽略一半的神經元激活。在這種情況下已經注意到,使用丟棄技術不僅加速了單次迭代的處理,而且還很好地防止了過擬合。沒有丟棄層,網絡制造商聲稱原始網絡過擬合。
## 遷移學習
遷移學習包括建立已經構建的網絡,并對各個層的參數進行適當的更改,以便它可以適應另一個數據集。例如,您可以在大型數據集(如 ImageNet)上使用預先測試的網絡,并在較小的數據集上再次訓練它。如果我們的數據集在內容上與原始數據集沒有明顯不同,那么預先訓練的模型已經具有與我們自己的分類問題相關的學習特征。
如果我們的數據集與預訓練模型訓練的數據集沒有太大差異,我們可以使用微調技術。 已在大型不同數據集上進行預訓練的模型可能會捕捉到早期層中的曲線和邊緣等通用特征,這些特征在大多數分類問題中都是相關且有用的。但是,如果我們的數據集來自一個非常特定的域,并且找不到該域中預先訓練好的網絡,我們應該考慮從頭開始訓練網絡。
## 預訓練的 AlexNet
我們會對預先訓練好的 AlexNet 進行微調,以區分狗和貓。 AlexNet 在 ImageNet 數據集上經過預先訓練。
要執行這個例子,你還需要安裝 scipy(參見[此鏈接](https://www.scipy.org/install.html))和 PIL(Pillow),這是 scipy 使用的讀取圖像:`pip install Pillow`或`pip3 install Pillow`。
然后,您需要下載以下文件:
* `myalexnet_forward.py`:2017 版 TensorFlow 的 AlexNet 實現和測試代碼(Python 3.5)
* `bvlc_alexnet.npy`:權重,需要在工作目錄中
* `caffe_classes.py`:類,與網絡輸出的順序相同
* `poodle.png`,`laska.png`,`dog.png`,`dog2.png`,`quail227.JPEG`:測試圖像(圖像應為`227×227×3`)
[從此鏈接下載這些文件](http://www.cs.toronto.edu/~guerzhoy/tf_alexnet/),或從本書的代碼庫中下載。
首先,我們將在之前下載的圖像上測試網絡。為此,只需從 Python GUI 運行`myalexnet_forward.py`即可。
通過簡單地檢查源代碼可以看到(參見下面的代碼片段),將調用預先訓練好的網絡對以下兩個圖像進行分類,`laska.png`和`poodle.png`,這些圖像之前已下載過:
```py
im1 = (imread("laska.png")[:,:,:3]).astype(float32)
im1 = im1 - mean(im1)
im1[:, :, 0], im1[:, :, 2] = im1[:, :, 2], im1[:, :, 0]
im2 = (imread("poodle.png")[:,:,:3]).astype(float32)
im2[:, :, 0], im2[:, :, 2] = im2[:, :, 2], im2[:, :, 0]
```

圖 10:要分類的圖像
的權重和偏置由以下語句加載:
```py
net_data = load(open("bvlc_alexnet.npy", "rb"), encoding="latin1").item()
```
網絡是一組卷積和池化層,后面是三個完全連接的狀態。該模型的輸出是 softmax 函數:
```py
prob = tf.nn.softmax(fc8)
```
softmax 函數的輸出是分類等級,因為它們表示網絡認為輸入圖像屬于`caffe_classes.py`文件中定義的類的強度。
如果我們運行代碼,我們應該得到以下結果:
```py
Image 0
weasel 0.503177
black-footed ferret, ferret, Mustela nigripes 0.263265
polecat, fitch, foulmart, foumart, Mustela putorius 0.147746
mink 0.0649517
otter 0.00771955
Image 1
clumber, clumber spaniel 0.258953
komondor 0.165846
miniature poodle 0.149518
toy poodle 0.0984719
kuvasz 0.0848062
0.40007972717285156
>>>
```
在前面的例子中,AlexNet 給鼬鼠的分數約為 50%。這意味著該模型非常有信心圖像顯示黃鼠狼,其余分數可視為噪音。
# 數據集準備
我們的任務是建立一個區分狗和貓的圖像分類器。我們從 Kaggle 那里得到一些幫助,[我們可以從中輕松下載數據集](https://www.kaggle.com/c/dogs-vs-cats/data)。
在此數據集中,訓練集包含 20,000 個標記圖像,測試和驗證集包含 2,500 個圖像。
要使用數據集,必須將每個圖像重新整形為`227×227×3`。為此,您可以使用`prep_images.py`中的 Python 代碼。否則,您可以使用本書倉庫中的`trainDir.rar`和`testDir.rar`文件。它們包含 6,000 個用于訓練的犬和貓的重塑圖像,以及 100 個重新成形的圖像用于測試。
以下部分中描述的以下微調實現在`alexnet_finetune.py`中實現,可以在本書的代碼庫中下載。
# 微調的實現
我們的分類任務包含兩個類別,因此網絡的新 softmax 層將包含 2 個類別而不是 1,000 個類別。這是輸入張量,它是一個`227×227×3`圖像,以及等級 2 的輸出張量:
```py
n_classes = 2
train_x = zeros((1, 227,227,3)).astype(float32)
train_y = zeros((1, n_classes))
```
微調實現包括截斷預訓練網絡的最后一層(softmax 層),并將其替換為與我們的問題相關的新 softmax 層。
例如,ImageNet 上預先訓練好的網絡帶有一個包含 1,000 個類別的 softmax 層。
以下代碼片段定義了新的 softmax 層`fc8`:
```py
fc8W = tf.Variable(tf.random_normal\
([4096, n_classes]),\
trainable=True, name="fc8w")
fc8b = tf.Variable(tf.random_normal\
([n_classes]),\
trainable=True, name="fc8b")
fc8 = tf.nn.xw_plus_b(fc7, fc8W, fc8b)
prob = tf.nn.softmax(fc8)
```
損失是用于分類的表現指標。它是一個始終為正的連續函數,如果模型的預測輸出與期望的輸出完全匹配,則交叉熵等于零。因此,優化的目標是通過改變模型的權重和偏置來最小化交叉熵,因此它盡可能接近零。
TensorFlow 具有用于計算交叉熵的內置函數。為了使用交叉熵來優化模型的變量,我們需要一個標量值,因此我們只需要對所有圖像分類采用交叉熵的平均值:
```py
loss = tf.reduce_mean\
(tf.nn.softmax_cross_entropy_with_logits_v2\
(logits =prob, labels=y))
opt_vars = [v for v in tf.trainable_variables()\
if (v.name.startswith("fc8"))]
```
既然我們必須最小化成本度量,那么我們可以創建`optimizer`:
```py
optimizer = tf.train.AdamOptimizer\
(learning_rate=learning_rate).minimize\
(loss, var_list = opt_vars)
correct_pred = tf.equal(tf.argmax(prob, 1), tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
```
在這種情況下,我們使用步長為`0.5`的`AdamOptimizer`。請注意,此時不執行優化。事實上,根本沒有計算任何東西,我們只需將優化器對象添加到 TensorFlow 圖中以便以后執行。然后我們在網絡上運行反向傳播以微調預訓練的權重:
```py
batch_size = 100
training_iters = 6000
display_step = 1
dropout = 0.85 # Dropout, probability to keep units
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
step = 1
```
繼續訓練,直到達到最大迭代次數:
```py
while step * batch_size < training_iters:
batch_x, batch_y = \
next(next_batch(batch_size)) #.next()
```
運行優化操作(反向傳播):
```py
sess.run(optimizer, \
feed_dict={x: batch_x, \
y: batch_y, \
keep_prob: dropout})
if step % display_step == 0:
```
計算批次損失和準確率:
```py
cost, acc = sess.run([loss, accuracy],\
feed_dict={x: batch_x, \
y: batch_y, \
keep_prob: 1.})
print ("Iter " + str(step*batch_size) \
+ ", Minibatch Loss= " + \
"{:.6f}".format(cost) + \
", Training Accuracy= " + \
"{:.5f}".format(acc))
step += 1
print ("Optimization Finished!")
```
網絡訓練產生以下結果:
```py
Iter 100, Minibatch Loss= 0.555294, Training Accuracy= 0.76000
Iter 200, Minibatch Loss= 0.584999, Training Accuracy= 0.73000
Iter 300, Minibatch Loss= 0.582527, Training Accuracy= 0.73000
Iter 400, Minibatch Loss= 0.610702, Training Accuracy= 0.70000
Iter 500, Minibatch Loss= 0.583640, Training Accuracy= 0.73000
Iter 600, Minibatch Loss= 0.583523, Training Accuracy= 0.73000
…………………………………………………………………
…………………………………………………………………
Iter 5400, Minibatch Loss= 0.361158, Training Accuracy= 0.95000
Iter 5500, Minibatch Loss= 0.403371, Training Accuracy= 0.91000
Iter 5600, Minibatch Loss= 0.404287, Training Accuracy= 0.91000
Iter 5700, Minibatch Loss= 0.413305, Training Accuracy= 0.90000
Iter 5800, Minibatch Loss= 0.413816, Training Accuracy= 0.89000
Iter 5900, Minibatch Loss= 0.413476, Training Accuracy= 0.90000
Optimization Finished!
```
要測試我們的模型,我們將預測與標簽集(`cat = 0`,`dog = 1`)進行比較:
```py
output = sess.run(prob, feed_dict = {x:imlist, keep_prob: 1.})
result = np.argmax(output,1)
testResult = [1,1,1,1,0,0,0,0,0,0,\
0,1,0,0,0,0,1,1,0,0,\
1,0,1,1,0,1,1,0,0,1,\
1,1,1,0,0,0,0,0,1,0,\
1,1,1,1,0,1,0,1,1,0,\
1,0,0,1,0,0,1,1,1,0,\
1,1,1,1,1,0,0,0,0,0,\
0,1,1,1,0,1,1,1,1,0,\
0,0,1,0,1,1,1,1,0,0,\
0,0,0,1,1,0,1,1,0,0]
count = 0
for i in range(0,99):
if result[i] == testResult[i]:
count=count+1
print("Testing Accuracy = " + str(count) +"%")
```
最后,我們有我們模型的準確率:
```py
Testing Accuracy = 82%
```
## VGG
VGG 是在 2014 年 ILSVRC 期間發明神經網絡的人的名字。我們談論的是復數網絡,因為創建了同一網絡的多個版本,每個擁有不同數量的層。根據層數`n`,這些網絡中的一個具有的權重,它們中的每一個通常稱為 VGG-n。所有這些網絡都比 AlexNet 更深。這意味著它們由多個層組成,其參數比 AlexNet 更多,在這種情況下,總共有 11 到 19 個訓練層。通常,只考慮可行的層,因為它們會影響模型的處理和大小,如前一段所示。然而,整體結構仍然非常相似:總是有一系列初始卷積層和最后一系列完全連接的層,后者與 AlexNet 完全相同。因此,使用的卷積層的數量,當然還有它們的參數有什么變化。下表顯示了 VGG 團隊構建的所有變體。
每一列,從左側開始,向右側,顯示一個特定的 VGG 網絡,從最深到最淺。粗體項表示與先前版本相比,每個版本中添加的內容。 ReLU 層未在表中顯示,但在網絡中它存在于每個卷積層之后。所有卷積層使用 1 的步幅:

表:VGG 網絡架構
請注意, AlexNet 沒有具有相當大的感受野的卷積層:這里,所有感受野都是`3×3`,除了 VGG-16 中有幾個具有`1×1`感受野的卷積層。回想一下,具有 1 步梯度的凸層不會改變輸入空間大小,同時修改深度值,該深度值與使用的內核數量相同。因此,VGG 卷積層不會影響輸入體積的寬度和高度;只有池化層才能這樣做。使用具有較小感受野的一系列卷積層的想法最終總體上模擬具有較大感受野的單個卷積層,這是由于這樣的事實,即以這種方式使用多個 ReLU 層而不是單獨使用一個,從而增加激活函數的非線性,從而使其更具區別性。它還用于減少使用的參數數量。這些網絡被認為是 AlexNet 的演變,因為總體而言,使用相同的數據集,它們的表現優于 AlexNet。 VGG 網絡演示的主要概念是擁塞神經網絡越來越深刻,其表現也越來越高。但是, 必須擁有越來越強大的硬件,否則網絡訓練就會成為問題。
對于 VGG,使用了四個 NVIDIA Titan Blacks,每個都有 6 GB 的內存。因此,VGG 具有更好的表現,但需要大量的硬件用于訓練,并且還使用大量參數:例如,VGG-19 模型大約為 550MB(是 AlexNet 的兩倍)。較小的 VGG 網絡仍然具有大約 507MB 的模型。
## 用 VGG-19 學習藝術風格
在這個項目中,我們使用預訓練的 VGG-19 來學習藝術家創建的樣式和模式,并將它們轉移到圖像中(項目文件是`style_transfer.py`,在本書的 GitHub 倉庫中)。這種技術被稱為`artistic style learning`([參見 Gatys 等人的文章 A Art Algorithm of Artistic Style](https://arxiv.org/pdf/1508.06576.pdf))。根據學術文獻,藝術風格學習定義如下:給定兩個圖像作為輸入,合成具有第一圖像的語義內容和第二圖像的紋理/風格的第三圖像。
為了使其正常工作,我們需要訓練一個深度卷積神經網絡來構建以下內容:
* 用于確定圖像 A 內容的內容提取器
* 用于確定圖像 B 樣式的樣式提取器
* 合并器將一些任意內容與另一個任意樣式合并,以獲得最終結果

圖 11:藝術風格學習操作模式
## 輸入圖像
輸入圖像,每個都是`478×478`像素,是您在本書的代碼庫中也可以找到的以下圖像(`cat.jpg`和`mosaic.jpg`):

圖 12:藝術風格學習中的輸入圖像
為了通過 VGG 模型分析 ,需要對這些圖像進行預處理:
1. 添加額外的維度
2. 從輸入圖像中減去`MEAN_VALUES`:
```py
MEAN_VALUES = np.array([123.68, 116.779, 103.939]).reshape((1,1,1,3))
content_image = preprocess('cat.jpg')
style_image = preprocess('mosaic.jpg')
def preprocess(path):
image = plt.imread(path)
image = image[np.newaxis]
image = image - MEAN_VALUES
return image
```
## 內容提取器和損失
為了隔離圖像的語義內容,我們使用預先訓練好的 VGG-19 神經網絡,對權重進行了一些微調,以適應這個問題,然后使用其中一個隱藏層的輸出作為內容提取器。下圖顯示了用于此問題的 CNN:

圖 13:用于藝術風格學習的 VGG-19
使用以下代碼加載預訓練的 VGG:
```py
import scipy.io
vgg = scipy.io.loadmat('imagenet-vgg-verydeep-19.mat')
```
`imagenet-vgg-verydeep-19.mat`模型應從[此鏈接](http://www.vlfeat.org/matconvnet/models/imagenet-vgg-verydeep-19.mat)下載。
該模型有 43 層,其中 19 層是卷積層。其余的是最大池/激活/完全連接的層。
我們可以檢查每個卷積層的形狀:
```py
[print (vgg_layers[0][i][0][0][2][0][0].shape,\
vgg_layers[0][i][0][0][0][0]) for i in range(43)
if 'conv' in vgg_layers[0][i][0][0][0][0] \
or 'fc' in vgg_layers[0][i][0][0][0][0]]
```
上述代碼的結果如下:
```py
(3, 3, 3, 64) conv1_1
(3, 3, 64, 64) conv1_2
(3, 3, 64, 128) conv2_1
(3, 3, 128, 128) conv2_2
(3, 3, 128, 256) conv3_1
(3, 3, 256, 256) conv3_2
(3, 3, 256, 256) conv3_3
(3, 3, 256, 256) conv3_4
(3, 3, 256, 512) conv4_1
(3, 3, 512, 512) conv4_2
(3, 3, 512, 512) conv4_3
(3, 3, 512, 512) conv4_4
(3, 3, 512, 512) conv5_1
(3, 3, 512, 512) conv5_2
(3, 3, 512, 512) conv5_3
(3, 3, 512, 512) conv5_4
(7, 7, 512, 4096) fc6
(1, 1, 4096, 4096) fc7
(1, 1, 4096, 1000) fc8
```
每種形狀以下列方式表示:`[kernel height, kernel width, number of input channels, number of output channels]`。
第一層有 3 個輸入通道,因為輸入是 RGB 圖像,而卷積層的輸出通道數從 64 到 512,所有內核都是`3x3`矩陣。
然后我們應用轉移學習技術,以使 VGG-19 網絡適應我們的問題:
1. 不需要完全連接的層,因為它們用于對象識別。
2. 最大池層代替平均池層,以獲得更好的結果。平均池層的工作方式與卷積層中的內核相同。
```py
IMAGE_WIDTH = 478
IMAGE_HEIGHT = 478
INPUT_CHANNELS = 3
model = {}
model['input'] = tf.Variable(np.zeros((1, IMAGE_HEIGHT,\
IMAGE_WIDTH,\
INPUT_CHANNELS)),\
dtype = 'float32')
model['conv1_1'] = conv2d_relu(model['input'], 0, 'conv1_1')
model['conv1_2'] = conv2d_relu(model['conv1_1'], 2, 'conv1_2')
model['avgpool1'] = avgpool(model['conv1_2'])
model['conv2_1'] = conv2d_relu(model['avgpool1'], 5, 'conv2_1')
model['conv2_2'] = conv2d_relu(model['conv2_1'], 7, 'conv2_2')
model['avgpool2'] = avgpool(model['conv2_2'])
model['conv3_1'] = conv2d_relu(model['avgpool2'], 10, 'conv3_1')
model['conv3_2'] = conv2d_relu(model['conv3_1'], 12, 'conv3_2')
model['conv3_3'] = conv2d_relu(model['conv3_2'], 14, 'conv3_3')
model['conv3_4'] = conv2d_relu(model['conv3_3'], 16, 'conv3_4')
model['avgpool3'] = avgpool(model['conv3_4'])
model['conv4_1'] = conv2d_relu(model['avgpool3'], 19,'conv4_1')
model['conv4_2'] = conv2d_relu(model['conv4_1'], 21, 'conv4_2')
model['conv4_3'] = conv2d_relu(model['conv4_2'], 23, 'conv4_3')
model['conv4_4'] = conv2d_relu(model['conv4_3'], 25,'conv4_4')
model['avgpool4'] = avgpool(model['conv4_4'])
model['conv5_1'] = conv2d_relu(model['avgpool4'], 28, 'conv5_1')
model['conv5_2'] = conv2d_relu(model['conv5_1'], 30, 'conv5_2')
model['conv5_3'] = conv2d_relu(model['conv5_2'], 32, 'conv5_3')
model['conv5_4'] = conv2d_relu(model['conv5_3'], 34, 'conv5_4')
model['avgpool5'] = avgpool(model['conv5_4'])
```
這里我們定義了`contentloss`函數來測量兩個圖像`p`和`x`之間的內容差異:
```py
def contentloss(p, x):
size = np.prod(p.shape[1:])
loss = (1./(2*size)) * tf.reduce_sum(tf.pow((x - p),2))
return loss
```
當輸入圖像在內容方面彼此非常接近并且隨著其內容偏離而增長時,該函數傾向于為 0。
我們將在`conv5_4`層上使用`contentloss`。這是輸出層,其輸出將是預測,因此我們需要使用`contentloss`函數將此預測與實際預測進行比較:
```py
content_loss = contentloss\
(sess.run(model['conv5_4']), model['conv5_4'])
```
最小化`content_loss`意味著混合圖像在給定層中具有與內容圖像的激活非常相似的特征激活。
## 樣式提取器和損失
樣式提取器使用過濾器的 Gram 矩陣作為給定的隱藏層。簡單來說,使用這個矩陣,我們可以破壞圖像的語義,保留其基本組件并使其成為一個好的紋理提取器:
```py
def gram_matrix(F, N, M):
Ft = tf.reshape(F, (M, N))
return tf.matmul(tf.transpose(Ft), Ft)
```
`style_loss`測量兩個圖像彼此之間的接近程度。此函數是樣式圖像和輸入`noise_image`生成的 Gram 矩陣元素的平方差的總和:
```py
noise_image = np.random.uniform\
(-20, 20,\
(1, IMAGE_HEIGHT, \
IMAGE_WIDTH,\
INPUT_CHANNELS)).astype('float32')
def style_loss(a, x):
N = a.shape[3]
M = a.shape[1] * a.shape[2]
A = gram_matrix(a, N, M)
G = gram_matrix(x, N, M)
result = (1/(4 * N**2 * M**2))* tf.reduce_sum(tf.pow(G-A,2))
return result
```
`style_loss`生長 ,因為它的兩個輸入圖像(`a`和`x`)傾向于偏離風格。
## 合并和總損失
我們可以合并內容和樣式損失,以便訓練輸入`noise_image`來輸出(在層中)與樣式圖像類似的樣式,其特征相似于內容圖像:
```py
alpha = 1
beta = 100
total_loss = alpha * content_loss + beta * styleloss
```
## 訓練
最小化網絡中的損失,以便樣式損失(輸出圖像的樣式和樣式圖像的樣式之間的損失),內容損失(內容圖像和輸出圖像之間的損失),以及總變異損失盡可能低:
```py
train_step = tf.train.AdamOptimizer(1.5).minimize(total_loss)
```
從這樣的網絡生成的輸出圖像應該類似于輸入圖像并且具有樣式圖像的造型師屬性。
最后,我們可以準備網絡進行訓練:
```py
sess.run(tf.global_variables_initializer())
sess.run(model['input'].assign(input_noise))
for it in range(2001):
sess.run(train_step)
if it%100 == 0:
mixed_image = sess.run(model['input'])
print('iteration:',it,'cost: ', sess.run(total_loss))
filename = 'out2/%d.png' % (it)
deprocess(filename, mixed_image)
```
訓練時間可能非常耗時,但結果可能非常有趣:
```py
iteration: 0 cost: 8.14037e+11
iteration: 100 cost: 1.65584e+10
iteration: 200 cost: 5.22747e+09
iteration: 300 cost: 2.72995e+09
iteration: 400 cost: 1.8309e+09
iteration: 500 cost: 1.36818e+09
iteration: 600 cost: 1.0804e+09
iteration: 700 cost: 8.83103e+08
iteration: 800 cost: 7.38783e+08
iteration: 900 cost: 6.28652e+08
iteration: 1000 cost: 5.41755e+08
```
經過 1000 次迭代后,我們創建了一個新的拼接:

圖 14:藝術風格學習中的輸出圖像
真是太棒了!你終于可以訓練你的神經網絡像畢加索一樣畫畫......玩得開心!
# Inception-v3
Szegedy 和其他人在 2014 年的論文“Going Deeper with Convolutions”中首次介紹了 Inception 微架構:

圖 15:GoogLeNet 中使用的 Original Inception 模塊
初始模塊的目標是通過在網絡的同一模塊內計算`1×1`,`3×3`和`5×5`卷積來充當多級特征提取器 - 這些濾波器的輸出然后,在被饋送到網絡中的下一層之前,沿著信道維度堆疊。這種架構的原始版本稱為 GoogLeNet,但后續形式簡稱為 InceptionVN,其中 N 表示 Google 推出的版本號。
您可能想知道為什么我們在同一輸入上使用不同類型的卷積。答案是,只要仔細研究了它的參數,就不可能總是獲得足夠的有用特征來用單個卷積進行精確分類。事實上,使用一些輸入它可以更好地使用卷積小內核,而其他輸入可以使用其他類型的內核獲得更好的結果。可能由于這個原因,GoogLeNet 團隊想要在他們自己的網絡中考慮一些替代方案。如前所述,為此目的,GoogLeNet 在同一網絡級別(即它們并行)使用三種類型的卷積層:`1×1`層,`3×3`層和`5×5`層。
這個 3 層并行局部結構的結果是它們所有輸出值的組合,鏈接到單個向量輸出,它將是下一層的輸入。這是通過使用連接層完成的。除了三個并行卷積層之外,在相同的本地結構中還添加了一個池化層,因為池化操作對于 CNN 的成功至關重要。
## 使用 TensorFlow 探索初始化
從[此鏈接](https://github.com/tensorflow/models),你應該能夠下載相應的模型庫。
然后鍵入以下命令:
```py
cd models/tutorials/image/imagenet python classify_image.py
```
當程序第一次運行時,`classify_image.py`從 [tensorflow.org](http://tensorflow.org) 下載經過訓練的模型。您的硬盤上需要大約 200MB 的可用空間。
上面的命令將對提供的熊貓圖像進行分類。如果模型正確運行,腳本將生成以下輸出:
```py
giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca (score = 0.88493)
indri, indris, Indri indri, Indri brevicaudatus (score = 0.00878)
lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens (score = 0.00317)
custard apple (score = 0.00149)
earthstar (score = 0.00127)
```
如果您想提供其他 JPEG 圖像,可以通過編輯來完成:
```py
image_file argument:
python classify_image.py --image=image.jpg
```
您可以通過從互聯網下載圖像并查看其產生的結果來測試初始階段。
例如,您可以嘗試從[此鏈接](https://pixabay.com/it/)獲取以下圖像(我們將其重命名為`inception_image.jpg`):

圖 16:使用 Inception-v3 進行分類的輸入圖像
結果如下:
```py
python classify_image.py --image=inception_example.jpg
strawberry (score = 0.91541)
crayfish, crawfish, crawdad, crawdaddy (score = 0.01208)
chocolate sauce, chocolate syrup (score = 0.00628)
cockroach, roach (score = 0.00572)
grocery store, grocery, food market, market (score = 0.00264)
```
聽起來不錯!
# CNN 的情感識別
深度學習中難以解決的一個問題與神經網絡無關:它是以正確格式獲取正確數據。但是,[Kaggle 平臺](https://www.kaggle.com/)提供了新的問題,并且需要研究新的數據集。
Kaggle 成立于 2010 年,作為預測建模和分析競賽的平臺,公司和研究人員發布他們的數據,來自世界各地的統計人員和數據挖掘者競爭生產最佳模型。在本節中,我們將展示如何使用面部圖像制作 CNN 以進行情感檢測。此示例的訓練和測試集可以從[此鏈接](https://inclass.kaggle.com/c/facial-keypoints-detector/data)下載。

圖 17:Kaggle 比賽頁面
訓練組由 3,761 個灰度圖像組成,尺寸為`48×48`像素,3,761 個標簽,每個圖像有 7 個元素。
每個元素編碼一個情感,0:憤怒,1:厭惡,2:恐懼,3:幸福,4:悲傷,5:驚訝,6:中立。
在經典 Kaggle 比賽中,必須由平臺評估從測試集獲得的標簽集。在這個例子中,我們將訓練一個來自訓練組的神經網絡,之后我們將在單個圖像上評估模型。
在開始 CNN 實現之前,我們將通過實現一個簡單的過程(文件`download_and_display_images.py`)來查看下載的數據。
導入庫:
```py
import tensorflow as tf
import numpy as np
from matplotlib import pyplot as plt
import EmotionUtils
```
`read_data`函數允許構建所有數據集,從下載的數據開始,您可以在本書的代碼庫中的`EmotionUtils`庫中找到它們:
```py
FLAGS = tf.flags.FLAGS
tf.flags.DEFINE_string("data_dir",\
"EmotionDetector/",\
"Path to data files")
images = []
images = EmotionUtils.read_data(FLAGS.data_dir)
train_images = images[0]
train_labels = images[1]
valid_images = images[2]
valid_labels = images[3]
test_images = images[4]
```
然后打印訓練的形狀并測試圖像:
```py
print ("train images shape = ",train_images.shape)
print ("test labels shape = ",test_images.shape)
```
顯示訓練組的第一個圖像及其正確的標簽:
```py
image_0 = train_images[0]
label_0 = train_labels[0]
print ("image_0 shape = ",image_0.shape)
print ("label set = ",label_0)
image_0 = np.resize(image_0,(48,48))
plt.imshow(image_0, cmap='Greys_r')
plt.show()
```
有 3,761 個`48×48`像素的灰度圖像:
```py
train images shape = (3761, 48, 48, 1)
```
有 3,761 個類標簽,每個類包含七個元素:
```py
train labels shape = (3761, 7)
```
測試集由 1,312 個`48x48`像素灰度圖像組成:
```py
test labels shape = (1312, 48, 48, 1)
```
單個圖像具有以下形狀:
```py
image_0 shape = (48, 48, 1)
```
第一張圖片的標簽設置如下:
```py
label set = [ 0\. 0\. 0\. 1\. 0\. 0\. 0.]
```
此標簽對應于快樂,圖像在以下 matplot 圖中可視化:

圖 18:來自情感檢測面部數據集的第一圖像
我們現在轉向 CNN 架構。
下圖顯示了數據將如何在 CNN 中流動:

圖 19:實現的 CNN 的前兩個卷積層
該網絡具有兩個卷積層,兩個完全連接的層,最后是 softmax 分類層。使用`5×5`卷積核在第一卷積層中處理輸入圖像(`48×48`像素)。這導致 32 個圖像,每個使用的濾波器一個。通過最大合并操作對圖像進行下采樣,以將圖像從`48×48`減小到`24×24`像素。然后,這些 32 個較小的圖像由第二卷積層處理;這導致 64 個新圖像(見上圖)。通過第二次池化操作,將得到的圖像再次下采樣到`12×12`像素。
第二合并層的輸出是`64×12×12`像素的圖像。然后將它們展平為長度為`12×12×64 = 9,126`的單個向量,其用作具有 256 個神經元的完全連接層的輸入。這將進入另一個具有 10 個神經元的完全連接的層,每個類對應一個類,用于確定圖像的類別,即圖像中描繪的情感。

圖 20:實現的 CNN 的最后兩層
讓我們繼續討論權重和偏置定義。以下數據結構表示網絡權重的定義,并總結了到目前為止我們所描述的內容:
```py
weights = {
'wc1': weight_variable([5, 5, 1, 32], name="W_conv1"),
'wc2': weight_variable([3, 3, 32, 64],name="W_conv2"),
'wf1': weight_variable([(IMAGE_SIZE // 4) * (IMAGE_SIZE // 4)
\* 64,256],name="W_fc1"),
'wf2': weight_variable([256, NUM_LABELS], name="W_fc2")
}
```
注意卷積濾波器是隨機初始化的,所以分類是隨機完成的:
```py
def weight_variable(shape, stddev=0.02, name=None):
initial = tf.truncated_normal(shape, stddev=stddev)
if name is None:
return tf.Variable(initial)
else:
return tf.get_variable(name, initializer=initial)
```
以相似方式,我們已經定義了偏差:
```py
biases = {
'bc1': bias_variable([32], name="b_conv1"),
'bc2': bias_variable([64], name="b_conv2"),
'bf1': bias_variable([256], name="b_fc1"),
'bf2': bias_variable([NUM_LABELS], name="b_fc2")
}
def bias_variable(shape, name=None):
initial = tf.constant(0.0, shape=shape)
if name is None:
return tf.Variable(initial)
else:
return tf.get_variable(name, initializer=initial)
```
優化器必須使用區分鏈規則通過 CNN 傳播誤差,并更新過濾器權重以改善分類誤差。輸入圖像的預測類和真實類之間的差異由`loss`函數測量。它將`pred`模型的預測輸出和所需輸出`label`作為輸入:
```py
def loss(pred, label):
cross_entropy_loss =\
tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2\
(logits=pred, labels=label))
tf.summary.scalar('Entropy', cross_entropy_loss)
reg_losses = tf.add_n(tf.get_collection("losses"))
tf.summary.scalar('Reg_loss', reg_losses)
return cross_entropy_loss + REGULARIZATION * reg_losses
```
`tf.nn.softmax_cross_entropy_with_logits_v2(pred, label)`函數在應用 softmax 函數后計算結果的`cross_entropy_loss`(但它以數學上仔細的方式一起完成)。這就像是以下結果:
```py
a = tf.nn.softmax(x)
b = cross_entropy(a)
```
我們計算每個分類圖像的`cross_entropy_loss`,因此我們將測量模型在每個圖像上的單獨表現。
我們計算分類圖像的交叉熵平均值:
```py
cross_entropy_loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2 (logits=pred, labels=label))
```
為了防止過擬合,我們將使用 L2 正則化,其中包括向`cross_entropy_loss`插入一個附加項:
```py
reg_losses = tf.add_n(tf.get_collection("losses"))
return cross_entropy_loss + REGULARIZATION * reg_losses
```
哪里:
```py
def add_to_regularization_loss(W, b):
tf.add_to_collection("losses", tf.nn.l2_loss(W))
tf.add_to_collection("losses", tf.nn.l2_loss(b))
```
### 注意
有關詳細信息,請參閱[此鏈接](http://www.kdnuggets.com/2015/04/preventing-overfitting-neural-networks.html/2)。
我們已經構建了網絡的權重和偏置以及優化過程。但是,與所有已實現的網絡一樣,我們必須通過導入所有必需的庫來啟動實現:
```py
import tensorflow as tf
import numpy as np
from datetime import datetime
import EmotionUtils
import os, sys, inspect
from tensorflow.python.framework import ops
import warnings
warnings.filterwarnings("ignore")
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
ops.reset_default_graph()
```
然后,我們在您的計算機上設置存儲數據集的路徑,以及網絡參數:
```py
FLAGS = tf.flags.FLAGS
tf.flags.DEFINE_string("data_dir",\
"EmotionDetector/",\
"Path to data files")
tf.flags.DEFINE_string("logs_dir",\
"logs/EmotionDetector_logs/",\
"Path to where log files are to be saved")
tf.flags.DEFINE_string("mode",\
"train",\
"mode: train (Default)/ test")
BATCH_SIZE = 128
LEARNING_RATE = 1e-3
MAX_ITERATIONS = 1001
REGULARIZATION = 1e-2
IMAGE_SIZE = 48
NUM_LABELS = 7
VALIDATION_PERCENT = 0.1
```
`emotion_cnn`函數實現我們的模型:
```py
def emotion_cnn(dataset):
with tf.name_scope("conv1") as scope:
tf.summary.histogram("W_conv1", weights['wc1'])
tf.summary.histogram("b_conv1", biases['bc1'])
conv_1 = tf.nn.conv2d(dataset, weights['wc1'],\
strides=[1, 1, 1, 1],\
padding="SAME")
h_conv1 = tf.nn.bias_add(conv_1, biases['bc1'])
h_1 = tf.nn.relu(h_conv1)
h_pool1 = max_pool_2x2(h_1)
add_to_regularization_loss(weights['wc1'], biases['bc1'])
with tf.name_scope("conv2") as scope:
tf.summary.histogram("W_conv2", weights['wc2'])
tf.summary.histogram("b_conv2", biases['bc2'])
conv_2 = tf.nn.conv2d(h_pool1, weights['wc2'],\
strides=[1, 1, 1, 1], \
padding="SAME")
h_conv2 = tf.nn.bias_add(conv_2, biases['bc2'])
h_2 = tf.nn.relu(h_conv2)
h_pool2 = max_pool_2x2(h_2)
add_to_regularization_loss(weights['wc2'], biases['bc2'])
with tf.name_scope("fc_1") as scope:
prob=0.5
image_size = IMAGE_SIZE // 4
h_flat = tf.reshape(h_pool2,[-1,image_size*image_size*64])
tf.summary.histogram("W_fc1", weights['wf1'])
tf.summary.histogram("b_fc1", biases['bf1'])
h_fc1 = tf.nn.relu(tf.matmul\
(h_flat, weights['wf1']) + biases['bf1'])
h_fc1_dropout = tf.nn.dropout(h_fc1, prob)
with tf.name_scope("fc_2") as scope:
tf.summary.histogram("W_fc2", weights['wf2'])
tf.summary.histogram("b_fc2", biases['bf2'])
pred = tf.matmul(h_fc1_dropout, weights['wf2']) +\
biases['bf2']
return pred
```
然后定義一個`main`函數,我們將在其中定義數據集,輸入和輸出占位符變量以及主會話,以便啟動訓練過程:
```py
def main(argv=None):
```
此函數中的第一個操作是加載數據集以進行訓練和驗證。我們將使用訓練集來教授分類器識別待預測的標簽,我們將使用驗證集來評估分類器的表現:
```py
train_images,\
train_labels,\
valid_images,\
valid_labels,\ test_images=EmotionUtils.read_data(FLAGS.data_dir)
print("Train size: %s" % train_images.shape[0])
print('Validation size: %s' % valid_images.shape[0])
print("Test size: %s" % test_images.shape[0])
```
我們為輸入圖像定義占位符變量。這允許我們更改輸入到 TensorFlow 圖的圖像。數據類型設置為`float32`,形狀設置為`[None, img_size, img_size, 1]`(其中`None`表示張量可以保存任意數量的圖像,每個圖像為`img_size`像素高和`img_size`像素寬),和`1`是顏色通道的數量:
```py
input_dataset = tf.placeholder(tf.float32, \
[None, \
IMAGE_SIZE, \
IMAGE_SIZE, 1],name="input")
```
接下來,我們為與占位符變量`input_dataset`中輸入的圖像正確關聯的標簽提供占位符變量。這個占位符變量的形狀是`[None, NUM_LABELS]`,這意味著它可以包含任意數量的標簽,每個標簽是長度為`NUM_LABELS`的向量,在這種情況下為 7:
```py
input_labels = tf.placeholder(tf.float32,\
[None, NUM_LABELS])
```
`global_step`保持跟蹤到目前為止執行的優化迭代數量。我們希望在檢查點中使用所有其他 TensorFlow 變量保存此變量。請注意`trainable=False`,這意味著 TensorFlow 不會嘗試優化此變量:
```py
global_step = tf.Variable(0, trainable=False)
```
跟隨變量`dropout_prob`,用于丟棄優化:
```py
dropout_prob = tf.placeholder(tf.float32)
```
現在為測試階段創建神經網絡。`emotion_cnn()`函數返回`input_dataset`的預測類標簽`pred`:
```py
pred = emotion_cnn(input_dataset)
```
`output_pred`是測試和驗證的預測,我們將在運行會話中計算:
```py
output_pred = tf.nn.softmax(pred,name="output")
```
`loss_val`包含輸入圖像的預測類(`pred`)與實際類別(`input_labels`)之間的差異:
```py
loss_val = loss(pred, input_labels)
```
`train_op`定義用于最小化成本函數的優化器。在這種情況下,我們再次使用`AdamOptimizer`:
```py
train_op = tf.train.AdamOptimizer\
(LEARNING_RATE).minimize\
(loss_val, global_step)
```
`summary_op`是用于 TensorBoard 可視化的 :
```py
summary_op = tf.summary.merge_all()
```
創建圖后,我們必須創建一個 TensorFlow 會話,用于執行圖:
```py
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
summary_writer = tf.summary.FileWriter(FLAGS.logs_dir, sess.graph)
```
我們定義`saver`來恢復模型:
```py
saver = tf.train.Saver()
ckpt = tf.train.get_checkpoint_state(FLAGS.logs_dir)
if ckpt and ckpt.model_checkpoint_path:
saver.restore(sess, ckpt.model_checkpoint_path)
print ("Model Restored!")
```
接下來我們需要獲得一批訓練示例。`batch_image`現在擁有一批圖像,`batch_label`包含這些圖像的正確標簽:
```py
for step in xrange(MAX_ITERATIONS):
batch_image, batch_label = get_next_batch(train_images,\
train_labels,\
step)
```
我們將批次放入`dict`中,其中包含 TensorFlow 圖中占位符變量的正確名稱:
```py
feed_dict = {input_dataset: batch_image, \
input_labels: batch_label}
```
我們使用這批訓練數據運行優化器。 TensorFlow 將`feed_dict_train`中的變量分配給占位符變量,然后運行優化程序:
```py
sess.run(train_op, feed_dict=feed_dict)
if step % 10 == 0:
train_loss,\
summary_str =\
sess.run([loss_val,summary_op],\
feed_dict=feed_dict)
summary_writer.add_summary(summary_str,\
global_step=step)
print ("Training Loss: %f" % train_loss)
```
當運行步長是 100 的倍數時,我們在驗證集上運行訓練模型:
```py
if step % 100 == 0:
valid_loss = \
sess.run(loss_val, \
feed_dict={input_dataset: valid_images, input_labels: valid_labels})
```
然后我們打印掉損失值:
```py
print ("%s Validation Loss: %f" \
% (datetime.now(), valid_loss))
```
在訓練過程結束時,模型將被保存:
```py
saver.save(sess, FLAGS.logs_dir\
+ 'model.ckpt', \
global_step=step)
if __name__ == "__main__":
tf.app.run()
```
這是輸出。如您所見,在模擬過程中損失函數減少:
```py
Reading train.csv ...
(4178, 48, 48, 1)
(4178, 7)
Reading test.csv ...
Picking ...
Train size: 3761
Validation size: 417
Test size: 1312
2018-02-24 15:17:45.421344 Validation Loss: 1.962773
2018-02-24 15:19:09.568140 Validation Loss: 1.796418
2018-02-24 15:20:35.122450 Validation Loss: 1.328313
2018-02-24 15:21:58.200816 Validation Loss: 1.120482
2018-02-24 15:23:24.024985 Validation Loss: 1.066049
2018-02-24 15:24:38.838554 Validation Loss: 0.965881
2018-02-24 15:25:54.761599 Validation Loss: 0.953470
2018-02-24 15:27:15.592093 Validation Loss: 0.897236
2018-02-24 15:28:39.881676 Validation Loss: 0.838831
2018-02-24 15:29:53.012461 Validation Loss: 0.910777
2018-02-24 15:31:14.416664 Validation Loss: 0.888537
>>>
```
然而,模型可以通過改變超參數或架構來改進。
在下一節中,我們將了解如何在您自己的圖像上有效地測試模型。
## 在您自己的圖像上測試模型
我們使用的數據集是標準化的。所有面部都指向相機,表情在某些情況下被夸大甚至滑稽。現在讓我們看看如果我們使用更自然的圖像會發生什么。確保臉部沒有文字覆蓋,情感可識別,臉部主要指向相機。
我從這個 JPEG 圖像開始(它是一個彩色圖像,你可以從書的代碼庫下載):

圖 21:輸入圖像
使用 Matplotlib 和其他 NumPy Python 庫,我們將輸入顏色圖像轉換為網絡的有效輸入,即灰度圖像:
```py
img = mpimg.imread('author_image.jpg')
gray = rgb2gray(img)
```
轉換函數如下:
```py
def rgb2gray(rgb):
return np.dot(rgb[...,:3], [0.299, 0.587, 0.114])
```
結果如下圖所示:

圖 22:灰度輸入圖像
最后,我們可以使用此圖像為網絡提供信息,但首先我們必須定義一個正在運行的 TensorFlow 會話:
```py
sess = tf.InteractiveSession()
```
然后我們可以回想起之前保存的模型:
```py
new_saver = tf.train.\
import_meta_graph('logs/EmotionDetector_logs/model.ckpt-1000.meta')
new_saver.restore(sess,'logs/EmotionDetector_logs/model.ckpt-1000')
tf.get_default_graph().as_graph_def()
x = sess.graph.get_tensor_by_name("input:0")
y_conv = sess.graph.get_tensor_by_name("output:0")
```
要測試圖像,我們必須將其重新整形為網絡的有效`48×48×1`格式:
```py
image_test = np.resize(gray,(1,48,48,1))
```
我們多次評估相同的圖片(`1000`),以便在輸入圖像中獲得一系列可能的情感:
```py
tResult = testResult()
num_evaluations = 1000
for i in range(0,num_evaluations):
result = sess.run(y_conv, feed_dict={x:image_test})
label = sess.run(tf.argmax(result, 1))
label = label[0]
label = int(label)
tResult.evaluate(label)
tResult.display_result(num_evaluations)
```
在幾秒后,會出現如下結果:
```py
>>>
anger = 0.1%
disgust = 0.1%
fear = 29.1%
happy = 50.3%
sad = 0.1%
surprise = 20.0%
neutral = 0.3%
>>>
```
最高的百分比證實(`happy = 50.3%`)我們走在正確的軌道上。當然,這并不意味著我們的模型是準確的。可以通過更多和更多樣化的訓練集,更改網絡參數或修改網絡架構來實現可能的改進。
## 源代碼
這里列出了實現的分類器的第二部分:
```py
from scipy import misc
import numpy as np
import matplotlib.cm as cm
import tensorflow as tf
from matplotlib import pyplot as plt
import matplotlib.image as mpimg
import EmotionUtils
from EmotionUtils import testResult
def rgb2gray(rgb):
return np.dot(rgb[...,:3], [0.299, 0.587, 0.114])
img = mpimg.imread('author_image.jpg')
gray = rgb2gray(img)
plt.imshow(gray, cmap = plt.get_cmap('gray'))
plt.show()
sess = tf.InteractiveSession()
new_saver = tf.train.import_meta_graph('logs/model.ckpt-1000.meta')
new_saver.restore(sess, 'logs/model.ckpt-1000')
tf.get_default_graph().as_graph_def()
x = sess.graph.get_tensor_by_name("input:0")
y_conv = sess.graph.get_tensor_by_name("output:0")
image_test = np.resize(gray,(1,48,48,1))
tResult = testResult()
num_evaluations = 1000
for i in range(0,num_evaluations):
result = sess.run(y_conv, feed_dict={x:image_test})
label = sess.run(tf.argmax(result, 1))
label = label[0]
label = int(label)
tResult.evaluate(label)
tResult.display_result(num_evaluations)
```
我們實現`testResult` Python 類來顯示結果百分比。它可以在`EmotionUtils`文件中找到。
以下是此類的實現:
```py
class testResult:
def __init__(self):
self.anger = 0
self.disgust = 0
self.fear = 0
self.happy = 0
self.sad = 0
self.surprise = 0
self.neutral = 0
def evaluate(self,label):
if (0 == label):
self.anger = self.anger+1
if (1 == label):
self.disgust = self.disgust+1
if (2 == label):
self.fear = self.fear+1
if (3 == label):
self.happy = self.happy+1
if (4 == label):
self.sad = self.sad+1
if (5 == label):
self.surprise = self.surprise+1
if (6 == label):
self.neutral = self.neutral+1
def display_result(self,evaluations):
print("anger = " +\
str((self.anger/float(evaluations))*100) + "%")
print("disgust = " +\
str((self.disgust/float(evaluations))*100) + "%")
print("fear = " +\
str((self.fear/float(evaluations))*100) + "%")
print("happy = " +\
str((self.happy/float(evaluations))*100) + "%")
print("sad = " +\
str((self.sad/float(evaluations))*100) + "%")
print("surprise = " +\
str((self.surprise/float(evaluations))*100) + "%")
print("neutral = " +\
str((self.neutral/float(evaluations))*100) + "%")
```
# 總結
在本章中,我們介紹了 CNN。我們已經看到 CNN 適用于圖像分類問題,使訓練階段更快,測試階段更準確。
最常見的 CNN 架構已經被描述:LeNet-5 模型,專為手寫和機器打印字符識別而設計; AlexNet,2012 年參加 ILSVRC; VGG 模型在 ImageNet 中實現了 92.7% 的前 5 個測試精度(屬于 1,000 個類別的超過 1400 萬個圖像的數據集);最后是 Inception-v3 模型,該模型負責在 2014 年 ILSVRC 中設置分類和檢測標準。
每個 CNN 架構的描述后面都是一個代碼示例。此外,AlexNet 網絡和 VGG 示例有助于解釋傳輸和樣式學習技術的概念。
最后,我們建立了一個 CNN 來對圖像數據集中的情感進行分類;我們在單個圖像上測試了網絡,并評估了模型的限制和質量。
下一章將介紹自編碼器:這些算法可用于降維,分類,回歸,協同過濾,特征學習和主題建模。我們將使用自編碼器進行進一步的數據分析,并使用圖像數據集測量分類表現。
- TensorFlow 1.x 深度學習秘籍
- 零、前言
- 一、TensorFlow 簡介
- 二、回歸
- 三、神經網絡:感知器
- 四、卷積神經網絡
- 五、高級卷積神經網絡
- 六、循環神經網絡
- 七、無監督學習
- 八、自編碼器
- 九、強化學習
- 十、移動計算
- 十一、生成模型和 CapsNet
- 十二、分布式 TensorFlow 和云深度學習
- 十三、AutoML 和學習如何學習(元學習)
- 十四、TensorFlow 處理單元
- 使用 TensorFlow 構建機器學習項目中文版
- 一、探索和轉換數據
- 二、聚類
- 三、線性回歸
- 四、邏輯回歸
- 五、簡單的前饋神經網絡
- 六、卷積神經網絡
- 七、循環神經網絡和 LSTM
- 八、深度神經網絡
- 九、大規模運行模型 -- GPU 和服務
- 十、庫安裝和其他提示
- TensorFlow 深度學習中文第二版
- 一、人工神經網絡
- 二、TensorFlow v1.6 的新功能是什么?
- 三、實現前饋神經網絡
- 四、CNN 實戰
- 五、使用 TensorFlow 實現自編碼器
- 六、RNN 和梯度消失或爆炸問題
- 七、TensorFlow GPU 配置
- 八、TFLearn
- 九、使用協同過濾的電影推薦
- 十、OpenAI Gym
- TensorFlow 深度學習實戰指南中文版
- 一、入門
- 二、深度神經網絡
- 三、卷積神經網絡
- 四、循環神經網絡介紹
- 五、總結
- 精通 TensorFlow 1.x
- 一、TensorFlow 101
- 二、TensorFlow 的高級庫
- 三、Keras 101
- 四、TensorFlow 中的經典機器學習
- 五、TensorFlow 和 Keras 中的神經網絡和 MLP
- 六、TensorFlow 和 Keras 中的 RNN
- 七、TensorFlow 和 Keras 中的用于時間序列數據的 RNN
- 八、TensorFlow 和 Keras 中的用于文本數據的 RNN
- 九、TensorFlow 和 Keras 中的 CNN
- 十、TensorFlow 和 Keras 中的自編碼器
- 十一、TF 服務:生產中的 TensorFlow 模型
- 十二、遷移學習和預訓練模型
- 十三、深度強化學習
- 十四、生成對抗網絡
- 十五、TensorFlow 集群的分布式模型
- 十六、移動和嵌入式平臺上的 TensorFlow 模型
- 十七、R 中的 TensorFlow 和 Keras
- 十八、調試 TensorFlow 模型
- 十九、張量處理單元
- TensorFlow 機器學習秘籍中文第二版
- 一、TensorFlow 入門
- 二、TensorFlow 的方式
- 三、線性回歸
- 四、支持向量機
- 五、最近鄰方法
- 六、神經網絡
- 七、自然語言處理
- 八、卷積神經網絡
- 九、循環神經網絡
- 十、將 TensorFlow 投入生產
- 十一、更多 TensorFlow
- 與 TensorFlow 的初次接觸
- 前言
- 1.?TensorFlow 基礎知識
- 2. TensorFlow 中的線性回歸
- 3. TensorFlow 中的聚類
- 4. TensorFlow 中的單層神經網絡
- 5. TensorFlow 中的多層神經網絡
- 6. 并行
- 后記
- TensorFlow 學習指南
- 一、基礎
- 二、線性模型
- 三、學習
- 四、分布式
- TensorFlow Rager 教程
- 一、如何使用 TensorFlow Eager 構建簡單的神經網絡
- 二、在 Eager 模式中使用指標
- 三、如何保存和恢復訓練模型
- 四、文本序列到 TFRecords
- 五、如何將原始圖片數據轉換為 TFRecords
- 六、如何使用 TensorFlow Eager 從 TFRecords 批量讀取數據
- 七、使用 TensorFlow Eager 構建用于情感識別的卷積神經網絡(CNN)
- 八、用于 TensorFlow Eager 序列分類的動態循壞神經網絡
- 九、用于 TensorFlow Eager 時間序列回歸的遞歸神經網絡
- TensorFlow 高效編程
- 圖嵌入綜述:問題,技術與應用
- 一、引言
- 三、圖嵌入的問題設定
- 四、圖嵌入技術
- 基于邊重構的優化問題
- 應用
- 基于深度學習的推薦系統:綜述和新視角
- 引言
- 基于深度學習的推薦:最先進的技術
- 基于卷積神經網絡的推薦
- 關于卷積神經網絡我們理解了什么
- 第1章概論
- 第2章多層網絡
- 2.1.4生成對抗網絡
- 2.2.1最近ConvNets演變中的關鍵架構
- 2.2.2走向ConvNet不變性
- 2.3時空卷積網絡
- 第3章了解ConvNets構建塊
- 3.2整改
- 3.3規范化
- 3.4匯集
- 第四章現狀
- 4.2打開問題
- 參考
- 機器學習超級復習筆記
- Python 遷移學習實用指南
- 零、前言
- 一、機器學習基礎
- 二、深度學習基礎
- 三、了解深度學習架構
- 四、遷移學習基礎
- 五、釋放遷移學習的力量
- 六、圖像識別與分類
- 七、文本文件分類
- 八、音頻事件識別與分類
- 九、DeepDream
- 十、自動圖像字幕生成器
- 十一、圖像著色
- 面向計算機視覺的深度學習
- 零、前言
- 一、入門
- 二、圖像分類
- 三、圖像檢索
- 四、對象檢測
- 五、語義分割
- 六、相似性學習
- 七、圖像字幕
- 八、生成模型
- 九、視頻分類
- 十、部署
- 深度學習快速參考
- 零、前言
- 一、深度學習的基礎
- 二、使用深度學習解決回歸問題
- 三、使用 TensorBoard 監控網絡訓練
- 四、使用深度學習解決二分類問題
- 五、使用 Keras 解決多分類問題
- 六、超參數優化
- 七、從頭開始訓練 CNN
- 八、將預訓練的 CNN 用于遷移學習
- 九、從頭開始訓練 RNN
- 十、使用詞嵌入從頭開始訓練 LSTM
- 十一、訓練 Seq2Seq 模型
- 十二、深度強化學習
- 十三、生成對抗網絡
- TensorFlow 2.0 快速入門指南
- 零、前言
- 第 1 部分:TensorFlow 2.00 Alpha 簡介
- 一、TensorFlow 2 簡介
- 二、Keras:TensorFlow 2 的高級 API
- 三、TensorFlow 2 和 ANN 技術
- 第 2 部分:TensorFlow 2.00 Alpha 中的監督和無監督學習
- 四、TensorFlow 2 和監督機器學習
- 五、TensorFlow 2 和無監督學習
- 第 3 部分:TensorFlow 2.00 Alpha 的神經網絡應用
- 六、使用 TensorFlow 2 識別圖像
- 七、TensorFlow 2 和神經風格遷移
- 八、TensorFlow 2 和循環神經網絡
- 九、TensorFlow 估計器和 TensorFlow HUB
- 十、從 tf1.12 轉換為 tf2
- TensorFlow 入門
- 零、前言
- 一、TensorFlow 基本概念
- 二、TensorFlow 數學運算
- 三、機器學習入門
- 四、神經網絡簡介
- 五、深度學習
- 六、TensorFlow GPU 編程和服務
- TensorFlow 卷積神經網絡實用指南
- 零、前言
- 一、TensorFlow 的設置和介紹
- 二、深度學習和卷積神經網絡
- 三、TensorFlow 中的圖像分類
- 四、目標檢測與分割
- 五、VGG,Inception,ResNet 和 MobileNets
- 六、自編碼器,變分自編碼器和生成對抗網絡
- 七、遷移學習
- 八、機器學習最佳實踐和故障排除
- 九、大規模訓練
- 十、參考文獻