人工智能 Java 坦克機器人系列: 神經網絡,下部
Robocode 中團隊作戰是很復雜的應用,如何在多變的環境下找到自己想要的目標是團隊作戰的關鍵。本文將用貝葉斯網絡來實現團隊作戰的目標的選擇,貝葉斯網絡是人工智能中機器學習的一種方法,它并不屬于神經網絡范圍。由于本文不僅介紹了貝葉斯網絡的應用,同
Robocode 中團隊作戰是很復雜的應用,如何在多變的環境下找到自己想要的目標是團隊作戰的關鍵。本文將用貝葉斯網絡來實現團隊作戰的目標的選擇,貝葉斯網絡是人工智能中機器學習的一種方法,它并不屬于神經網絡范圍。由于本文不僅介紹了貝葉斯網絡的應用,同樣涉及到神經網絡公共包的應用、Robocode 中使用神經網絡的例子機器人分析,最后還介紹了 AI-CODE 這個類似于 Robocode 的
編程工具的體系結構,以方便 C,
C++,C# 用戶在本文
Java 代碼的基礎上對神經
網絡知識的理解。
貝葉斯網絡
貝葉斯網絡亦稱信念網絡(Belief Network),于 1985 年由 Judea Pearl 首先提出。它是一種模擬人類推理過程中因果關系的不確定性處理模型,其網絡拓樸結構是一個有向無環圖(DAG)。它的節點用隨機變量或命題來標識,認為有直接關系的命題或變量則用弧來連接。例如,假設結點 E 直接影響到結點 H,即 E→H,則建立結點 E 到結點 H 的有向弧(E,H),權值(即連接強度)用條件概率 P(H/E)來表示,如圖所示:

有兩個結點的貝葉斯網絡示意圖 |

有6個節點的貝葉斯網絡 |
一般來說,有 n 個命題 x1,x2,,xn 之間相互關系的一般知識可用聯合概率分布來描述。但是,這樣處理使得問題過于復雜。Pearl 認為人類在推理過程中,知識并不是以聯合概率分布形表現的,而是以變量之間的相關性和條件相關性表現的,即可以用條件概率表示。如
例如,對如圖所示的 6 個節點的貝葉斯網絡,有
一旦命題之間的相關性由有向弧表示,條件概率由弧的權值來表示,則命題之間靜態結構關系的有關知識就表示出來了。當獲取某個新的證據事實時,要對每個命題的可能取值加以綜合考查,進而對每個結點定義一個信任度,記作 Bel(x)??梢幎?Bel(x) = P(x=xi / D) 來表示當前所具有的所有事實和證據 D 條件下,命題 x 取值為 xi 的可信任程度,然后再基于 Bel 計算的證據和事實下各命題的可信任程度。


