遺傳算法

遺傳算法(Genetic Algorithm, GA)是近幾年發展起來的一種嶄新的全局優化算法。本文將講解這種算法,並介紹如何 Robocode Java 坦克機器人中採用此算法以實現機器人進化。

遺傳算法

遺傳算法(Genetic Algorithm, GA)是近幾年發展起來的一種嶄新的全局優化算法。1962年霍蘭德(Holland)教授首次提出了GA算法的思想,它借用了仿真生物遺傳學和自然選擇 機理,通過自然選擇、遺傳、變異等作用機制,實現各個個體的適應性的提高。從某種程度上說遺傳算法是對生物進化過程進行的數學方式仿真。

這一點體現了自然界中"物競天擇、適者生存"進化過程。與自然界相似,遺傳算法對求解問題的本身一無所知,它所需要的僅是對算法所產生的每個染色體 進行評價,把問題的解表示成染色體,並基於適應值來選擇染色體,使適應性好的染色體有更多的繁殖機會。在算法中也即是以二進制編碼的串。並且,在執行遺傳 算法之前,給出一羣染色體,也即是假設解。然後,把這些假設解置於問題的“環境”中,也即一個適應度函數中來評價。並按適者生存的原則,從中選擇出較適應 環境的染色體進行復制, 淘汰低適應度的個體,再通過交叉,變異過程產生更適應環境的新一代染色體羣。對這個新種羣進行下一輪進化,至到最適合環境的值。

遺傳算法已用於求解帶有應用前景的一些問題,例如遺傳程序設計、函數優化、排序問題、人工神經網絡、分類系統、計算機圖像處理和機器人運動規劃等。

術語說明

由於遺傳算法是由進化論和遺傳學機理而產生的搜索算法,所以在這個算法中會用到很多生物遺傳學知識,下面是我們將會用來的一些術語說明:

一、染色體(Chronmosome)

染色體又可以叫做基因型個體(individuals),一定數量的個體組成了羣體(population),羣體中個體的數量叫做羣體大小。

二、基因(Gene)

基因是串中的元素,基因用於表示個體的特徵。例如有一個串S=1011,則其中的1,0,1,1這4個元素分別稱爲基因。它們的值稱爲等位基因 (Alletes)。

三、基因地點(Locus)

基因地點在算法中表示一個基因在串中的位置稱爲基因位置(Gene Position),有時也簡稱基因位。基因位置由串的左向右計算,例如在串 S=1101 中,0的基因位置是3。

四、基因特徵值(Gene Feature)

在用串表示整數時,基因的特徵值與二進制數的權一致;例如在串 S=1011 中,基因位置3中的1,它的基因特徵值爲2;基因位置1中的1,它的基因特徵值爲8。

五、適應度(Fitness)

各個個體對環境的適應程度叫做適應度(fitness)。爲了體現染色體的適應能力,引入了對問題中的每一個染色體都能進行度量的函數,叫適應度函 數. 這個函數是計算個體在羣體中被使用的概率。

操作算法

霍蘭德(Holland)教授最初提出的算法也叫簡單遺傳算法,簡單遺傳算法的遺傳操作主要有三種:選擇(selection)、交叉 (crossover)、變異(mutation)這也是遺傳算法中最常用的三種算法:

1.選擇(selection)

選擇操作也叫複製操作,從羣體中按個體的適應度函數值選擇出較適應環境的個體。一般地說,選擇將使適應度高的個體繁殖下一代的數目較多,而適應度較 小的個體,繁殖下一代的數目較少,甚至被淘汰。最通常的實現方法是輪盤賭(roulette wheel)模型。令Σfi表示羣體的適應度值之總和,fi表示種羣中第i個染色體的適應度值,它被選擇的概率正好爲其適應度值所佔份額fi/Σfi。如 下圖表中的數據適應值總和Σfi=6650,適應度爲2200變選擇的可能爲fi/Σfi=2200/6650=0.394.


圖1. 輪盤賭模型
圖1. 輪盤賭模型

Fitness 值: 2200 1800 1200 950 400 100
選 擇概率: 3331 0.271 0.18 0.143 0.06 0.015

2.交叉(Crossover)

