先向各位小夥伴道歉,文中可能會出現許多錯别字,表達不清楚,病句,标點符号使用不當,圖檔難看且潦草的情況,必須誠懇地向大家表示:湊合看吧,還能咬我咋的...
在之前的文章中,有提到過,所謂的 AI 技術,本質上是一種資料處理處理技術,它的強大來自于兩方面:1.網際網路的發展帶來的海量資料資訊 2.計算機深度學習算法的快速發展。 是以說 AI 其實并沒有什麼神秘,隻是在算法上更為複雜。要想了解這一點,我們要從一個問題說起:找資料的規律...
如果你是一名上過大學的人,有幾個數學上的方法你應該不太陌生:線性拟合,多項式拟合,最小二乘法...如果這個你都不知道的話,我建議你現在 假裝明白 ,然後往下看,應該不難。
先看一下下面這組資料:
x | y |
1 | |
1 | 2 |
2 | 9 |
3 | 28 |
4 | 65 |
5 | 126 |
6 | 217 |
7 | 344 |
8 | 513 |
9 | 730 |
這裡沒什麼好說的,這個表是一個 x 與 y 的對應關系,我們現在的目标比較明确,找到x與y的對應關系,也就是求y=f(x)的關系式。
第 1 部分 傳統數學方法回顧
這部分内容就很簡單了,關鍵記住一個名詞 —— 一通(tòng)操作 ...
方法一:線性拟合
我們知道,在大多數情況下,當我們拿來一組資料,進行拟合時,首先想到的肯定是線性拟合,因為其方法簡單暴力直接有效,往往很快就能得到一個差不多的結論。雖然不是很精确,但是有句名言說得好,要啥自行車?直接來看結果。
關于線性拟合在數學上的方法,這裡就不講了,随便找本教材應該就有,我相信看到上面的圖,你應該已經了解了。總之就是經過 一通操作 ,得到線性關系。我這裡并沒有用數學方法親自去進行計算和拟合關系式,而是用了一種很高端的工具,叫 Excel ... 以示說明,領會精神即可。
紅色虛線為拟合線,藍色實線為實際點的連線,可以看到,利用線性拟合,得到的結果是 y = 75.4x - 135.8 這樣一個數學關系,很顯然,它的效果不是他别理想,可以看到,誤差還是不小的。
方法二:多項式拟合
這個的思路也比較直接,其實就是假設
, 比如我們要拟合一個2次的多項式,就可以假設
。同樣的,3次方的關系就是
。應該是很好了解吧······
跟剛才一樣,又是一通操作,拟合的方法我不就不贅述了,直接用我們的高端工具 Excel 來完成這個工作。效果如下:
可以看到,二次拟合的效果比線性拟合的結果要更接近于真實的結果,而三次曲線就是真實的關系(當然大多數實際情況下并不是嚴格對應)。
通常利用更高階的多項式,得到的結果就更加接近于實際的資料。
方法三:其他
最小二乘法,指數拟合,對數拟合,根據資料的不同,用不同的方法來進行拟合得到接近真實情況的數學關系。差別就是利用不同的 一通操作 ... 但是無論是哪一種,解決的數學問題相對來說比較有限,并不能準确拟合出很複雜的數學關系。
如果利用邏輯回歸、貝葉斯、決策樹、KNN、套袋法等等,也能夠解決很多很複雜的數學問題,但這又是另外一個很大的領域,不過建議有機會還是要把這些基礎打好,但是這篇部落格中,我們不探讨,完全不熟悉也沒關系,隻要知道這些都是傳統的資料處理方法就好。
第 2 部分 現代技術中的難題
下面我們來思考一個 重!要!問!題!:别人爸爸 跟 你爸爸 的不同之處,在數學上的表達是怎麼樣的 ?
多麼深奧的問題,也許你覺得這是一個顯而易見的問題,你爸爸就是你爸爸,他爸爸就是他爸爸。這點我十分相信,雖然你不知道如何去回答這個問題,但是你這輩子應該是沒喊錯過你父親... 可是問題來了,你怎麼讓計算機去熟練的分辨出兩個人誰是誰?這就必須要依賴數學了...
是以回到問題中來:别人爸爸 跟 你爸爸 的不同之處,在數學上的表達是怎麼樣的 ?你可能要打我了。但是先别急,先來分析分析。首先,必須明搞明白一件事,這個世界上的事情可以分為兩種,可歸納的問題 與 不可歸納的問題。
首先什麼是不可歸納的問題,舉個例子,你不能用一套完美的數學公式去表達 所有的質數 , 因為目前的研究表明,還沒有什麼方法是能夠表達質數的,也就是說,質數的出現,本身不具備嚴格的數學規律,是以無法歸納。
可歸納問題就比較好了解了,一隻貓 和 一隻狗 出現在你的面前時 ,你能夠清晰地将他們進行分辨,這說明在貓和狗之間,确實存在着不同,雖然你很難說清楚它們的不同到底是什麼,但是可以知道,這背後是可以通過一套數學表達來完成的,隻是很複雜而已。理論上來講,凡是人類能夠掌握的事情,比如再怎麼複雜的語言,人類的快速分辨物體的視覺,複雜的邏輯思考,都是可以用數學來表達的可歸納問題。我們人類之是以能夠快速地對這些複雜的問題進行快速地反應,得益于我們的大腦内部複雜的神經網絡構造。當我們不經意間看到一些物體時,大腦其實是在高速的進行計算,我們天生擁有這種能力,以至于我們根本沒有察覺。多麼神奇,可以說我們每個人其實都是超級算法工程師...
對比第一部分的那個表格,和如何分辨爸爸的問題,可以得到結論是,這是同一個層次的問題:可歸納數學問題,隻是用到的方法不同,複雜度不同而已。都可以用公式來表達:
問題一: x 與 y 的對應關系 y = f(x)
問題二:你爸爸 = f (你爸爸的特征)
這當然是一個複雜的問題,因為首先需要将人的特征轉化為數字資訊,比如圖像(圖像本質上就是二位的數組),然後根據不同人的特征,對應的不同人的代号,來拟合一個複雜的,一一對應的函數關系,就是現在技術中的一個難題。解決的方法就是 AI :神經網絡。
是以所謂的 AI 技術 ,說到底 是找規律的問題,是拟合複雜函數的問題,是資料處理的問題。
下面将通過執行個體,來給大家搭建一個最簡單的神經網絡:Bp神經網絡,來了解 AI 。
第 3 部分 最簡單的神經網絡Bp神經網絡
1.Bp 神經網絡的簡單了解
這裡要從名字開始說起了,首先從名稱中可以看出,Bp神經網絡可以分為兩個部分,bp和神經網絡。
bp是 Back Propagation 的簡寫 ,意思是反向傳播。而神經網絡,聽着高大上,其實就是一類相對複雜的計算網絡。舉個簡單的例子來說明一下,什麼是網絡。
看這樣一個問題,假如我手裡有一筆錢,N個億吧(既然是假設那就不怕吹牛逼),我把它分别投給5個公司,分别占比 M1,M2,M3,M4,M5(M1到M5均為百分比 %)。而每個公司的回報率是不一樣的,分别為 A1, A2, A3, A4, A5,(A1到A5也均為百分比 %)那麼我的收益應該是多少?這個問題看起來應該是夠簡單了,你可能提筆就能搞定 收益 = N*M1*A1 + N*M2*A2+N*M3*A3+N*M4*A4+N*M5*A5 。這個完全沒錯,但是展現不出水準,我們可以把它轉化成一個網絡模型來進行說明。如下圖:
圖有點醜,領會精神,領會精神。上面的問題是不是莫名其妙的就被整理成了一個三層的網絡,N1到N5表示每個公司獲得的錢,R表示最終的收益。R = N*M1*A1 + N*M2*A2+N*M3*A3+N*M4*A4+N*M5*A5 。我們可以把 N 作為輸入層 ,R作為輸出層,N1到N5則整體作為隐藏層,共三層。而M1到M5則可以了解為輸入層到隐藏層的權重,A1到A5為隐藏層到輸出層的權重。
這裡提到了四個重要的概念 輸入層(input) , 隐藏層 (hidden),輸出層(output)和權重(weight) 。而所有的網絡都可以了解為由這三層和各層之間的權重組成的網絡,隻是隐藏層的層數和節點數會多很多。
輸入層:資訊的輸入端,上圖中 輸入層 隻有 1 個節點(一個圈圈),實際的網絡中可能有很多個
隐藏層:資訊的處理端,用于模拟一個計算的過程,上圖中,隐藏層隻有一層,節點數為 5 個。
輸出層:資訊的輸出端,也就是我們要的結果,上圖中,R 就是輸出層的唯一一個節點,實際上可能有很多個輸出節點。
權重:連接配接每層資訊之間的參數,上圖中隻是通過乘機的方式來展現。
在上面的網絡中,我們的計算過程比較直接,用每一層的數值乘以對應的權重。這一過程中,權重是恒定的,設定好的,是以,是将 輸入層N 的 資訊 ,單向傳播到 輸出層R 的過程,并沒有反向傳播資訊,是以它不是神經網絡,隻是一個普通的網絡。
而神經網絡是一個資訊可以反向傳播的網絡,而最早的Bp網絡就是這一思想的展現。先不急着看Bp網絡的結構,看到這兒你可能會好奇,反向傳播是什麼意思。再來舉一個通俗的例子,猜數字:
當我提前設定一個數值 50,讓你來猜,我會告訴你猜的數字是高了還是低了。你每次猜的數字相當于一次資訊正向傳播給我的結果,而我給你的提示就是反向傳播的資訊,往複多次,你就可以猜到我設定的數值 50 。 這就是典型的反向傳播,即根據輸出的結果來反向的調整模型,隻是在實際應用中的Bp網絡更為複雜和數學,但是思想很類似。
2.Bp 神經網絡的結構與數學原理(可以不細看)
此節的内容 極!其!重!要!但是要涉及到一些數學,是以我盡量用人話去跟大家細細解釋,并且結合執行個體來給大家進行一下分析。
如果你不想看太多的推導和數學,那麼隻需要大概了解 Bp 網絡的運作思想就好:我們知道,一個函數是由自變量x和決定它的參數θ組成。比如 y=ax + b 中,a,b為函數的固定參數 θ ,x為自變量。那麼對于任意一個函數我們可以把它寫成 y = f(θ,x)的形式,這裡的 θ 代表所有參數的集合[
,
,
,...],x代表所有自變量的集合[
,
,
,...]。而 Bp 網絡的運作流程就是根據已有的 x 與 y 來不停的疊代反推出參數 θ 的過程,這一過程結合了最小二乘法與梯度下降等特殊的計算技巧。這一節看到這兒就基本上可以了,但是如果還想繼續深入了解,可以跟着思路,往下接着看。
事實上,這些内容已經被各路神仙們寫爛了,因為 Bp網絡對于 AI 技術來說,實在太基礎,太重要,但是由于在實際學習中,我也遇到過一些困難,現在根據我的學習過程和了解過程,還是要再拿出來寫一遍。大神們勿噴···
還是老樣子,先來看一個問題,找到下列資料中,y 與 x1,x2,x3的關系,即 y = f(x1,x2,x3)的數學表達式。
x1 | x2 | x3 | y |
1 | 1 | 2 | 2 |
1 | 2 | 3 | 6 |
2 | 1 | 6 | 12 |
5 | 2 | 5 | 50 |
8 | 3 | 4 | 96 |
7 | 7 | 4 | 196 |
7 | 7 | 7 | 343 |
13 | 8 | 3 | 312 |
6 | 10 | 11 | 660 |
13 | 17 | ||
14 | 7 | 12 | 1176 |
這裡一共是 11 組資料(資料量很少),很明顯 y 是關于 x1,x2,x3 的三元函數,通常情況下,想要通過一套固定的套路來拟合出一個三元函數的關系式,是一件很複雜的事。而實際問題中的參數往往不止三個,可能成千上百,也就是說 決定 y 的參數會有很多,這樣的問題更是複雜的很,用正常的方法去拟合,幾乎不可能,那麼換一種思路,用 Bp神經網絡的方法來試一下。
根據上表給出的條件和問題,我們先來分析一下。首先,我們的輸入資訊是 3 個參數,x1,x2,x3 。輸出結果是 1 個數 y 。那麼可以畫一個這樣的關系網路圖(直接手畫了,湊合看吧···):
在這個網絡中,輸入層(input )有三個節點(因為有三個參數),隐藏層(hidden )先不表示,輸出層(output )有1個節點(因為我們要的結果隻有一個 y )。那麼關鍵的問題來了,如何進行這一通操作,它的結構究竟是怎樣的?
2.1 正向傳播
正向傳播就是讓資訊從輸入層進入網絡,依次經過每一層的計算,得到最終輸出層結果的過程。
我直接把設計好的結構圖給大家畫出來,然後再一點一點地解釋。結構如下:
看到這兒你可能會有點懵,不過不要緊,一步一步來分析。先來看網絡的結構,輸入層(input )沒有變,還是三個節點。輸出層(input )也沒有變。重點看隐藏層(hidden ),就是圖中紅色虛線框起的部分,這裡我設計了一個隐藏層為兩層的網絡,hidden_1和hidden_2 ,每層的節點為 2 個,至于為什麼是兩層,節點數為什麼是 2 兩個 ,這裡你隻需要知道,實驗證明,解決這個問題,這樣的網絡就夠用了。具體的一會兒講。
關鍵看一下連線代表的意義,和計算過程。可以從圖上看到,每層的節點都與下一層的每個節點有一一對應的連線,每條連線代表一個權重,這裡你可以把它了解為資訊傳輸的一條通路,但是每條路的寬度是不一樣的,每條通路的寬度由該通道的參數,也就是該通路的權重來決定。為了說明這個問題,拿一個節點的計算過程來進行說明,看下圖:
這上上圖中的一部分,輸入層(input )與 第一層隐藏層(hidden )的第一個節點
的連接配接關系。根據上邊的圖你可能自然的會想到:
。如果你這麼想,那就說明你已經開竅了,不過實際過程要複雜一些。我們可以把
這個節點看做是一個有輸入,有輸出的節點,我們規定輸入為
, 輸出為
,則真實的過程如下:
計算的方法我直接寫到圖裡了,字兒醜,但是應該能看清楚···解釋一下,
就是x1,x2,x3與各自權重乘積的和,但是為什麼非要搞一個 sigmoid() ,這是什麼鬼? 其實最早人們在設計網絡的時候,是沒有這個過程的,統統使用線性的連接配接來搭建網絡,但是線性函數沒有上界,經常會造成一個節點處的數字變得很大很大,難以計算,也就無法得到一個可以用的網絡。是以人們後來對節點上的資料進行了一個操作,利用sigmoid()函數來處理,使資料被限定在一定範圍内。此外sigmoid函數的圖像是一個非線性的曲線,是以,能夠更好的逼近非線性的關系,因為絕大多數情況下,實際的關系是非線性的。sigmoid在這裡被稱為 激勵函數 ,這是神經網絡中的一個非常重要的基本概念。下面來具體說一下什麼是 sigmoid() 函數。
不作太具體的分析,直接看公式和圖像:
圖像來自百度百科,可以看到sigmoid函數能夠将函數限制在 0到1 的範圍之内。
這裡還要進行一下說明,sigmoid 是最早使用的激勵函數,實際上還有更多種類的激勵函數 ,比如 Relu ,tanh 等等,性質和表達式各有不同,以後再說,這裡先用 sigmoid 來說明。
如果說看到這兒,你對 激勵函數 這個概念還是不太懂的話 ,沒關系,可以假裝自己明白了,你就知道這個東西很有用,裡面必有道道就行了,以後慢慢體會,慢慢了解,就行了。接着往下看。
剛剛解釋了一個節點的計算過程,那麼其他節點也就可以舉一反三,一一計算出來。現在我們來簡化一下網絡。我們可以把x1,x2,x3作為一個向量 [x1,x2,x3] ,權重矩陣 u 也作為一個 3x2 的矩陣 ,w 作為一個 2x2 的矩陣 ,v作為一個 2x1 的矩陣,三個矩陣如下:
可以看到這三個矩陣與網絡中的結構圖中是一一對應的。下面我們把隐藏層與輸出層也寫成矩陣的形式:
可以看到這兩層隐藏層(hidden)的輸入Hi 與 Ho 均為 1x2 的矩陣,輸出層(output )為 1x1 的矩陣。下面就可以把網絡簡化為下面的結構:
根據我們剛才講過的每個節點的計算方法,以及我們簡化後的網絡,則可以将整個計算過程等效的化為以下幾個矩陣相城的步驟(矩陣相乘是怎麼會回事,請複習線性代數...):
注意:下式中,除sigmoid代表激勵函數以外,其餘各個符号都代表一個矩陣(或者向量),而非常數,乘積符号“ x ”代表正常的矩陣乘法計算。
※ 由于矩陣 x 次元為 1*3 ,u 次元為 3*2 ,是以自然得到次元為1*2的矩陣(或者向量)
。
※ 次元不變
※ (1*2) x (2*2) →(1*2) 括号内為各矩陣次元
※ 次元不變
※ (1*2) x (2*1) →(1*1) 括号内為各矩陣次元
注意:細心的小夥伴應該發現公式中出現了幾個之前沒有提到的符号
,
,
。它們也各自代表一個矩陣,它們的概念為門檻值,通常用符号b來表示。門檻值的意義是,每個節點本身就具有的一個數值,設定門檻值能夠使網絡更快更真實的去逼近一個真實的關系。
以上這個過程,就是該網絡的資訊進行了一次 正向傳播 。
2.2 反向傳播
那麼有正向傳播,就必須得有反向傳播,下面來講一下 反向傳播 的過程。首先明确一點,反向傳播的資訊是什麼,不賣關子,直接給答案,反向傳播的資訊是誤差,也就是 輸出層(output )的結果 與 輸入資訊 x 對應的真實結果 之間的差距(表達能力比較差,畫個圖說明...)。
拿出上文的資料表中的第一組資料 x1 = 1,x2=1,x3=2,y=2 為例。
假設我們将資訊x1,x2,x3 輸入給網絡,得到的結果為
= 8 ,而我們知道真實的 y 值為 2,是以此時的誤差為 |
-y| ,也就是 6 。 真實結果與計算結果的誤差被稱作 損失 loss , loss = |
- y| 記作 損失函數 。這裡有提到了一個很重要的概念,損失函數,其實在剛才的例子中,損失函數 loss = |
- y| 隻是衡量誤差大小的一種方式,稱作L1損失(先知道就行了),在實際搭建的網絡中,更多的用到的損失函數為 均方差損失,和交叉熵損失。原則是分類問題用交叉熵,回歸問題用均方差,綜合問題用綜合損失,特殊問題用特殊損失···以後慢慢說吧,因為損失函數是一個超級龐大的問題。
總之我們先知道,損失函數 loss 是一個關于 網絡輸出結果
與真實結果 y 的,具有極小值的函數 。那麼我們就可以知道,如果一個網絡的計算結果
與 真是結果 y 之間的損失總是很小,那麼就可以說明這個網絡非常的逼近真實的關系。是以我們現在的目的,就是不斷地通過調整權重u,w,v(也就是網絡的參數)來使網絡計算的結果
盡可能的接近真實結果 y ,也就等價于是損失函數盡量變小。那麼如何調整u,w,v 的大小,才能使損失函數不斷地變小呢?這理又要說到一個新的概念:梯度下降法 。
梯度下降法 是一個很重要很重要的計算方法,要說明這個方法的原理,就又涉及到另外一個問題:邏輯回歸。為了簡化學習的過程,不展開講,大家可以自己去搜一下邏輯回歸,學習一下。特别提醒一下,邏輯回歸是算法工程師必須掌握的内容,因為它對于 AI 來說是一個很重要的基礎。下面隻用一個圖(圖檔來自百度)進行一個簡單地說明。
假設上圖中的曲線就是損失函數的圖像,它存在一個最小值。梯度是一個利用求導得到的數值,可以了解為參數的變化量。從幾何意義上來看,梯度代表一個損失函數增加最快的方向,反之,沿着相反的方向就可以不斷地使損失逼近最小值,也就是使網絡逼近真實的關系。
那麼反向傳播的過程就可以了解為,根據 損失loss ,來反向計算出每個參數(如
,
等)的梯度 d(
) ,d(
) ....等等,再将原來的參數分别加上自己對應的梯度,就完成了一次反向傳播。
來看看 損失loss 如何完成一次反向傳播,這裡再定義一些變量
,
和
。注意:它們都代表矩陣(向量),而非一個數值。它們分别代表第一層,第二層隐藏層,以及輸出層每個神經元節點反向輸出的值。
分别代表權值矩陣與門檻值矩陣對應的梯度矩陣,用符号
代表損失,
來表示sigmoid函數的導數。這裡隻簡單的說一下計算公式,推導過程後邊講。
計算梯度,注意:下式中未标紅的都代表一個矩陣(或者向量),标紅符号的代表一個常數。
更新權值與門檻值
※ 公式中的 乘号 “ x ”表示正常的矩陣乘積運算,運算後會發生次元的變化。 符号 “ · ” 表示按位乘積,運算後次元不變。
以上就是一次完整的反向傳播過程,需要說明的是,上式當中用到了一個符号
,這又是一個重要的概念,學習率,一個小于1的實數,它的大小會影響網絡學習的速率以及準确度。可以把它了解為梯度下降時的步長。
反向傳播過程實際上還是有點複雜的,下面我來簡單說一下為什麼梯度是這樣求的。
我們知道,整個網絡可以簡化成一個函數
,也就是說這個函數的表達式,主要由各個參數
來決定,而現在為了确定網絡的參數,則可以把
作為函數的自變量,而x作為參數,對
求偏導
,這個偏導的結果就是該參數
對應的梯度,這個思想實際上來自于最小二乘法,反正求完就是上邊式子中的結果,這裡不再進行推導。
2.3 網絡的訓練
通過一次正向傳播,和一次反向傳播,我們就可以将網絡的參數更新一次,所謂訓練網絡,就是讓正向傳播和反向傳播不斷的往複進行,不斷地更新網絡的參數,最終使網絡能夠逼近真實的關系。
理論上,隻要網絡的層數足夠深,節點數足夠多,可以逼近任何一個函數關系。但是這比較考驗你的電腦性能,事實上,利用 Bp 網絡,能夠處理的資料其實還是有限的,比如 Bp 網絡在圖像資料的識别和分類問題中的表現是很有限的。但是這并不影響 Bp 網絡是一種高明的政策,它的出現也為後來的 AI 技術做了重要的鋪墊。
3.Bp 神經網絡的代碼實作
回到 表 3.1 中的資料,将用 python 來實作一個 Bp 網絡 ,對資料的關系建立一個網絡模型。
這裡有幾點需要說明,首先在資料進入網絡之前,要先進行歸一化處理,即将資料除以一個數,使它們的值都小于 1 ,這樣做的目的是避免梯度爆炸。其次為了更好、更快的收斂得到準确的模型,這裡采用了對資料進行特征化的處理。最後,這段代碼中用到的激勵函數是Relu,并非我們之前所講的 sigmoid ,因為Relu的計算速度更快,更容易收斂。
這裡有幾個參數和數組需要說明,其中 p_s 中的數組代表 表 3.1 中 11組資料的 [x1,x2,x3] ,t_s代表對應的 y 。p_t 與t_t用來存放測試網絡訓練效果的 測試資料集 。我們用p_s與t_s來訓練 Bp 網絡 ,用 p_t 與 t_t 來檢驗訓練的效果。表 3.1 的資料中,y 與 x1,x2,x3 的對應關系實際上是 y = x1 * x2 * x3 。
代碼如下:
import time
from numpy import *
######## 資料集 ########
p_s = [[1,1,2],[1,2,3],[2,1,6],[5,2,5],[8,3,4],[7,7,4],[7,7,7],[13,8,3],[6,10,11],[13,0,17],[14,7,12]] # 用來訓練的資料集 x
t_s = [[2],[6],[12],[50],[96],[196],[343],[312],[660],[0],[1176]] # 用來訓練的資料集 y
p_t = [[6,9,1017],[2,3,4],[5,9,10]] # 用來測試的資料集 x_test
t_t = [[54918],[24],[450]] # 用來測試的資料集 對應的實際結果 y_test
######## 超參數設定 ########
n_epoch = 20000 # 訓練次數
HNum = 2; # 各層隐藏層節點數
HCNum = 2; # 隐藏層層數
AFKind = 3; # 激勵函數種類
emax = 0.01; # 最大允許均方差根
LearnRate = 0.01; # 學習率
######## 中間變量設定 ########
TNum = 7; # 特征層節點數 (特征數)
SNum = len(p_s); # 樣本數
INum = len(p_s[0]); # 輸入層節點數(每組資料的次元)
ONum = len(t_s[0]); # 輸出層節點數(結果的次元)
StudyTime = 0; # 學習次數
KtoOne = 0.0; # 歸一化系數
e = 0.0; # 均方差跟
######################################################### 主要矩陣設定 ######################################################
I = zeros(INum);
Ti = zeros(TNum);
To = zeros(TNum);
Hi = zeros((HCNum,HNum));
Ho = zeros((HCNum,HNum));
Oi = zeros(ONum);
Oo = zeros(ONum);
Teacher = zeros(ONum);
u = 0.2*ones((TNum,HNum)) # 初始化 權值矩陣u
w = 0.2*ones(((HCNum-1,HNum,HNum))) # 初始化 權值矩陣w
v = 0.2*ones((HNum,ONum)) # 初始化 權值矩陣v
dw = zeros((HCNum-1,HNum,HNum))
Hb = zeros((HCNum,HNum));
Ob = zeros(ONum);
He = zeros((HCNum,HNum));
Oe = zeros(ONum);
p_s = array(p_s)
t_s = array(t_s)
p_t = array(p_t)
################################# 時間參數 #########################################
time_start = 0.0
time_gyuyihua = 0.0
time_nnff = 0.0
time_nnbp = 0.0
time_begin = 0.0
time_start2 = 0.0
time_nnff1 = 0.0
time_nnff2 = 0.0
time_nnbp_v = 0.0
time_nnbp_w = 0.0
time_nnbp_u = 0.0
time_nnbp_b = 0.0
######################################################### 方法 #######################################################
def Calcu_KtoOne(p,t): # 确定歸一化系數
p_max = p.max();
t_max = t.max();
return max(p_max,t_max);
def trait(p): # 特征化
t = zeros((p.shape[0],TNum));
for i in range(0,p.shape[0],1):
t[i,0] = p[i,0]*p[i,1]*p[i,2]
t[i,1] = p[i,0]*p[i,1]
t[i,2] = p[i,0]*p[i,2]
t[i,3] = p[i,1]*p[i,2]
t[i,4] = p[i,0]
t[i,5] = p[i,1]
t[i,6] = p[i,2]
return t
def AF(p,kind): # 激勵函數
t = []
if kind == 1: # sigmoid
pass
elif kind == 2: # tanh
pass
elif kind == 3: # ReLU
return where(p<0,0,p)
else:
pass
def dAF(p,kind): # 激勵函數導數
t = []
if kind == 1: # sigmoid
pass
elif kind == 2: # tanh
pass
elif kind == 3: # ReLU
return where(p<0,0,1)
else:
pass
def nnff(p,t):
pass
def nnbp(p,t):
pass
def train(p,t): # 訓練
global e
global v
global w
global dw
global u
global I
global Ti
global To
global Hi
global Ho
global Oi
global Oo
global Teacher
global Hb
global Ob
global He
global Oe
global StudyTime
global KtoOne
global time_start
global time_gyuyihua
global time_nnff
global time_nnbp
global time_start2
global time_nnff1
global time_nnff2
global time_nnbp_v
global time_nnbp_w
global time_nnbp_u
global time_nnbp_b
time_start = time.clock()
e = 0.0
p = trait(p)
KtoOne = Calcu_KtoOne(p,t)
time_gyuyihua += (time.clock()-time_start)
time_start = time.clock()
for isamp in range(0,SNum,1):
To = p[isamp]/KtoOne
Teacher = t[isamp]/KtoOne
################ 前向 nnff #############################
time_start2 = time.clock()
######## 計算各層隐藏層輸入輸出 Hi Ho ########
for k in range(0,HCNum,1):
if k == 0:
Hi[k] = dot(To,u)
Ho[k] = AF(add(Hi[k],Hb[k]),AFKind)
else:
Hi[k] = dot(Ho[k-1],w[k-1])
Ho[k] = AF(add(Hi[k],Hb[k]),AFKind)
time_nnff1 += (time.clock()-time_start2)
time_start2 = time.clock()
######## 計算輸出層輸入輸出 Oi Oo ########
Oi = dot(Ho[HCNum-1],v)
Oo = AF(add(Oi,Ob),AFKind)
time_nnff2 += (time.clock()-time_start2)
time_start2 = time.clock()
time_nnff += (time.clock()-time_start)
time_start = time.clock()
################ 反向 nnbp #############################
######## 反向更新 v ############
Oe = subtract(Teacher,Oo)
Oe = multiply(Oe,dAF(add(Oi,Ob),AFKind))
e += sum(multiply(Oe,Oe))
#### v 梯度 ####
dv = dot(array([Oe]),array([Ho[HCNum-1]])).transpose() # v 的梯度
v = add(v,dv*LearnRate) # 更新 v
time_nnbp_v += (time.clock()-time_start2)
time_start2 = time.clock()
######## 反向更新 w #############
He = zeros((HCNum,HNum))
for c in range(HCNum-2,-1,-1):
if c == HCNum-2:
He[c+1] = dot(v,Oe)
He[c+1] = multiply(He[c+1],dAF(add(Hi[c+1],Hb[c+1]),AFKind))
#dw[c] = dot(array([He[c+1]]),array([Ho[c]]).transpose())
dw[c] = dot(array([Ho[c]]).transpose(),array([He[c+1]]))
#dw[c] = dw[c].transpose() #@@@@@@ 若結果不理想,可嘗試用此條語句
w[c] = add(w[c],LearnRate*dw[c])
else:
He[c+1] = dot(w[c+1],He[c+2])
He[c+1] = multiply(He[c+1],dAF(add(Hi[c+1],Hb[c+1]),AFKind))
dw[c] = dot(array([Ho[c]]).transpose(),array([He[c+1]]))
w[c] = add(w[c],LearnRate*dw[c])
time_nnbp_w += (time.clock()-time_start2)
time_start2 = time.clock()
######## 反向更新 u #############
He[0] = dot(w[0],He[1])
He[0] = multiply(He[0],dAF(add(Hi[0],Hb[0]),AFKind))
du = dot(array([To]).transpose(),array([He[0]]))
u = add(u,du)
time_nnbp_u += (time.clock()-time_start2)
time_start2 = time.clock()
######### 更新門檻值 b ############
Ob = Ob + Oe*LearnRate
Hb = Hb + He*LearnRate
time_nnbp += (time.clock()-time_start)
time_start = time.clock()
time_nnbp_b += (time.clock()-time_start2)
time_start2 = time.clock()
e = sqrt(e)
def predict(p):
p = trait(p)
p = p/KtoOne
p_result = zeros((p.shape[0],1))
for isamp in range(0,p.shape[0],1):
for k in range(0,HCNum,1):
if k == 0:
Hi[k] = dot(p[isamp],u)
Ho[k] = AF(add(Hi[k],Hb[k]),AFKind)
else:
Hi[k] = dot(Ho[k-1],w[k-1])
Ho[k] = AF(add(Hi[k],Hb[k]),AFKind)
######## 計算輸出層輸入輸出 Oi Oo ########
Oi = dot(Ho[HCNum-1],v)
Oo = AF(add(Oi,Ob),AFKind)
Oo = Oo*KtoOne
p_result[isamp] = Oo
return p_result
time_begin = time.clock()
for i in range(1,n_epoch,1):
if i%1000 == 0:
print('已訓練 %d 千次 ,誤差均方差 %f'%((i/1000),e))
train(p_s,t_s)
print('訓練完成,共訓練 %d 次,誤差均方差 %f'%(i,e))
print('共耗時: ',time.clock()-time_begin)
print()
result = predict(p_t)
print('模型預測結果 : ')
for i in result:
print('%.2f'%i)
print('\n實際結果 : ')
for i in t_t:
print(i)
運作代碼後,得到的結果如下圖:
可以看到,經過訓練後,該 Bp 網絡确實從原始資料中學到了特征 , 并且較為準确地對測試資料進行了推測。
此外還要說明,此段代碼曆史較為悠久,是以很多地方寫的很不規範(很多地方保持了C的習慣···實際上是多餘的),符号使用的也比較混亂(但是實在懶得整理),僅拿來供大家參考和了解,望小夥伴們見諒。
4.Bp 神經網絡的經驗總結
以上内容對 Bp 網絡的基本用法和數學關系 進行了講解。下面有幾個重要的知識點,需要特别指出:
a.對于一個神經網絡來說,更寬更深的網絡,能夠學到更加複雜的特征,其能夠解決的問題也就越複雜,但是其計算過程也越繁瑣,參數越多,越容易出現過拟合的情況(過拟合即網絡過度學習了資料的特征,将噪聲也同時考慮到了網絡中,造成網絡隻在訓練集上表現良好,而無法泛化到其他資料上,說白了就是這個網絡已經學傻了...),是以要根據資料的實際情況來設計網絡的層數,節點數,激勵函數類型 以及 學習率。
b.對于一個神經網絡來說,用來訓練神經網絡的資料集的品質,很大程度上決定了網絡的預測效果。資料越豐富,神經網絡越能夠貼近實際關系,泛化能力越強。
c.Bp神經網絡是差別于傳統資料處理的一種方法,其特點在于尋找資料之間的相關性,并非嚴格地數學關系,是以是一種有效但是并非嚴格地網絡。對于實際問題的處理非常有用,但不能作為嚴謹數學計算的方法。
Bp網絡的出現,為後來的 AI 技術提供了理論基礎,無論是 AlphaGo ,計算機視覺,還是自然語言處理等複雜問題,都可以了解為這一結構的更新和變種(不過更新幅度有點大,變化樣式有點多···)。是以這一對于這一網絡的了解,大家應該親自寫寫代碼,多看一看大神們寫的推導過程,深入了解。