|
回頁首 |
|
團隊作戰目標選擇
在 Robocode 中,特別在團隊作戰中。戰場上同時存在很多機器人,在你附近的機器人有可能是隊友,也有可能是敵人。如何從這些復雜的信息中選擇目標機器人,是團隊作戰的一大問題,當然我們可以人工做一些簡單的判斷,但是戰場的信息是變化的,人工假定的條件并不是都能成立,所以讓機器人能自我選擇,自我推理出最優目標才是可行之首。而貝葉斯網絡在處理概率問題上面有很大的優勢。首先,貝葉斯網絡在聯合概率方面有一個緊湊的表示法,這樣比較容易根據一些事例搜索到可能的目標。另一方面,目標選擇很容易通過貝葉斯網絡建立起模型,而這種模型能依據每個輸入變量直接影響到目標選擇。
貝葉斯網絡是一個具有概率分布的有向弧段(DAG)。它是由節點和有向弧段組成的。節點代表事件或變量,弧段代表節點之間的因果關系或概率關系,而弧段是有向的,不構成回路。下圖所示為一個簡單的貝葉斯網絡模型。它有 5 個節點
和 5 個弧段
組成。圖中沒有輸入的 A1 節點稱為根節點,一段弧的起始節點稱為其末節點的母節點,而后者稱為前者的子節點。
簡單的貝葉斯網絡模型
貝葉斯網絡能夠利用簡明的圖形方式定性地表示事件之間復雜的因果關系或概率關系,在給定某些先驗信息后,還可以定量地表示這些關系。網絡的拓撲結構通常是根據具體的研究對象和問題來確定的。目前貝葉斯網絡的研究熱點之一就是如何通過學習自動確定和優化網絡的拓撲結構。
變量
由上面貝葉斯網絡模型要想得到理想的目標機器人,我們就必須知道需要哪些輸入變量。如果想得到最好的結果,就要求我們在 Robocode 中每一個可知的數據塊都要模擬為變量。但是如果這樣做,在貝葉斯網絡結束計算時,我們會得到一個很龐大的完整概率表,而維護如此龐大的概率表將會花費我們很多的系統資源和計算時間。所以在開始之前我們必須要選擇最重要的變量輸入。這樣從比賽中得到的關于敵人的一些有用信息有可能不會出現在貝葉斯網絡之內,比如速度和方向。下面我們列出對選擇系統最重要的 6 個變量數據,并用一個 6 維數組保存這些變量值。
1.自身機器人的能量:
機器人的能量是多少。它預計選擇的目標敵人的能量和自己的能量相差數。
public double getScore (Enemy e)
{
int a1, a2, a3, a4 = 0, a5 = 0; //初始化每個變量
Vektor mig = new TVektor (robot); //得到自身機器人類
a1 = getEnergyIndex (robot.getEnergy ()); //初始化自身能量變量
|
mig為自身的機器人類,在上部的反向傳播算法部分有說明,getEnergyIndex()為能量的狀態值,在下面的狀態部分有這個函數的詳細說明。
2.敵人的能量:
當我們選擇了兩個目標時,如果此時我們的能量比較低,我們可以從已知的敵人中選擇能量低的機器人作戰,這樣我們更有贏得勝利的機率。
a2 = getEnergyIndex (e.getEnergy ());
|
3.敵人的距離:
距離能讓我們預防從遠處向我們靠近的目標敵人。而且當我們穿過整個戰場去攻擊敵人,敵我距離也能讓我們預防一些潛在的危險。在有效的范圍內作出反應。
a3 = getDistanceIndex (mig.substract (new TVektor (e)).getLength ());
|
4.敵人和它的隊友的距離:
在團隊戰斗中,由于目標機器人過多,我們選擇的敵人身邊可能有離得很近的對方隊友。如果此時我們只是針對選擇的敵人作出反應而不考慮其隊友將對我們十分不利。所以考慮到敵人與它的隊友的距離就變得很重要了。
Enemy[]en = worldmodel.getEnemies (); //得到地圖中所有收集到的敵人
mig = new TVektor (e);
double aafstand = Double.MAX_VALUE;
//分析容器類中每個敵人的距離
for (int i = 0; i < en.length; i++) {
double aff = mig.substract (new TVektor (en[i])).getLength ();
if (aff < aafstand && aff > 2)
a4 = getDistanceIndex (aff);
}
|
5.有多少隊友選擇了給出的目標:
讓隊友去攻擊同一敵人是一個不錯的主意,這樣可集中火力消滅敵人。但是,有可能有隊友已經選擇了目標機器人,如果這時讓所有的機器人都去攻擊同一個目標對已方是很不利的。所以只有知道有多少隊友已經選擇了敵人,才能讓自己得到最大的利益。
Teammate[]ttt = worldmodel.getTeammates (); //隊友列表
for (int i = 0; i < ttt.length; i++)
//判斷隊友是否選擇目標敵人
if (e.getName ().equals (ttt[i].getEnemy ()))
a5++;
a5 = a5 > 2 ? 2 : a5;
|
6.勝利或失?。?/p>
當機器人戰死,我們必須知道是否成功使用了當前的變量設置。此處勝利用 0 表示,失敗用 1 表示.
貝葉斯網絡設計
由于隊友(Friend)變量基本是類似的,所以我們用 Friends_targets 替代其實幾個隊友變量,即把 Friend{1-4} 移除,這樣我們可以大大減少網絡的開銷。如下圖 Fridends-target 代表了所有其他的隊友:
減少貝葉斯網絡大小
我們用一個 6 維數組保存這些變量,最后把當前戰斗情況下的變量加權保存到磁盤文件當中,在使用中通過讀取操作從文件中讀取。下面是讀取變量加權的過程,保存過程與其類似:
private void load () {
int[] a;
dataFile = robot.getDataFile ("-bayesian.dat");
if (dataFile.exists ()) {
while ((a = readLineIn ()) != null) {
data[a[0]][a[1]][a[2]][a[3]][a[4]][a[5]] = a[6];
}
}
else {
int a1, a2, a3, a4, a5, a6;
for (a1 = 0; a1 < data.length; a1++)
for (a2 = 0; a2 < data[a1].length; a2++)
for (a3 = 0; a3 < data[a1][a2].length; a3++)
for (a4 = 0; a4 < data[a1][a2][a3].length; a4++)
for (a5 = 0; a5 < data[a1][a2][a3][a4].length; a5++)
for (a6 = 0; a6 < data[a1][a2][a3][a4][a5].length; a6++)
data[a1][a2][a3][a4][a5][a6]++;
}
}
|
狀態
貝葉斯網絡是它是由節點和有向弧段組成的,所以在貝葉斯網絡中只有節點是不夠的,我們還要知道每個節點的弧段,也即每個可能的輸入都要有一個狀態。但是在 Robocode 中給每個變量都設置狀態是不可能的,比如我們要給距離這個 double 類型的變量構造相應的狀態數。每個數字對應一個狀態數,這將會生成一個龐大的狀態數集數據表,如此巨大的數據表很有可能耗費系統資源。為了解決這個問題,我們把輸入變量的范圍分成區間,即把連續的問題分成線性問題求解。這樣我們就可以基于每個變量區間來構造狀態。這些區間變量可被看成是離散化的連續變量。
所以狀態問題就轉變為區間變量的劃分問題,好的區間變量能為網絡提供好的目標選擇結果。根據上面變量的說明,我們主要是劃分好能量、距離以及隊友三個變量區間。
能量變量可能的區間范圍如下:[0-20]、[21-50]、[51-120]、[121 無窮大]這個方法能區分"常規"機器人中三分之一的能量變量。如果在比賽開始,這個方法還能發現能量為 120 的敵人隊長。
private static int getEnergyIndex (double d) {
if (d < 20) return 0;
else if (d < 50) return 1;
else if (d < 120) return 2;
else return 3;
}
|
此處我們分別用 0,1,2,3 分別表示不同能量區間下的狀態。
對于距離變量可能的范圍為:[0-100]、[101-300]、[301-700]、[701-1200]、[1201 無窮大]。這能讓機器人區分其他機器人是很近,剛剛在雷達范圍內還是在雷達之外。這個變量對雷達的掃描范圍很重要,因為只有能掃描到的機器人才能更好的選擇。
private static int getDistanceIndex (double d) {
if (d < 100) return 0;
else if (d < 300) return 1;
else if (d < 700) return 2;
else if (d < 1200) return 3;
else return 4;
}
|
現在我們看看 Friend 變量的狀態如何指定?從直觀上來看,隊友包括如下的一些狀態:沒有目標(No target)、死亡(Dead)、同一目標(Same target)、另一目標(Another target)。但是在真正使用當中,我們并不需要知道隊友是死亡還是有沒有目標,我們只要知道隊友是否同時選擇的同一目標。這樣,我們把"多少隊友選擇了給出的目標"變量狀態設為 0,1,2 三種狀態,0 表示沒有隊友選擇目標,1 表示有一個隊友選擇了目標,2 表示有 2 個或 2 個以上的了隊友選擇了目標。
最后只剩下"勝利或失敗"變量的狀態了,由上面的變量說明我們知道"勝利或失敗"變量只有兩種狀態,0 表示勝利,1 表示失敗。
知道了所有變量的狀態,相當于我們知道了這些變量的維數空間,這樣我們在最初變量的初始化過程中,就可按變量的狀態指定變量大小。
public class TargetSelection {
//分別設定各變量的維數空間大小
private int[][][][][][] data = new int[4][4][5][5][3][2];
|
為了更新貝葉斯網絡我們要知道如何判斷選擇的目標是好的還是壞的。其實判斷標準很簡單:如果選擇的目標在我們之前死去,這個選擇就是好的;如果我們在目標這前死去,這個選擇就是不好的。


|
回頁首 |
|
貝葉斯公式與目標選擇實現
貝葉斯網絡是一種概率網絡,它是基于概率推理的圖形化網絡,而貝葉斯公式則是這個概率網絡的基礎。讓我們先來看一看貝葉斯基本公式,設
、
是兩個事件,且
,則貝葉斯公式如下:
目標變量有兩種狀態,一種表示打贏了當前選擇的目標,另一種表示失敗。所以我們不需要存儲其他所有變量的概率,只要在網絡更新時存儲下面三個整數就能得出選擇的最高概率:
我們給出一個變量參數 (s)所用的時間數;在這個配置下贏得(nwin)戰斗的時間數,我們失敗的時間數(nlost)。這樣我們就可求得勝利的概率是(nwin)/s,失敗的概率是 (nlost)/s.
從上面貝葉斯公式的定義我們推理得到 (nwin)/s+(nlost)/s=1 ==>(nwin+nlost)/s=1==>nwin+nlost=s,這意味著我們不需要存儲特定的變量參數 s,只要存儲每個設置的勝利和失敗數,這樣贏的概率就是(nwin)/s=(nwin)/ (nwin+nlost)。
//根據給出的參數求勝利的概率
public double getScore (int a1, int a2, int a3, int a4, int a5) {
return (((double) data[a1][a2][a3][a4][a5][0]) /
(double) (data[a1][a2][a3][a4][a5][0] +
data[a1][a2][a3][a4][a5][1]));
}
|


|
回頁首 |
|
更新網絡
最后一步我們要做的就是更新貝葉斯網絡。這是在 robocode 平臺運行時執行的。輸出變量是機器人可能勝利而選擇的 Target。當選擇一個目標,機器人本身關于敵人和狀態信息將被保存直到敵人或自身死亡,貝葉斯網絡因此能被更新。從上面的貝葉斯網絡原理每時刻一個正的示例將會產生,而且可能的選擇目標變量增加。
//在機器人死亡事件中更新貝葉斯網絡
public void robotDead (String rb) {
private static TargetSelection ts = null;
if (robot.getName ().equals (rb) || rb.equals (valgt.getName ())) {
ts.learn (myenergy, valgt.getEnergy (), afstand, aafstand,
antalPaaHam, robot.getName ().equals (rb));
ts.save ();
}
}
//根據給出的參數加權選擇目標
public void learn (double a1, double a2, double a3, double a4, int a5,
boolean won) {
data[getEnergyIndex (a1)][getEnergyIndex (a2)][getDistanceIndex (a3)]
[getDistanceIndex (a4)][a5][won ? 0 : 1]++;
}
|


|
回頁首 |
|
神經網絡Java公共包(Neural Networks Management Library)
神經網絡具有以下特性:具有很強的容錯性,這是因為信息是分布存貯于網絡內的神經元中;并行處理方法,人工神經元網絡在結構上是并行的,而且網絡的各個單 元可以同時進行類似的處理過程,使得計算快速;自學習、自組織、自適應性,神經元之間的連接多種多樣,各元之間聯接強度具有一定可塑性,使得神經網絡可以處理不確定或不知道的系統; 可以充分逼近任意復雜的非線性關系;具有很強的信息綜合能力,能同時處理定量和定性的信息,能很好的協調多種輸入信息關系,適用于處理復雜非線性和不確定對象。
這些特性很容易讓我們把神經網絡封裝為一公共應用包以隱藏神經網絡內在特性,而使用者只要調用這些公共包就可完成神經網絡算法的實現。NNLibrary 就是這樣的一種 Java 包。
NNLibrary結構與定義
NNLibrary類庫名又名 nrlibj,它能幫助我們更好的定義和管理一個簡單的人工神經網絡。神經網絡其實就是用不同方法計算各個連接節點的體系結構。所以本神經網絡包用通用的節點計算方法定義了一個通用的框架。在本包設計中把神經網絡建立在各層之上,這樣只要我們想,我們可以建立任意多的層。每一層都是以一個從 0 開始的累加數字來表示,所有的這些數字都按一定的順序排列。包中一部分神經網絡函數是正向函數,能計算 0 層到最后一個層這樣的順序排列的層內的每一節點。另一部分函數是反向傳播誤差函數,能處理節點反向傳播誤差值。
雖然本包允許定義一個再循環的連接。但是由于神經網絡是一個前饋循環,在循環中每層只能計算一次。要想再次使用連接,我們必須重新開始循環,如前饋函數 frwNNet():它能關聯一個緩沖到層中。這個緩沖是另一個層,當在這層計算節點時,它會把一個輸出值推入到此層的輸入變量中。這樣緩沖層就可使用這個值。如果此層的順序數小于整個緩沖的層,將在下一個循環計算這些推入的值,緩沖在此時將作為一個內存存在層中。如果緩沖小于或大于緩沖層對象中定義的節點數,這個函數還能計算同一層的橫向連接關系。
這個公共包不僅能定義同一層中不同的連接,一層中不同的節點群的連接,而且能定義 N 到 N 的連接或 1 到 1 的連接,或者定義一個二維層。
NN 應用示例
本例我們將用 NN 庫實現異或(XOR)函數的進化。XOR 函數如下表有每個對應的點都有兩個輸入值和一個輸出值。
0 0 -> 0 ;1 0 -> 1;0 1 -> 1;1 1 -> 0
|
為了方便操作,我們在這只計算帶有兩個輸入節點,兩個隱藏節點和一個輸出節點的神經網絡。
import nrlib50.*; //引入神經網絡公共包
import java.io.*;
import java.lang.*;
import java.util.*;
/*
類Tlex由一個main主函數和evoClicle,newPop兩個子函數組成。
本程序進化100個異或神經網絡,每個神經網絡根據輸入、隱藏、輸出分為三層:
第一層兩個節點,第二層兩個節點,第三層一個節點
*/
public class T1ex {
static String train[]= // 初始化XOR 表結構
{"0 0 0", "1 0 1", "0 1 1", "1 1 0"};
//網絡群體數組描述
static String PopNNdescr[]=
{"net=1,100", //定義100個網絡點
//定義各層的層數、節點以及類名稱
"layer=0 tnode=2 nname=NodeLin",
"layer=1 tnode=2 nname=NodeSigm",
"layer=2 tnode=1 nname=NodeSigm",
"linktype=all fromlayer=0 tolayer=1", //連接第一層和隱藏層
"linktype=all fromlayer=1 tolayer=2"}; //連接隱藏層和最后一層
static NrPop pop; //實例化網絡群
static int gn=300; // 定義進化后代為300
static int pn; // 定義神經網絡群緩沖器
static int nfathers;
public static void main(String[] args) {
int g; //后代計數器
NrPop.setSeed(0); //給每個種子設置隨機后代
//創建種群,這里也可用簡單的方法pop= new NrPop(100,2,2,1,false)
pop= new NrPop(PopNNdescr);
pn=pop.PopSize();
nfathers=(int)(pn*0.1); //定義父比例為10%
pop.fitInit(); //創建一個適應度數組
//循環處理下一代
for (g=1;g<=gn;g++) {
evoCicle(train); //群體循環處理函數
pop.fitRankingMin(); //把適合度數組按最小平方差排序
newPop(); //通過復制和變異產生下一代
pop.nrPopSave("NnetXorTrained.nnt");//保存訓練后的種群
try {System.in.read();} catch (IOException e){}
}
/**********************群體循環處理*****************************************/
static void evoCicle(String train[]) {
int i;
float fitnet;
NNet net;
for (i=1;i<=pn;i++) {
net=pop.getNNet(i);
fitnet=net.testNNet(train); //測試網絡并輸入調整誤差到適合度中
pop.fitSet(i,fitnet); //輸入適合度到數組當中
}
}
/***********產生新一代*******************/
static void newPop() {
int i;
NNet netfrom,netto;
//把后代中的適合度按順序排列
for (i=nfathers+1;i<=pn;i++) {
netfrom=pop.fitGetNetAtPos(NrPop.riab(1,nfathers)); //產生隨機父代
netto=pop.fitGetNetAtPos(i); //產生子代
netfrom.copyWNNet(netto); //復制父代到子代
netto.wgtmutNNet((float)0.05,false); //高斯變異值為0.05
}
}
}
|

 |


|
回頁首 |
|
應用神經網絡的 Robocode 例子機器人分析
在 Robocode 的倉庫中(Robcode倉庫),來自世界各地的 Robocode 發明和創造了很多有意思的機器人例子,其中就不缺神經網絡的應用機器人例子。比如:Albert 的 ScruchiPu 和 TheBrainPi 機器人。PEZ 的 OrcaM 機器人, Synnalagama 的 NeuralPremier 和 MiniNeural 機器人,Krabb 的 Fe4r機器人,WCSV 的 Engineer 機器人,這些都或多或少利用了神經網絡的相關知道設計自己的機器人,而且這些機器人都比較短小但威力卻很強大。
大家可以直接點擊機器人鏈接下載這些機器人,在測試中你會發現這些機器人大部分都是在瞄準攻擊系統中使用了神經網絡。也由此我們更可以看出瞄準攻擊的特性是完全適合神經網絡的應用的。
PEZ 的 OrcaM 機器人,Albert 的 ScruchiPu 和 TheBrainPi 機器人,Synnalagama 的 NeuralPremier 都利用了神經網絡公共包 nrlibj。下面我們以 Albert 的開源神經網絡機器人 ScruchiPu 為例來看看這些機器人是如何利用公共神經網絡包 nrlibj?如何在瞄準攻擊系統中用上神經網絡的?注意此代碼省略了很多與神經網絡無關的定義,完整代碼請直接點機器人鏈接下載。
import apv.nrlibj.NNet;
/** ScruchiPu - by Albert **/
public class ScruchiPu extends AdvancedRobot {
//定義各層的層數、節點以及類名稱,這里共定義了四層
static String NNdescr[]=
{"layer=0 tnode=80 nname=NodeLin", //第一層 80個節點
"layer=1 tnode=10 nname=NodeSigm", //二層10個節點
"layer=2 tnode=5 nname=NodeSigm", //三層5個節點
"layer=3 tnode=2 nname=NodeSigm", //四層2個節點
"linktype=all fromlayer=0 tolayer=1", //連接第一層和第二層
"linktype=all fromlayer=1 tolayer=2",
"linktype=all fromlayer=2 tolayer=3"};
…//神經網絡學習過程
public void learn(NNet network, int n) {
float[] input = buildInput(n-1);
float[] output = buildOutput(n-1);
if (input != null && output !=null) {
network.ebplearnNNet(input,output); }
}
//瞄準系統設置
public double aim(NNet network) {
double x = getX() + Math.sin(targetBearing)*eGetDistance;
double y = getY() + Math.cos(targetBearing)*eGetDistance;
double ah = lastHeading;
int time = 0;
int match = n;
float[] output = new float[2];
double vel = 0;
//在子彈到達敵人未來位置之前調整攻擊角度
while ((time * (20.0 - 3.0 * power)) < Point2D.distance(getX(),getY(),x,y))
{
float[] input = buildInput(n+time);
//利用前饋函數設置輸入、輸出值
if (input != null) network.frwNNet(input,output);
else { output[0] = 0; output[1] = 0; }
vel = Math.max(-8,Math.min(8,(output[0]-0.5)*VOUT_SF));
if (vel - ev[n+time] > 2) vel = ev[n+time] + 2;
if (ev[n+time] - vel > 2) vel = ev[n+time] - 2;
x+=Math.sin(ah)*vel; //調整x值
x = Math.max(18,Math.min(getBattleFieldWidth()-18,x));
y+=Math.cos(ah)*vel; //調整y值
y = Math.max(18,Math.min(getBattleFieldHeight()-18,y));
ev[n+time+1] = vel;
double hed =
Math.max(-(0.17453293-0.01308997*Math.abs(vel)),
Math.min(0.17453293-0.01308997*Math.abs(vel),
(output[1]-0.5)*HOUT_SF));
ah += hed;
eh[n+time+1] = hed;
time++;
}
return Math.atan2(x-getX(),y-getY()); //返回調整后的攻擊角度
}
//構造輸出N+1的輸入N
public float[] buildInput(int n) {
if (n<embed*delay) return null;
else {
float[] input = new float[embed*2];
for (int i=0; i<embed; i++) {
input[2*i] = (float) (ev[n-i*delay]/VIN_SF);
input[2*i+1] = (float) (eh[n-i*delay]/HIN_SF);
}
return input;
}
}
|
Synnalagama 更在這個公式神經網絡包 nrlibj 上設計一個簡化版的神經網絡包 Neuralib 用于自己的小型神經網絡機器人 MiniNeural。Neuralib 由兩個類組成:NeuralNetwork,NeuronLayer。
package synnalagma.test.neuralib;
/* @author Synnalagma */
public class NeuralNetwork implements java.io.Serializable {
private NeuronLayer[] layers; //設置新層
//創建一個神經網絡對象,初始化各層
public NeuralNetwork(int[] network) {
layers = new NeuronLayer[network.length - 1];
for(int i = 0; i < this.layers.length; i++) {
layers[i] = new NeuronLayer(network[i], network[i + 1]);
}
}
//從輸入值得到輸出
public double[] forwardPass(double[] input) {
for(int i = 0; i < this.layers.length; i++) {
input = layers[i].getOutput(input);
}
return input;
}
//以<input,output>偶對方式訓練網絡示例
public void train(double[] input, double[] output) {
input = this.forwardPass(input);
for(int i = 0; i < input.length; i++) {
input[i] = (output[i] - input[i]) * input[i] * (1 - input[i]);
}
for(int i = this.layers.length - 1; i > -1; i--) {
input = layers[i].propagate(input);
}}}
|
處理各層節點及輸入、輸出
package synnalagma.test.neuralib;
public class NeuronLayer implements java.io.Serializable {
//transient private double learningRate = 0.3, momentum=0.3;
private int in; //輸入層的大小
private int out; //輸出層大小
private transient double[] output; //輸出層緩沖器
private transient double[] input; //輸入層緩沖器
private double[][] weight; //加權二維數組
private transient double[][] lastWeightChange; //改變后的加權數組
public NeuronLayer(int in, int out) {
this.in = in;
this.out = out;
this.input = new double[in];
output = new double[out];
this.lastWeightChange = new double[in + 1][out];
weight = new double[in + 1][out];
for(int i = 0; i <= in; i++) {
for(int o = 0; o < out; o++) {
weight[i][o] = Math.random() - 0.5;
}
}
}
//得到層的輸出
public double[] getOutput(double[] inp) {
//直接復制反向傳播數據
System.arraycopy(inp, 0, this.input, 0, in);
for(int o = 0; o < out; o++) {
this.output[o] = 0;
//加權計算輸出
for(int i = 0; i <= in; i++) {
output[o] += (weight[i][o] * ((i == in) ? 1 : input[i]));
}
output[o] = 1.0 / (1.0 + Math.exp(-output[o])); //神經網絡公式計算
}
return (double[])output.clone() }
//反向傳播方法,error為誤差變量
public double[] propagate(double[] error)
{
double[] deltaE = new double[out], delta= new double[in];
//改變加權數
for(int o = 0; o < out; o++) {
deltaE[o] = error[o] * (output[o] * (1 - output[o]));
for(int i = 0; i <= in; i++) {
weight[i][o]+=(this.lastWeightChange[i][o]*0.3)
weight[i][o]+= (this.lastWeightChange[i][o] =
0.3 * deltaE[o] * ((i == in) ? 1 : input[i])) //檢查誤差偏離
} }
//利用delta訓練法則計算誤差
for(int i = 0; i < in; i++) {
for(int o = 0; o < out; o++) {
delta[i] += (deltaE[o] * weight[i][o]);
}
}
}
|

 |


|
回頁首 |
|
AI-CODE體系結構和編程接口
在本人前兩篇強化學習和遺傳算法學習坦克機器人的文章中提及到了AI-CODE,收到很多讀者來信,特別是一些非 Java 程序員,很想有一個類似于 Robocode 的工具在 C,C++ 等語言下學習相關的人工智能知識。在此基礎上,下文給出了 AI-CODE 的一體系結構以及它多語言編程接口實現原理,以方便大家更多的了解這個工具和程序的移植。以移植本文的 Java 智能機器人代碼到 AI-CODE 平臺上。由于 AI-CODE 是國內的產品,所以中文文檔支持很好,如想了解更多,大家可以到(http://www.ai-code.org)上查看相關的文章。
AI-CODE 是集虛擬機器人運行平臺、機器人程序圖形編輯器、機器人程序代碼編輯器于一體的軟件系統。下面是它的體系結構。
AI-CODE內部框架
在 AI-CODE 中,機器人程序作為一個單獨的程序運行,機器人程序與 AIRobot 運行平臺之間通過 socket 建立連接,數據以 XML 的形式在 AIRobot 運行平臺和機器人程序之間傳輸。從理論上說,只要能夠建立 socket 連接,解析 XML 數據的編程語言,就可以用來編寫機器人程序。
在機器人的一個運行周期中,首先是 AIRobot 將比賽信息發送給機器人,這些信息包括所有機器人的狀態(如坐標,運動速度)、當前所發生的事件(各種 Action 處理函數)、比賽場地的大小等。機器人程序接收到這些信息后,經過對這些數據的分析和處理,就可以依據分析的結果來控制機器人運動。在下達機器人控制命令后,機器人程序將把這些命令發送給 AIRobot,AIRobot 將根據這些命令來更新比賽信息,控制比賽的過程。整個比賽的過程就是連續的由這樣的運行周期組成。
機器人程序是一個獨立運行的應用程序,這個程序實際上是由兩部分代碼組成的,一部分是適配器代碼,另一部分是用戶代碼。用戶代碼的主要功能就是控制機器人的運動,這也是編程的焦點。適配器代碼對用戶來說是透明的,它向用戶隱藏了機器人程序和 AIRobot 交互的細節,并對用戶代碼呈現一個簡單的編程接口,這部分代碼有以下的幾個重要的功能:
- 通過 socket 和 AIROBOT 建立連接。
- 接收 AIRobot 發送過來的比賽信息,并對其進行解析:將 XML 形式的數據轉換為用戶代碼容易使用的形式,如 Java,C++ 中的對象。
- 調度用戶代碼的運行:根據當前的 Action,觸發用戶代碼的 Action 處理函數。
- 對用戶代碼設置的機器人控制命令加以編碼:將命令轉換為 XML 形式,并將其發送給 AIRobot。
用戶代碼必須和適配器代碼連接在一起,才能生成一個完整的應用程序。拿 C++ 機器人程序來說,適配器代碼以".lib"的形式存放在 c/lib 下,系統為不同的編譯器提供了相應的版本,在創建機器人程序的時候,就要和這些 .lib 進行連接。
數據傳輸
數據以 XML 的形式(UTF-8編碼)在 AIRobot 運行平臺和機器人程序之間傳輸,這個過程是通過對 socket 進行讀寫來完成的。實際上 XML 數據就是一種有著特殊格式的字符串,在 AIRobot 和機器人之間的數據傳輸就是通過 socket 傳輸字符串數據。在一般情況下,socket 都會提供讀寫字節的操作,字符串數據可以通過字節數組的形式在 socket 上傳輸。下面將介紹 AIRobot 和機器人在傳輸數據時的約定。
在接收字符串的時候:
1.讀入字符串的長度。這個長度描述了字符串數據所占的字節數。這個長度占用兩個字節,通過 socket 提供的讀字節操作讀出這兩個字節,假設第一個字節為 a,第二個字節為 b,那么這個長度的計算公式為 length=(((a & 0xff) << 8) | (b & 0xff))。注意,這個長度只是描述了字符串的長度,它并不屬于字符串的內容。
2.讀入字符串數據。根據字符串的長度 length,從 socket 中讀入 length 字節的數據,這些數據就是我們真正需要的XML數據。
這個過程用偽代碼實現就像是這樣:
/* 從socket中讀入一個字符串 */
String readString(Socket socket){
byte a = socket.readByte(); //讀入第一個字節
byte b = socket.readByte(); //讀入第二個字節
int length = (((a & 0xff) << 8) | (b & 0xff)); //計算字符串長度
byte[] strbuff = new byte[length]; //創建用于存放字符串數據的緩存
socket.readBytes(strbuff, length); //將字符串數據讀入緩存中(length個字節)
return new String(strbuff); //將緩存中的內容作為一個字符串返回
}
|
在發送字符串的時候:
1.寫入字符串的長度。這個長度占用兩個字節,假設字符串的長度為 length,那么要寫入的第一個字節 a=(byte)(0xff & (length >> 8)),第二個字節 b=(byte)(0xff & length)。
2.寫入字符串數據。將字符串作為字節數組寫入 socket。
這個過程用偽代碼實現就像是這樣:
/* 將一個字符串string寫入socket */
void readString(Socket socket, String string){
int length = string.length(); //字符串的長度
byte a = (byte)(0xff & (length >> 8)); //計算要寫入的第一個字節
byte b = (byte)(0xff & length); //計算要寫入的第二個字節
socket.writeByte(a); //寫入長度的第一個字節
socket.writeByte(b); //寫入長度的第二個字節
byte[] strbuff = string.toBytes(); //將字符串轉換為字節數組
socket.writeBytes(strbuff, length); //寫入字符串數據(length個字節)
}
|
從上面的細節中我們可以看到,讀寫字符串的操作是相對的。對于這兩個操作,不同的語言有不同的實現,具體細節和語言提供的 socket 和字符串實現有關。在 C++ 機器人適配器代碼中,commons/NetTransporter.hpp 中定義的類 NetTransporter 實現了這兩個操作(readString,writeString),大家可以作為參考。
通過以上的分析我們可以看到,適配器代碼對 Robot 隱藏了機器人程序和AIROBOT 交互的細節,并以抽象類的形式對其呈現一個簡單的編程接口。在對機器人編程的過程中,用戶只需實現自己的 Action 處理函數就夠了。
原文轉自:http://www.kjueaiud.com
- 評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
-
老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月
|