YJango的前饋神經網路--代碼LV1
介紹
用TensorFlow來演示XOR門問題(可以換成任何問題,並用相同方法來解決)。
下面的代碼只是沒有任何枝葉的核心內容。全部代碼在github上。
準備工作
- ubuntu系統
- 安裝工具
模塊分布
訓練神經網路主要就三大模塊。掌握模塊核心任務後再學習分支內容,中途可任意增添,並且以後還會不斷發展。我不希望讀者在該篇展開過深。只要對神經網路有一個大概的感覺即可。如下圖所示,先抓住三大模塊。越靠右邊的內容,初學者就越不要上來就展開。
一、網路說明
- 打開jupyter notebook(安裝完anaconda直接就有)
- new->python
- 引入庫:
# tensorflow引入,並重命名為tfnimport tensorflow as tfnn# 矩陣操作庫nimport numpy as npn
- 網路結構:2維輸入 1維輸出
- 結構表達式:
- 正向傳遞: (1)
- 用於表達隨機變數 的值, 表示隨機變數 的值, 是我們的神經網路模型,等號右側是具體的表達。
- 損失函數:
- 該loss就是比較 和 中所有值的差別。
二、核心功能
計算圖說明:
請先閱讀:TensorFlow整體把握,這裡解釋了:
- 為什麼使用tensorflow、theano等這樣的工具。
- 計算圖是什麼
- TensorFlow基本用法
TensorFlow所建立的只是一個網路框架。在編程時,並不會有任何實際值出現在框架中。所有權重和偏移都是框架中的一部分,初始時至少給定初始值才能形成框架。因此需要initialization初始化。
實例說明:
以下是利用tensorboard生成的我們想要建立的學習XOR gate的網路可視化結構圖。
- 整體結構:左側的圖表示網路結構。綠色方框表示操作,也叫作層(layer)。該結構中,輸入 經過hid_layer算出隱藏層的值 ,再傳遞給out_layer,計算出預測值 ,隨後與真實值 進行比較,算出損失 ,再從反向求導得出梯度後對每一層的 和 進行更新。
- 正向傳遞:如果放大hid_layer內部,從下向上,會看到 先用truncated_normal的方法進行了初始化,隨後與輸入 進行矩陣相乘,加上 ,又經過了activation後,送給了用於計算 的out_layer中。而 的計算方式和 完全一致,但用的是不同的權重 和偏移 。最後將算出的預測值 與真實值 一同求出
- 反向傳遞:如果放大train的內部,再放大內部中的gradients,就可以看到框架是從 開始一步步反向求得各個層中 和 的梯度的。
- 權重更新:求出的各個層 和 的梯度,將會被用於更新對應的 和 ,並用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()快速取值等功能。
- 初始化:雖然在先前定義權重 時選擇了初始化方式,但要實際執行該操作需要將操作(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
- 說明:預測時與目標值 無關,只需要將輸入 到網路中即可預測該 對應哪個 ,而預測好壞取決於訓練的網路本身。
- 隱藏層:
#查看隱藏層的輸出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()獲取網路框架中的任何值。比如查看隱藏層的權重 和 :
sess.run([W_h,W_o])n
- 多值輸出:可將多個想要的值放入一個list中傳遞給 sess.run()實現多值輸出。
- 變數無需容器: 和 的獲取並未需要輸入placeholder。因為訓練好後,它們的值與placeholder的值無關,所以不需要輸出。
上面已經演示了最基本,也是核心代碼內容。雖然簡陋,但完全可以完成任務。
深層神經網路就是簡單的在此基礎上增加更多的隱藏層。深層神經網路的實現非常簡單,並沒有人們想想的那麼高大上。
然而深層學習的難點不在網路實現上,而在對數據的分析和反饋、當遇到無法擬合的情況該如何處理。為了能夠記錄和分析結果,我們會想知道誤差下降的過程,網路在某時訓練的如何,比如下圖:可以看出來訓練在第30000次後的誤差就幾乎為0了。但上述的代碼可以讓我們完成訓練卻沒有這些分析功能。下章是關於記錄、分析、代碼重用性等功能所需要的「枝葉」。
推薦閱讀:
※TensorFlow與中文手寫漢字識別
※深入淺出Tensorflow(四):卷積神經網路
※利用TensorFlow搞定知乎驗證碼之《讓你找中文倒轉漢字》
※深入淺出Tensorflow(五):循環神經網路簡介
TAG:深度学习DeepLearning | TensorFlow |