YJango的前饋神經網路--代碼LV1

介紹

用TensorFlow來演示XOR門問題(可以換成任何問題,並用相同方法來解決)。

下面的代碼只是沒有任何枝葉的核心內容。全部代碼在github上。

準備工作

  • ubuntu系統
  • 安裝工具

模塊分布

訓練神經網路主要就三大模塊。掌握模塊核心任務再學習分支內容,中途可任意增添,並且以後還會不斷發展。我不希望讀者在該篇展開過深。只要對神經網路有一個大概的感覺即可。如下圖所示,先抓住三大模塊。越靠右邊的內容,初學者就越不要上來就展開。

一、網路說明

  • 打開jupyter notebook(安裝完anaconda直接就有)
  • new->python
  • 引入庫:

# tensorflow引入,並重命名為tfnimport tensorflow as tfnn# 矩陣操作庫nimport numpy as npn

  • 網路結構:2維輸入 rightarrowrightarrow1維輸出

  • 結構表達式
    • 正向傳遞: y=M(x)=relu(W_{h} cdot relu(W_{x} cdot x+b_{x})+b_{h}) (1)
      • y 用於表達隨機變數 Y 的值, x 表示隨機變數 X 的值, M(x) 是我們的神經網路模型,等號右側是具體的表達。
    • 損失函數: loss=1/2cdot sumlimits_i (y_i-t_i)^2
      • 該loss就是比較 yt 中所有值的差別。

二、核心功能

計算圖說明:

請先閱讀:TensorFlow整體把握,這裡解釋了:

  • 為什麼使用tensorflow、theano等這樣的工具。
  • 計算圖是什麼
  • TensorFlow基本用法

TensorFlow所建立的只是一個網路框架。在編程時,並不會有任何實際值出現在框架中。所有權重和偏移都是框架中的一部分,初始時至少給定初始值才能形成框架。因此需要initialization初始化。

實例說明:

以下是利用tensorboard生成的我們想要建立的學習XOR gate的網路可視化結構圖。

  • 整體結構:左側的圖表示網路結構。綠色方框表示操作,也叫作層(layer)。該結構中,輸入 x 經過hid_layer算出隱藏層的值 h ,再傳遞給out_layer,計算出預測值 y ,隨後與真實值 t 進行比較,算出損失 loss ,再從反向求導得出梯度後對每一層的 Wb 進行更新。

  • 正向傳遞:如果放大hid_layer內部,從下向上,會看到 W_h 先用truncated_normal的方法進行了初始化,隨後與輸入 x 進行矩陣相乘,加上 b_h ,又經過了activation後,送給了用於計算 y 的out_layer中。而 y 的計算方式和 h 完全一致,但用的是不同的權重 W_o 和偏移 b_o 。最後將算出的預測值 y 與真實值 t 一同求出 loss

  • 反向傳遞:如果放大train的內部,再放大內部中的gradients,就可以看到框架是從 loss 開始一步步反向求得各個層中 Wb 的梯度的。

  • 權重更新:求出的各個層 Wb 的梯度,將會被用於更新對應的 Wb ,並用learning rate控制一次更新多大。(beta1_power和beta2_power是Adam更新方法中的參數,目前只需要知道權重更新的核心是各自對應的梯度。)

三、代碼實現

網路模塊

  • 變數定義:定義節點數和學習速率

# 網路結構:2維輸入 --> 2維隱藏層 --> 1維輸出n# 學習速率(learning rate):0.0001nnD_input = 2nD_label = 1nD_hidden = 2nlr = 1e-4n

正向傳遞:到預測值為止的網路框架。在預測時,只拿的輸出。

  • 容器:用tf.placeholder創建輸入值和真實值的容器,編程過程中始終是個空的,只有載入到sess中才會放入具體值。這種容器便是存放tensor的數據類型。

x = tf.placeholder(tf.float32, [None, D_input], name="x")nt = tf.placeholder(tf.float32, [None, D_label], name="t")n

    • 精度: 如果是用GPU訓練,浮點精度要低於32bit,由第一個參數tf.float32定義。
    • 矩陣形狀: 輸入輸出的容器都是矩陣。為的是可以進行mini-batch一次算多個樣本的平均梯度來訓練。None意味著樣本數可隨意改變。
    • 命名:控制tensorboard生成圖中的名字,也會方便debug。
  • 隱藏層

