tensorflow 图像教程 の TF Layers 教程:构建卷积神经网络
文章目錄
- TF Layers 教程:構建卷積神經網絡
- 卷積神經網絡的簡介
- 構建基于卷積神經網絡的 MNIST 分類器
- 輸入層
- 第一個卷積層
- 第一個池化層
- 第二個卷積層和池化層
- 全連接層
- Logits 層
- 生成預測
- 計算損失
- 配置訓練操作
- 添加評價指標
- 訓練和評價 CNN MNIST 分類器
- 加載訓練和測試數據
- 創建評估器(Estimator)
- 建立一個日志鉤子
- 訓練模型
- 評估模型
- 運行模型
- 其他的資料
參考文章:TF Layers 教程:構建卷積神經網絡
TF Layers 教程:構建卷積神經網絡
卷積神經網絡的簡介
卷積神經網絡(CNNs{Convolutional Neural Networks})是當前用戶圖像分類任務中最前沿的模型。CNNs 對圖像的原始像素數據應用了一系列的過濾器,以提取和學習更高層次的特征,然后模型利用這些特征對圖像進行分類。CNNs 主要包含下面三個組件:
-
卷積層,它表示應用在圖像中卷積核的數量。對于圖片的子區域,卷積層會執行一系列的數學變換,從而輸出特征映射的值。卷積層一般情況下會使用 ReLU 做為激活函數來讓模型引入非線性變換。
-
池化層,它是對卷積層提取出的圖像數據進行下采樣,作用是可以減少特征映射的維度,從而減少計算的時間。常用的池化算法是最大池化算法,它提取的像素值是池化窗口(e.g., 2x2-像素塊)中值最大的,而子區域中其他的像素值則被拋棄。
-
稠密(全連接)層,在經過卷積層和池化層的采樣后,全連接層就可以對特征進行分類了。具體來說,在全連接層中,層中的每個節點都與上一層的結點相連。
一般來說,CNN 是通過多層卷積模塊來提取特征的。每一個模塊都包含一個卷積層,后面跟著一個池化層。最后一個卷積模塊后面跟著一層或者多層的全連接層來獲得分類結果。CNN 中的最后一個全連接層結點的數量等于預測任務所有可能類別的數量,而這些結點的值通過 softmax 激活函數后會產生一個 0~1 的值(該層所有的結點值之和為 1)。這些 softmax 值可以解釋為輸入圖片最有可能是屬于哪個類別的概率。
注意:想要更深入了解 CNN 的架構,請看斯坦福大學的卷積神經網絡課程資料
構建基于卷積神經網絡的 MNIST 分類器
基于 CNN 架構,讓我們構建一個模型來對 MNIST 數據集中的圖像進行分類:
tf.layers 模塊中包含創建上述卷積神經網絡三種類型的層的方法:
- conv2d():構建一個兩維的卷積層。輸入的參數是卷積的核數,大小,邊緣填充方式和選擇的激活函數。
- max_pooling2d():使用 max-pooling 池化算法構建一個二維的池化層。輸入參數是池化的大小和步長。
- dense():構建稠密全連接層。輸入參數是神經元數目和激活函數。
每一個方法都是接受一個張量然后再將轉換后的張量作為輸出。這使得層與層之間的連接變得簡單:即上一層的輸出可以直接作為下一層的輸入。
tf.estimator.ModeKeys
def cnn_model_fn(features, labels, mode):"""CNN 的模型函數"""# 輸入層input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])# 第一個卷積層conv1 = tf.layers.conv2d(inputs=input_layer,filters=32,kernel_size=[5, 5],padding="same",activation=tf.nn.relu)# 第一個池化層pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)# 第二個卷積層和池化層conv2 = tf.layers.conv2d(inputs=pool1,filters=64,kernel_size=[5, 5],padding="same",activation=tf.nn.relu)pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)# 全連接層pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)dropout = tf.layers.dropout(inputs=dense, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN)# Logits 層logits = tf.layers.dense(inputs=dropout, units=10)predictions = {# (為 PREDICT 和 EVAL 模式)生成預測值"classes": tf.argmax(input=logits, axis=1),# 將 `softmax_tensor` 添加至計算圖。用于 PREDICT 模式下的 `logging_hook`."probabilities": tf.nn.softmax(logits, name="softmax_tensor")}if mode == tf.estimator.ModeKeys.PREDICT:return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions)# 計算損失(可用于`訓練`和`評價`中)loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)# 配置訓練操作(用于 TRAIN 模式)if mode == tf.estimator.ModeKeys.TRAIN:optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)train_op = optimizer.minimize(loss=loss,global_step=tf.train.get_global_step())return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)# 添加評價指標(用于評估)eval_metric_ops = {"accuracy": tf.metrics.accuracy(labels=labels, predictions=predictions["classes"])}return tf.estimator.EstimatorSpec(mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)創建定制化 Estimator
輸入層
在 layer 模塊中,用于二維圖像數據的卷積層和池化層期望輸入的張量維度默認為[batch_size, image_height, image_width, channels]。可以通過修改 data_format 的參數改變這種行為,定義如下:
- batch_size:在訓練過中,每次執行梯度下降時使用的樣本子集大小。
- image_height:樣本圖片的高度。
- image_width:樣本圖片的寬度。
- channels:樣本圖片的通道數。對于彩色圖片,通道數為 3(紅,綠,藍)。對于灰度圖片,就只有一個通道(黑)。
- data_format:字符串,channels_last(default)或 channels_first。channels_last 對應于具有 (batch, …, channels) 形狀的輸入,而 channels_first 對應于 具有 (batch, channels, …) 形狀的輸入。
在這里,我們的 MNIST 數據集圖片是灰度圖片,每張圖片的大小是 28x28 像素,因此我們輸入層數據的維度為[batch_size, 28, 28, 1]
如果輸入的特征不能滿足這個維度,我們可以使用下面的 reshape 操作來進行轉換。
input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])注意,這里的 batch_size 值為 -1,該值會根據輸入 features[x] 和另外 3 個維度的值自動計算出來。這使我們可以將 batch_size 當成一個超參數來進行調整。舉個例子,如果我們輸入到模型的樣本子集大小為 5,那么 features[“x”] 會包含 3,920 個值(每個值對應每張圖像像素的一個值,也即 5x28x28 = 3920),也就是說 input_layer 的形狀為 [5, 28, 28, 1],同樣的,如果我們的輸入樣本子集大小為 100,features[“x”] 就會包含 78,400 個值,也就是說 input_layer 的形狀為 [100, 28, 28, 1]
第一個卷積層
第一個卷積層中,我們對輸入層應用了 32 個 5x5 的卷積核和 ReLU 激活函數。我們用到了 layer 模塊中的 conv2d 方法,如下所示:
conv1 = tf.layers.conv2d(inputs=input_layer,filters=32,kernel_size=[5, 5],padding="same",activation=tf.nn.relu)inputs 參數指定了我們的輸入張量,這個張量的形狀必須為 [batch_size, image_height, inage_width, channels]。在這里,我們將 input_layer 連接到第一個卷積層,它的形狀是 [batch_size, 28, 28, 1]。
注意:如果你傳入了參數 data_format=channels_first,那么 conv2d() 所接受的維數是 [batch_size, channels, image_height, image_width]。
參數 filters 指定的是具體應用的卷積核的數量(在這里,數量為 32),kernel_size 指定的是卷積核的尺寸 [height, width](在這里,尺寸為 [5, 5])
小建議:如果卷積核的高度和寬度一致的話,你可以傳遞一個單獨整數給參數 kernel_size,譬如 kernel_size=5。
參數 padding 的輸入值是兩個枚舉值中的一個(值不區分大小寫):valid (默認值)或 same。當你設置 padding=same 的時候,TensorFlow 將會在邊界填充 0 值從而讓輸出的張量和輸入的張量有相同的寬高,也即 28x28。(如果沒有填充,那么 5x5 的卷積核會產生一個 24x24 形狀的張量)
tf.nn.relu
函數 conv2d() 的輸出張量的形狀為 [batch_size, 28, 28, 32]:以相同的高度和寬度作為輸入,但是有 32 個通道,每個通道對應著一個卷積核的輸出。
第一個池化層
接下來,我們將第一個池化層連接到我們剛創建的卷積層上去。我們使用 layers 中的 max_pooling2d() 方法來創建一個 2x2 大小,步長為 2 的最大池化過濾器。
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)再次說明,inputs 指定了輸入的張量,它的形狀為 [batch_size, image_height, image_width, channels]。在這里,我們的輸入的張量是 conv1,也就是第一個卷積層的輸出,它的形狀是 [batch_size, 28, 28, 32]。
注意:如果你傳入了參數 data_format=channels_first,那么 conv2d() 所接受的形狀是[batch_size, channels, image_height, image_width]。
參數 pool_size 指定了最大池化過濾器的維度 [height, width](在這里維度值為 [2, 2]),該參數也可以接受一個單獨的數字(譬如 pool_size=2)
參數 strides 指定了滑動步長的大小。在這里,我們設置步長的值為 2,它的含義是過濾器提取的子區域在高度和寬度上都間隔有 2 個像素(對于 2x2 的過濾器,我們所提取的子區域都不會重疊)。如果你要為高度和寬度設置不同的步長值,你可以傳入一個類型為元組或列表的值(e.g., stride=[3, 6])。
方法 max_pooling2d() 輸出的張量(pool1)的形狀為 [batch_size, 14, 14, 32]:2x2 的過濾器讓高和寬分別減少了 50%。
第二個卷積層和池化層
如前所述,我們使用 conv2d() 和 max_pooling2d() 方法就可以連接和創建我們 CNN 的第二個卷積層和池化層。對于第二個卷積層,我們配置了 64 個窗口大小為 5x5 的卷積核,使用了 ReLU 激活函數,對于第二個池化層,我們使用了和第一個池化層一樣的設置(大小為 2x2 且步長為 2 的最大池化過濾器):
conv2 = tf.layers.conv2d(inputs=pool1,filters=64,kernel_size=[5, 5],padding="same",activation=tf.nn.relu)pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)注意第二個卷積層將第一個池化層的輸出(pool1)作為輸入,然后得到的輸出張量為 conv2。張量 conv2 的形狀為 [batch_size, 14, 14, 62],高和寬與第一個池化層(pool1)相同,64 個通道表示應用的 64 個卷積核。
第二個池化層拿 conv2 作為輸入,然后得到的 pool2 作為輸出。pool2 的形狀為 [batch_size, 7, 7, 64](將高和寬的長度分別減少了 50%)
全連接層
接下來,我們將要為 CNN 添加全連接層(擁有 1,024 個神經元和 ReLU 激活函數),以用來對我們前面的卷積層和池化層所提取到的特征來做分類。在我們連接該層時,我們需要拉平 pool2 的形狀為 [batch_size, features],這時張量只有兩維:
pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])在上面 reshape() 操作中,-1 表示 batch_size 的維數,它會根據輸入的數據樣本數動態的計算出來。每一個樣本有 7 (pool2 的 height) 7 (pool2 的 width) 64 (pool2 的通道數) 個特征,因此我們的特征維數為 7 7 64(總共 3136 個)。輸出的張量 pool2_flat 的形狀是 [batch_size, 3136]
現在,我們可以使用 layers 模塊中的 dense() 方法連接全連接層了。
dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)參數 inputs 指定了輸入的張量:也就是拉平后的特征映射 pool2_flat。參數 units 指定了全連接層的神經元數(1,024)。參數 activation 指定了激活函數;同樣,我們使用了 ReLU 激活函數,也即傳入了 tf.nn.relu 值。
為了提高模型的效果,我們還在全連接層中應用了 dropout 正則化,使用 layers 模塊中的 dropout 方法來定義:
dropout = tf.layers.dropout(inputs=dense, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN)同樣,參數 inputs 指定了輸入張量,它是上一個全連接層(dense)的輸出張量。
參數 rate 指定了 dropout 的比率;在這里,我們的值是 0.4,意味著 40% 的神經元在訓練期間會被隨機的屏蔽。
參數 training 接受一個布爾值,它指定模型當前是否正在訓練模式下運行;dropout 操作只會在此布爾值為 True 的時候執行。在這里,在這里,我們檢查傳遞到我們的模型函數 cnn_model_fn 的 mode 是否是 TRAIN 模式。
我們的輸出張量 dropout 的形狀是 [batch_size, 1024]。
Logits 層
神經網絡中的最后一層是 logits 層,它將返回我們預測的原始值。邏輯層是一個有 10 個神經元,且默認有線性激活函數的全連接層(每個神經元對應 0~9 中的一個類別)。
logits = tf.layers.dense(inputs=dropout, units=10)CNN 最終張量由 logits 層輸出,它的形狀是 [batch_size, 10]
生成預測
我們的模型的 logits 層將我們的原始預測值作為一維張量返回,形狀為 [batch_size, 10]。讓我們將這些原始值轉換成模型函數所支持的兩種不同格式:
- 每個樣本的預測的類別:0~9 的數字。
- 每個樣本在不同類別下的概率:樣本是 0 的概率,樣本是 1 的概率,樣本是 2 的概率,等等。
tf.argmax
tf.argmax(input=logits, axis=1)參數 input 指定了提取最大值的張量,這里傳入的張量是 logits,用于提取最大值。參數 axis 指定了應該沿著 input 的哪個軸找最大值,這里傳入的值是 1,它意味著我們沿著第二個維度來找最大值,這對應我們輸出的預測張量的形狀 [batch_size, 10] 中的 10。
tf.nn.softmax
tf.nn.softmax(logits, name="softmax_tensor")注意:我們使用參數 name 給這個操作命名為 softmax_tensor,這樣的話我們就可以在后面引用他。(我們將在“設置日志鉤”中為 softmax 值設置日志記錄)。
我們用一個字典數據結構來表示預測,然后生成一個 EstimatorSpec 對象:
predictions = {"classes": tf.argmax(input=logits, axis=1),"probabilities": tf.nn.softmax(logits, name="softmax_tensor") } if mode == tf.estimator.ModeKeys.PREDICT:return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions)計算損失
對于訓練(TRAIN)和評價(EVAL)環節,我們需要定義損失函數來衡量預測類別和真實類別之間的差距。對于像 MNIST 這樣的多分類問題,我們常用交叉熵作為損失的度量。下面的代碼將會在訓練或者驗證模式下計算對應的交叉熵。
onehot_labels = tf.one_hot(indices=tf.cast(labels, tf.int32), depth=10) loss = tf.losses.softmax_cross_entropy(onehot_labels=onehot_labels, logits=logits)讓我們清楚地了解一下上面的代碼做了什么。
張量 labels 包含了樣本對應的真實類別,他是一個 list 結構,e.g. [1 ,9, …]。為了能夠計算出交叉熵值,首先我們需要對 labels 值做 one-hot 編碼:
[[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 1],...]tf.one_hot
- indices:one-hot 編碼后的下標值,也就是上面張量值為 1 所對應的下標值。
- depth:one-hot 編碼后的深度,也就是目標類別的總數,在這里,深度值是 10。
通過執行 one-hot 編碼后,我們可以得到 onehot_labels 張量:
onehot_labels = tf.one_hot(indices=tf.cast(labels, tf.int32), depth=10)因為原始的 labels 包含了 0~9 的數字,所以 indices 實際上是轉換為整數值后的 labels 張量。參數 depth 值為 10 是因為我們有 10 個可能的類別,每個數字對應的一個類別。
接下來,我們就可以根據 onehot_labels 和由 logits 層預測值得到的 softmax 值來計算交叉熵值了。tf.losses.softmax_cross_entropy() 函數拿 onehot_labels 和 logits 張量作為輸入,然后在 logits 上執行 softmax 激活函數,接著計算交叉熵,最后返回張量類型的 loss 值。
loss = tf.losses.softmax_cross_entropy(onehot_labels=onehot_labels, logits=logits)配置訓練操作
在上面的章節,我們定義了交叉熵損失函數。接下來讓我們在訓練中配置我們的模型來最優化這個損失值。我們使用的最優化算法是隨機梯度下降法,對應的學習率為 0.001 。
if mode == tf.estimator.ModeKeys.TRAIN:optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)train_op = optimizer.minimize(loss=loss,global_step=tf.train.get_global_step())return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)創建定制化 Estimator
添加評價指標
通過在 EVAL 模式中定義 eval_metric_ops 字典,我們可以給模型添加準確度評價指標:
eval_metric_ops = {"accuracy": tf.metrics.accuracy(labels=labels, predictions=predictions["classes"])} return tf.estimator.EstimatorSpec(mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)訓練和評價 CNN MNIST 分類器
我們已經完成了 CNN 模型的代碼工作;現在我們準備訓練和評價它。
加載訓練和測試數據
首先,我們需要加載訓練和測試數據。在 cnn_mnist.py 文件中的 main() 函數添加下面的代碼:
def main(unused_argv):# Load training and eval datamnist = tf.contrib.learn.datasets.load_dataset("mnist")train_data = mnist.train.images # Returns np.arraytrain_labels = np.asarray(mnist.train.labels, dtype=np.int32)eval_data = mnist.test.images # Returns np.arrayeval_labels = np.asarray(mnist.test.labels, dtype=np.int32)我們將訓練特征數據(55, 000 張手寫數字圖片數據的原始像素值)和標注數據(每張圖片對應的 0~9 的值)分別存儲為 train_data 和 train_labels 中,格式為 numpy 數組。類似地,我們將用于評價的特征數據(10,000 張圖片)和相應的標注數據分別存儲在 eval_data 和 eval_labels 中。
創建評估器(Estimator)
接下來,在 main() 函數添加下面的代碼,它的作用是為我們的模型創建 Estimator(一個用于執行模型訓練,評價和推斷的 TensorFlow 類):
# Create the Estimator mnist_classifier = tf.estimator.Estimator(model_fn=cnn_model_fn, model_dir="/tmp/mnist_convnet_model")參數 model_fn 指定了用于訓練,評價和預測的模型函數;我們傳入的 cnn_model_fn 函數是在構建 CNN MNIST 分類器中創建的。參數 model_dir 指定了模型數據(檢查點)保存的目錄(這里我們傳入的目錄是 /tmp/mnist_convnet_model,這個目錄是可以更改的)。
建立一個日志鉤子
tf.train.LoggingTensorHook
# 為預測過程設置日志tensors_to_log = {"probabilities": "softmax_tensor"}logging_hook = tf.train.LoggingTensorHook(tensors=tensors_to_log, every_n_iter=50)我們可以用字典儲存想要打印的張量 tensors_to_log。每個鍵值只不過是用于日志輸出的一個別名,它的值則是 TensorFlow 計算圖中的某個張量的名稱。這里的的 softmax_tensor 是前面 cnn_model_fn 中創建的一個用于生成概率的張量的名稱,而 probabilities 是這里給它取的別名。
TensorFlow 調試器
接下來,通過給 tensors 參數傳遞 tensor_to_log 變量來創建 LoggingTensorHook 對象,并且設置 every_n_iter 的值為 50,每訓練 50 步后在日志中輸出概率。
訓練模型
準備完成后,在 main() 函數中調用 train_input_fn 中的 train() 方法就可以訓練我們的模型了:
# 模型訓練 train_input_fn = tf.estimator.inputs.numpy_input_fn(x={"x": train_data},y=train_labels,batch_size=100,num_epochs=None,shuffle=True) mnist_classifier.train(input_fn=train_input_fn,steps=20000,hooks=[logging_hook])在 numpy_input_fn 函數調用中,我們訓練的特征數據和標注值分別傳遞給參數 x (字典類型)和 y。參數 batch_size 的值為 100(意味著模型每一步訓練都會用到 100 個樣本)。參數 num_epochs=None 指定訓練迭代的次數。參數 shuffle 值為 True 表示訓練時的樣本是亂序的。在 train 調用中,steps=20000 表示模型總共會訓練 20000 步。hooks 參數指定為 logging_hook,表示訓練過程中會觸發日志打印。
評估模型
訓練完成后,我們可以調用 evaluate 方法來評價模型,它會根據我們定義在 model_fn 上的 eval_metrics_ops 的指標來評價模型在測試集上的準確度。
# 評估模型并輸出結果 eval_input_fn = tf.estimator.inputs.numpy_input_fn(x={"x": eval_data},y=eval_labels,num_epochs=1,shuffle=False) eval_results = mnist_classifier.evaluate(input_fn=eval_input_fn) print(eval_results)在創建 eval_input_fn 時,我們設置 num_epochs=1,意味著迭代一次數據來得到模型的評價指標。我們同時也設置 shuffle 參數為 False 從而順序的迭代數據。
運行模型
我們已經編寫了 CNN 模型的函數,Estimator,以及訓練/評價的邏輯;現在來運行 cnn_mnist.py 來看看結果。
注意:訓練 CNNs 是一個計算密集型任務。cnn_mnist.py 的運行時長取決于你的處理器的性能,很有可能會耗費一個小時的時間來訓練。當然為了加快訓練的速度,你可以調低在 train() 函數中參數 steps 的取值,但注意這會影響到模型的準確性。
在模型訓練過程中,你將會看到下面的輸出日志:
INFO:tensorflow:loss = 2.36026, step = 1 INFO:tensorflow:probabilities = [[ 0.07722801 0.08618255 0.09256398, ...]] ... INFO:tensorflow:loss = 2.13119, step = 101 INFO:tensorflow:global_step/sec: 5.44132 ... INFO:tensorflow:Loss for final step: 0.553216.INFO:tensorflow:Restored model from /tmp/mnist_convnet_model INFO:tensorflow:Eval steps [0,inf) for training step 20000. INFO:tensorflow:Input iterator is exhausted. INFO:tensorflow:Saving evaluation summary for step 20000: accuracy = 0.9733, loss = 0.0902271 {'loss': 0.090227105, 'global_step': 20000, 'accuracy': 0.97329998}在這里,我們最后在測試集上的準確度是 97.3%。
其他的資料
如果你想了解更多有關于 TensorFlow 中評估器(Estimators)和 CNNs 的內容,請查閱下面的資料:
- 卷積神經網絡
總結
以上是生活随笔為你收集整理的tensorflow 图像教程 の TF Layers 教程:构建卷积神经网络的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 卷积神经网络CNNs 为什么要用relu
- 下一篇: 聚焦和增强卷积神经网络