交叉算子將被選中的兩個個體的基因鏈按一定概率pc進行交叉,從而生成兩個新的個體,交叉位置pc是隨機的。其中Pc是一個系統參數。根據問題的不 同,交叉又爲了單點交叉算子(Single Point Crossover)、雙點交叉算子(Two Point Crossover)、均勻交叉算子 (Uniform Crossover),在此我們只討論單點交叉的情況。

單點交叉操作的簡單方式是將被選擇出的兩個個體S1和S2作爲父母個體,將兩者的部分基因碼值進行交換。假設如下兩個8位的個體:

S1	1000  1111	S2	1110  1100

 

產生一個在1到7之間的隨機數c,假如現在產生的是2,將S1和S2的低二位交換:S1的高六位與S2的低六位組成數串10001100,這就是 S1和S2的一個後代P1個體;S2的高六位與S1的低二位組成數串11101111,這就是S1和S2的一個後代P2個體。其交換過程如下圖所示:

Crossover 11110000 Crossover 11110000
S1 1000 1111 S2 1110 1100
P1 1000 1100 P2 1110 1111

3.變異(Mutation)

這是在選中的個體中,將新個體的基因鏈的各位按概率pm進行異向轉化,最簡單方式是改變串上某個位置數值。對二進制編碼來說將0與1互換:0變異爲 1,1變異爲0。

如下8位二進制編碼:

1	1	1	0	1	1	0	0

 

隨機產生一個1至8之間的數i,假如現在k=6,對從右往左的第6位進行變異操作,將原來的1變爲0,得到如下串:

1	1	0	0	1	1	0	0

 

整個交叉變異過程如下圖:


圖2. 交叉變異過程
圖2. 交叉變異過程圖2. 交叉變異過程

4.精英主義 (Elitism)

僅僅從產生的子代中選擇基因去構造新的種羣可能會丟失掉上一代種羣中的很多信息。也就是說當利用交叉和變異產生新的一代時,我們有很大的可能把在某 箇中間步驟中得到的最優解丟失。在此我們使用精英主義(Elitism)方法,在每一次產生新的一代時,我們首先把當前最優解原封不動的複製到新的一代 中,其他步驟不變。這樣任何時刻產生的一個最優解都可以存活到遺傳算法結束。

上述各種算子的實現是多種多樣的,而且許多新的算子正在不斷地提出,以改進GA某些性能。比如選擇算法還有分級均衡選擇等等。

遺傳算法的所需參數

說簡單點遺傳算法就是遍歷搜索空間或連接池,從中找出最優的解。搜索空間中全部都是個體,而羣體爲搜索空間的一個子集。並不是所有被選擇了的染色體 都要進行交叉操作和變異操作,而是以一定的概率進行,一般在程序設計中交叉發生的概率要比變異發生的概率選取得大若干個數量級。大部分遺傳算法的步驟都很 類似,常使用如下參數:

Fitness函數:見上文介紹。

Fitnessthreshold(適應度閥值):適合度中的設定的閥值,當最優個體的適應度達到給定的閥值,或者最優個體的適應度和羣體適應度不 再上升時(變化率爲零),則算法的迭代過程收斂、算法結束。否則,用經過選擇、交叉、變異所得到的新一代羣體取代上一代羣體,並返回到選擇操作處繼續循環 執行。

P:種羣的染色體總數叫種羣規模,它對算法的效率有明顯的影響,其長度等於它包含的個體數量。太小時難以求出最優解,太大則增長收斂時間導致程序運 行時間長。對不同的問題可能有各自適合的種羣規模,通常種羣規模爲 30 至 160。

pc:在循環中進行交叉操作所用到的概率。交叉概率(Pc)一般取0.6至0.95之間的值,Pc太小時難以向前搜索,太大則容易破壞高適應值的結 構。

Pm:變異概率,從個體羣中產生變異的概率,變異概率一般取0.01至0.03之間的值變異概率Pm太小時難以產生新的基因結構,太大使遺傳算法成 了單純的隨機搜索。

另一個系統參數是個體的長度,有定長和變長兩種。它對算法的性能也有影響。由於GA是一個概率過程,所以每次迭代的情況是不一樣的,系統參數不同, 迭代情況也不同。

遺傳步驟

瞭解了上面的基本參數,下面我們來看看遺傳算法的基本步驟。

