【轉】游戲編程中的人工智能技術--神經網絡

?

原文:http://blog.csdn.net/ecitnet/article/details/1799444

游戲編程中的人工智能技術
.
>
.?
(連載之一)
用平常語言介紹神經網絡
(Neural Networks in Plain English)
因為我們沒有很好了解大腦,我們經常試圖用最新的技術作為一種模型來解釋它。在我童年的時候,我們都堅信大腦是一部電話交換機。(否 則它還能是什么呢?)我當時還看到英國著名神經學家謝林頓把大腦的工作挺有趣地比作一部電報機。更早些時候,弗羅伊德經常把大腦比作一部水力發電機,而萊 布尼茨則把它比作了一臺磨粉機。我還聽人說,古希臘人把大腦功能想象為一付彈弓。顯然,目前要來比喻大腦的話,那只可能是一臺數字電子計算機了。??????????????????????????????????????????????????????????????????? ?-John R.Searle[注1]
神經網絡介紹Introduction to Neural Networks
   曾有很長一個時期,人工神經網絡對我來說是完全神秘的東西。當然,有關它們我在文獻中已經讀過了,我也能描述它們的結構和工作機理,但我始終沒有能“啊 哈!”一聲,如同你頭腦中一個難于理解的概念有幸突然得到理解時的感覺那樣。我的頭上好象一直有個榔頭在敲著,或者像電影Animal House(中文片名為“動物屋”)中那個在痛苦地尖叫“先生,謝謝您,再給我一個啊!”的可憐家伙那樣。我無法把數學概念轉換成實際的應用。有時我甚至 想把我讀過的所有神經網絡的書的作者都抓起來,把他們縛到一棵樹上,大聲地向他們吼叫:“不要再給我數學了,快給我一點實際東西吧!”。但無需說,這是永遠不可能發生的事情。我不得不自己來填補這個空隙...由此我做了在那種條件下唯一可以做的事情。我開始干起來了。<一笑>
這 樣幾個星期后,在一個美麗的日子里,當時我在蘇格蘭海邊度假,當我越過一層薄霧凝視著狹長的海灣時,我的頭腦突然受到一個沖擊。一下子悟到了人工神經網絡 是怎樣工作的。我得到“啊哈!”的感覺了!但我此時身邊只有一個帳篷和一個睡袋,還有半盒子的脆玉米片,沒有電腦可以讓我迅速寫出一些代碼來證實我的直 覺。Arghhhhh!這時我才想到我應該買一臺手提電腦。不管怎樣,幾天后我回到家了,我立刻讓我的手指在鍵盤上飛舞起來。幾個小時后我的第一人工神經 網絡程序終于編成和運行了,并且工作得挺好!自然,代碼寫的有點亂,需要進行整理,但它確實已能工作了,并且,更重要的是,我還知道它為什么能工作!我可 以告訴你,那天我是一位非常得意的人。
我希望本書傳遞給你的就是這種“啊哈!”感覺。當我們學完遺傳算法時,你可能已嘗到了一點感覺,但你希望這種感覺是美妙的話,那就要等把神經網絡部分整個學完。

生物學的神經網絡-大腦
????????????
(A Biological Neural Network–The Brain)
.... 你的大腦是一塊灰色的、像奶凍一樣的東西。它并不像電腦中的CPU那樣,利用單個的處理單元來進行工作。如果你有一具新鮮地保存到福爾馬林中的尸體,用一 把鋸子小心地將它的頭骨鋸開,搬掉頭蓋骨后,你就能看到熟悉的腦組織皺紋。大腦的外層象一個大核桃那樣,全部都是起皺的[圖0左],這一層組織就稱皮層(Cortex)。如果你再小心地用手指把整個大腦從頭顱中端出來,再去拿一把外科醫生用的手術刀,將大腦切成片,那么你將看到大腦有兩層[圖0右]: 灰色的外層(這就是“灰質”一詞的來源,但沒有經過福爾馬林固定的新鮮大腦實際是粉紅色的。) 和白色的內層。灰色層只有幾毫米厚,其中緊密地壓縮著幾十億個被稱作neuron(神經細胞、神經元)的微小細胞。白色層在皮層灰質的下面,占據了皮層的 大部分空間,是由神經細胞相互之間的無數連接組成。皮層象核桃一樣起皺,這可以把一個很大的表面區域塞進到一個較小的空間里。這與光滑的皮層相比能容納更 多的神經細胞。人的大腦大約含有1OG(即100億)個這樣的微小處理單元;一只螞蟻的大腦大約也有250,OOO個。
以下表l顯示了人和幾種動物的神經細胞的數目。
圖0-1 大腦半球像核桃圖0-2 大腦皮層由灰質和白質組成


圖0? 大腦的外形和切片形狀

表l 人和幾種動物的神經細胞的數目
動 物
?神經細胞的數目(數量級)
?蝸 牛
?10,000 (=10^4)
?蜜 蜂
?100,000 (=10^5)
?蜂 雀
?10,000,000 (=10^7)
?老 鼠
?100,000,000 (=10^8)
?人 類
??10,000,000,000 (=10^10)
?大 象
??100,000,000,000 (=10^11)
?
圖1神經細胞的結構

   在人的生命的最初9個月內,這些細胞以每分鐘25,000個的驚人速度被創建出來。神經細胞和人身上任何其他類型細胞十分不同,每個神經細胞都長著一根 像電線一樣的稱為軸突(axon)的東西,它的長度有時伸展到幾厘米[譯注],用來將信號傳遞給其他的神經細胞。神經細胞的結構如圖1所 示。它由一個細胞體(soma)、一些樹突(dendrite) 、和一根可以很長的軸突組成。神經細胞體是一顆星狀球形物,里面有一個核(nucleus)。樹突由細胞體向各個方向長出,本身可有分支,是用來接收信號 的。軸突也有許多的分支。軸突通過分支的末梢(terminal)和其他神經細胞的樹突相接觸,形成所謂的突觸(Synapse,圖中未畫出),一個神經 細胞通過軸突和突觸把產生的信號送到其他的神經細胞。
每個神經細胞通過它的樹突和大約10,000個其他的神經細胞相連。這就使得你的頭腦中所有神經細胞之間連接總計可能有l,000,000,000,000,000個。這比100兆個現代電話交換機的連線數目還多。所以毫不奇怪為什么我們有時會產生頭疼毛病!
有趣的事實
???????
曾經有人估算過,如果將一個人的大腦中所有神經細胞的軸突和樹突依次連接起來,并拉成一根直線,可從地球連到月亮,再從月亮返回地球。如果把地球上所有人腦的軸突和樹突連接起來,則可以伸展到離開們最近的星系!
神 經細胞利用電-化學過程交換信號。輸入信號來自另一些神經細胞。這些神經細胞的軸突末梢(也就是終端)和本神經細胞的樹突相遇形成突觸 (synapse),信號就從樹突上的突觸進入本細胞。信號在大腦中實際怎樣傳輸是一個相當復雜的過程,但就我們而言,重要的是把它看成和現代的計算機一 樣,利用一系列的0和1來進行操作。就是說,大腦的神經細胞也只有兩種狀態:興奮(fire)和不興奮(即抑制)。發射信號的強度不變,變化的僅僅是頻 率。神經細胞利用一種我們還不知道的方法,把所有從樹突上突觸進來的信號進行相加,如果全部信號的總和超過某個閥值,就會激發神經細胞進入興奮 (fire)狀態,這時就會有一個電信號通過軸突發送出去給其他神經細胞。如果信號總和沒有達到閥值,神經細胞就不會興奮起來。這樣的解釋有點過分簡單 化,但已能滿足我們的目的。

  神經細胞利用電-化學過程交換信號。輸入信號來自另一些神經細胞。這些神經細胞的軸突末梢(也就是終端) 和本神經細胞的樹突相遇形成突觸(synapse),信號就從樹突上的突觸進入本細胞。信號在大腦中實際怎樣傳輸是一個相當復雜的過程,但就我們而言,重 要的是把它看成和現代的計算機一樣,利用一系列的0和1來進行操作。就是說,大腦的神經細胞也只有兩種狀態:興奮(fire)和不興奮(即抑制)。發射信 號的強度不變,變化的僅僅是頻率。神經細胞利用一種我們還不知道的方法,把所有從樹突上突觸進來的信號進行相加,如果全部信號的總和超過某個閥值,就會激 發神經細胞進入興奮(fire)狀態,這時就會有一個電信號通過軸突發送出去給其他神經細胞。如果信號總和沒有達到閥值,神經細胞就不會興奮起來。這樣的 解釋有點過分簡單化,但已能滿足我們的目的。
正是由于數量巨大的連接,使得大腦具備難以置信的能力。盡管每一個神經細胞僅僅工作于大約100Hz的頻率,但因各個神經細胞都以獨立處理單元的形式并行工作著,使人類的大腦具有下面這些非常明顯的特點:

  能實現無監督的學習。?有 關我們的大腦的難以置信的事實之一,就是它們能夠自己進行學習,而不需要導師的監督教導。如果一個神經細胞在一段時間內受到高頻率的刺激,則它和輸入信號 的神經細胞之間的連接強度就會按某種過程改變,使得該神經細胞下一次受到激勵時更容易興奮。這一機制是50多年以前由Donard Hebb在他寫的Organination of Behavior一書中闡述的。他寫道:
“當神經細胞 A的一個軸突重復地或持久地激勵另一個神經細胞B后,則其中的一個或同時兩個神經細胞就會發生一種生長過程或新陳代謝式的變化,使得勵 B細胞之一的A細胞,它的效能會增加”
與此相反的就是,如果一個神經細胞在一段時間內不受到激勵,那么它的連接的有效性就會慢慢地衰減。這一現象就稱可塑性(plasticity)。
對損傷有冗余性(tolerance)。大 腦即使有很大一部分受到了損傷,它仍然能夠執行復雜的工作。一個著名的試驗就是訓練老鼠在一個迷宮中行走。然后,科學家們將其大腦一部分一部分地、越來越 大地加以切除。他們發現,即使老鼠的很大的一部大腦被切除后,它們仍然能在迷宮中找到行走路徑。這一事實證明了,在大腦中,知識并不是保存在一個局部地 方。另外所作的一些試驗則表明,如果大腦的一小部分受到損傷,則神經細胞能把損傷的連接重新生長出來。
處理信息的效率極高。神 經細胞之間電-化學信號的傳遞,與一臺數字計算機中CPU的數據傳輸相比,速度是非常慢的,但因神經細胞采用了并行的工作方式,使得大腦能夠同時處理大量 的數據。例如,大腦視覺皮層在處理通過我們的視網膜輸入的一幅圖象信號時,大約只要100ms的時間就能完成。考慮到你的神經細胞的平均工作頻率只有 100Hz,100ms的時間就意味只能完成10個計算步驟!想一想通過我們眼睛的數據量有多大,你就可以看到這真是一個難以置信的偉大工程了。
善于歸納推廣。大腦和數字計算機不同,它極擅長的事情之一就是模式識別,并能根據已熟悉信息進行歸納推廣(generlize)。例如,我們能夠閱讀他人所寫的手稿上的文字,即使我們以前從來沒見過他所寫的東西。
它是有意識的。意 識(consciousness)是神經學家和人工智能的研究者廣泛而又熱烈地在辯論的一個話題。有關這一論題已有大量的文獻出版了,但對于意識實際究竟 是什么,至今尚未取得實質性的統一看法。我們甚至不能同意只有人類才有意識,或者包括動物王國中人類的近親在內才有意識。一頭猩猩有意識嗎?你的貓有意識 嗎?上星期晚餐中被你吃掉的那條魚有意識嗎?
因此,一個人工神經網絡(Artificial neural network,簡稱ANN)就是要在當代數字計算機現有規模的約束下,來模擬這種大量的并行性,并在實現這一工作時,使它能顯示許多和生物學大腦相類似的特性。下面就讓我們瞧瞧它們的表演吧!

?

