1. 程式人生 > >利用tensorflow實現簡單的卷積神經網路

利用tensorflow實現簡單的卷積神經網路

一、什麼是神經網路(CNN)

    卷積神經網路(Convolutional Neural Network,簡稱CNN),是一種前饋神經網路,人工神經元可以影響周圍單元,可以進行大型影象處理。卷積神經網路包括卷積層和池化層。卷積神經網路是受到生物思考方式的啟發的MLPs(多層感知器),它有著不同的類別層次,並且各層的工作方式和作用也不同。

  在卷積神經網路的卷積層中,一個神經元只與部分鄰層神經元連線。在CNN的一個卷積層中,通常包含若干個特徵平面(featureMap),每個特徵平面由一些矩形的神經元排列組成,同一特徵平面的神經元共享權值,這裡共享權值就是卷積核。卷積核一般以隨機小數矩陣的形式初始化,在網路訓練的過程中卷積核將學習得到合理的權值。共享權值(卷積核)帶來的直接好處是減少網路各層之間的連線,同時又降低了過擬合的風險。子取樣又叫池化(pooling),通常有均值子取樣(mean pooling)和最大值子取樣(max pooling)兩種形式。子取樣可以看作是一種特殊的卷積過程。卷積和子取樣大大簡化了模型複雜度,減少了模型的引數。

1.1 區域性感受野

  為降低引數數目,採用區域性感知野。對區域性進行感知,然後在更高層將區域性的資訊綜合起來就得到了全域性的資訊。網路部分聯通思想,也是受啟發於生物學中的視覺系統結構。視覺皮層的神經元就是區域性接受資訊的(即這些神經元只響應某些特定區域的刺激)。如下圖:左為全連線,右為區域性連線。

1.2權值共享

即讓一組神經元使用相同的連線權。在卷積層中,每個卷積核都是一種特徵提取方式,卷積核的過程就是兩個層之間的連線引數,而這些引數在影象的不同區域是區域性,且在區域性是共享的。所以,如果卷積核的大小是m*n,那麼對應引數的數目就是m*n。

1.4多卷積核

為了提取足夠充分的特徵,我們可以新增多個卷積核,比如32個卷積核可以學習32種特徵。多個卷積核的情況如下。

1.5池化(pooling)

子取樣有兩種方式,一種是均值子取樣(mean-pooling),一種是最大值子取樣(max-pooling)。兩種子取樣可以看成是特殊的卷積過程,如下圖所示:

(1)均值子取樣的卷積核中每個權重都是0.25,卷積核在原圖inputX上的滑動步長為2.均值子取樣效果相當於把原圖模糊縮減至原來的1/4.

(2)最大值子取樣的卷積核中各權值中只有一個1,其餘為0,卷積核中為1

的位置對應inputX被卷積核覆蓋部分最大值的位置。卷積核在原圖inputX上滑動步長為2.最大值子取樣的效果是把原圖縮小至原來的1/4,並保每個2*2區域的最強輸入。

詳細概念,在下面這個blog中有闡述。

二、實現簡單cnn

前面闡述了一些理論,下面上具體程式碼,程式碼中有註釋

'''匯入mnist資料集,建立預設的interactive session'''
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
mnist = input_data.read_data_sets("MNIST_data", one_hot = True)
sess = tf.InteractiveSession()
#定義權重和偏差的初始化函式
def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev = 0.1)
    return tf.Variable(initial)

def bias_variable(shape):
    initial = tf.constant(0.1, shape = shape)
    return tf.Variable(initial)
#定義卷積層和池化層
def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides = [1, 1, 1, 1], padding = 'SAME')

def max_pool_2_2(x):
    return tf.nn.max_pool(x, ksize = [1, 2, 2, 1], strides = [1, 2, 2, 1], padding = 'SAME')