基本過程爲:

  1. 對待解決問題進行編碼,我們將問題結構變換爲位串形式編碼表示的過程叫編碼;而相反將位串形式編碼表示變換爲原問題結構的過程叫譯碼。
  2. 隨機初始化羣體P(0):=(p1, p2, … pn);
  3. 計算羣體上每個個體的適應度值(Fitness)
  4. 評估適應度,對當前羣體P(t)中每個個體Pi計算其適應度F(Pi),適應度表示了該個體的性能好壞
  5. 按由個體適應度值所決定的某個規則應用選擇算子產生中間代Pr(t)
  6. 依照Pc選擇個體進行交叉操作
  7. 仿照Pm對繁殖個體進行變異操作
  8. 沒有滿足某種停止條件,則轉第3步,否則進入9
  9. 輸出種羣中適應度值最優的個體

程序的停止條件最簡單的有如下二種:完成了預先給定的進化代數則停止;種羣中的最優個體在連續若干代沒有改進或平均適應度在連續若干代基本沒有改進 時停止。

根據遺傳算法思想可以畫出如右圖所示的簡單遺傳算法框圖:


圖3. 簡單遺傳算法框圖
圖3. 簡單遺傳算法框圖

下面僞代碼簡單說明了遺傳算法操作過程:

choose an intial population
For each h in population,compute Fitness(h)
While(max Fitness(h) < Fitnessthreshold)
do selection
do crossover
do mutation
update population
For each h in population,compute Fitness(h)
Return best Fitness





回頁首


Robocode 說明

能有效實現遺傳算法的應用例子有很多,像西洋雙陸棋、國際名模等等都是遺傳程序設計學習的工具,但是 Robocode 有着其他幾個無可比擬的優勢:

  1. 它是基於面嚮對象語言 Java 開發,而遺傳算法本身的思想也是存在繼承等面向對象概念;
  2. Robocode 是一種基於遊戲與編程語言之間的平臺,有一個充滿競技與樂趣的坦克戰鬥平臺,你能很快的通過與其他坦克機器比賽而測試自己的遺傳算法;
  3. Robocode 社羣有 4000 個左右各種策略的例子機器人可供你選擇,這些機器人足以讓我們模擬真實的遺傳環境。而且很多代碼可直接開放源代碼供我們借鑑 ;
  4. Robocode 是一個開源軟件,你可直接上Robocode控制器上加入自己的遺傳特點,而加快遺傳過程的收斂時間;
  5. Robocoe是一個很容易使用的機器人戰鬥仿真器,您在此平臺上創建自己的坦克機器人,並與其它開發者開發的機器人競技。以得分排名的方式判定 優勝者。每個 Robocode 參加者都要利用 Java 語言元素創建他或她的機器人,這樣就使從初學者到高級黑客的廣大開發者都可以參與這一娛樂活動。如果您對Robocode不是很瞭解,請參考 developerWorks 網站 Java 技術專區文章:“重錘痛擊 Robocode ”;

在 Robocode 中其實有很多種遺傳算法方法來實現進化機器人,從全世界的 Robocode 流派中也發展幾種比較成熟的方法,比如預設策略遺傳、自開發解釋語言遺傳、遺傳移動我們就這幾種方法分別加以介紹。由於遺傳算法操作過程都類似,所以前面 二部分都是一些方法的介紹和部分例子講解,後面部分會給出使用了遺傳算法的移動機器人人例子。 在附錄中,也提供了機器人倉庫中有關遺傳算法機器人的下載,大家可參考。

 




回頁首


預設策略進化機器人

Robocode 坦克機器人所有行爲都離不開如移動、射擊、掃描等基本操作。所以在此把這些基本操作所用到的策略分別進化如下編碼:移動策略move-strategy (MS), 子彈能量bullet-power-strategy (BPS), 雷達掃描radar-strategy (RS), 和瞄準選擇策略target- strategy (TS)。由於Robocode愛好者社羣的發展,每一種基本操作都發展了很多比較成熟的策略,所有在此我們直接在下面預先定義的這些策略如下表:

MS BPS RS TS
random distance-based always-turn HeadOn
Linear light-fast target-focus Linear
circular Powerful-slow target-scope-focus Circular
Perpendicular Medium nearest robot
arbitary hit-rate based Log
anti gravity Statistic
Stop Angular
Bullet avoid wave
wall avoid
track
Oscillators