游戲編程中的人工智能技術
.
>
.
(連載之二)
3 數字版的神經網絡 (The Digital Version)
上面我們看到了生物的大腦是由許多神經細胞組成,同樣,模擬大腦的人工神經網絡ANN是由許多叫做人工神經細胞(Artificial neuron,也稱人工神經原,或人工神經元)的細小結構模塊組成。人工神經細胞就像真實神經細胞的一個簡化版,但采用了電子方式來模擬實現。一個人工神 經網絡中需要使用多少個數的人工神經細胞,差別可以非常大。有的神經網絡只需要使用10個以內的人工神經細胞,而有的神經網絡可能需要使用幾千個人工神經 細胞。這完全取決于這些人工神經網絡準備實際用來做什么。
?有趣的事實
?????? 有 一個叫 Hugo de Garis的同行,曾在一個雄心勃勃的工程中創建并訓練了一個包含1000,000,000個人工神經細胞的網絡。這個人工神經網絡被他非常巧妙地建立起 來了,它采用蜂房式自動機結構,目的就是為一機器客戶定制一個叫做CAM BrainMachine(“CAM大腦機器”) 的機器(CAM就是Cellular Automata Machine的縮寫)。此人曾自夸地宣稱這一人工網絡機器將會有一只貓的智能。許多神經網絡研究人員認為他是在“登星”了,但不幸的是,雇用他的公司在 他的夢想尚未實現之前就破產了。此人現在猶他州,是猶他州大腦工程(Utah Brain Project)的領導。時間將會告訴我們他的思想最終是否能變成實際有意義的東西。[譯注]?


?  我想你現在可能很想知道,一個人工神經細胞究竟是一個什么樣的東西?但是,它實際上什么東西也不像; 它只是一種抽象。還是讓我們來察看一下圖2吧,這是表示一個人工神經細胞的一種形式。

[譯注]Hugo de Garis現在為猶他州立大學教授,有關他和他的CAM機器,可在該校網站的一個網頁上看到報道,其上有真實的照片,見http://www.cs.usu.edu/~degaris

圖2 一個人工神經細胞
圖中,左邊幾個灰底圓中所標字母w代表浮點數,稱為權重(weight,或權值,權數)。進入人工神經細胞的每一個input(輸入)都與一個權重w相 聯系,正是這些權重將決定神經網絡的整體活躍性。你現在暫時可以設想所有這些權重都被設置到了-1和1之間的一個隨機小數。因為權重可正可負,故能對與它 關聯的輸入施加不同的影響,如果權重為正,就會有激發(excitory)作用,權重為負,則會有抑制(inhibitory)作用。當輸入信號進入神經 細胞時,它們的值將與它們對應的權重相乘,作為圖中大圓的輸入。大圓的‘核’是一個函數,叫激勵函數(activation function),它把所有這些新的、經過權重調整后的輸入全部加起來,形成單個的激勵值(activation value)。激勵值也是一浮點數,且同樣可正可負。然后,再根據激勵值來產生函數的輸出也即神經細胞的輸出:如果激勵值超過某個閥值(作為例子我們假設 閥值為1.0),就會產生一個值為1的信號輸出;如果激勵值小于閥值1.0,則輸出一個0。這是人工神經細胞激勵函數的一種最簡單的類型。在這里,從激勵 值產生輸出值是一個階躍函數[譯注]。看一看圖3后你就能猜到為什么有這樣的名稱。
圖3 階躍激勵函數
[譯注] 由圖可知階躍函數是一元的,而激勵函數既然能把多個輸入相加應為多元,故需加以區別。
?如果到目前為止你對這些還沒有獲得很多感覺,那也不必擔心。竅門就是: 不要企圖去感覺它,暫時就隨波逐流地跟我一起向前走吧。在經歷本章的若干處后,你最終就會開始弄清楚它們的意義。而現在,就放松一點繼續讀下去吧。
3.1 現在需要一些數學了(Now for Some Math)
   ?今后討論中,我將盡量把數學降低到絕對少量,但學習一些數學記號對下面還是很有用的。我將把數學一點一點地喂給你,在到達有關章節時向你介紹一些新概 念。我希望采用這樣的方式能使你的頭腦能更舒適地吸收所有的概念,并使你在開發神經網絡的每個階段都能看到怎樣把數學應用到工作中。現在首先讓我們來看一 看,怎樣把我在此之前告訴你的所有知識用數學方式表達出來。
一個人工神經細胞(從現在開始,我將把“人工神經細胞”簡稱它為“神經細胞”) 可以有任意n個輸入,n代表總數。可以用下面的數學表達式來代表所有n個輸入:
x1, x2, x3, x4, x5, ..., xn
同樣 n 個權重可表達為:
w1, w2, w3, w4, w5 ..., wn
請記住,激勵值就是所有輸入與它們對應權重的之乘積之總和,因此,現在就可以寫為:
a = w1x1 + w2x2 + w3x3 + w4x4 + w5x5 +...+ wnxn
以這種方式寫下的求和式,我在第5章“建立一個更好的遺傳算法”中已提到,可以用希臘字母Σ來簡化:
注:
?神經網絡的各個輸入,以及為各個神經細胞的權重設置,都可以看作一個n維的向量。你在許多技術文獻中常常可以看到是以這樣的方式來引用的。
?
下面我們來考察在程序中應該怎樣實現?假設輸入數組和權重數組均已初始化為x[n]和w[n],則求和的代碼如下:
double activation = 0;
for(int i=0; i<n; ++i)
?{
?activation += x[i] * w[i];
?}
圖4以圖形的方式表示了此方程。請別忘記,如果激勵值超過了閥值,神經細胞就輸出1; 如果激活小于閥值,則神經細胞的輸出為0。這和一個生物神經細胞的興奮和抑制是等價的。我們假設一個神經細胞有5個輸入,他們的權重w都初始化成正負1之 間的隨機值(-1 < w < 1) 。 表2說明了激勵值的求和計算過程。
圖4 神經細胞的激勵函數
如果我們假定激活所需閥值=1,則因激勵值1.1 > 激活閥值1,所以這個神經細胞將輸出1。
?在進一步讀下去之前,請你一定要確切弄懂激勵函數怎樣計算。
?

?表2? 神經細胞激勵值的計算
輸 入
?權 重
?輸入*權重的乘積
?運行后總和?
1
?0.5
?0.5
?0.5?
0
-0.2
?0
?0.5
1?
-0.3?
-0.3?
?0.2?
1?
0.9
?0.9?
?1.1?
0
0.1?
?0
?1.1

?
3.2? 行,我知道什么是神經細胞了,但用它來干什么呢?
?   大腦里的生物神經細胞和其他的神經細胞是相互連接在一起的。為了創建一個人工神經網絡,人工神經細胞也要以同樣方式相互連接在一起。為此可以有許多不同 的連接方式,其中最容易理解并且也是最廣泛地使用的,就是如圖5所示那樣,把神經細胞一層一層地連結在一起。這一種類型的神經網絡就叫前饋網絡 (feedforword network)。這一名稱的由來,就是因為網絡的每一層神經細胞的輸出都向前饋送(feed)到了它們的下一層(在圖中是畫在它的上面的那一層),直到 獲得整個網絡的輸出為止。

?
    圖5 一個前饋網絡
由圖可知,網絡共有三層(譯注:輸入層不是神經細胞,神經細胞只有兩層)。輸入層中的每個輸入都饋送到了隱藏層,作為該層每一個神經細胞的輸入;然 后,從隱藏層的每個神經細胞的輸出都連到了它下一層(即輸出層)的每一個神經細胞。圖中僅僅畫了一個隱藏層,作為前饋網絡,一般地可以有任意多個隱藏層。 但在對付你將處理的大多數問題時一層通常是足夠的。事實上,有一些問題甚至根本不需要任何隱藏單元,你只要把那些輸入直接連結到輸出神經細胞就行了。另 外,我為圖5選擇的神經細胞的個數也是完全任意的。每一層實際都可以有任何數目的神經細胞,這完全取決于要解決的問題的復雜性。但神經細胞數目愈多,網絡 的工作速度也就愈低,由于這一緣故,以及為了其他的幾種原因(我將在第9章作出解釋),網絡的規模總是要求保持盡可能的小。

  ?到此我能想象你或許已對所有這些信息感到有些茫然了。我認為,在這種情況下,我能做的最好的事情,就是向你介紹一個神經網絡在現實世界中的實際應用例子,它有望使你自己的大腦神經細胞得到興奮!不錯吧?好的,下面就來了...

???  你可能已聽到或讀到過神經網絡常常用來作模式識別。這是因為它們善于把一種輸入狀態(它所企圖識別的模式)映射到一種輸出狀態(它曾被訓練用來識別的模式)。

?  下面我們來看它是怎么完成的。我們以字符識別作為例子。設想有一個由8x8個格子組成的一塊面板。每一個格子里放了一個小燈,每個小燈都可獨立地被打開(格子變亮)或關閉(格子變黑),這樣面板就可以用來顯示十個數字符號。圖6顯示了數字“4”。
圖6? 用于字符顯示的矩陣格點
要解決這一問題,我們必需設計一個神經網絡,它接收面板的狀態作為輸入,然后輸出一個1或0;輸出1代表ANN確認已顯示了數字“4”,而輸出0表示沒 有顯示“4”。因此,神經網絡需要有64個輸入(每一個輸入代表面板的一個具體格點) 和由許多神經細胞組成的一個隱藏層,還有僅有一個神經細胞的輸出層,隱藏層的所有輸出都饋送到它。我真希望你能在你的頭腦中畫出這個圖來,因為要我為你把 所有這些小圓和連線統統畫出來確實不是一樁愉快的事<一笑>。

?  一旦神經網絡體系創建成功后,它必須接受訓練來認出數字 “4”。為此可用這樣一種方法來完成:先把神經網的所有權重初始化為任意值。然后給它一系列的輸入,在本例中,就是代表面板不同配置的輸入。對每一種輸入 配置,我們檢查它的輸出是什么,并調整相應的權重。如果我們送給網絡的輸入模式不是“4”, 則我們知道網絡應該輸出一個0。因此每個非“4”字符時的網絡權重應進行調節,使得它的輸出趨向于0。當代表“4”的模式輸送給網絡時,則應把權重調整到 使輸出趨向于1。

  ?如果你考慮一下這個網絡,你就會知道要把輸出增加到10是很容易的。然后通過訓練,就可以使網絡能識別0到9 的所有數字。但為什么我們到此停止呢?我們還可以進一步增加輸出,使網絡能識別字母表中的全部字符。這本質上就是手寫體識別的工作原理。對每個字符,網絡 都需要接受許多訓練,使它認識此文字的各種不同的版本。到最后,網絡不單能認識已經訓練的筆跡,還顯示了它有顯著的歸納和推廣能力。也就是說,如果所寫文 字換了一種筆跡,它和訓練集中所有字跡都略有不同,網絡仍然有很大幾率來認出它。正是這種歸納推廣能力,使得神經網絡已經成為能夠用于無數應用的一種無價 的工具,從人臉識別、醫學診斷,直到跑馬賽的預測,另外還有電腦游戲中的bot(作為游戲角色的機器人)的導航,或者硬件的robot(真正的機器人)的 導航。

????  這種類型的訓練稱作有監督的學習(supervised learnig),用來訓練的數據稱為訓練集(training set)。調整權重可以采用許多不同的方法。對本類問題最常用的方法就是反向傳播(backpropagation,簡稱backprop或BP)方法。 有關反向傳播問題,我將會在本書的后面,當你已能訓練神經網絡來識別鼠標走勢時,再來進行討論。在本章剩余部分我將集中注意力來考察另外的一種訓練方式, 即根本不需要任何導師來監督的訓練,或稱無監督學習(unsupervised learnig)。

????  這樣我已向你介紹了一些基本的知識,現在讓我們來考察一些有趣的東西,并向你介紹第一個代碼工程。
游戲編程中的人工智能技術
>
?(連載之三)
4. 聰明的掃雷機工程(Smart Minesweeper Project)
????? ?我要向你介紹的第一個完整例子,是怎么使用神經網絡來控制具有人工智能的掃雷機的行為。掃雷機工作在一個很簡單的環境中,那里只有掃雷機以及隨機散布的許多地雷。


圖7 運行中的演示程序。
?   盡管書上圖形畫成了黑白色,但當你運行程序時性能最好的掃雷機將顯現為紅色。地雷,你可能已經猜到,就是那些小方形。工程的目標是創建一個網絡,它不需 要從我們這里得到任何幫助,就能自己進行演化(evolve)去尋找地雷。為了實現這一功能,網絡的權重將被編碼到基因組中,并用一個遺傳算法來演化它 們。
?
??????? 怎么樣,很酷吧?
提示(重要)
???? ?如果你跳過前面的一些章節來到這里,而你又不了解怎樣使用遺? 傳算法,則在進一步閱讀下面的內容之前,你應回到前面去補讀一下有關遺傳算法的內容。

