一、多層感知機簡介

Softmax迴歸可以算是多分類問題logistic迴歸,它和神經網路的最大區別是沒有隱含層。理論上只要隱含節點足夠多,即時只有一個隱含層的神經網路也可以擬合任意函式,同時隱含層越多,越容易擬合複雜結構。為了擬合複雜函式需要的隱含節點的數目,基本上隨著隱含層的數量增多呈指數下降的趨勢,也就是說層數越多,神經網路所需要的隱含節點可以越少。層數越深,概念越抽象,需要背誦的知識點就越少。在實際應用中,深層神經網路會遇到許多困難,如過擬合、引數除錯、梯度彌散等。

過擬合是機器學習中的一個常見問題,是指模型預測準確率在訓練集上升高,但是在測試集上的準確率反而下降,這通常意味著模型的泛化能力不好,過度擬合了訓練集。針對這個問題,Hinton教授團隊提出了Dropout的解決辦法,在使用CNN訓練影象資料時效果尤其有效,其大體思路是在訓練時將神經網路某一層的輸出節點資料隨機丟失一部分。這種做法實質上等於創造出了許多新的隨機樣本,通過增大樣本量、減少特徵數量來防止過擬合。

引數除錯問題尤其是SGD(StochasticGradient Descent)的引數,對SGD設定不同的學習率learning rate,最後得到的結果可能差異巨大。神經網路的優化通常不是一個簡單的凸優化問題,它處處充滿了區域性最優。有理論表示,神經網路可能有很多個區域性最優解都可以達到比較好的分類效果,而全域性最優很可能造成過擬合。對SGD,我們希望一開始學習率大一些,加速收斂,在訓練的後期又希望學習率小一些,這樣可以低速進入一個區域性最優解。不同的機器學習問題的學習率設定也需要針對性的除錯,像Adagrad、Adam、Adadelta等自適應的方法可以減輕除錯引數的負擔。對於這些優化演算法,通常我們使用其預設的引數設定就可以得到比較好的效果。

梯度彌散(Gradient Vanishment)是另一個影響深層神經網路訓練的問題,在ReLU啟用函數出現之前,神經網路訓練是使用Sigmoid作為啟用函式。非線性的Sigmoid函式在訊號的特徵空間對映上,對中央區的訊號增益較大,對兩側區的訊號增益小。當神經網路層數較多時,Sigmoid函式在反向傳播中梯度值會逐漸減小,到達前面幾層的梯度值就變得非常小了,在神經網路訓練的時候,前面幾層的神經網路引數幾乎得不到訓練更新。指導ReLU,y = max(0, x),的出現才比較完美的解決了梯度彌散的問題。訊號在超過某個閾值時,神經元才會進入興奮和啟用的狀態,否則會處於抑制狀態。ReLU可以很好的反向傳遞梯度,經過多層的梯度反向傳播,梯度依舊不會大幅減小,因此非常適合深層神經網路的訓練。ReLU對比於Sigmoid的主要特點有以下幾點:(1)單側抑制;(2)相對寬闊的興奮邊界;(3)稀疏啟用性。目前,ReLU及其變種EIU、PReLU、RReLU已經成為最主流的啟用函式。實踐中大部分情況下(包括MLP、CNN、RNN)將隱含層的啟用函式從Sigmoid替換為ReLU都可以帶來訓練速度和模型準確率的提升。當然神經網路的輸出層一般都是Sigmoid函式,因為它最接近概率輸出分佈。

二、TensorFlow實現過程

完整程式碼:

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
in_units = 784 #輸入節點數
h1_units = 300 #隱含層節點數
W1 = tf.Variable(tf.truncated_normal([in_units, h1_units], stddev=0.1)) #初始化隱含層權重W1,服從預設均值為0,標準差為0.1的截斷正態分佈
b1 = tf.Variable(tf.zeros([h1_units])) #隱含層偏置b1全部初始化為0
W2 = tf.Variable(tf.zeros([h1_units, 10])) 
b2 = tf.Variable(tf.zeros([10]))
x = tf.placeholder(tf.float32, [None, in_units])
keep_prob = tf.placeholder(tf.float32) #Dropout失活率

#定義模型結構
hidden1 = tf.nn.relu(tf.matmul(x, W1) + b1)
hidden1_drop = tf.nn.dropout(hidden1, keep_prob)
y = tf.nn.softmax(tf.matmul(hidden1_drop, W2) + b2)

#訓練部分
y_ = tf.placeholder(tf.float32, [None, 10])
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))
train_step = tf.train.AdagradOptimizer(0.3).minimize(cross_entropy)

#定義一個InteractiveSession會話並初始化全部變數
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()
correct_prediction = tf.equal(tf.arg_max(y, 1), tf.arg_max(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
for i in range(3001):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    train_step.run({x: batch_xs, y_: batch_ys, keep_prob: 0.75})
    if i % 200 ==0:
		#訓練過程每200步在測試集上驗證一下準確率,動態顯示訓練過程
        print(i, 'training_arruracy:', accuracy.eval({x: mnist.test.images, y_: mnist.test.labels, 
                             keep_prob: 1.0}))
print('final_accuracy:', accuracy.eval({x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))

在TensorFlow上實現的Softmax迴歸模型對MNIST資料集取得了92%的正確率,現在我們給神經網路加上一層隱含層,並使用減輕過擬合的Dropout、自適應學習率的Adagrad以及解決梯度彌散問題啟用函式ReLU。

首先,載入TensorFlow的並載入MNIST、資料集。指定輸入節點數in_units和隱含層節點數h1_units。初始化隱含層的全中W1和偏置b1,因為模型使用的啟用函式是ReLU,需要使用正態分佈對W1進行初始化,給權重引數增加一些噪聲來打破完全對稱並避免0梯度。在其它一些模型中,有時還需要給偏置初始化一些非零初始值來避免dead neuron(死亡神經元)。對於輸出層Softmax,直接將全中W2和偏置b2全部初始化為0即可。接下來為輸入x設定placeholder,併為不同的Dropout設定一個輸入placeholder,通常在訓練時小於1,預測時等於1。

下面定義模型結構,首先定義一個隱含層hidden1,通過tf.nn.relu(tf.matmul(x,W1) + b1)實現一個啟用函式為ReLU的隱含層,這個隱含層的計算公式就是y = relu(W1x + b1)。接下來呼叫tf.nn.dropout實現Dropout功能,隨機將一部分神經元節點置為0,這裡的keep_prob引數是保留的資料比例而不是置為0的比例。在訓練的時候應該是小於1用以製造隨機性,防止過擬合;在預測的時候應該等於1,即全部特徵用來預測樣本的類別。

在優化器選擇上,我們選擇Adagrad並把學習率設定為0.3,這裡我們直接使用tf.train.AdagradOptimizer(0.3)就可以了。

最終,但隱含層MLP模型在測試集上可以達到98%的準確率,相比之前Softmax迴歸模型的92%的準確率有了飛躍性的提高。

三、其他補充說明

1. tf.truncated_normal

tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32,seed=None, name=None)

函式功能:返回指定形狀的服從指定均值和標準差的截斷正態分佈的tensor。