下面是基本移動策略的說明:

  • Random:隨機移動主要用來混亂敵人的預測,其最大的一個缺點是有可能撞擊到其他機器人
  • Linear:直線移動,機器人做單一的直線行走
  • circular:圓周移動,這種移動是以某一點爲圓心,不停的繞圈
  • Perpendicular:正對敵人移動,這是很多人採用的一種移動方式,這在敵人右邊, 以隨時調整與敵人的相對角
  • Arbitrary:任意移動
  • AntiGravity:假設場地有很多力點的反重力移動,本方法是模擬在重力場作用下,物體總是遠離重力勢高的點,滑向重力勢低的點,開始戰 場是一個平面然後生成一些勢點重力勢大的勢點的作用就像是一個山(起排斥作用),其衰減係數與山的坡度對應。重力勢小的勢點的作用就像是一個低谷(起吸引 作用),其衰減係數與谷的坡度對應。這樣使本來的平面變得不平了,從來物體沿着最陡的方向向下滑動
  • Track:跟蹤敵人,敵人移動到哪,機器人也移動到哪,但是總與敵人保持一定最佳躲避子彈距離和角度
  • Oscillators:重複做一振盪移動
  • Bullet avoid:每當雷達覺察到敵人時有所動作。機器人保持與敵人成30度傾向角。自身成 90 度角靜止並逐漸接近目標。如果機器人覺察到能量下降介於 0.1 和 3.0 之間(火力範圍),那麼機器人就立即切換方向,向左或向右移動。
  • wall avoid:這是一種使自己的機器人不會被困在角落裏或者不會撞牆的移動方式

瞄準策略說明如下:

  • Headon:正對瞄準
  • Linear:直線瞄準
  • circular:圓周瞄準
  • nearest robot:接近機器人瞄準
  • Log:保存每次開火記錄
  • Statistic:統計學瞄準,分析所有打中及未打中的次數,以其中找出最高打中敵人的概率爲準則
  • Angular:找到最佳角度瞄準
  • Wave:波形瞄準,子彈以波的方式進行探測

Robocode 行爲事件

坦克的主要都定義在一個主循環中,我們在程序中定義爲上面四個策略定義四種戰略如Move,Radar,Power,Target,當某一事件發 生,基於這個事件而定的行爲就會觸發。而每個戰略中都有不同的行爲處理方式。這些行爲通過遺傳算法觸發,遺傳算法將調用這些基本動作並搜索這些策略的最佳 組合。基於這些基本動作將有4224 (=4*11*4*3*8)種可能發生。在Robocode AdvancedRobot 類下有如下的移動函數:

  • setAhead和ahead:讓機器人向前移動一定距離.
  • setBack和back:讓機器人向後移動一定距離
  • setMaxTurnRate:設置機器人最大的旋轉速度
  • setMaxVelocity:設置機器人最大的運動速度
  • setStop和stop:停止移動或暫停機器人,並記住停止的位置
  • setResume和resume:重新開始移動停止的機器人
  • setTurnLeft和turnLeft:向左旋轉機器人
  • setTurnRight和turnRight:向右旋轉機器人

下面是 doMove 移動方法中使用部分程序代碼:

Random:

switch(Math.random()*2) {
case 0: setTurnRight(Math.random()*90);
break;
case 1: setTurnLeft(Math.random()*90);
break; }
execute();

 

Linear:

ahead(200);
setBack(200);

 

Circular:

setTurnRight(1000);
setMaxVelocity(4);
ahead(1000);

 

anti gravity:

	double forceX = 0;
double forceY = 0;
for (int i=0; i<targetInfo.size(); i++){
TargetInformation ti = (TargetInformation)targetInfo.get(i);
double targetToMeX = getX()-ti.x;
double targetToMeY = getY()-ti.y;
double targetDistance = Math.sqrt(ti.x * ti.x + ti.y * ti.y);
forceX += (targetToMeX/(ti.distance * ti.distance));
forceY += (targetToMeY/(ti.distance * ti.distance));
}
forceX += 1/(getX());
forceY += 1/(getY());
forceX += 1/(getX()-getBattleFieldWidth());
forceY += 1/(getY()-getBattleFieldHeight());
double forceMagnitude = Math.sqrt(forceX*forceX+forceY*forceY);
forceX*=8/forceMagnitude;
forceY*=8/forceMagnitude;
desiredX = getX() + forceX;
desiredY = getY() + forceY;

 