?
?????? 首先讓我解釋人工神經網絡(ANN)的體系結構。我們需要決定輸入的數目、輸出的數目、還有隱藏層和每個隱藏層中隱藏單元的數目。
4.1 選擇輸出(Choosing the Outputs)

??????? 那么,人工神經網絡怎樣控制掃雷機的行動呢?很好!我們把掃雷機想象成和坦克車一樣,通過左右2個能轉動的履帶式輪軌(track)來行動的。見圖案9.8。
?圖8? 掃雷機的控制

?  掃雷機向前行進的速度,以及向左、向右轉彎的角度,都是通過改變2個履帶輪的相對速度來實現的。因此,神經網絡需要2個輸入,1個是左側履帶輪的速度,另一個是右側履帶輪的速度。

?  啊,但是...,我聽見你在嘀咕了。如果網絡只能輸出一個1或一個0,我們怎么能控制車軌移動的快慢呢? 你是對的;如果利用以前描述的階躍函數來決定輸出,我們就根本無法控制掃雷機實際移動。幸好,我有一套戲法,讓我卷起袖子來,把激勵函數的輸出由階躍式改 變成為在0-1之間連續變化的形式,這樣就可以供掃雷機神經細胞使用了。為此,有幾種函數都能做到這樣,我們使用的是一個被稱為邏輯斯蒂S形函數 (logistic sigmoid function)[譯注1]。該函數所實現的功能,本質上說,就是把神經細胞原有的階躍式輸出曲線鈍化為一光滑曲線,后者繞y軸0.5處點對稱[譯注 2],如圖9所示。
?
[譯注1] logistic有’計算的’或’符號邏輯的’等意思在內,和’邏輯的(logic)’意義不同。
[譯注2] 點對稱圖形繞對稱點轉180度后能與原圖重合。若f(x)以原點為點對稱,則有f(-x)=-f(x)

圖9 S形曲線。
?  當神經細胞的激勵值趨于正、負無窮時,S形函數分別趨于1或0。負的激勵值對應的函數值都<0.5; 正激勵值對應的函數值都>0.5。S形函數用數學表達式寫出來則為:???????????????
??   這個方程看上去可能會嚇唬一些人,但其實很簡單。e是數學常數,近似等于2.7183,a是神經細胞的激勵值,它是函數的自變量,而p是一個用來控制曲 線形狀變化快慢或陡峭性的參數。p通常設定為1。當p賦以較大值時,曲線就顯得平坦,反之,就會使曲線變為陡峭。見圖1O。很低的p值所生成的函數就和階 躍函數近似。P值的大小用來控制何時使神經網絡由低變高開始翻轉有很大作用,但是在本例子中我們將它保持為1。
?
注:“S型”的英文原名Sigmoid 或Sigmoidal 原來是根據希臘字“Sigma”得來的,但非常巧它也可以說成是曲線的一種形狀。?

?
圖7。10? 不同的S形響應曲線。
4.2 選擇輸入(Choosing the Inputs)

?  上面我們已經把輸出安排好了,現在我們來考慮輸入,確定網絡需要什么樣的輸入?為此,我們必須想象一下掃雷機的具體細節:需要什么樣的信息才能使它朝地雷前進?你可能想到的第一個輸入信息清單是:
  • 掃雷機的位置(x1,y1)
  • 與掃雷機最靠近的地雷的位置(x2,y2)
  • 代表掃雷機前進方向的向量(x3,y3)
?   這樣一共得到6個輸入。但是,要網絡使用這些輸入,工作起來就非常困難,因為,網絡在像我們希望的那樣執行工作之前,必須尋找所有6個輸入之間的數學關 系,而這有相當工作量。可以把此作為一個練習倒是很理想的:去試試如何給出最少數量的輸入而仍能為網絡傳達解決問題所需要的全部信息。 你的網絡使用的輸入愈少,網絡所要求的神經細胞數目也愈少。而較少的神經細胞就意味更快速的訓練和更少的計算,有利于網絡更高速度的工作。

?  只要作少量的額外考慮,就能夠把輸入的個數減少為4,這就是圖11中所畫出的兩個向量的4個參數。
??????? 把神經網絡的所有輸入進行規范化是一種好想法。這里的意思并不是說每個輸入都要改變大小使它們都在0~1間,而是說每一個輸入應該受到同等重視。例如,拿 我們已經討論過的掃雷機輸入為例。瞄準向量或視線向量(look-at vector)總是一個規范化向量,即長度等于1,分量x和y都在0~1間。但從掃雷機到達其最近地雷的向量就可能很大,其中的一個分量甚至有可能和窗體 的寬度或高度一樣大。如果這個數據以它的原始狀態輸入到網絡,網絡對有較大值的輸入將顯得更靈敏,由此就會使網絡性能變差。因此,在信息輸入到神經網絡中 去之前,數據應預先定比(scaled)和標準化(standardized),使它們大小相似(similar)。在本特例中,由掃雷機引到與其最接近 地雷的向量需要進行規范化(normalized)。這樣可以使掃雷機的性能得到改良。
?

?
圖11 選擇輸入。
  
?小技巧:
??? 有時,你把輸入數據重新換算(rescale)一下,使它以0點為中心,就能從你的神經網絡獲得最好的性能。這一小竅門在你設計網絡時永遠值得一試。但我在掃雷機工程中沒有采用這一方法,這是因為我想使用一種更直覺的方法。
?
4.3 隱藏的神經細胞要多少?(How many Hidden Neurons?)

?   到此我們已把輸入、輸出神經細胞的數目和種類確定下來了,下一步是確定隱藏層的數目,并確定每個隱藏層中神經細胞必須有多少?但遺憾的是,還沒有一種確 切的規則可用來計算這些。它們的開發又需要憑個人的“感覺”了。某些書上和文章中確實給過一些提綱性的東西,告訴你如何去決定隱藏神經細胞個數,但業內專 家們的一致看法是:你只能把任何建議當作不可全信的東西,主要還要靠自己的不斷嘗試和失敗中獲得經驗。但你通常會發現,你所遇到的大多數問題都只要用一個 隱藏層就能解決。所以,本領的高低就在于如何為這一隱藏層確定最合適的神經細胞數目了。顯然,個數是愈少愈好,因為我前面已經提及,數目少的神經細胞能夠 造就快速的網絡。通常,為了確定出一個最優總數,我總是在隱藏層中采用不同數目的神經細胞來進行試驗。我在本章所編寫的神經網絡工程的.
   第一版本中一共使用了10個隱藏神經細胞(當然,我的這個數字也不一定是最好的<一笑>)。你應圍繞這個數字的附近來做游戲,并觀察隱藏層 神經細胞的數目對掃雷機的演化會產生什么樣的影響。不管怎樣,理論已經夠了,讓我們拿一個具體程序來看看吧!你可以在本書所附光盤的 Chapter7/Smart Sweepers v1.0文件夾中找到本章下面幾頁即將描述的所有程序的源碼。
游戲編程中的人工智能技術
.
>
.
(連載之四)
?
4.4? CNeuralNet.h(神經網絡類的頭文件)
?  在CNeuralNet.h 文件中,我們定義了人工神經細胞的結構、定義了人工神經細胞的層的結構、以及人工神經網絡本身的結構。首先我們來考察人工神經細胞的結構。
4.4.1? SNeuron(神經細胞的結構)
這是很簡單的結構。人工神經細胞的結構中必須有一個正整數來紀錄它有多少個輸入,還需要有一個向量std:vector來表示它的權重。請記住,神經細胞的每一個輸入都要有一個對應的權重。
Struct SNeuron
{
???? // 進入神經細胞的輸入個數
???? int m_NumInputs;
????
???? // 為每一輸入提供的權重
???? vector<double> m_vecWeight;
????
???? //構造函數
???? SNeuron(int NumInputs);
? };
以下就是SNeuron 結構體的構造函數形式:
SNeuron::SNeuron(int NumInputs): m_NumInputs(NumInputs+1)
?(
???? // 我們要為偏移值也附加一個權重,因此輸入數目上要 +1
???? for (int i=0; i<NumInputs+1; ++i)
???? {
???????? // 把權重初始化為任意的值
??????? ?m_vecWeight.push_back(RandomClamped());
???? }
?}
?  由上可以看出,構造函數把送進神經細胞的輸入數目NumInputs作為一個變元,并為每個輸入創建一個隨機的權重。所有權重值在-1和1之間。

??????? 這是什么? 我聽見你在說。這里多出了一個權重!不 錯,我很高興看到你能注意到這一點,因為這一個附加的權重十分重要。但要解釋它為什么在那里,我必須更多地介紹一些數學知識。回憶一下你就能記得,激勵值 是所有輸入*權重的乘積的總和,而神經細胞的輸出值取決于這個激勵值是否超過某個閥值(t)。這可以用如下的方程來表示:
w1x1 + w2x2 + w3x3 +...+ wnxn >= t
上式是使細胞輸出為1的條件。因為網絡的所有權重需要不斷演化(進化),如果閥值的數據也能一起演化,那將是非常重要的。要實現這一點不難,你使用一個簡單的詭計就可以讓閥值變成權重的形式。從上面的方程兩邊各減去t,得:
w1x1 + w2x2 + w3x3 +...+ wnxn –t >= 0
這個方程可以再換用一種形式寫出來,如下:
w1x1 + w2x2 + w3x3 +...+ wnxn + t *(–1) >= 0
到此,我希望你已能看出,閥值t為什么可以想像成為始終乘以輸入為 -1的權重了。這個特殊的權重通常叫偏移(bias),這就是為什么每個神經細胞初始化時都要增加一個權重的理由。現在,當你演化一個網絡時,你就不必再 考慮閥值問題,因為它已被內建在權重向量中了。怎么樣,想法不錯吧?為了讓你心中絕對敲定你所學到的新的人工神經細胞是什么樣子,請再參看一下圖12。

?
圖12 帶偏移的人工神經細胞。
4.4.2? SNeuronLayer(神經細胞層的結構)
  ?神經細胞層SNeuronLayer的結構很簡單;它定義了一個如圖13中所示的由虛線包圍的神經細胞SNeuron所組成的層。?

?
     圖13 一個神經細胞層。
以下就是層的定義的源代碼,它應該不再需要任何進一步的解釋:
struct SNeuronLayer
{
??  // 本層使用的神經細胞數目
 ? int????????????????     m_NumNeurons;
?
???? ?// 神經細胞的層
 ? vector<SNeuron>?? m_vecNeurons;
?
?  SNeuronLayer(int NumNeurons, int NumInputsPerNeuron);
};
4.4.3? CNeuralNet(神經網絡類)
這是創建神經網絡對象的類。讓我們來通讀一下這一個類的定義:
class CNeuralNet
{
private:
??? int???????? ??? ???m_NumInputs;
??? int???? ??????? ???m_NumOutputs;
??? int? ??????????? ??m_NumHiddenLayers;
??? int ??????  m_NeuronsPerHiddenLyr;
??? // 為每一層(包括輸出層)存放所有神經細胞的存儲器
??? vector<SNeuronLayer> ?m_vecLayers;
所有private成員由其名稱容易得到理解。需要由本類定義的就是輸入的個數、輸出的個數、隱藏層的數目、以及每個隱藏層中神經細胞的個數等幾個參數。
public:
???? CNeuralNet();
該構造函數利用ini文件來初始化所有的Private成員變量,然后再調用CreateNet來創建網絡。
???? // 由SNeurons創建網絡
???? void ???CreateNet();
我過一會兒馬上就會告訴你這個函數的代碼。
?
??? ?// 從神經網絡得到(讀出)權重
???? vector<double> ? GetWeights()const;
由于網絡的權重需要演化,所以必須創建一個方法來返回所有的權重。這些權重在網絡中是以實數型向量形式表示的,我們將把這些實數表示的權重編碼到一個基因組中。當我開始談論本工程的遺傳算法時,我將為您確切說明權重如何進行編碼。
?
??? // 返回網絡的權重的總數
??? int GetNumberOfWeights()const;
?? ?// 用新的權重代替原有的權重
??? void PutWeights(vector<double> &weights);

??????? 這一函數所做的工作與函數GetWeights所做的正好相反。當遺傳算法執行完一代時,新一代的權重必須重新插入神經網絡。為我們完成這一任務的是PutWeight方法。
???
???? // S形響應曲線
??? inline double  Sigmoid(double activation, double response);
?
??? ?當已知一個神經細胞的所有輸入*重量的乘積之和時,這一方法將它送入到S形的激勵函數。
?
???? // 根據一組輸入,來計算輸出
???? vector<double> Update(vector<double> &inputs);
對此Update函數函數我馬上就會來進行注釋的。
}; // 類定義結束
4.4.3.1? CNeuralNet::CreateNet(創建神經網絡的方法)
我在前面沒有對CNeuralNet的2個方法加以注釋,這是因為我要為你顯示它們的更完整的代碼。這2個方法的第一個是網絡創建方法 CreateNet。它的工作就是把由細胞層SNeuronLayers所收集的神經細胞SNeurons聚在一起來組成整個神經網絡,代碼為:

void CNeuralNet::CreateNet()
{
?? ?// 創建網絡的各個層
??? if (m_NumHiddenLayers > 0)
???? ?{
????? //創建第一個隱藏層[譯注]
???? ?m_vecLayers.push_back(SNeuronLayer(m_NeuronsPerHiddenLyr,
?????????????????????????????????????????? m_NumInputs));
?
??? ?for( int i=O; i<m_NumHiddenLayers-l; ++i)
??? ?{
??????? m_vecLayers.push_back(SNeuronLayer(m_NeuronsPerHiddenLyr,
????????????????????????????????????????????????? m_NeuronsPerHiddenLyr));
????? }
?
[譯注]如果允許有多個隱藏層,則由接著for循環即能創建其余的隱藏層。
????? // 創建輸出層
????? m_vecLayers.push_back(SNeuronLayer(m_NumOutput,m_NeuronsPerHiddenLyr));
?? }
else //無隱藏層時,只需創建輸出層
?? {
????? ?// 創建輸出層
?????? ?m_vecLayers.push_back(SNeuronLayer(m_NumOutputs, m_NumInputs));
?? }
}

4.4.3.2? CNeuralNet::Update(神經網絡的更新方法)
Update函數(更新函數)稱得上是神經網絡的“主要勞動力”了。這里,輸入網絡的數據input是以雙精度向量std::vector的數據格式傳 遞進來的。Update函數通過對每個層的循環來處理輸入*權重的相乘與求和,再以所得的和數作為激勵值,通過S形函數來計算出每個神經細胞的輸出,正如 我們前面最后幾頁中所討論的那樣。Update函數返回的也是一個雙精度向量std::vector,它對應的就是人工神經網絡的所有輸出。
請你自己花兩分鐘或差不多的時間來熟悉一下如下的Update函數的代碼,這能使你正確理解我們繼續要講的其他內容:
vector<double> CNeuralNet::Update(vector<double> &inputs)
{
??? ?// 保存從每一層產生的輸出
??? ?vector<double> outputs;
???? int cWeight = 0;
??? ?// 首先檢查輸入的個數是否正確
??? ?if (inputs.size() != m_NumInputs)
????? {
??????? ? // 如果不正確,就返回一個空向量
????????? return outputs;
???? ?}
?
???? // 對每一層,...
???? for (int i=0; i<m_NumHiddenLayers+1; ++i)
???? {
?????? if (i>O)
???????? {
??????????? inputs = outputs;
???????? }
?? ?outputs.clear();
?
?? ?cWeight = 0;
?
?? ?// 對每個神經細胞,求輸入*對應權重乘積之總和。并將總和拋給S形函數,以計算輸出
?? for (int j=0; j<m_vecLayers[i].m_NumNeurons; ++j)
??????? {
????????? double netinput = 0;
????
????????? int NumInputs = m_vecLayers[i].m_vecNeurons[j].m_NumInputs;
????
???????? // 對每一個權重
???????? for (int k=O; k<NumInputs-l; ++k)
???????? {
??????????? // 計算權重*輸入的乘積的總和。
??????????? netinput += m_vecLayers[i].m_vecNeurons[j].m_vecWeight[k] *
????  ??      ????? inputs[cWeight++];
???????? }
????
??????? // 加入偏移值
??????? netinput += m_vecLayers[i].m_vecNeurons[j].m_vecWeight[NumInputs-1] *
????????????????? ??CParams::dBias;
別忘記每個神經細胞的權重向量的最后一個權重實際是偏移值,這我們已經說明過了,我們總是將它設置成為 –1的。我已經在ini文件中包含了偏移值,你可以圍繞它來做文章,考察它對你創建的網絡的功能有什么影響。不過,這個值通常是不應該改變的。
???? // 每一層的輸出,當我們產生了它們后,我們就要將它們保存起來。但用Σ累加在一起的
???? // 激勵總值首先要通過S形函數的過濾,才能得到輸出
outputs.push_back(Sigmoid(netinput,CParams::dActivationResponse)); cWeight = 0:
??? }
? }
? return outputs;
}
游戲編程中的人工智能技術
.
>
.
(連載之五)
4.5 神經網絡的編碼(Encoding the Network)
  ?在本書的開始幾章中,你已經看到過怎樣用各種各樣的方法為遺傳算法編碼。但當時我并沒有向你介紹過
一個用實數編碼的具體例子,因為我知道我要留在這里向你介紹。我曾經講到,為了設計一個前饋型神經網絡,
編碼是很容易的。我們從左到右讀每一層神經細胞的權重,讀完第一個隱藏層,再向上讀它的下一層,把所讀
到的數據依次保存到一個向量中,這樣就實現了網絡的編碼。因此,如果我們有圖14所示的網絡,則它的權重
編碼向量將為:
0.3, -O.8, -O.2, 0.6, O.1, -0.l, 0.4, 0.5
在這一網絡中,為了簡單,我沒有把偏移值的權重包括進去。但在實際實現編碼時,你必須包含偏移值這
個權重,否則你肯定無法獲得你所需要的結果。
圖14 為權重編碼。
在此之前講的事情你都懂了嗎?好極了,那下面就讓我們轉來考慮,怎樣用遺傳算法來操縱已編碼的基因吧。

4.6? 遺傳算法(The Genetic Algorithm)
??????? 到此,所有的權重已經象二進制編碼的基因組那樣,形成了一個串,我們就可以象本書早先討論過的那樣
來應用遺傳算法了。遺傳算法(GA)是在掃雷機已被允許按照用戶指定的幀數(為了某種緣故, 我下面更喜歡
將幀數稱作滴答數,英文是ticks)運轉后執行的。你可以在ini文件中找到這個滴答數(iNumTicks)的設置。
下面是基因組結構體的代碼。這些對于你應該是十分面熟的東西了。
?
Struct SGenome
?{
? vector <double>   vecWeights;
?
? double      ?dFitness;
?
? SGenome():dFitness(0) {}
?
? SGenome(vector <double> w, double f):vecWeights(w),dFitness(f){}
?
? //重載'<'的排序方法
????? friend bool? operator<(const SGenome& lhs, const SGenome& rhs)
???????? {
????  ????? return (lhs.dFitness < rhs.dFitness);
???????? }
?};
從上面的代碼你可看出,這一SGenome結構和我們在本書所有其他地方見到的SGenome結構幾乎完全一致,唯一的差別就是這里的染色體是一個雙精度向量std::vector。因此,可以和通常一樣來應用雜交操作和選擇
操作。但突變操作則稍微有些不同,這里的權重值是用一個最大值為dMaxPerturbation的隨機數來搔擾的。這一
參數dMaxPerturbation在ini文件中已作了聲明。另外,作為浮點數遺傳算法,突變率也被設定得更高些。在本工
程中,它被設成為0.1。
下面就是掃雷機工程遺傳算法類中所見到的突變函數的形式:
void CGenAlg::Mutate(vector<double> &chromo)
{
? // 遍歷權重向量,按突變率將每一個權重進行突變
? for (int i=0; i<chromo.size(); ++i)
? {
???? // 我們要騷擾這個權重嗎?
???? if (RandFloat() < m_dMutationRate)
???? {
?????? // 為權重增加或減小一個小的數量
?????? chromo[i] += (RandomClamped() * CParams::dMaxPerturbatlon);
???? }
? }
}
如同以前的工程那樣,我已為v1.0版本的Smart Minesweepers工程保留了一個非常簡單的遺傳算法。這樣
就能給你留下許多余地,可讓你利用以前學到的技術來改進它。就象大多數別的工程一樣,v1.O版只用輪盤賭
方式選精英,并采用單點式雜交。
注意:
?當程序運行時,權重可以被演化成為任意的大小,它們不受任何形式的限制。?

4.7? 掃雷機類(The CMinesweeper Class)
  ?這一個類用來定義一個掃雷機。就象上一章描述的登月艇類一樣,掃雷機類中有一個包含了掃雷機位置、
速度、以及如何轉換方向等數據的紀錄。類中還包含掃雷機的視線向量(look-at vector);它的2個分量被用
來作為神經網絡的2個輸入。這是一個規范化的向量,它是在每一幀中根據掃雷機本身的轉動角度計算出來的,
它指示了掃雷機當前是朝著哪一個方向,如圖11所示。
下面就是掃雷機類CMinesweeper的聲明:
class CMinesweeper
{
private:
???? // 掃雷機的神經網絡
????? CNeuralNet??????? m_ItsBrain;
???? // 它在世界坐標里的位置
???? SVector2D???????? m_vPosition;

???? // 掃雷機面對的方向
???? SVector2D????????? m_vLookAt;
???? // 它的旋轉(surprise surprise)
???? double ???? m_dRotation;
???? double ???? m_dSpeed;
???? // 根據ANN保存輸出
???? double???????? m_lTrack,
?????????? ???????? m_rTrack;
???? m_lTrack和m_rTrack根據網絡保存當前幀的輸出。
???? 這些就是用來決定掃雷機的移動速率和轉動角度的數值。
???? // 用于度量掃雷機適應性的分數
???? double ??    m_dFitness;
???? 每當掃雷機找到一個地雷,它的適應性分數就要增加。
??? // 掃雷機畫出來時的大小比例
???? double????? m_dScale;
??? // 掃雷機最鄰近地雷的下標位置
???? int?????? m_iClosestMine;
??? 在控制器類CControl1er中,有一個屬于所有地雷的成員向量std::vector。
??? 而m_iClosestMine就是代表最靠近掃雷機的那個地雷在該向量中的位置的下標。
public:
????CMinesweeper();
????// 利用從掃雷機環境得到的信息來更新人工神經網
????bool Update(vector<SVector2D> &mines);
????// 用來對掃雷機各個頂點進行變換,以便接著可以畫它出來
????void? WorldTransform(vector<SPoint> &sweeper);

??? // 返回一個向量到最鄰近的地雷
??? 5Vector2D GetClosestMine(vector<SVector2D> &objects);
??? // 檢查掃雷機看它是否已經發現地雷
??? int??????? CheckForMine(vector<SVector2D> &mines, double size);
????
??? void?????? Reset();
??? // ----------------- 定義各種供訪問用的函數
??? SVector2D? Position()const { return m_vPosition; }
??? void?????? IncrementFitness(double val) { m_dFitness += val; }
??? double???? Fitness()const { return m_dFitness; }
??? void?????? PutWeights(vector<double> &w) { m_ItsBrain.PutWeights(w); }
??? int??????? GetNumberOfWeights()const
????????????????????????????? { return m_ItsBrain.GetNumberOfWeights(); }
};

