簡單的CNN架構通常包含卷積層(tf.nn.conv2d)、非線性變換層(tf.nn.relu)、池化層(tf.nn.max_pool)及全連接層(tf.nn.matmul)。如果沒有這些層,模型便很難與複雜的模式匹配,因爲網絡將被填充過多的信息。一個設計良好的CNN架構會突出那些重要的信息,而將噪聲忽略。
1.get_shape
TensorFlow的輸入流水線(用於讀取和解碼文件)擁有一種爲使用一個批數據中的多幅圖像而設計的專門格式,它包括了任意一幅圖像所需的信息([image_batch_size,image_height,image_width,image_channels])
import tensorflow as tf import numpy as np sess = tf.InteractiveSession() image_batch = tf.constant([ [ # First Image [[0, 255, 0], [0, 255, 0], [0, 255, 0]], [[0, 255, 0], [0, 255, 0], [0, 255, 0]] ], [ # Second Image [[0, 0, 255], [0, 0, 255], [0, 0, 255]], [[0, 0, 255], [0, 0, 255], [0, 0, 255]] ] ]) #image_batch.get_shape #sess.run(image_batch)[0][0][0] print(image_batch) # Tensor("Const:0", shape=(2, 2, 3, 3), dtype=int32) print(sess.run(image_batch)[0][0][0]) # [ 0 255 0]
上述代碼創建了一個包含兩幅圖像的圖像批數據。每幅圖像的高爲2個像素,寬爲3個像素,且顏色空間爲RGB。執行後的輸出的第1組維度表明了圖像數量,第2組維度對應圖像的高度,第3組維度表明了圖像的寬度,而顏色通道數量對應於最後一組維度。
變量image_batch並不會從磁盤直接加載圖像,而是將自身當成作爲輸入流水線的一部分而被加載的圖像。使用輸入流水線從磁盤加載的圖像擁有相同的格式和行爲。一種常見的做法是創建一些與上述image_batch實例相似的假數據對CNN的輸入和輸出進行測試。這種簡化的輸入會使診斷和調試一些簡單問題更加容易。簡化調試過程非常重要,因爲CNN架構極爲複雜,訓練經常需要耗費數日。
2.卷積
import tensorflow as tf import numpy as np sess = tf.InteractiveSession() input_batch = tf.constant([ [ # First Input [[0.0], [1.0]], [[2.0], [3.0]] ], [ # Second Input [[2.0], [4.0]], [[6.0], [8.0]] ] ]) kernel = tf.constant([ [ [[1.0, 2.0]] ] ]) conv2d = tf.nn.conv2d(input_batch, kernel, strides=[1, 1, 1, 1], padding='SAME') sess.run(conv2d)
lower_right_image_pixel = sess.run(input_batch)[0][1][1] lower_right_kernel_pixel = sess.run(conv2d)[0][1][1] lower_right_image_pixel, lower_right_kernel_pixel
設置跨度是一種調整輸入張量維數的方法。降維可減少所需的運算量,並可避免創建一些完全重疊的感受域。strides參數的格式與輸入向量相同,即(image_batch_size_stride、image_height_stride、image_width_stride、image_channels_stride)。第1個和最後一個跨度參數通常很少修改,因爲它們會在tf.nn.conv2d運算中跳過一些數據,從而不將這部分數據予以考慮。如果希望降低輸入的維數,可修改image_height_stride和image_width_stride參數。
input_batch = tf.constant([ [ # First Input (6x6x1) [[0.0], [1.0], [2.0], [3.0], [4.0], [5.0]], [[0.1], [1.1], [2.1], [3.1], [4.1], [5.1]], [[0.2], [1.2], [2.2], [3.2], [4.2], [5.2]], [[0.3], [1.3], [2.3], [3.3], [4.3], [5.3]], [[0.4], [1.4], [2.4], [3.4], [4.4], [5.4]], [[0.5], [1.5], [2.5], [3.5], [4.5], [5.5]], ], ]) kernel = tf.constant([ # Kernel (3x3x1) [[[0.0]], [[0.5]], [[0.0]]], [[[0.0]], [[1.0]], [[0.0]]], [[[0.0]], [[0.5]], [[0.0]]] ]) conv2d = tf.nn.conv2d(input_batch, kernel, strides=[1, 3, 3, 1], padding='SAME') sess.run(conv2d) print(conv2d.eval())
tf.nn.conv2d的零填充數量或錯誤狀態是由參數padding控制的,它的取值可以是SAME或VALID。
·SAME :卷積輸出與輸入的尺寸相同。這裏在計算如何跨越圖像時,並不考慮濾波器的尺寸。選用該設置時,缺失的像素將用0填充,卷積核掃過的像素數將超過圖像的實際像素數。
·VALID :在計算卷積核如何在圖像上跨越時,需要考慮濾波器的尺寸。這會使卷積核儘量不越過圖像的邊界。在某些情形下,可能邊界也會被填充。
3.池化層
池化層能夠減少過擬合,並通過減小輸入的尺寸來提高性能。它們可用於對輸入降採樣,但會爲後續層保留重要的信息。只使用tf.nn.conv2d來減小輸入的尺寸也是可以的,但池化層的效率更高。
4.歸一化
歸一化層並非CNN所獨有。在使用tf.nn.relu時,考慮輸出的歸一化是有價值的。由於ReLU是無界函數,利用某些形式的歸一化來識別那些高頻特徵通常是十分有用的。