這裏我們用遺傳算法來控制機器人移動位置。這些策略是基於下面幾點:機器人人自己的位置、速度和方位;對手的位置(x,y座標)、速度、方位以及相 對角;所有機器人和子彈位置,方位及速度;場地大小等參數。

當上面的信息在下一回移動中使用時,出輸出一對座標值,根據這對座標在Robocode就能得到距離和角度。要想讓移動實現遺傳必須要讓它實現在線 學習:所以我們的代碼必須做下面幾件事:要有一個函數收集適應度值,在Robocode運行過程中要運用到遺傳操作,遺傳後代要在Robocode運行中 產生,而不是事後由手寫入代碼。

遺傳操作

本例中遺傳算法爲實現移動用到兩個類GA和MovePattern。此處的GA比較簡單主要完成數據和羣體的定義,以及這些定義的讀寫文件操作。基 中包括如下參數:羣體大小、交叉概率、變異概率、精英概率(既告訴從當前羣體到下一代中有多少移動不需要改變)、方程式中使用的加權係數大小,它通過一個 主循環完成MovePattern的封裝。MovePattern類中實現交叉、變異方法等方法,完成移動模式操作。而所有的輸出保存在一個vector 函數當中。Vector函數擁有一對實數數組,一個用於計算x座標,另一個用於計算y座標。通過對x,y座標的計算,從而得到距離、角度等值,併產生相就 在移動策略。如下,MovePattern包含三個參數,grad表示vector函數排列順序,input即表示算法給出的輸入編號,rang是加權的 範圍。

public class MovePatteren implements Comparable {
private int grad, input;
private double range;
protected double fitness=0;
protected double[] weightsX, weightsY;
… }

 

交叉操作:每一個交叉操作執行如下步驟,先在交叉操作中產生一個特徵碼。這個特徵碼是個0到1之間的變量數組。有關交叉的基本原理可參考上面部分。 最後通過遍歷vector函數,把相應的加權值進行交叉操作。

protected MovePatteren crossOver(MovePatteren mate, boolean[] maskx, boolean[] masky) {
double[] wx= new double[weightsX.length];
double[] wy= new double[weightsX.length];
for(int mask=0; mask<maskx.length; mask++) {
for(int g=0; g<=grad; g++) {
int i=mask*(grad+1)+g;
wx[i]=(maskx[mask]?this:mate).weightsX[i];
wy[i]=(masky[g]?this:mate).weightsY[i];
}
}
return new MovePatteren(grad, input, range, wx, wy);
}

 

這裏的變異操作比較簡單。把加權範圍內的隨機數值去代替0到數組長之間的隨機數並保存到移動模式中。則完成整個數組的變異過程:

protected void mutate() {
weightsX[(int)(Math.random()*weightsX.length)]=Math.random()*range*2-range;
weightsY[(int)(Math.random()*weightsX.length)]=Math.random()*range*2-range;
}

 

從上面的例子我們知道了遺傳算法的大概實現,但並沒有告訴我們這些組件是如何一起工作的。當Robocode開始時,如果文件中沒有數據,所以系統 會依照輸入的策略隨機生成一個移動模式,如果文件中有數據,則加載這些數據。每一個移動模式在開始都會給出了一個適應度值。當所有的移動模式都接收到適應 度值,並完成各自的編號後,下面的操作將開始執行:

  1. 對所有的移動模式依據它們的適應度值進行分級處理
  2. 執行精英操作
  3. 執行交叉操作
  4. 應用變異操作
  5. 保存加權
  6. 算法重新開始

適應度值在進行運算過程中由機器人程序不斷調整,以找到最優適應度。

限於篇副其他的一些策略本文不與詳細說明,上面所有提到的策略和行爲程序都可在網上或IBM的開發雜誌上找到成熟的講解和例子機器人。有興趣的朋友 可以把這些策略都加入到自己的遺傳算法中來。我們取羣體大小爲50,選擇概率爲0.7,交叉概率爲0.6,變異概率爲0.3,與Robocode部分例子 機器人測試,經過150代後你會發現系統產生了很多有趣的策略。比如撞擊策略,這些策略都不在我們定義的策略之中。

 