4.7.1 The CMinesweeper::Update Function(掃雷機更新函數)
需要更詳細地向你說明的CMinesweeper類的方法只有一個,這就是Update更新函數。該函數在每一幀中
都要被調用,以更新掃雷機神經網絡。讓我們考察這函數的肚子里有些什么貨色:
bool CMinesweeper::Update(vector<SVector2D> &mines)
{
???? //這一向量用來存放神經網絡所有的輸入
???? vector<double> inputs;
???? //計算從掃雷機到與其最接近的地雷(2個點)之間的向量
???? SVector2D vClosestMine = GetClosestMine(mines);
???? //將該向量規范化
???? Vec2DNormalize(vClosestMine);
首先,該函數計算了掃雷機到與其最靠近的地雷之間的向量,然后使它規范化。(記住,向量規范化后它
的長度等于1。)但掃雷機的視線向量(look-at vector)這時不需要再作規范化,因為它的長度已經等于1了。
由于兩個向量都有效地化成了同樣的大小范圍,我們就可以認為輸入已經是標準化了,這我前面已講過了。
?
???? //加入掃雷機->最近地雷之間的向量
???? Inputs.push_back(vClosestMine.x);
???? Inputs.push_back(vCIosestMine.y);
???? //加入掃雷機的視線向量
???? Inputs.push_back(m_vLookAt.x);
???? Inputs.push_back(m_vLookAt.y);
???? //更新大腦,并從網絡得到輸出
???? vector<double> output = m_ItsBrain.Update(inputs);
然后把視線向量,以及掃雷機與它最接近的地雷之間的向量,都輸入到神經網絡。函數CNeuralNet::Update利
用這些信息來更新掃雷機網絡,并返回一個std::vector向量作為輸出。
???? //保證在輸出的計算中沒有發生錯誤
???? if (output.size() < CParams::iNumOutputs)
????? {
???????? return false;
????? }
???? // 把輸出賦值到掃雷機的左、右輪軌
???? m_lTrack = output[0];
???? m_rTrack = output[1];
在更新神經網絡時,當檢測到確實沒有錯誤時,程序把輸出賦給m_lTrack和m_rTrack。 這些值代表施加
到掃雷機左、右履帶輪軌上的力。
???? // 計算駕駛的力
???? double RotForce = m_lTrack - m_rTrack;
???? // 進行左轉或右轉
???? Clamp(RotForce, -CParams::dMaxTurnRate, CParams::dMaxTurnRate);
??
??? ?m_dSpeed = (m_lTrack + m_rTrack);

  ?掃雷機車的轉動力是利用施加到它左、右輪軌上的力之差來計算的。并規定,施加到左軌道上的力減去施
加到右軌道上的力,就得到掃雷機車輛的轉動力。然后就把此力施加給掃雷機車,使它實行不超過ini文件所規
定的最大轉動率的轉動。而掃雷機車的行進速度不過就是它的左側輪軌速度與它的右側輪軌速度的和。既然我
們知道了掃雷機的轉動力和速度,它的位置和偏轉角度也就都能更新了。
???? //更新掃雷機左右轉向的角度
???? m_dRotation += RotForce;
????
???? // 更新視線角度
???? m_vLookAt.x = -sin(m_dRotation);
???? m_vLookAt.y = cos(m_dRotation);
???? // 更新它的位置
???? m_vPosition += (m_vLookAt* m_dSpeed);
???? // 如果掃雷機到達窗體四周,則讓它實行環繞,使它不至于離開窗體而消失
???? If (m_vPosition.x > CParams::WindowWidth) m_vPosition.x = 0;
???? If (m_vPosition.x < 0) m_vPosition.x = CParams::WindowWidth;
???? If (m_vPosition.y > CParams::WindowHeight) m_vPosition.y = 0;
???? If (m_vPosition.y < D) m_vPosition.y = CParams::WindowHeight;
?
?  為了使事情盡可能簡單,我已讓掃雷機在碰到窗體邊框時就環繞折回(wrap)。采用這種方法程序就不再需
要做任何碰撞-響應方面的工作。環繞一塊空地打轉對我們人來說是一樁非常不可思議的動作,但對掃雷機,這
就像池塘中的鴨子。
??? Returen true;
?}
4.8? CController Class(控制器類)
?CController類是和一切都有聯系的類。圖15指出了其他的各個類和CController類的關系。
?下面就是這個類的定義:
?
?class CController
?{
?private:
???? // 基因組群體的動態存儲器(一個向量)
????? vector<SGenome> ??m_vecThePopulation;

?
????? 圖15? minesweeper工程的程序流程圖

??? // 保存掃雷機的向量
??? vector<CMinesweeper> ?m_vecSweepers;
????
??? // 保存地雷的向量
??? vector<SVector2D> ??m_vecMines;
??? // 指向遺傳算法對象的指針
? ? CGenAIg* ???????? m_pGA;
?
? ? int ????????????? m_NumSweepers;
?
? ? int ????????????? m_NumMines;
? ? // 神經網絡中使用的權重值的總數
? ? int ????????????? m_NumWeightsInNN;
? ? // 存放掃雷機形狀各頂點的緩沖區
??? vector<SPoint> ???m_SweeperVB;
? ? // 存放地雷形狀各頂點的緩沖區
? ? vector<SPoint> ???m_MineVB;
? ? // 存放每一代的平均適應性分數,供繪圖用
? ? vector<double> ???m_vecAvFitness;
?
? ? // 存放每一代的最高適應性分
??? vector<double> ???m_vecBestFitness;
? ? // 我們使用的各種不同類型的畫筆
? ? HPEN ???????????? m_RedPen;
? ? HPEN ???????????? m_BluePen;
? ? HPEN ???????????? m_GreenPen;
? ? HPEN ???????????? m_OldPen;
? ? // 應用程序窗口的句柄
? ? HWND ???????????? m_hwndMain;
? ?// 切換掃雷機程序運行的速度
??? bool ???????????? m_bFastRender;
???
??? // 每一代的幀數(滴答數)
??? int ????????????? m_iTicks;
??? // 代的計數
??? int ????????????? m_iGenerations;
??? // 窗體客戶區的大小
??? int ????????????? cxClient,cyClient;
??? // 本函數在運行過程中畫出具有平均-,和最優適應性值的圖
??? void????????????? PlotStats(HDC surface);
public:
??? CController(HWND hwndMain);
??? ~CController();
??? void???Render(HDC surface);
??? void???WorldTransform(vector<SPoint> &VBuffer,
?????????? SVector2D??????? vPos);
??? bool???Update();
??? // 幾個公用的訪問方法
??? bool ??FastRender() { return m_bFastRender; }
??? void ??FastRender(bool arg){ m_bFastRender = arg; }
??? void ??FastRenderToggle() { m_bFastRender = !m_bFastRender; }
};
當創建CController類的某個實例時,會有一系列的事情發生:
創建CMinesweeper對象。
統計神經網絡中所使用的權重的總數,然后此數字即被利用來初始化遺傳算法類的一個實例。
從遺傳算法對象中隨機提取染色體(權重)并(利用細心的腦外科手術)插入到掃雷機的經網絡中。
創建了大量的地雷并被隨機地散播到各地。
為繪圖函數創建了所有需要用到的GDI畫筆。
為掃雷機和地雷的形狀創建了頂點緩沖區。
?
?所有的一切現都已完成初始化,由此Update方法就能在每一幀中被調用來對掃雷機進行演化。
?
?
4.8.1? CController::Update Method(控制器的更新方法)
控制器更新方法CController::Update方法(或函數)在每一幀中都要被調用。當調用update函數時,函數
的 前一半通過對所有掃雷機進行循環,如發現某一掃雷機找到了地雷,就update該掃雷機的適應性分數。由于m_vecThePopulation包含了所 有基因組的拷貝,相關的適應性分數也要在這時進行調整。如果為完成一個代(generation)所需要的幀數均已通過,本方法就執行一個遺傳算法的時代 (epoch)來產生新一代的權重。這些
權重被用來代替掃雷機神經網絡中原有的舊的權重,使掃雷機的每一個參數被重新設置,從而為進入新一generation做好準備。
???
?bool CController::Update()
?{
???? // 掃雷機運行總數為CParams::iNumTicks次的循環。在此循環周期中,掃雷機的神經網絡
???? // 不斷利用它周圍特有的環境信息進行更新。而從神經網絡得到的輸出,使掃雷機實現所需的
????
// 動作。如果掃雷機遇見了一個地雷,則它的適應性將相應地被更新,且同樣地更新了它對應
???? // 基因組的適應性。
???? if (m_iTicks++ < CParams::iNumTicks)
?????? {
??????? for (int i=O; i<m_NumSweepers; ++i)
??????? {
??????? //更新神經網絡和位置
????????? if (!m_vecSweepers[i].Update(m_vecMines))
?????????? {
??????????? //處理神經網絡時出現了錯誤,顯示錯誤后退出
???????????? MessageBox(m_hwndMain, 'Wrong amount of NN inputs!",
????????????? ?"Error", MB_OK);
?
???????????? return false;
????????? ?}
??????? ?// 檢查這一掃雷機是否已經發現地雷
???????? int GrabHit = m_vecSweepers[i].CheckForMine(m_vecMines,
???????????????????????????????????????????????? ??? CParams::dMineScale);
???????? if (GrabHit >= 0)
????????? {
??????????? // 掃雷機已找到了地雷,所以要增加它的適應性分數
??????????? m_vecSweepers[i].IncrementFitness();
?????????? ?// 去掉被掃雷機找到的地雷,用在隨機位置放置的一個新地雷來代替
??????????? m_vecMines[GrabHit] = SVector2D(RandFloat() * cxClient,
?????????????????????????????????????? ?? RandFloat() * cyClient);
????????? }
?????? // 更新基因組的適應性值
??????? m-vecThePopulation[i].dFitness = m_vecSweepers[i].Fitness();
????? }
?? }
?? // 一個代已被完成了。
?? // 進入運行遺傳算法并用新的神經網絡更新掃雷機的時期
?? else
??? {
???? // 更新用在我們狀態窗口中狀態
???? m_vecAvFitness.push_back(m_pGA->AverageFitness());
???? m_vecBestFitness.push_back(m_pGA->BestFitness());
???? // 增加代計數器的值
???? ++m_iGenerations;
?
???? // 將幀計數器復位
???? m_iTicks = 0;
???? // 運行GA創建一個新的群體
???? m-vecThePopulation = m_pGA->Epoch(m_vecThePopulation);
???? // 在各掃雷機中從新插入新的(有希望)被改進的大腦
???? // 并將它們的位置進行復位,等
???? for(int i=O; i<m_NumSweepers; ++i)
????? {m_vecSweepers[i].m_ItsBrain.PutWeights(m_vecThePopulation[i].vecWeights);
?????? m_vecSweepers[i].Reset();
????? }
??? }
? returen true;
}
概括起來,程序為每一世代做的工作是:
l.為所有掃雷機和為iNumTicks個幀組織循環,調用Update函數
??????  并根據情況增加掃雷機適應值的得分。
???? 2.從掃雷機神經網絡提取權重向量。
???? 3.用遺傳算法去演化出一個新的網絡權重群體。
???? 4.把新的權重插入到掃雷機神經網絡。
? 5.轉到第1步進行重復,直到獲得理想性能時為止。
?
?最后,表3列出了Smart Sweepers工程 v1.0版所有缺省參數的設置值。
?表3? Smart Sweepers v1.0工程的缺省設置?
神經網絡
參???? 數
設 置 值
輸入數目
4
輸出數目
2
隱藏層數目
1
隱藏層神經元數目
10
激勵響應
1
遺 傳 算 法
參?? 數
設 置 值
群體大小
30
選擇類型
旋轉輪
雜交類型
單點
雜交率
0.7
突變率
0.1
精英設置(on/off)
On
精英數目(N/copies)
4/1
總 體 特 性
參?? 數
設 置 值
每時代的幀數
2000

4.9? 運行此程序 (Running the Program)

  ??? 當你運行程序時,“F”鍵用來切換2種不同的顯示狀態,一種是顯示掃雷機怎樣學習尋找地雷,一種是
示在運行期中產生的最優的與平均的適當性分數的統計圖表。?當顯示圖表時,程序將會加速運行。
游戲編程中的人工智能技術
.
>
.
(連載之六)
4.10? 功能的兩個改進 (A Couple of Improvements)

  盡管掃雷機學習尋找地雷的本領十分不錯,這里我仍有兩件事情要告訴你,它們能進一步改進掃雷機的性能。
4.10.1 改進一(Improvement Number One)

  首先,單點crossover算子留下了許多可改進的余地。按照它的規定,算子是沿著基因組長度任意地方切開的,這樣常有可能使個別神經細胞的基因組在權重的中間被一刀兩段地分開。