# 初始化WnW_h1 = tf.Variable(tf.truncated_normal([D_input, D_hidden], stddev=0.1), name="W_h")n# 初始化bnb_h1 = tf.Variable(tf.constant(0.1, shape=[D_hidden]), name="b_h")n# 計算Wx+bnpre_act_h1 = tf.matmul(x, W_h1) + b_h1n# 計算a(Wx+b)nact_h1 = tf.nn.relu(pre_act_h1, name=act_h)n

    • 變數:tensorflow中的變數tf.Variable是用於定義在訓練過程中可以更新的值。權重W和偏移b正符合該特點。
    • 初始化:合理的初始化會給網路一個比較好的訓練起點,幫助逃脫局部極小值(or 鞍點)。詳細請回顧梯度下降訓練法。tf.truncated_normal([D_input, D_hidden], stddev=0.1)是初始化的一種方法(還有很多種),其中[imcoing_dim, outputing_dim]是矩陣的形狀,前後參數的意義是進入該層的維度(節點個數)和輸出該層的維度。stddev=是用於初始化的標準差。
    • 矩陣乘法:tf.matmul(x, W_h1)用於計算矩陣乘法
    • 激活函數:除了tf.nn.relu()還有tf.nn.tanh(),tf.nn.sigmoid()
  • 輸出層:同隱藏層的計算方式一致,但輸出層的「輸入」是隱藏層的「輸出」。

W_o = tf.Variable(tf.truncated_normal([D_hidden, D_label], stddev=0.1), name="W_o")nb_o = tf.Variable(tf.constant(0.1, shape=[D_label]), name="b_o")npre_act_o = tf.matmul(act_h1, W_o) + b_ony = tf.nn.relu(pre_act_o, name=act_y)n

反向傳遞:計算誤差值來更新網路權重的結構。

  • 損失函數:定義想要不斷縮小的損失函數。

loss = tf.reduce_mean((self.output-self.labels)**2)n

  • 更新方法:選擇想要用於更新權重的訓練方法和每次更新步伐(lr),除tf.train.AdamOptimizer外還有tf.train.RMSPropOptimizer等。默認推薦AdamOptimizer。

train_step = tf.train.AdamOptimizer(lr).minimize(loss)n

    • 學習速率:AdamOptimizer(lr)的括弧內放入學習速率。
    • 優化目標:minimize(loss)的括弧內放入想要縮小的值。

準備數據:用numpy.array格式準備XOR的輸入和輸出,即訓練數據

#X和Y是4個數據的矩陣,X[i]和Y[i]的值始終對應。nX=[[0,0],[0,1],[1,0],[1,1]]nY=[[0],[1],[1],[0]]nX=np.array(X).astype(int16)nY=np.array(Y).astype(int16)n

  • 數據類型:用python使用tensorflow時,輸入到網路中的訓練數據需要以np.array的類型存在。並且要限制dtype為32bit以下。變數後跟著「.astype(float32)」總可以滿足要求。

載入訓練:將建好的網路載入到session中執行操作。

#創建sessionnsess = tf.InteractiveSession()n#初始化權重ntf.initialize_all_variables().run()n

  • 創建方式:sess = tf.InteractiveSession()是比較方便的創建方法。也有sess = tf.Session()方式,但該方式無法使用tensor.eval()快速取值等功能。
  • 初始化:雖然在先前定義權重 W 時選擇了初始化方式,但要實際執行該操作需要將操作(op)載入到session中。
  • 訓練網路

T=10000 #訓練幾次nfor i in range(T):n sess.run(train_step,feed_dict={x:X,t:Y})n

    • 說明:sess.run(,)接受的兩個參數。前一個參數的類型是list,表示所有想要執行的操作(op)或者想要獲取的值。而後一個參數會需要執行第一個參數時相關placeholder的值。並且以feed_dict=字典的方式賦值。{x:X,t:Y}中的x是key名,對應著placeholder,而冒號後的是計算時的具體輸入值。
    • GD(Gradient Descent):X和Y是4組不同的訓練數據。上面將所有數據輸入到網路,算出平均梯度來更新一次網路的方法叫做GD。效率很低,也容易卡在局部極小值,但更新方向穩定。
    • SGD(Gradient Descent):一次只輸入一個訓練數據到網路,算出梯度來更新一次網路的方法叫做SGD。效率高,適合大規模學習任務,容易掙脫局部極小值(或鞍點),但更新方向不穩定。代碼如下:

T=10000 #訓練幾epochnfor i in range(T):n for j in range(X.shape[0]): #X.shape[0]表示樣本個數n sess.run(train_step,feed_dict={x:X[j],t:Y[j]})n

    • batch-GD:這是上面兩個方法的折中方式。每次計算部分數據的平均梯度來更新權重。部分數據的數量大小叫做batch_size,對訓練效果有影響。一般10個以下的也叫mini-batch-GD。代碼如下:

T=10000 #訓練幾epochnb_idx=0 #batch計數nb_size=2 #batch大小nfor i in range(T):n while batch_idx<=X.shape[0]:n sess.run(train_step,feed_dict={x:X[b_idx:b_idx+b_size],t:Y[b_idx:b_idx+b_size]})n b_idx+=b_size #更新batch計數n

    • shuffle:SGD和batch-GD由於只用到了部分數據。若數據都以相同順序進入網路會使得隨後的epoch影響很小。shuffle是用於打亂數據在矩陣中的排列順序,提高後續epoch的訓練效果。代碼如下:

#shuffle函數ndef shufflelists(lists): #多個序列以相同順序打亂n ri=np.random.permutation(len(lists[1]))n out=[]n for l in lists:n out.append(l[ri])n return outn#訓練網路nT=10000 #訓練幾epochnb_idx=0 #batch計數nb_size=2 #batch大小nfor i in range(T):n#每次epoch都打亂順序n X,Y = shufflelists([X,Y])n while batch_idx<=X.shape[0]:n sess.run(train_step,feed_dict={x:X[b_idx:b_idx+b_size],t:Y[b_idx:b_idx+b_size]})n b_idx+=b_size #更新batch計數n

  • 預測

#計算預測值nsess.run(y,feed_dict={x:X})n#輸出:已訓練100000次narray([[ 0.],n [ 1.],n [ 1.],n [ 0.]], dtype=float32)n

    • 說明:預測時與目標值 t 無關,只需要將輸入 x 到網路中即可預測該 x 對應哪個 y ,而預測好壞取決於訓練的網路本身。
  • 隱藏層

#查看隱藏層的輸出nsess.run(act_h1,feed_dict={x:X})n#輸出:已訓練100000次narray([[ 1.10531139, 1.00508392],n [ 0.55236477, 0. ],n [ 0.55236477, 0. ],n [ 0. , 0. ]], dtype=float32)n

    • 說明:act_h1是隱藏層的輸出。可以看到隱藏層前有4個變體,而經過隱藏層後,只有3個變體。
  • 查看其他值:用戶可以用 sess.run()獲取網路框架中的任何值。比如查看隱藏層的權重 W_hW_o

sess.run([W_h,W_o])n

    • 多值輸出:可將多個想要的值放入一個list中傳遞給 sess.run()實現多值輸出。
    • 變數無需容器W_hW_o 的獲取並未需要輸入placeholder。因為訓練好後,它們的值與placeholder的值無關,所以不需要輸出。

上面已經演示了最基本,也是核心代碼內容。雖然簡陋,但完全可以完成任務。

深層神經網路就是簡單的在此基礎上增加更多的隱藏層。深層神經網路的實現非常簡單,並沒有人們想想的那麼高大上。

然而深層學習的難點不在網路實現上,而在對數據的分析和反饋、當遇到無法擬合的情況該如何處理。為了能夠記錄和分析結果,我們會想知道誤差下降的過程,網路在某時訓練的如何,比如下圖:可以看出來訓練在第30000次後的誤差就幾乎為0了。但上述的代碼可以讓我們完成訓練卻沒有這些分析功能。下章是關於記錄、分析、代碼重用性等功能所需要的「枝葉」。


推薦閱讀:

TensorFlow與中文手寫漢字識別
深入淺出Tensorflow(四):卷積神經網路
利用TensorFlow搞定知乎驗證碼之《讓你找中文倒轉漢字》
深入淺出Tensorflow(五):循環神經網路簡介

TAG:深度学习DeepLearning | TensorFlow |