回頁首


中間解釋程序進化機器人

遺傳算法可被看做任意基因組字符串。但是你必須決定這些字符所代表的意義,也就是說如何解釋每一個基因組。最簡單的方法是把每一個基因組視爲 java代碼,編譯並運行它們。但是這些程序編譯都很困難,所以也就有可能不能工作。Jacob Eisenstein設計了一種機器翻譯語言TableRex來解決這個問題。在java中,TableRex被用於進化和解釋動行時的Robocode 機器人。通過測試,只要我把TableRex解釋程序作爲文件放入Robocode控制器目錄中,這些控制器就會讀取文件並開始戰鬥。TableRex是 一些最適合遺傳算法的二進制編程。只要符合TableRex程序文法,每個程序都能被解釋。

編碼

下表中顯示了TableRex編碼結構,它由一個行動作函數,二個輸入和一個輸出組成。如行6的值 ,這是個布爾型的表達式“值 line4 小於 90”,這個結果會在最後一行輸出布爾爲1的值。

Function Input 1 Input 2 Output
1. Random ignore ignore 0,87
2. Divide Const_1 Const_2 0,5
3. Greater Than Line 1 Line 2 1
4. Normalize Angle Enemy bearing ignore -50
5. Absolute Value Line 4 ignore 50
6. Less Than Line 4 Const_90 1
7. And Line 6 Line 3 1
8. Multiply Const_10 Const_10 100
9. Less Than Enemy distance Line 8 0
10. And Line 9 Line 7 0
11. Multiply Line 10 Line 4 0
12 Output Turn gun left Line 11 0

輸入的函數我們依照Robocode定義而定。如下表:

velocity
energy
heading
gunHeading
gunHeat
distance to west wall
distance to north wall
distance to east wall
distance to south wall
constant: 1
constant: 2
constant: 10
constant: 90
enemyVelocity
enemyEnergy
enemyHeading
enemyBearing
enemyDistance

 

TableRex有三個設計標準:

  1. 它是一種解釋程序,能更快的進化程序,基於TableRex設計的機器人能有效的讀寫遺傳數據;
  2. 擁有一個容易編碼的固定基因組,使遺傳中更容易交叉操作;
  3. 只要給TableRex一個簡單的輸入,它就很容易通過操作命令輸出要的命令序列。如上表的最後輸出左轉炮管;

而整個TableRex解釋程序由三部分組成:

  • SmallBrain:TableRex的實現部分,此部分直接寫在例子機器人處,也即自己寫的測試機器人;
  • BrainWorld:這是實現遺傳算法的主方法,直接寫入Robocode控制器當中,在Robocode運行當中運行;
  • GeneticAlgorithm:這是遺傳算法的定義部分,裏面直接定義了所要用到的遺傳操作函數;

下面我們來分析一個機器人如何通過TableRex達到遺傳。

GeneticAlgorithm:

主要是聲明選擇、交叉、變異的方法。

GeneticAlgorithm是一個靜態類,其中定義了遺傳所要的基本參數,如下:

public abstract class GeneticAlgorithm {
public int population_size; // 羣體長度
public int genome_size; //基因個體長度
public GFPair population[]; //產生的羣體
public int best_index;
public double best_fitness = Double.MIN_VALUE; //最優適應度
public double mutation = 0.03; //變異概率
public double crossover = 0.9; //交叉概率
public double copy = 0.1;
public int tourney_size = 3;

 

其中變異概率取0.03, 交叉概率取0.9,最優適應度爲實型的最小。此部分是從保存的文件中讀取各個基本參數遺傳初始化羣體。

依照適應度值選擇羣體中個體:

public String tourneySelect (){
double best_fit = -1;
int best_guy = -1;
for (int i = 0; i < tourney_size; i++){
int cur_guy = (int) (Math.random() * population.length);
if (population[cur_guy].fitness > best_fit){
best_fit = population[cur_guy].fitness;
best_guy = cur_guy;
}
}

return population[best_guy].genome;
}

 

交叉操作:通過從字符串中取子串的方法達到交叉操作:

public static String crossover (String g1, String g2){
int num_points = (int) Math.round (Math.random() * 4f);
int point = (int) (g1.length() * Math.random());
return g1.substring (0, point) + g2.substring (point);
}

 

變異操作:此部分先把基因轉換爲字符串流,通過setCharAt函數從指定的位置取反字符而達到變異:

public static String mutate (String genome, double p_mutation){
StringBuffer genome_b = new StringBuffer (genome);
if (genome_b.charAt (point) == '1'){
genome_b.setCharAt(point, '0');
}
else {
genome_b.setCharAt (point, '1');

return new String (genome_b);
}

 

BrainWorld:

BrainWorld直接嵌入到Robocode控制器中,通過實現RobocodeListener接口來完成遺傳的實例化。其最重要的有兩個方 法,計算最優適應度和產生遺傳後代。

1. 實例化 GeneticAlgorithm:

genome_size = num_events * num_boxes * (input_bits * 2 + func_bits);
int population_size = Integer.parseInt (in.readLine());
ga = new GeneticAlgorithm (population_size, genome_size);

 

2. 通過文件讀取操作從遺傳保存文件中讀取參數到遺傳類中,文件格式如下所示:

       Robocode Location
Storage_directory
population_size
elitism
crossover
copy
base_mutation
...

 

3. 計算最優適應度:

protected void evaluateAll (){
ga.best_fitness = Double.MIN_VALUE;
ga.worst_fitness = Double.MAX_VALUE;

for (int i = 0; i < ga.population_size; i++){
ga.population[i].fitness = 0;
for (int j = 0; j < rspecs.length; j++){
ga.population[i].fitness +=
Math.pow (2.0, (all_results[i][j] - all_means[j]) / all_stdevs[j]);
}
ga.mean_fitness += ga.population[i].fitness;
if (ga.population[i].fitness > ga.best_fitness) {
ga.best_fitness = ga.population[i].fitness;
ga.best_index = i;
}

}

}

 

通過三個循環遍歷整個羣體,對各個適應度進行比較後找出最優適應度。

4. 產生遺傳後代

public void newGeneration (){

String copy_pop[] = ga.copy (ga.population, ga.copy);
String cross_pop[] = ga.crossover (ga.population, ga.crossover);
copy_pop = ga.mutate (copy_pop, ga.mutation);
cross_pop = ga.mutate (cross_pop, ga.mutation);
for (int i = 0; i < copy_pop.length; i++){
ga.population[i+elite_pop.length].genome = copy_pop[i];
}
for (int i = 0; i < cross_pop.length; i++){
ga.population[i+elite_pop.length+copy_pop.length].genome = cross_pop[i];
}

current_generation++;
evaluateAll();
}

 

通過複製(ga.copy)、交叉(ga.crossover)、變異(ga.mutate)操作,產生出遺傳後代。

SmallBrain:

SmallBrain也即我們寫的利用遺傳算法的例子機器人,它開始讀取遺傳文件"genome.dat",產生新的編碼,當掃描到敵人時把所有相 關的信息寫入數組robot_data,再通過循環操作進化寫入輸入運算,最後遍歷輸入運算決定輸出機器人的動作。

1.編碼:

public void parseGenome (String genome){
functions = new int [num_boxes][num_events];
inputs1 = new int [num_boxes][num_events];
inputs2 = new int [num_boxes][num_events];
robot_data = new double [num_boxes + num_system_inputs][num_events];

 

通過parseGenome方法,設置function,input1,input2等數組的參數,對要操作的機器人進行編碼。這部分和最上面提來 的TableRex編碼表是一致的。

2.寫入狀態信息

public void onScannedRobot (ScannedRobotEvent e){
robot_data[0][kSCAN_EVENT] = getVelocity();
robot_data[1][kSCAN_EVENT] = getEnergy();
robot_data[2][kSCAN_EVENT] = getHeading();
….

 

3.根據函數數組寫入輸入運算

for (int i = 0; i < num_boxes; i++){
switch (functions[i][event_num]){
case 0: //greater than
robot_data [i + num_system_inputs][event_num] =
robot_data[inputs1[i][event_num]][event_num] >
robot_data[inputs2[i][event_num]][event_num]?1f:0f;
break;

case 2: //equal to
break;
case 15: //output
handleOutput (inputs1[i][event_num], robot_data [inputs2[i][event_num]][event_num]);
break;

 

此處注意最後是根據寫入的操作運算進行輸出

4.輸出機器人動作命令

  public void handleScanOutput (int outputType, double value){ 
switch (outputType % 16){
case 0:
ahead (value); break;
case 1:
back (value); break;
case 2:
//maybe shouldn't use mod here
fire (value % 3); break;

 

最後我們可以看出TableRex程序中,smallBrain和BrainWorld之間以文件方式並行交互,smallBrain掃描信息,寫 入文件。BrainWorld根據文件數據進化機器人,並把進化結果寫入文件,smallBrain根據進化後的數據產生機器人的動作。

 




回頁首


GPBot 小型遺傳機器人

Geep 的機器人GPBot系統正是採用了TableRex解釋程序的簡單例子。

GPBot僅由四行代碼組成(每行都以分號結束),它做了如下一些定製達到代碼最優化:忽略雷達旋轉,讓它直接隨着炮管而轉動 TurnGunRight(INFINITY);把行爲做爲常量來實現,讓它們能顯示在進化代碼序列的任意點。

OnScannedRobot() {
MoveTank(<GP#1>);
TurnTankRight(<GP#2>);
TurnGunRight(<GP#3>);
}

 

GPBot所有事件都寫在ScannedRobotEvent事件。每個方法都利用到了遺傳算法進化機器人。第一行代碼移動系統進化,適應度值依照 個體躲避子彈,避牆和敵人的能力而設置;第二行代碼指示坦克旋轉指定的方向角。第三行代碼瞄準系統進化指示炮管旋轉指定的方向角, 適應度值依照個體打中敵人概率來設置。GPBot羣體大小爲256個個體,變異、交叉概率分別爲0.9,選擇長度爲5。在最附錄中提供了例子機器人人下 載。

 




回頁首


測試結果

最後我們給出一些測試數據,看看我們的程序不同的測試結果。

變異概率變化測試:

羣體大小: 25
變異概率(Mutation): tested
精英概率(Elitism): 0.1
比賽回合: 20 rounds
後代: 25

 

我們選擇變異概率分級從0.01到0.5這間。依照上面的遺傳算法介紹,0.01的概率是比較合理的,所以我們以此爲初始化值,下圖中顯示了所有的 測試概率數據,從下圖我們可以看出,開始一個很小的適應度值,從6代開始圖形就增長很慢了,在13代的時候,有一個小的變化,到最後每個後代都相當逼近。


圖4. 變異測試
圖4. 變異測試

圖5. 6 到 9代放大特寫
圖5. 6 到 9代放大特寫

測試說明:

我們從上可以看出當我們給出初始的適應度在很小時,在第一代增長很多,經過一定數量的後代開始匯聚到一些最大的適應度值。由此我們得到證明,機器人 在我們的學習環境中聰明的開始躲避子彈、牆、和其他機器人。而且它自己找到一個很好的瞄準位置給與敵人打擊。

 




回頁首


JGAP 的使用

最後不能不說一下Jgap的使用, JGAP 是一款用Java編寫的遺傳算法包,由sourceforce上開發而來。它提供了基本的遺傳算法,你可以使用它來解決一些適合用遺傳算法解決的問題。而 且它給出了很多例子程序,可用於我們一些遺傳算法的測試工作。由於它來自於開源組織sourceforce,所以開源的遺傳功能將是研究簡單遺傳算法很好 工具。

 




回頁首


AI-CODE

近來在研究人工智能過程和坦克機器人時,發現國內也開發出了一個類似於 Robocode 仿真器的平臺 AI-CODE,其思想延用 Robocode,但在 Robocode 基礎上做了很多的改進,封裝了一些函數模塊,讓開發者更側重於算法和程序設計的學習。最有意思的這個平臺能同時支持 Java,C,C++,C# 語言,從理論上看它支持任何語言。美中不足的是國內應用的例子還不是很多,遠沒有 Robocode 那麼多可參考的例子。如果大家有興趣可嘗試在 AI-CODE 平臺上用不同語言做一些遺傳算法的測試。我想能幫助更多人工智能愛好者。其相關網站大家可到 http:///www.ai-code.org 去了解。


參考資料


關於作者

Robocool,編程遊戲愛好者,自由撰稿人。您可以通過[email protected] 聯繫到他。

 

發佈了11 篇原創文章 · 獲贊 4 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章