為清楚起見,我們來考察圖16的權重。這是我們以前在說明基因組如何編碼時看過的一個簡單網絡。?在這
里,雜交算子可以沿向量長度的任意一處切開,這樣,就會有極大幾率在某個神經細胞(如第二個)的權重中
間斷開,也就是說,在權重0.6和-0.1之間某處切開。這可能不會是我們想要的,因為,如果我們把神經細胞作
為一個完整的單元來看待,則它在此以前所獲得的任何改良就要被騷擾了。事實上,這樣的雜交操作有可能非
常非常象斷裂性突變(disruptive mutation)操作所起的作用。

?
圖16 簡單的網絡
與此針鋒相對,我已創建了另一種類型的雜交運算,它只在神經細胞的邊界上進行切開。在圖16的例子中,
就是在第3、4或第6、7的兩個基因之間切開,如小箭頭所示。 為了實現這一算法,我已在CNeuralNet類中補
充了另一個切割方法: CalculateSplitPoints。這一方法創建了一個用于保存所有網絡權重邊界的矢量,它的代
碼如下:
vector<int> CNeuralNet::CalculateSplitPoints() const
{
?? vector<int> SplitPoints;
?
?? int WeightCounter = 0;
?
?? // 對每一層
?? for (int i=O; i<m_NumHiddenLayers + 1; ++i)
??? {
???? // 對每一個神經細胞
???? for (int j=O; j<m_vecLayers[i].m_NumNeurons; ++j)
?  {
???????  // 對每一個權重
???????  for (int k=O; k<m_vecLayers[i].m_vecNeurons[j].m_NumInputs; ++k)
????????  {
???????????  ++WeightCounter;
????????  }
??????? SplitPoints.push_back(WeightCounter - 1);
????? }
?? }
?? return SplitPoints;
}
這一方法是CController類構造函數在創建掃雷機并把斷裂點向量傳遞給遺傳算法類時調用的。它們被存儲
在一個名叫m_vecSplitPoints的std::vector向量中。然后遺傳算法就利用這些斷裂點來實現兩點雜交操作,其代
碼如下:
void CGenAlg::CrossoverAtSplits(const vector<double>? &mum,
??????????????????????????????? ?? const vector<double>? &dad,
??????????????????????????????? ?? vector<double>???????? &babyl,
??????????????????????????????? ?? vector<double>???????? &baby2)
{
?? // 如果超過了雜交率,就不再進行雜交,把2個上代作為2個子代輸出
?? // 如果2個上輩相同,也把它們作為2個下輩輸出
?? if ( (RandFloat() > m_dCrossoverRate) || (mum == dad))
???? {
????? baby1 = mum;
????? baby2 = dad;
????? return;
???? }
?? // 確定雜交的2個斷裂點
?? int index1 = RandInt(0, m_vecSplitPoints.size()-2);
?? int index2 = RandInt(Index1, m_vecSplitPoints.size()-1);
?? int cp1 = m_vecSplitPoints[Index1];
?? int cp2 = m_vecSplitPoints[Index2];
?? // 創建子代
?
?for (int i=0; i<mum.size(); ++i)
?? {
???? if ( (i<cp1) || (i>=cp2) )
?????? {
?????????? // 如果在雜交點外,保持原來的基因
?????????? babyl.push_back(mum[i]);
?????????? baby2.push_back(dad[i]);
?????? }
??? else
?????? {
?????????? // 把中間段進行交換
?????????? baby1.push_back(dad[1]);
?????????? baby2.push_back(mum[1]);
?????? }
?? }
?? return;
}
根據我的經驗,我已發現,在進行雜交時,把神經細胞當作一個不可分割的單位,比在染色體長度上任意
一點分裂基因組,能得到更好的結果。
4.10.2 改進二(Improvement Number Two)
我想和你討論的另一個性能改進,是用另一種方式來觀察網絡的那些輸入。在你已看到的例子中,我們為
網絡使用了4個輸入參數: 2個用于表示掃雷機視線方向的向量,另外2個用來指示掃雷機與其最靠近的地雷的方
向的向量。然而,有一種辦法,可以把這些參數的個數減少到只剩下一個。
其實你想一想就可知道,掃雷機為了確定地雷的位置,只要知道從它當前的位置和朝向出發,需要向左或
向右轉動多大的一個角度這一簡單的信息就夠了(如果你已經考慮到了這一點,那我在這里要順便向您道賀了)。
由于我們已經計算了掃雷機的視線向量和從它到最鄰近地雷的向量,再來計算它們之間的角度(θ)應是一件極為
簡單的事情 – 這就是這兩個向量的點積,這我們在第6章“使登陸月球容易一點”中已討論過。見圖17。
圖17 計算到最鄰近地雷的轉動角度。
不幸的是,點積僅僅給出角度的大小; 它不能指示這一角度是在掃雷機的那一側。因此,我已寫了另一個向
量函數返回一個向量相對于另一個向量的正負號。該函數的原型如下所示:
???  inline int Vec2DSign(SVector2D &v1,SVector2D &v2);
如果你對它的機理感興趣,你可以在文件SVector2D.h中找到它的源碼。但它的基本點就是: 如果v1至v2是
按順時針方向轉的,則函數返回 +1;如果v1至v2是按逆時針方向轉,則函數返回 -1。

  把點積和Vec2Dsign二者聯合起來,就能把輸入的精華提純出來,使網絡只需接受一個輸入就行了。下面
就是新的CMinesweeper::Update函數有關段落的代碼形式:
?
???? // 計算到最鄰近地雷的向量
???? SVector2D vClosestMine = GetClosestMine(mines);
???? // 將它規范化
? Vec2DNormalize(vClosestMine);
?
 ?// 計算掃雷機視線向量和它到最鄰近地雷的向量的點積。它給出了我們要面對
 ?// 最鄰近地雷所需轉動的角度
???? double dot = Vec2DDot(m_vLookAt, vClosestMine);
???? // 計算正負號
???? int sign = Vec2DSign(m_vLookAt, vClosestMine);
???? Inputs.push_back(dot*sign);
????
?  運行一下光盤Chapter7/Smart Sweepers v1.1目錄下的可執行程序executable,你就知道經過以上2個改
進,能為演化過程提速多少。

?  需要注意的一樁重要事情是,帶有4個輸入的網絡要花很長時間進行演化,因為它必須在各輸入數據之間找
出更多的關系才能確定它應如何行動。事實上,網絡實際就是在學習怎么做點積并確定它的正負極性。因此,當
你設計自己的網絡時,你應仔細權衡一下,是由你自己預先來計算許多輸入數據好呢(它將使CPU負擔增加,但
導致進化時間加快)還是讓網絡來找輸入數據之間的復雜關系好(它將使演化時間變長,但能使CPU減少緊張)?
5 結束語(last words)

  ?我希望你已享受到了你第一次攻入神經網絡這一奇妙世界的快樂。我打賭你一定在為如此簡單就能使用它
們而感到驚訝吧,對嗎?我想我是猜對了。

?  在下面幾章里我將要向你介紹更多的知識,告訴你一些新的訓練手段和演繹神經網絡結構的更多的方法。
但首先請你利用本章下面的提示去玩一下游戲是有意義的。
6 練習題 (Stuff to Try)

  1。 在v1.0中,不用look-at向量作為輸入,而改用旋轉角度θ作為輸入,由此就可以使網絡的輸入個數減少
成為1個。請問這對神經網絡的演化有什么影響?你對此的看法怎樣?

  2。 試以掃雷機的位置(x1,y1)、和掃雷機最接近的地雷的位置(x2,y2)、以及掃雷機前進方向的向量
(x3,y3)等6個參數作為輸入,來設計一個神經網絡,使它仍然能夠演化去尋找地雷。

  3。 改變激勵函數的響應。試用O.1 - O.3 之間的低端值,它將產生和階躍函數非常相像的一種激勵函數。
然后再試用高端值,它將給出較為平坦的響應曲線。考察這些改變對演化進程具有什么影響?

  4。 改變神經網絡的適應性函數,使得掃雷機不是去掃除地雷,而是要演化它,使它能避開地雷。

  5。 理一理清楚有關遺傳算法的各種不同設置和運算中使你感到模糊的東西!

  6。 加入其他的對象類型,比如人。給出一個新環境來演化掃雷機,使它能避開人,但照樣能掃除地雷。
(這可能沒有你想象那么容易!)
















神經網絡編程入門

Posted on 2011-03-07 22:30 蒼梧 閱讀(48399) 評論(22) 編輯 收藏

  本文主要內容包括: (1) 介紹神經網絡基本原理,(2)AForge.NET實現前向神經網絡的方法,(3) Matlab實現前向神經網絡的方法 。

?

第0節、引例?

?????? 本文以Fisher的Iris數據集作為神經網絡程序的測試數據集。Iris數據集可以在http://en.wikipedia.org/wiki/Iris_flower_data_set? 找到。這里簡要介紹一下Iris數據集:

有一批Iris花,已知這批Iris花可分為3個品種,現需要對其進行分類。不同品種的Iris花的花萼長度、花萼寬度、花瓣長度、花瓣寬度會有差異。我們現有一批已知品種的Iris花的花萼長度、花萼寬度、花瓣長度、花瓣寬度的數據。

  一種解決方法是用已有的數據訓練一個神經網絡用作分類器。

  如果你只想用C#或Matlab快速實現神經網絡來解決你手頭上的問題,或者已經了解神經網絡基本原理,請直接跳到第二節——神經網絡實現。

?

第一節、神經網絡基本原理?

1. 人工神經元(Artificial Neuron )模型?

?????? 人工神經元是神經網絡的基本元素,其原理可以用下圖表示:

圖1. 人工神經元模型

?

?????? 圖中x1~xn是從其他神經元傳來的輸入信號,wij表示表示從神經元j到神經元i的連接權值,θ表示一個閾值 ( threshold ),或稱為偏置( bias )。則神經元i的輸出與輸入的關系表示為:

?

  圖中yi表示神經元i的輸出,函數f稱為激活函數 ( Activation Function )或轉移函數 ( Transfer Function ) ,net稱為凈激活(net activation)。若將閾值看成是神經元i的一個輸入x0的權重wi0,則上面的式子可以簡化為:

?

  若用X表示輸入向量,用W表示權重向量,即:

X = [ x0 , x1 , x2 , ....... , xn ]

?

  則神經元的輸出可以表示為向量相乘的形式:

?

?

?????? 若神經元的凈激活net為正,稱該神經元處于激活狀態或興奮狀態(fire),若凈激活net為負,則稱神經元處于抑制狀態。

?????? 圖1中的這種“閾值加權和”的神經元模型稱為M-P模型 ( McCulloch-Pitts Model ),也稱為神經網絡的一個處理單元( PE, Processing Element )

?

2. 常用激活函數?

?????? 激活函數的選擇是構建神經網絡過程中的重要環節,下面簡要介紹常用的激活函數。

(1) 線性函數( Liner Function )

?

(2) 斜面函數( Ramp Function )

?

(3) 閾值函數( Threshold Function )

?

?

?

?????? 以上3個激活函數都屬于線性函數,下面介紹兩個常用的非線性激活函數。

(4) S形函數( Sigmoid Function )

  該函數的導函數:

(5) 雙極S形函數?

  該函數的導函數:

  S形函數與雙極S形函數的圖像如下:


圖3. S形函數與雙極S形函數圖像

  雙極S形函數與S形函數主要區別在于函數的值域,雙極S形函數值域是(-1,1),而S形函數值域是(0,1)。

  由于S形函數與雙極S形函數都是可導的(導函數是連續函數),因此適合用在BP神經網絡中。(BP算法要求激活函數可導)

?

3. 神經網絡模型?

?????? 神經網絡是由大量的神經元互聯而構成的網絡。根據網絡中神經元的互聯方式,常見網絡結構主要可以分為下面3類:

(1) 前饋神經網絡 ( Feedforward Neural Networks )

?????? 前饋網絡也稱前向網絡。這種網絡只在訓練過程會有反饋信號,而在分類過程中數據只能向前傳送,直到到達輸出層,層間沒有向后的反饋信號,因此被稱為前饋網絡。感知機( perceptron)與BP神經網絡就屬于前饋網絡。

?????? 圖4 中是一個3層的前饋神經網絡,其中第一層是輸入單元,第二層稱為隱含層,第三層稱為輸出層(輸入單元不是神經元,因此圖中有2層神經元)。