#定義輸入的placehoder,x是特徵,y_是真實的label。因為卷積神經網路會用到2D的空間資訊,所以需要把784維資料恢復成28*28結構,使用的是tf.shape函式
x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])
x_image = tf.reshape(x, [-1, 28, 28, 1])
#定義第一個卷積層
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2_2(h_conv1)
#定義第二個卷積層
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2_2(h_conv2)
#定義第一個全連線層
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
#在資料較小情況下,為防止過擬合,隨機將一些結點置零,增加網路泛化能力
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
#最後一個輸出層也要對權重和偏差進行初始化
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
#定義損失函式和訓練的步驟,用adam優化器最小化損失函式
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv), reduction_indices = [1]))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
#計算預測的精確度
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
#對全域性變數進行初始化,迭代2000訓練,使用的minibatch為50,所以總共訓練的樣本數量為10萬
tf.global_variables_initializer().run()
for i in range(2000):
    batch = mnist.train.next_batch(50)
    if i % 100 == 0:
        train_accuracy = accuracy.eval(feed_dict = {x: batch[0], y_: batch[1], keep_prob: 1.0})
        print("step %d, training accuracy %g"%(i, train_accuracy))
    train_step.run(feed_dict = {x: batch[0], y_: batch[1], keep_prob: 0.5})
#輸出最後準確率
print("test accuracy %g"%accuracy.eval(feed_dict = {x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))


訓練結果如下

三、部分細節闡釋

3.1

from tensorflow.examples.tutorials.mnist import input_data import tensorflow as tf mnist = input_data.read_data_sets("MNIST_data", one_hot = True) sess = tf.InteractiveSession()

線上下載mnist資料集時,有可能會出現網路連線錯誤。我們可以選擇先將資料集下載到本地,然後下載路徑即可。

3.2

def weight_variable(shape):     initial = tf.truncated_normal(shape, stddev = 0.1)     return tf.Variable(initial)

def bias_variable(shape):     initial = tf.constant(0.1, shape = shape)     return tf.Variable(initial)

#tf.truncated_normal(shape,mean=0.0,stddev=1.0,dtype=tf.float32,seed=None,name=None) 從截斷的正態分佈中輸出隨機值。生成的值服從具有指定平均值和標準偏差的正態分佈,如果生成的值大於平均值2個標準偏差的值則丟棄重新選擇。

引數:

shape:一維張量,也是輸出張量。

mean:正態分佈的均值

stddev:正態分佈的標準差

dtype:輸出型別

seed:一個整數,當設定之後,每次生成的隨機數都一樣

name:操作的名字

這個函式產生正態分佈,均值和標準差都是自己設定。

#tf.Variable.init(inital_value,trainable=True,collections=None,validate_shape=True,name=None)

 只有第一個引數initial_value是必須的。

因為在tensorFlow的世界裡,變數的定義和初始化是分開的,所有關於圖變數的賦值和計算都要通過tf.Session的run來進行。想要將所有圖變數進行集體初始化時,應該使用tf.global_variables_initializer.

tf.Variable是定義圖變數的一種方式,另一種是tf.get_variable,其必需引數(第一個引數)並不是圖變數的初始值,而是圖變數的名稱。但tf.Variable的用法更要豐富一點,當指定名稱的圖變數已經存在時表示獲取它,當指定名稱的圖變數不存在時表示定義它。

3.3

tf.nn.conv2d(input,w,strides,padding)

其中input為輸入,格式為[batch,height,width,channels],分別為【輸入的批次數量,影象的高(行數),寬(列數),通道(彩色為3,灰色為1)】

w為卷積矩陣,二維,分別為【高,寬】

strides為滑動視窗尺寸,分別為[1,height,width,1],通常strides[0]=strdes[3]=1,因為一般不會在一個個影象,一個個通道之間滑動

padding為擴充套件方式,有vaild和same(vaild是採用丟棄的方式,比如上述的input_width=13,只允許滑動2次,多餘的全部丟掉;same的方式,採用補全方式,對於上述情況,允許滑動三次,但是需要補三個元素,左奇右偶,在左邊補一個0,右邊補兩個0)

鑑於篇幅問題,下一篇中將函式等再做相應介紹。