本節涉及:
- 身份證問題
- 單層網絡的模型
- 多層全連接神經網絡
- 激活函數 tanh
- 身份證問題新模型的代碼實現
- 模型的優化
一、身份證問題
身份證號碼是18位的數字【此處暫不考慮字母的情況】,身份證倒數第2個數字代表着性別。
奇數,代表男性,偶數,代表女性
假設事先不知道這個規則,但收集了足夠多的身份證及相應的性別信息。希望通過神經網絡來找到這個規律
分析:
- 顯然,身份證號可以作為神經網絡的輸入,而持有者的性別即是神經網絡計算結果的目標值,所以,我們有完備的訓練數據
- 性別有男女,顯然是一個二分類問題
- 初步判斷,顯然不是一個 線性問題 ----> 線性問題一般會隨着權重值的變化有一個線性變化的范圍
- 分析可得到,這也不是一個 跳變的非線性問題,因為它不像之前的三號學生問題,有一個門檻,門檻內外就是兩個分類,所以,原有的單神經元結構可能很難解決這個問題
二、單層網絡的模型
import tensorflow as tf import random random.seed() x = tf.placeholder(tf.float32) yTrain = tf.placeholder(tf.float32) w = tf.Variable(tf.random_normal([4], mean=0.5, stddev=0.1), dtype=tf.float32) # random_normal 函數是一個產生隨機數的函數,此處w 的形態是[4] ,即一個四維的向量,使用此函數賦值后,w其中的每一個數字都會被賦值位隨機數 # random_normal 產生的隨機數是符合 正態分布概率的,即 隨機數會在某個平均值附近的一定范圍內波動 # mean 是指定這個平均值的, stddev 是指定這個波動范圍的 b = tf.Variable(0, dtype=tf.float32) # 增加了一個 偏移量b,這里是一個標量。也可以定義為和可變參數 w 形態相同的 向量或矩陣 n1 = w * x + b y = tf.nn.sigmoid(tf.reduce_sum(n1)) # 求和 + sigmoid 函數 loss = tf.abs(y - yTrain) optimizer = tf.train.RMSPropOptimizer(0.01) train = optimizer.minimize(loss) sess = tf.Session() sess.run(tf.global_variables_initializer()) lossSum = 0.0 for i in range(10): xDataRandom = [int(random.random() * 10), int(random.random() * 10), int(random.random() * 10), int(random.random() * 10)] # 生成隨機訓練數據 if xDataRandom[2] % 2 == 0: yTrainDataRandom = 0 else: yTrainDataRandom = 1 # 根據生成的隨機訓練數據的第3項數字得到標准輸出數據 result = sess.run([train, x, yTrain, y, loss], feed_dict={x: xDataRandom, yTrain: yTrainDataRandom}) lossSum = lossSum + float(result[len(result) - 1])# 記錄訓練中誤差的總和,並且在每次訓練時,將它的值除以 訓練次數得到平均誤差輸出,作為參考 print("i: %d, loss: %10.10f, avgLoss: %10.10f" % (i, float(result[len(result) - 1]), lossSum / (i + 1))) #可變參數 w b 的取值在本題中 意義不大,僅觀察誤差的變化情況就可以了,所以此處輸出 本次訓練誤差 + 累計平均誤差
i: 0, loss: 0.9999923706, avgLoss: 0.9999923706 i: 1, loss: 1.0000000000, avgLoss: 0.9999961853 i: 2, loss: 0.1363981962, avgLoss: 0.7121301889 i: 3, loss: 0.9999995232, avgLoss: 0.7840975225 i: 4, loss: 0.9998092055, avgLoss: 0.8272398591 i: 5, loss: 0.0000855923, avgLoss: 0.6893808146 i: 6, loss: 0.9999967813, avgLoss: 0.7337545242 i: 7, loss: 0.0000003576, avgLoss: 0.6420352533 i: 8, loss: 0.9997699857, avgLoss: 0.6817835569 i: 9, loss: 0.9982397556, avgLoss: 0.7134291768
平均誤差有一定的變化,說明神經網絡的調節在發揮作用,加大訓練次數為5000:
i: 4991, loss: 0.9999963045, avgLoss: 0.4365434793 i: 4992, loss: 1.0000000000, avgLoss: 0.4366563286 i: 4993, loss: 0.0255949926, avgLoss: 0.4365740175 i: 4994, loss: 1.0000000000, avgLoss: 0.4366868155 i: 4995, loss: 0.0000000000, avgLoss: 0.4365994082 i: 4996, loss: 1.0000000000, avgLoss: 0.4367121560 i: 4997, loss: 1.0000000000, avgLoss: 0.4368248587 i: 4998, loss: 0.0178810358, avgLoss: 0.4367410531 i: 4999, loss: 0.0090432763, avgLoss: 0.4366555136
平均誤差在 0.43 左右浮動,基本穩定下來,再多加訓練次數也不會讓誤差減小,說明,目前的神經網絡模型無法解決這個問題,需要優化
常見的優化神經網絡結構的方法包括:
增加神經元節點數量 增加隱藏層的數量
另外本模型中的隱藏層並不是全連接層,全連接層應該是 前后兩層的所有節點之間都有連線,我們的結構顯然不是,這也是可以優化的
三、多層全連接神經網絡
神經網絡中,最常見的形式就是 全連接層 ----- 各個節點與上一層的每個節點都有連線
深度學習神經網絡中,一般都有多個隱藏層順序排列組成完整的神經網絡
(1)矩陣乘法
向量點乘:
兩個形態相同的向量相乘,得到的結果是一個形態與他們都相同的新向量,其中的每一個位置的數值都是參與相乘的兩個向量相同位置的數值相乘的結果
[1,2,3] * [2,3,4] = [2,6,12]
矩陣的點乘也是類似的,得到的還是相同形態的矩陣,在tensorflow 中,用 “ * ” 表示 點乘
用點乘的形式做出來的神經網絡模型就是類似之前的模型結構,輸入層各個節點與隱藏層的各個節點之間是有一對一的連線,一對一的連線就代表了 點乘的關系【即向量中相同位置的項才能進行運算】
但遇上的問題,很難用 一對一 的關系 來限定,往往是 事物之間互項有聯系且聯系關系強弱未知。因此,我們往往 在隱藏層中 設置較多的全連接結構的節點,來覆蓋盡可能多的可能性。如果某兩節點直接沒關系,經過運算會發現 w =0, 說明這條連線其實沒有意義
要實現全連接的關系,就要用到 矩陣的乘法:
import numpy as np a = [[1,2,3],[4,5,6]] b = [[1,2],[3,4],[5,6]] c = np.matmul(a,b) print(c)
[[22 28] [49 64]]
numpy 包中的 matmul() 函數就是用來進行矩陣乘法運算的
由上方示例可知:
- 當且僅當 前面矩陣的列數等於等於后面矩陣的行數時,二矩陣才可以進行矩陣乘法【叉乘】
- 新矩陣的行數 等於前面矩陣的行數,新矩陣的列數 等於后面矩陣的列數
- 矩陣的乘法是有順序的
另外,結果矩陣中,每個數值都是 點乘求和的結果,也就是說明這個值與參與計算的對應矩陣的對應行列的數值都是有關系的,把整個結果矩陣的所有數值合起來看,里面有參與計算的兩個矩陣中的所有數值的貢獻,因此,我們可以把這看作一種”全連接關系“。 ————> 即,可以用矩陣的乘法實現”全連接“關系
(2)用矩陣乘法實現全連接層
用之前三好學生的例子來做用矩陣乘法實現全連接關系的詳細說明
相關解釋:
- 輸入層仍然使用 x 表示,有3個項,分別代表德 智 體 三個得分
- 隱藏層有4個節點,用張量n 表示,且輸入層與隱藏層的各個節點是全連接的
用矩陣乘法實現這個全連接關系:
(A)確定參與運算的都是矩陣
x 是一個向量,並非矩陣【因為向量在計算機中的表達形式是 一維數組,而矩陣是二維數組】
所以,需要把x 轉化為 矩陣
x = [90,80,70]
可以把x 轉換為 1 * 3 矩陣 ,即形態為 [1,3] 的二維數組
x = [[90,80,70]]
二維數組 x ,第一個維度只有一項,對於矩陣來說就是一行
第二個維度是3項分數,對於矩陣來說就是3列
在隱藏層中,我們准備用 4 個神經元節點來處理,意味着 隱藏層對下一層輸出的數據項會是4個,用矩陣表達,就是需要隱藏層的輸出結果是一個 1X 4 矩陣,也就是一個形態為 [1,4] 的二維數組
所以,隱藏層中的可變參數 w 現在也需要是 矩陣的形式。由 新矩陣的行數 等於前面矩陣的行數,新矩陣的列數 等於后面矩陣的列數 可知:
二矩陣相乘后的結果矩陣:
所以,w 需要是一個 3 X 4 的矩陣
(B)體現全連接關系
第一個節點的輸出 46 = 90 * 0. 1 + 80 * 0.2 + 70 * 0.3 = 9 + 16 + 21 = 46。其余節點同理
設計思想:
對於輸入形態[1,3] 的矩陣x ,將其每一項 看作是 輸入層的一個節點,對於隱藏層的節點n ,把它每一項也看做 一個節點,也就是 46 70 94 118 對應的節點n1 n2 n3 n4
對於可變參數w,我們把它的每一列組成一個向量,w1 w2 w3 w4
可以由矩陣的運算規律,隱藏層中的第一個節點 n1 的計算過程是 x1 x2 x3 組成的向量 與 向量w1 點乘求和后得到,即節點n1 的輸出結果中包含了所有輸入節點的貢獻,即 n1 是與輸入層所有節點都是有關系的,n2 n3 n4也同理。反過來看,對於每個輸入層的節點 ,都與每個隱藏層的節點都有關系,這樣的 兩層之間不同節點都有連接關系的層,就是全連接層。
由上,用矩陣乘法可以很方便的實現全連接關系
總結:
如果把神經網絡的每一層的輸出 都看成矩陣,矩陣w 的行數要等於上一層的輸出矩陣的列數,w的列數就是本層神經元節點的數量,也是本層輸出矩陣的列數
一般會讓全連接層的節點多於輸入層的節點數,以便實現更靈活的可變參數調整
(3)使用均方誤差作為計算誤差的方法
我們之前計算誤差的時候,都是
loss = tf.abs(y - yTrain)
y 是神經網絡的計算結果,yTrain 是目標值,abs 是 tensorFlow 中取絕對值的函數
這種方式更適合數值范圍較大的情況
而三好學生的評選結果 身份證問題 都是一個 二分類問題中的概率: 比如,“1” 是三號學生的概率為 100% ,“0” 是三好學生的概率是 0% , "0.2" 是三好學生的概率是 20%
對於結果是分類概率的問題,均方誤差是一種更好的誤差計算方法
均方誤差:是指結果值向量中各數據項偏離目標值的距離的平方和的平均數,也就是誤差平方和的平均數
代碼驗證:
import tensorflow as tf y = tf.placeholder(dtype=tf.float32) yTrain = tf.placeholder(dtype=tf.float32) loss = tf.reduce_mean(tf.square(y-yTrain)) sess = tf.Session() print(sess.run(loss,feed_dict={y:[0.2,0.8],yTrain:[1,0]})) print(sess.run(loss,feed_dict={y:[0.2,0.8],yTrain:[0,1]})) print(sess.run(loss,feed_dict={y:[1.0,0.0],yTrain:[0,1]})) print(sess.run(loss,feed_dict={y:[1.0,0.0],yTrain:[1,0]})) print(sess.run(loss,feed_dict={y:[0.2,0.3,0.5],yTrain:[1,0,0]})) print(sess.run(loss,feed_dict={y:[0.2,0.3,0.5],yTrain:[0,1,0]})) print(sess.run(loss,feed_dict={y:[0.2,0.3,0.5],yTrain:[0,0,1]}))
0.64000005 0.04 1.0 0.0 0.32666668 0.26 0.12666667
解釋:
上方代碼中,為了直接看到均方誤差的計算結果,把y 也定義成一個占位符,以便后續直接喂數據
tf.square 求平方值的函數,可以對標量求平方,也可以對向量求平方【每一項平方】
tf.reduce_mean 是對一個矩陣(或向量) 中的所有數求平均值
loss = tf.reduce_mean(tf.square(y-yTrain)) 就是求y 與 yTrain 的均方差
最后幾條,嘗試用均方誤差計算三分類問題的誤差
四、激活函數 tanh
之前,我們使用的是 sigmoid函數 進行神經網絡的去線性化,把任意一個數字收斂到 [0,1] 的范圍內,從而把一組線性的數據 轉換為 非線性的數據
tanh 也是一個去線性化的函數,會把任何一個數字轉換為 [-1,1] 范圍內的數字
在設計神經網絡的隱藏層時,根據實際情況 選擇 哪種激活函數(一般一層只使用一種激活函數)
五、身份證問題新模型及代碼實現
將本題的模型改成多層全連接神經網絡:
對圖7.4中的模型做簡單說明:這是一個由一 個輸入層、 兩個隱藏層和一個輸出層組成的多層全連接神經網絡模型,其中兩個隱藏層都是全連接層。輸入層有4個節點;隱藏層 1 有8個節點,分別表示為 n11~n18;隱藏層 2有兩個節點,分別表示為n21和n22。隱藏層1使用了激活函數tanh,隱藏層2沒有使用激活函數;隱藏層1的計算操作是nl = tanh(x * wI+b1 ),其中nl為圖7.4中的n11~n18這些節點組成的向量,wI和 bl分別為本層節點的權重和偏移量,乘法符號 " * " 是表示又乘:隱藏層2的計算操作是n2 = tanh(n2 * w2+b2 ),其中 m2為圖7.4中的n21和n22組成的向量,w2 和b2分別為本層節點的權重和偏移量,n2與w2間的乘法也是又乘。輸出層有兩個節點。是對隱藏層2的輸出應用了softmax函數來進行二分類的結果。
代碼實現:
import tensorflow as tf import random random.seed() x = tf.placeholder(tf.float32) yTrain = tf.placeholder(tf.float32) w1 = tf.Variable(tf.random_normal([4, 8], mean=0.5, stddev=0.1), dtype=tf.float32) # 可變參數 w1 形態為[ 4,8 ] 是為了保證輸出到下一層的結點數是8,同樣w2 的形態是 [ 8,2 ] ,是為了保證 輸出到 輸出層的節點數 為 2 # 以便用 softmax 函數二分類 # 其中的 [x , y] 可理解為 “承前啟后” ,x = 前面的神經元節點 , y = 后面的神經元節點 # 具體含義,往上翻,見“矩陣乘法” b1 = tf.Variable(0, dtype=tf.float32) # b1 b2 都被定義為標量,標量在於向量或舉證進行加法等操作時,會對向量或矩陣中的每一個元素都執行相同的操作。也可以把偏移量設置成向量 xr = tf.reshape(x, [1, 4]) # 把輸入數據x 從一個4維向量 准換為一個形態為 [1,4] 的矩陣,並保存至向量 xr 中 n1 = tf.nn.tanh(tf.matmul(xr, w1) + b1) # 定義隱藏層 1 的結構,tf.matmul 函數為 tensorFlow 中進行矩陣乘法的函數,tf.nn.tanh 為激活函數 tanh w2 = tf.Variable(tf.random_normal([8, 2], mean=0.5, stddev=0.1), dtype=tf.float32) b2 = tf.Variable(0, dtype=tf.float32) n2 = tf.matmul(n1, w2) + b2 y = tf.nn.softmax(tf.reshape(n2, [2])) # 是輸出層對隱藏層 2 的輸出 n2 進行 softmax 函數處理,使得結果是一個相加為 1 的向量 # 由於 n2 的形態 [1,2] 是一個矩陣,所以先把它轉化為 一個形態為 [2] 的一維向量【一維數組】 loss = tf.reduce_mean(tf.square(y - yTrain)) # 誤差計算 采用了 均方誤差 optimizer = tf.train.RMSPropOptimizer(0.01) train = optimizer.minimize(loss) sess = tf.Session() sess.run(tf.global_variables_initializer()) lossSum = 0.0 for i in range(5): xDataRandom = [int(random.random() * 10), int(random.random() * 10), int(random.random() * 10), int(random.random() * 10)] if xDataRandom[2] % 2 == 0: yTrainDataRandom = [0, 1] else: yTrainDataRandom = [1, 0] # 由於采用了 均方誤差的計算方法,需要將目標值 由 1 變為 [1,0] ,由 0 變為 [0,1],這樣,喂給目標值 yTrian 的才是需要的 二維向量 result = sess.run([train, x, yTrain, y, loss], feed_dict={x: xDataRandom, yTrain: yTrainDataRandom}) lossSum = lossSum + float(result[len(result) - 1]) print("i: %d, loss: %10.10f, avgLoss: %10.10f" % (i, float(result[len(result) - 1]), lossSum / (i + 1))) # 輸出訓練誤差 + 平均誤差
i: 0, loss: 0.2222173959, avgLoss: 0.2222173959 i: 1, loss: 0.2899080217, avgLoss: 0.2560627088 i: 2, loss: 0.2773851454, avgLoss: 0.2631701877 i: 3, loss: 0.2356697619, avgLoss: 0.2562950812 i: 4, loss: 0.2243882120, avgLoss: 0.2499137074
可以看到,平均誤差趨勢在減小,增大訓練次數為50000:
i: 49992, loss: 0.1980789304, avgLoss: 0.1934479160 i: 49993, loss: 0.0004733017, avgLoss: 0.1934440560 i: 49994, loss: 0.3544868231, avgLoss: 0.1934472772 i: 49995, loss: 0.2939709425, avgLoss: 0.1934492878 i: 49996, loss: 0.0005629645, avgLoss: 0.1934454299 i: 49997, loss: 0.0000229554, avgLoss: 0.1934415612 i: 49998, loss: 0.0003532485, avgLoss: 0.1934376994 i: 49999, loss: 0.2427534610, avgLoss: 0.1934386857
多次訓練后,還是未能達到一個非常小的誤差,說明這一類非線性跳動問題解決起來比較復雜
六、模型的優化
●增加隱藏層的神經元節點數量。這是最容易實施的方法了,例如要把圖7.4模型中,隱藏層1的節點數增加到32個,只需要把wl定義成形態為[14, 32]的變量,同時把w2定義為形態為[32, 2]的變量。修改后,循環50萬次后,誤差大約已經能夠減小到0.09左右。我們要注意,由於神經網絡調節可變參數的初值一般是隨機數,另外優化器對可變參數的調節也有定隨機性, 所以每次程序執行訓練后的結果有所不同是正常的,但大體趨勢應該近似。也不排除優化器落入“陷阱”, 導致誤差始終無法收斂的情況,這時候重新進行訓練,從另組可變參 數隨機初始值開始即可。
●增加隱藏層的層數。 一般來說, 增加某個隱藏層中神經元節點的數量,可以形象地理解成讓這層變“胖”;那么,增加隱藏層的層數就是讓神經網絡變“高”或者變“長”;一般來說, 增加層數比增加神經元的數量會效果好點,但是每一層上激話函數的選擇需要動些胸筋,有些層之間激活函數的組合會遇上意料不到的問題。層數太多時,有時候還會造成優化器感知不暢而無法調節可變參數。另外,只增加線性全連接層的數量一般是沒有意義的, 因為兩個線性全連接層是可以轉換成一層的。這些留到進階學習的時候去提高。
下面給出段代碼, 是在代碼7.2的基礎上加入一個隱藏層n3. 大家可以看看如何增加新的隱藏層,同時回顧矩陣乘法的有關知識。
import tensorflow as tf import random random.seed() x = tf.placeholder(tf.float32) yTrain = tf.placeholder(tf.float32) w1 = tf.Variable(tf.random_normal([4, 32], mean=0.5, stddev=0.1), dtype=tf.float32) b1 = tf.Variable(0, dtype=tf.float32) xr = tf.reshape(x, [1, 4]) n1 = tf.nn.tanh(tf.matmul(xr, w1) + b1) w2 = tf.Variable(tf.random_normal([32, 32], mean=0.5, stddev=0.1), dtype=tf.float32) b2 = tf.Variable(0, dtype=tf.float32) n2 = tf.nn.sigmoid(tf.matmul(n1, w2) + b2) w3 = tf.Variable(tf.random_normal([32, 2], mean=0.5, stddev=0.1), dtype=tf.float32) b3 = tf.Variable(0, dtype=tf.float32) n3 = tf.matmul(n2, w3) + b3 y = tf.nn.softmax(tf.reshape(n3, [2])) loss = tf.reduce_mean(tf.square(y - yTrain)) optimizer = tf.train.RMSPropOptimizer(0.01) train = optimizer.minimize(loss) sess = tf.Session() sess.run(tf.global_variables_initializer()) lossSum = 0.0 for i in range(5000): xDataRandom = [int(random.random() * 10), int(random.random() * 10), int(random.random() * 10), int(random.random() * 10)] if xDataRandom[2] % 2 == 0: yTrainDataRandom = [0, 1] else: yTrainDataRandom = [1, 0] result = sess.run([train, x, yTrain, y, loss], feed_dict={x: xDataRandom, yTrain: yTrainDataRandom}) lossSum = lossSum + float(result[len(result) - 1]) print("i: %d, loss: %10.10f, avgLoss: %10.10f" % (i, float(result[len(result) - 1]), lossSum / (i + 1)))
●適當調節學習率。有時候,學習率不合適也會導致訓練效率不高,可以適當修改。具體見以后