圖4. 前饋神經網絡

?

  對于一個3層的前饋神經網絡N,若用X表示網絡的輸入向量,W1~W3表示網絡各層的連接權向量,F1~F3表示神經網絡3層的激活函數。

  那么神經網絡的第一層神經元的輸出為:

O1 = F1( XW1 )

  第二層的輸出為:

O2 = F2 ( F1( XW1 ) W2 )

  輸出層的輸出為:

O3 = F3( F2 ( F1( XW1 ) W2 ) W3 )

?????? 若激活函數F1~F3都選用線性函數,那么神經網絡的輸出O3將是輸入X的線性函數。因此,若要做高次函數的逼近就應該選用適當的非線性函數作為激活函數。

(2) 反饋神經網絡 ( Feedback Neural Networks )

?????? 反饋型神經網絡是一種從輸出到輸入具有反饋連接的神經網絡,其結構比前饋網絡要復雜得多。典型的反饋型神經網絡有:Elman網絡和Hopfield網絡。

圖5. 反饋神經網絡

?

(3) 自組織網絡 ( SOM ,Self-Organizing Neural Networks )

?????? 自組織神經網絡是一種無導師學習網絡。它通過自動尋找樣本中的內在規律和本質屬性,自組織、自適應地改變網絡參數與結構。

圖6. 自組織網絡

?

4. 神經網絡工作方式?

?????? 神經網絡運作過程分為學習和工作兩種狀態。

(1)神經網絡的學習狀態?

?????? 網絡的學習主要是指使用學習算法來調整神經元間的聯接權,使得網絡輸出更符合實際。學習算法分為有導師學習( Supervised Learning )無導師學習( Unsupervised Learning )兩類。

?????? 有導師學習算法將一組訓練集 ( training set )送入網絡,根據網絡的實際輸出與期望輸出間的差別來調整連接權。有導師學習算法的主要步驟包括:

1)? 從樣本集合中取一個樣本(Ai,Bi);

2)? 計算網絡的實際輸出O;

3)? 求D=Bi-O;

4)? 根據D調整權矩陣W;

5) 對每個樣本重復上述過程,直到對整個樣本集來說,誤差不超過規定范圍。

  BP算法就是一種出色的有導師學習算法。

?????? 無導師學習抽取樣本集合中蘊含的統計特性,并以神經元之間的聯接權的形式存于網絡中。

?????? Hebb學習律是一種經典的無導師學習算法。

(2) 神經網絡的工作狀態?

?????? 神經元間的連接權不變,神經網絡作為分類器、預測器等使用。

  下面簡要介紹一下Hebb學習率與Delta學習規則 。

(3) 無導師學習算法:Hebb學習率?

  Hebb算法核心思想是,當兩個神經元同時處于激發狀態時兩者間的連接權會被加強,否則被減弱。?

?????? 為了理解Hebb算法,有必要簡單介紹一下條件反射實驗。巴甫洛夫的條件反射實驗:每次給狗喂食前都先響鈴,時間一長,狗就會將鈴聲和食物聯系起來。以后如果響鈴但是不給食物,狗也會流口水。

圖7. 巴甫洛夫的條件反射實驗

?

   受該實驗的啟發,Hebb的理論認為在同一時間被激發的神經元間的聯系會被強化。比如,鈴聲響時一個神經元被激發,在同一時間食物的出現會激發附近的另 一個神經元,那么這兩個神經元間的聯系就會強化,從而記住這兩個事物之間存在著聯系。相反,如果兩個神經元總是不能同步激發,那么它們間的聯系將會越來越 弱。

  Hebb學習律可表示為:

?????? 其中wij表示神經元j到神經元i的連接權,yi與yj為兩個神經元的輸出,a是表示學習速度的常數。若yi與yj同時被激活,即yi與yj同時為正,那么Wij將增大。若yi被激活,而yj處于抑制狀態,即yi為正yj為負,那么Wij將變小。

(4) 有導師學習算法:Delta學習規則

  Delta學習規則是一種簡單的有導師學習算法,該算法根據神經元的實際輸出與期望輸出差別來調整連接權,其數學表示如下:

?

?????? 其中Wij表示神經元j到神經元i的連接權,di是神經元i的期望輸出,yi是神經元i的實際輸出,xj表示神經元j狀態,若神經元j處于激活態則xj為 1,若處于抑制狀態則xj為0或-1(根據激活函數而定)。a是表示學習速度的常數。假設xi為1,若di比yi大,那么Wij將增大,若di比yi小, 那么Wij將變小。

?????? Delta規則簡單講來就是:若神經元實際輸出比期望輸出大,則減小所有輸入為正的連接的權重,增大所有輸入為負的連接的權重。反之,若神經元實際輸出比 期望輸出小,則增大所有輸入為正的連接的權重,減小所有輸入為負的連接的權重。這個增大或減小的幅度就根據上面的式子來計算。

(5)有導師學習算法:BP算法?

  采用BP學習算法的前饋型神經網絡通常被稱為BP網絡。

圖8. 三層BP神經網絡結構

?

  BP網絡具有很強的非線性映射能力,一個3層BP神經網絡能夠實現對任意非線性函數進行逼近(根據Kolrnogorov定理)。一個典型的3層BP神經網絡模型如圖7所示。

  BP網絡的學習算法占篇幅較大,我打算在下一篇文章中介紹。

?

第二節、神經網絡實現?

?

1. 數據預處理?

?????? 在訓練神經網絡前一般需要對數據進行預處理,一種重要的預處理手段是歸一化處理。下面簡要介紹歸一化處理的原理與方法。

(1) 什么是歸一化??

數據歸一化,就是將數據映射到[0,1]或[-1,1]區間或更小的區間,比如(0.1,0.9) 。

(2) 為什么要歸一化處理??

<1>輸入數據的單位不一樣,有些數據的范圍可能特別大,導致的結果是神經網絡收斂慢、訓練時間長。

<2>數據范圍大的輸入在模式分類中的作用可能會偏大,而數據范圍小的輸入作用就可能會偏小。

<3> 由于神經網絡輸出層的激活函數的值域是有限制的,因此需要將網絡訓練的目標數據映射到激活函數的值域。例如神經網絡的輸出層若采用S形激活函數,由于S形 函數的值域限制在(0,1),也就是說神經網絡的輸出只能限制在(0,1),所以訓練數據的輸出就要歸一化到[0,1]區間。

<4>S形激活函數在(0,1)區間以外區域很平緩,區分度太小。例如S形函數f(X)在參數a=1時,f(100)與f(5)只相差0.0067。

(3) 歸一化算法?

  一種簡單而快速的歸一化算法是線性轉換算法。線性轉換算法常見有兩種形式:

?????? <1>

y = ( x - min )/( max - min )

  其中min為x的最小值,max為x的最大值,輸入向量為x,歸一化后的輸出向量為y 。上式將數據歸一化到 [ 0 , 1 ]區間,當激活函數采用S形函數時(值域為(0,1))時這條式子適用。

?????? <2>

y = 2 * ( x - min ) / ( max - min ) - 1

?????? 這條公式將數據歸一化到 [ -1 , 1 ] 區間。當激活函數采用雙極S形函數(值域為(-1,1))時這條式子適用。

(4) Matlab數據歸一化處理函數?

  Matlab中歸一化處理數據可以采用premnmx , postmnmx , tramnmx 這3個函數。

<1> premnmx

語法:[pn,minp,maxp,tn,mint,maxt] = premnmx(p,t)

參數:

pn: p矩陣按行歸一化后的矩陣

minp,maxp:p矩陣每一行的最小值,最大值

tn:t矩陣按行歸一化后的矩陣

mint,maxt:t矩陣每一行的最小值,最大值

作用:將矩陣p,t歸一化到[-1,1] ,主要用于歸一化處理訓練數據集。

<2> tramnmx

語法:[pn] = tramnmx(p,minp,maxp)

參數:

minp,maxp:premnmx函數計算的矩陣的最小,最大值

pn:歸一化后的矩陣

作用:主要用于歸一化處理待分類的輸入數據。

<3> postmnmx

語法: [p,t] =postmnmx(pn,minp,maxp,tn,mint,maxt)

參數:

minp,maxp:premnmx函數計算的p矩陣每行的最小值,最大值

mint,maxt:premnmx函數計算的t矩陣每行的最小值,最大值

作用:將矩陣pn,tn映射回歸一化處理前的范圍。postmnmx函數主要用于將神經網絡的輸出結果映射回歸一化前的數據范圍。

2. 使用Matlab實現神經網絡?

使用Matlab建立前饋神經網絡主要會使用到下面3個函數:

newff :前饋網絡創建函數

train:訓練一個神經網絡

sim :使用網絡進行仿真

?下面簡要介紹這3個函數的用法。

(1) newff函數

<1>newff函數語法?

?????? newff函數參數列表有很多的可選參數,具體可以參考Matlab的幫助文檔,這里介紹newff函數的一種簡單的形式。

語法:net = newff ( A, B, {C} ,‘trainFun’)

參數:

A:一個n×2的矩陣,第i行元素為輸入信號xi的最小值和最大值;

B:一個k維行向量,其元素為網絡中各層節點數;

C:一個k維字符串行向量,每一分量為對應層神經元的激活函數

trainFun :為學習規則采用的訓練算法

<2>常用的激活函數

  常用的激活函數有:

  a) 線性函數 (Linear transfer function)

f(x) = x

  該函數的字符串為’purelin’。

?

b) 對數S形轉移函數( Logarithmic sigmoid transfer function )

??? 該函數的字符串為’logsig’。

c) 雙曲正切S形函數 (Hyperbolic tangent sigmoid transfer function )

  也就是上面所提到的雙極S形函數。

?

  該函數的字符串為’ tansig’。

  Matlab的安裝目錄下的toolbox\nnet\nnet\nntransfer子目錄中有所有激活函數的定義說明。

<3>常見的訓練函數

??? 常見的訓練函數有:

traingd :梯度下降BP訓練函數(Gradient descentbackpropagation)

traingdx :梯度下降自適應學習率訓練函數

<4>網絡配置參數

一些重要的網絡配置參數如下:

net.trainparam.goal ?:神經網絡訓練的目標誤差

net.trainparam.show ??: 顯示中間結果的周期

net.trainparam.epochs  :最大迭代次數

net.trainParam.lr ?? : 學習率

(2) train函數

??? 網絡訓練學習函數。

語法:[ net, tr, Y1, E ]? = train( net, X, Y )

參數:

X:網絡實際輸入

Y:網絡應有輸出

tr:訓練跟蹤信息

Y1:網絡實際輸出

E:誤差矩陣

(3) sim函數

語法:Y=sim(net,X)

參數:

net:網絡

X:輸入給網絡的K×N矩陣,其中K為網絡輸入個數,N為數據樣本數

Y:輸出矩陣Q×N,其中Q為網絡輸出個數

(4) Matlab BP網絡實例?

?????? 我將Iris數據集分為2組,每組各75個樣本,每組中每種花各有25個樣本。其中一組作為以上程序的訓練樣本,另外一組作為檢驗樣本。為了方便訓練,將3類花分別編號為1,2,3 。

  使用這些數據訓練一個4輸入(分別對應4個特征),3輸出(分別對應該樣本屬于某一品種的可能性大小)的前向網絡。

?????? Matlab程序如下:

