一、簡介
我們在前面的數據科學學習手札34中也介紹過,作為最典型的神經網絡,多層感知機(MLP)結構簡單且規則,並且在隱層設計的足夠完善時,可以擬合任意連續函數,而除了利用前面介紹的sklearn.neural_network中的MLP來實現多層感知機之外,利用tensorflow來實現MLP更加形象,使得使用者對要搭建的神經網絡的結構有一個更加清醒的認識,本文就將對tensorflow搭建MLP模型的方法進行一個簡單的介紹,並實現MNIST數據集的分類任務;
二、MNIST分類
作為數據挖掘工作中處理的最多的任務,分類任務占據了機器學習的大半江山,而一個網絡結構設計良好(即隱層層數和每個隱層神經元個數選擇恰當)的多層感知機在分類任務上也有着非常優越的性能,依然以MNIST手寫數字數據集作為演示,上一篇中我們利用一層輸入層+softmax搭建的分類器在MNIST數據集的測試集上達到93%的精度,下面我們使用加上一層隱層的網絡,以及一些tricks來看看能夠提升多少精度;
網絡結構:
這里我們搭建的多層前饋網絡由784個輸入層神經元——200個隱層神經元——10個輸出層神經元組成,而為了減少梯度彌散現象,我們設置relu(非線性映射函數)為隱層的激活函數,如下圖:
這種激活函數更接近生物神經元的工作機制,即在達到閾值之前持續抑制,在超越閾值之后開始興奮;而對輸出層,因為對數據做了one_hot處理,所以依然使用softmax進行處理;
Dropout:
過擬合是機器學習尤其是神經網絡任務中經常發生的問題,即我們的學習器將訓練集的獨特性質當作全部數據集的普遍性質,使得學習器在訓練集上的精度非常高,但在測試集上的精度卻非常低(這里假設訓練集與測試集數據分布一致),而除了隨機梯度下降的一系列方法外(如上一篇中我們提到的在每輪訓練中使用全體訓練集中一個小尺寸的訓練批來進行本輪的參數調整),我們可以使用類似的思想,將神經網絡某一層的輸出節點數據隨機丟棄一部分,即令這部分被隨機選中的節點輸出值令為0,這樣做等價於創造出很多新樣本,通過增大樣本量,減少特征數量來防止過擬合,dropout也算是一種bagging方法,可以將每次丟棄節點輸出視為對特征的一次采樣,相當於我們訓練了一個ensemble的神經網絡模型,對每個樣本都做特征采樣,並構成一個融合的神經網絡
學習效率:
因為神經網絡的訓練通常不是一個凸優化問題,它充滿了很多局部最優,因此我們通常不會采用標准的梯度下降算法,而是采用一些有更大可能跳出局部最優的算法,常用的如SGD,而SGD本身也不穩定,其結果也會在最優解附近波動,且設置不同的學習效率可能會導致我們的網絡落入截然不同的局部最優之中,對於SGD,我們希望開始訓練時學習率大一些,以加速收斂的過程,而后期學習率低一些,以更穩定地落入局部最優解,因此常使用Adagrad、Adam等自適應的優化方法,可以在其默認參數上取得較好的效果;
下面。就結合上述策略,利用tensorflow搭建我們的多層感知機來對MNIST手寫數字數據集進行訓練:
2.1 風格一
先使用朴素的風格來搭建網絡,首先還是照例從tensorflow自帶的數據集中提取出mnist數據集:
import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data '''導入MNIST手寫數據''' mnist = input_data.read_data_sets('MNIST_data/', one_hot = True)
接着使用交互環境下會話的方式,將生成的第一個會話作為默認會話:
'''注冊默認的session,之后的運算都會在這個session中進行''' sess = tf.InteractiveSession()
接着初始化輸入層與隱層間的784x300個權值、隱層神經元的300個bias、隱層與輸出層之間的300x10個權值、輸出層的10個bias,其中為了避免隱層的relu激活時陷入0梯度的情況,對輸入層和隱層間的權值初始化為均值為0,標准差為0.2的正態分布隨機數,對其他參數初始化為0:
'''定義輸入層神經元個數''' in_units = 784 '''定義隱層神經元個數''' h1_units = 300 '''為輸入層與隱層神經元之間的連接權重初始化持久的正態分布隨機數,這里權重為784乘300,300是隱層的尺寸''' W1 = tf.Variable(tf.truncated_normal([in_units, h1_units],mean=0,stddev=0.2)) '''為隱層初始化bias,尺寸為300''' b1 = tf.Variable(tf.zeros([h1_units])) '''初始化隱層與輸出層間的權重,尺寸為300X10''' W2 = tf.Variable(tf.zeros([h1_units, 10])) '''初始化輸出層的bias''' b2 = tf.Variable(tf.zeros([10]))
接着我們定義自變量、隱層神經元dropout中的保留比例keep_prob的輸入部件:
'''定義自變量的輸入部件,尺寸為 任意行X784列''' x = tf.placeholder(tf.float32, [None, in_units]) '''為dropout中的保留比例設置輸入部件''' keep_prob = tf.placeholder(tf.float32)
接着定義隱層relu激活部分的計算部件、隱層dropout部分的操作部件、輸出層softmax的計算部件:
'''定義隱層求解部件''' hidden1 = tf.nn.relu(tf.matmul(x, W1) + b1) '''定義隱層dropout操作部件''' hidden1_drop = tf.nn.dropout(hidden1, keep_prob) '''定義輸出層softmax計算部件''' y = tf.nn.softmax(tf.matmul(hidden1_drop, W2) + b2)
還有樣本真實分類標簽的輸入部件以及loss_function部分的計算組件:
'''定義訓練label的輸入部件''' y_ = tf.placeholder(tf.float32, [None, 10]) '''定義均方誤差計算部件,這里注意要壓成1維''' loss_function = tf.reduce_mean(tf.reduce_sum((y_ - y)**2, reduction_indices=[1]))
這樣我們的網絡結構和計算部分全部搭建完成,接下來至關重要的一步就是定義優化器的組件,它會完成自動求導調整參數的工作,這里我們選擇自適應的隨機梯度下降算法Adagrad作為優化器,學習率盡量設置小一些,否則可能會導致網絡的測試精度維持在一個很低的水平不變即在最優解附近來回震盪卻難以接近最優解(親測):
'''定義優化器組件,這里采用AdagradOptimizer作為優化算法,這是種變種的隨機梯度下降算法''' train_step = tf.train.AdagradOptimizer(0.18).minimize(loss_function)
接下來就到了正式的訓練過程了,我們激活當前會話中所有計算部件,並定義訓練步數為15000步,每一輪迭代選擇一個批量為100的訓練批來進行訓練,dropout的keep_prob設置為0.76,並在每50輪訓練完成后將測試集輸入到當前的網絡中計算預測精度,注意在正式預測時dropout的keep_prob應設置為1.0,即不進行特征的丟棄:
'''激活當前session中的全部部件''' tf.global_variables_initializer().run() '''開始迭代訓練過程,最大迭代次數為3001次''' for i in range(15000): '''為每一輪訓練選擇一個尺寸為100的隨機訓練批''' batch_xs, batch_ys = mnist.train.next_batch(100) '''將當前輪迭代選擇的訓練批作為輸入數據輸入train_step中進行訓練''' train_step.run({x: batch_xs, y_: batch_ys, keep_prob:0.76}) '''每500輪打印一次當前網絡在測試集上的訓練結果''' if i % 50 == 0: print('第',i,'輪迭代后:') '''構造bool型變量用於判斷所有測試樣本與其真是類別的匹配情況''' correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)) '''將bool型變量轉換為float型並計算均值''' accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) '''激活accuracy計算組件並傳入mnist的測試集自變量、標簽及dropout保留比率,這里因為是預測所以設置為全部保留''' print(accuracy.eval({x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
經過全部迭代后,我們的多層感知機在測試集上達到了0.9802的精度表現如下圖:
事實上在訓練到10000輪左右的時候我們的多層感知機就已經到達這個精度了,說明此時的網絡已經穩定在當前的最優解中,后面的訓練過程只是在這個最優解附近微弱的震盪而已,所以實際上可以設置更小的迭代輪數;
2.2 風格二
除了上述的將網絡結構部件的所有語句一條一條平鋪直敘般編寫的風格外,還有一種以自編函數來快捷定義網絡結構的編程風格廣受歡迎,如下:
在添加神經層的時候,我們可以事先編寫自編函數如下:
'''自定義神經層添加函數''' def add_layer(inputs, in_size, out_size, activation_function=None): '''定義權重項''' Weights = tf.Variable(tf.random_normal([in_size,out_size])) '''定義bias項''' biases = tf.Variable(tf.zeros([1,out_size]) + 0.1) '''權重與輸入值的矩陣乘法+bias項''' Wx_plus_b = tf.matmul(inputs, Weights) + biases '''根據激活函數的設置來處理輸出項''' if activation_function is None: outputs = Wx_plus_b else: outputs = activation_function(Wx_plus_b) return outputs
這樣就可以用一個函數來代替數條語句,下面我們用這種風格來實現前面同樣的功能:
import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data '''導入MNIST手寫數據''' mnist = input_data.read_data_sets('MNIST_data/', one_hot = True) '''自定義神經層添加函數''' def add_layer(inputs, in_size, out_size, activation_function=None): '''定義權重項''' Weights = tf.Variable(tf.truncated_normal([in_size,out_size],mean=0, stddev=0.2)) '''定義bias項''' biases = tf.Variable(tf.zeros([1,out_size]) + 0.1) '''權重與輸入值的矩陣乘法+bias項''' Wx_plus_b = tf.matmul(inputs, Weights) + biases '''根據激活函數的設置來處理輸出項''' if activation_function is None: outputs = Wx_plus_b else: outputs = activation_function(Wx_plus_b) return outputs '''創建樣本數據和dropout參數的輸入部件''' x = tf.placeholder(tf.float32, [None, 784]) y = tf.placeholder(tf.float32, [None, 10]) keep_prob = tf.placeholder(tf.float32) '''利用自編函數來生成隱層的計算部件,這里activation_function設置為relu''' l1 = add_layer(x, 784,300, activation_function=tf.nn.relu) '''對l1進行dropout處理''' l1_dropout = tf.nn.dropout(l1,keep_prob) '''利用自編函數對dropout后的隱層輸出到輸出層輸出創建計算部件,這里將activation_function改成softmax''' prediction = add_layer(l1_dropout, 300, 10, activation_function=tf.nn.softmax) '''根據均方誤差構造loss function計算部件''' loss = tf.reduce_mean(tf.reduce_sum((prediction - y)**2, reduction_indices=[1])) '''定義優化器部件''' train_step = tf.train.AdagradOptimizer(0.3).minimize(loss) '''激活所有部件''' init = tf.global_variables_initializer() '''創建新會話''' sess = tf.Session() '''在新會話中激活所有部件''' sess.run(init) '''10001次迭代訓練,每200次輸出一次當前網絡在測試集上的精度''' for i in range(10001): '''每次從訓練集中抽出批量為200的訓練批進行訓練''' x_batch,y_batch = mnist.train.next_batch(200) '''激活sess中的train_step部件''' sess.run(train_step, feed_dict={x:x_batch, y:y_batch, keep_prob:0.75}) '''每200次迭代打印一次當前精度''' if i %200 == 0: print('第',i,'輪迭代后:') '''創建精度計算部件''' whether_correct = tf.equal(tf.argmax(y, 1), tf.argmax(prediction, 1)) accuracy = tf.reduce_mean(tf.cast(whether_correct, tf.float32)) '''在sess中激活精度計算部件來計算當前網絡的精度''' print(sess.run(accuracy, feed_dict={x:mnist.test.images, y:mnist.test.labels, keep_prob:1.0}))
同樣的,在10000次迭代后,我們的單隱層前饋網絡取得了0.98的精度:
以上就是關於tensorflow搭建MLP模型的基本內容,如有筆誤,望指出。