復制代碼
%讀取訓練數據 [f1,f2,f3,f4,class] = textread('trainData.txt' , '%f%f%f%f%f',150); %特征值歸一化 [input,minI,maxI] = premnmx( [f1 , f2 , f3 , f4 ]') ;%構造輸出矩陣 s = length( class) ; output = zeros( s , 3 ) ; for i =1 : s output( i , class( i ) ) =1 ; end %創建神經網絡 net = newff( minmax(input) , [103] , { 'logsig''purelin' } , 'traingdx' ) ; %設置訓練參數 net.trainparam.show =50 ; net.trainparam.epochs =500 ; net.trainparam.goal =0.01 ; net.trainParam.lr =0.01 ; %開始訓練 net = train( net, input , output' ) ;%讀取測試數據 [t1 t2 t3 t4 c] = textread('testData.txt' , '%f%f%f%f%f',150); %測試數據歸一化 testInput = tramnmx ( [t1,t2,t3,t4]' , minI, maxI ) ;%仿真 Y = sim( net , testInput ) %統計識別正確率 [s1 , s2] = size( Y ) ; hitNum = 0 ; for i =1 : s2 [m , Index] = max( Y( : , i ) ) ; if( Index == c(i) ) hitNum = hitNum +1 ; end end sprintf('識別率是 %3.3f%%',100* hitNum / s2 )
復制代碼

?

  以上程序的識別率穩定在95%左右,訓練100次左右達到收斂,訓練曲線如下圖所示:

圖9. 訓練性能表現

?

(5)參數設置對神經網絡性能的影響?

?????? 我在實驗中通過調整隱含層節點數,選擇不通過的激活函數,設定不同的學習率,

?

<1>隱含層節點個數?

  隱含層節點的個數對于識別率的影響并不大,但是節點個數過多會增加運算量,使得訓練較慢。

?

<2>激活函數的選擇?

?????? 激活函數無論對于識別率或收斂速度都有顯著的影響。在逼近高次曲線時,S形函數精度比線性函數要高得多,但計算量也要大得多。

?

<3>學習率的選擇?

?????? 學習率影響著網絡收斂的速度,以及網絡能否收斂。學習率設置偏小可以保證網絡收斂,但是收斂較慢。相反,學習率設置偏大則有可能使網絡訓練不收斂,影響識別效果。

?

3. 使用AForge.NET實現神經網絡?

(1) AForge.NET簡介?

?????? AForge.NET是一個C#實現的面向人工智能、計算機視覺等領域的開源架構。AForge.NET源代碼下的Neuro目錄包含一個神經網絡的類庫。

AForge.NET主頁:http://www.aforgenet.com/

AForge.NET代碼下載:http://code.google.com/p/aforge/

Aforge.Neuro工程的類圖如下:

?

圖10. AForge.Neuro類庫類圖

?

下面介紹圖9中的幾個基本的類:

Neuron —神經元的抽象基類

Layer — 層的抽象基類,由多個神經元組成

Network —神經網絡的抽象基類,由多個層(Layer)組成

IActivationFunction - 激活函數(activation function)的接口

IUnsupervisedLearning - 無導師學習(unsupervised learning)算法的接口ISupervisedLearning - 有導師學習(supervised learning)算法的接口

?

(2)使用Aforge建立BP神經網絡?

?????? 使用AForge建立BP神經網絡會用到下面的幾個類:

<1> ?SigmoidFunction : S形神經網絡

  構造函數:public SigmoidFunction( doublealpha )

   參數alpha決定S形函數的陡峭程度。

<2>? ActivationNetwork :神經網絡類

  構造函數:

  public ActivationNetwork( IActivationFunction function, int inputsCount, paramsint[] neuronsCount )

???????????????????????? : base(inputsCount, neuronsCount.Length )

  public virtual double[] Compute( double[]input )

?

參數意義:

inputsCount:輸入個數

neuronsCount :表示各層神經元個數

<3>? BackPropagationLearning:BP學習算法

?構造函數:

public BackPropagationLearning( ActivationNetwork network )

?參數意義:

network :要訓練的神經網絡對象

BackPropagationLearning類需要用戶設置的屬性有下面2個:

learningRate :學習率

momentum :沖量因子

下面給出一個用AForge構建BP網絡的代碼。

?

復制代碼
// 創建一個多層神經網絡,采用S形激活函數,各層分別有4,5,3個神經元
//(其中4是輸入個數,3是輸出個數,5是中間層結點個數)ActivationNetwork network =new ActivationNetwork( new SigmoidFunction(2), 4, 5, 3); // 創建訓練算法對象BackPropagationLearning teacher =new BackPropagationLearning(network); // 設置BP算法的學習率與沖量系數teacher.LearningRate =0.1; teacher.Momentum =0; int iteration =1 ; // 迭代訓練500次while( iteration <500 ) { teacher.RunEpoch( trainInput , trainOutput ) ; ++iteration ; } //使用訓練出來的神經網絡來分類,t為輸入數據向量network.Compute(t)[0]
復制代碼

?

?????? 改程序對Iris 數據進行分類,識別率可達97%左右 。

?

?? ? 點擊下載源代碼

?

  文章來自:http://www.cnblogs.com/heaad/ ?

?












轉載于:https://www.cnblogs.com/zhanlang96/p/3994632.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/259893.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/259893.shtml
英文地址,請注明出處:http://en.pswp.cn/news/259893.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

w8計算機配置要求,win8系統最低配置要求有哪些|win8系統是否有最低配置要求-系統城...

2013-10-17 17:08:08  瀏覽量&#xff1a;5753小編這里要為大家帶來的是win8系統最低配置要求和部分安裝截圖&#xff0c;很多用戶想要將自己的電腦裝上win8&#xff0c;但也不是每一臺電腦都可以安裝win8系統的&#xff0c;為了避免一些低配置的用戶安裝了win8之后卻無法運行…

Session 丟失問題

項目從.NET Framework3.5 升級 .NET Framework4.0后&#xff0c;如果用Response.Redirect();進行頁面的跳轉&#xff0c;服務端會把這個跳轉動作當作是一個“新”的用戶去訪問網頁。 而這個時候&#xff0c;會給這個“新”的用戶一個SessionID&#xff0c;那造成的結果是&#…

財務管理專業應該報計算機二級哪個科目,我是應該報計算機二級還是三級呢

2008-12-01怎樣學好財務管理&#xff1f;“五步”學好財務管理:學習這門課程前&#xff0c;首先就不要認為它“很難”&#xff0c;只要相信“難而不會&#xff0c;會而不難”&#xff0c;充滿信心一定就能學好。我在學習過程中總結了幾條經驗&#xff0c;以供各位學友參考&…

libsvm java 調用說明

libsvm是著名的SVM開源組件&#xff0c;目前有JAVA.C/C,.NET 等多個版本&#xff0c;本人使用的是2.9libsvm命名空間下主要使用類&#xff1a;svm_model 為模型類&#xff0c;通過訓練或加載訓練好的模型文件獲得svm_parameter 為參數類&#xff0c;主要為支持向量機設定參數&a…

java字符串排序_對字符串排序持一種寬容的心態

在Java中一涉及中文處理就會冒出很多問題來&#xff0c;其中排序也是一個讓人頭疼的課題&#xff0c;我們來看下面的代碼&#xff1a;上面的代碼定義一個數組&#xff0c;然后進行升序排序&#xff0c;我們期望的結果是按照拼音升序排列&#xff0c;即為李四、王五、張三&#…

rails開發隨手記-0

helper默認是只在view中可用的&#xff0c;如果在controller中也要使用&#xff0c;要在ApplicationController中 include 如果model中如果有叫做type的列的話&#xff0c;會觸發rails的Single Table Inheritance &#xff0c;放棄它吧&#xff0c;不好用&#xff0c;還是安心使…

nagios 監控配置介紹(二)

#配置服務端監控客戶端[rootnagios etc]# cd objects/[rootnagios objects]# vi hosts.cfg# Define a host for the local machinedefine host{use linux-serverhost_name 1.3-sambaalias 1.3-sambaaddress …

spoj SUBLEX (Lexicographical Substring Search) RE的歡迎來看看

SPOJ.com - Problem SUBLEX 這么裸的一個SAM&#xff0c;放在了死破OJ上面就是個坑。 注意用SAM做的時候輸出要用一個數組存下來&#xff0c;然后再puts&#xff0c;不然一個一個字符輸出會更慢。 還有一個就是不要多數據輸入&#xff0c;估計最后多了幾個沒用的數字&#xff0…

mt4雙線macd_3年內從虧損90多萬到獲利近760萬,我只堅持我的:60分鐘MACD雙回拉戰法!附選股公式...

MACD指標被普遍認為是最經典實用的技術指標之一。其實并不是因為MACD有多么精妙的算法&#xff0c;而是MACD遵循了最基本的“均線指導原則”&#xff0c;形象的將經典雙均線系統換了一種更加直觀的表達方式。在MT4中&#xff0c;默認應用的是單線MACD指標&#xff0c;而在證券市…

計算機專業書籍速讀方法,格式你玩的轉?速讀5分鐘就懂

小編又接到了新問題&#xff0c;有小伙伴說自己64GB的U盤在電腦里格式化只能選ExFAT或者NTFS&#xff0c;不能選擇FAT32&#xff0c;求小編解答&#xff0c;小編正好借著這個機會&#xff0c;說說現在電腦格式問題。如果你懶得讀&#xff0c;↓↓↓最后一段有答案&#xff0c;如…

java項目打jar包

http://www.cnblogs.com/tianguook/archive/2012/03/14/2396335.html java項目打jar包分為2種情況&#xff1a; 一、java項目沒有導入第三方jar包 這時候打包就比較簡單&#xff1a; 1. 首先在Eclipse中打開項目&#xff0c; 右鍵點擊項目&#xff0c;選擇“Export”&#xff1…

第一天 :學習node.js

第一天 &#xff1a;學習node.js ① node.js環境配置 我學過的語言最簡單的一門 直接百度就可以配置 ② 每個入門 的程序都是從helloworld開始 代碼如下 &#xff1a; var httprequire(http); http.createServer(function(req,res){ res.writeHead(200,{content-type:text/htm…

c語言從入門到精通第四版電子書_C語言從入門到精通(吐血分享)4.pdf

C語言從入門到精通(吐血分享)4成功&#xff01;結構體、鏈表、文件數組、字符串函數、指針三種結構化程序設計三種數據類型、六大表達式一、簡單的程序#include 數學函數 命令行main() /*主函數*/{ /*左花括號&#xff0c;函數體的開始 */int a,b,c; /*定義語句*/a 3; /*執行語…

從硬盤上把數據傳回到計算機稱為什么,計算機基礎知識 第一章 習題三

計算機基礎知識第一章習題三一、填空題1. 高級語言不能直接被計算機識別并執行&#xff0c;必須翻譯成機器語言&#xff0c;翻譯的方式有兩種&#xff1a;一種是編譯方式&#xff0c;另一種是方式。2. 計算機中存儲數據的最小單位是&#xff1b;存儲容量的基本單位是。3. CAI的…

Mentor PADS 9.5下載安裝及破解指南

Pads&#xff0c;是一款用于設計、模擬電子線路及設計電路板的電腦軟件&#xff0c;原由Innoveda公司開發&#xff0c;其后改名為PowerPCB&#xff0c;在2002年4月Innoveda被Mentor Graphics收購&#xff0c;近年再次改用原名Pads。目前該軟件是國內從事電路設計的工程師和技術…

Thymeleaf 學習筆記 (4)~~~~

2019獨角獸企業重金招聘Python工程師標準>>> 模板布局 模板布局主要用到的標記有這么幾個&#xff1a; th:fragment &#xff0c;用來定義片段的&#xff0c;用法&#xff1a;th:fragment"fragmentName"&#xff0c;起一個名字方便被其他地方引用&#xf…

憑證 金蝶_金蝶軟件賬務處理流程之——憑證錄入

金蝶是我們財務人非常熟悉的財務軟件&#xff0c;但是我們很多財務人只在應用軟件的時候還是會出現很多的問題&#xff0c;為了幫助大家更好地應用這個軟件&#xff0c;小編今天就來和大家講講關于金蝶軟件憑證查詢環節的一些基本處理流程。點擊主界面“憑證查詢”→彈出憑證過…

計算機網申興趣愛好怎么寫,銀行網申個人特長和興趣愛好怎么寫

銀行網申個人特長和興趣愛好怎么寫銀行網申中個人簡歷及興趣愛好怎么寫?下面jyj135小編為大家整理了銀行網申中個人特長和興趣愛好的寫作技巧&#xff0c;希望能為大家提供幫助!銀行網申特長及興趣愛好怎么寫?特長Strong Point(1)寫強項。弱項一定不要寫&#xff0c;面試人員…

單例模式討論篇:單例模式與垃圾回收

出處&#xff1a;http://blog.csdn.net/zhengzhb/article/details/7331354 Jvm的垃圾回收機制到底會不會回收掉長時間不用的單例模式對象&#xff0c;這的確是一個比較有爭議性的問題。將這一部分內容單獨成篇的目的也是為了與廣大博友廣泛的討論一下這個問題。為了能讓更多的人…