遺傳算法-智能鳥群


提綱:

一、遺傳算法概述

  1.1 群體

  1.2 基因型和表現型  

  1.3 突變

  1.4 選擇

  1.5 遺傳

二、智能鳥群的實現

  2.1 小鳥的基因型和表現型

  2.2 小鳥的基因如何影響小鳥的飛行路線

  2.3 小鳥的適應性

  2.4 選擇:選擇適應度更高的小鳥

  2.5 突變:增加基因的豐富性

  2.6 繁殖:產生新的DNA

  2.7 群體的繁殖

、總結和優化

智能鳥群

本文通過使用遺傳算法實現了一個智能鳥群。場景如下圖,小鳥從屏幕右側開始,越過障礙物最終到達屏幕左側的目標點。鳥群在初始狀態時會在屏幕中亂飛(下圖1),隨着不斷的進化,最終鳥群會直接飛向目標點(下圖2),可點擊這里看效果。下面先介紹下遺傳算法。

 

 

一、遺傳算法概述

遺傳算法借用了達爾文自然選擇的思想,即在一個群體中,適應性更高的個體,會更有可能將自己的基因(特性)遺傳下去。下面是自然選擇中的一些關鍵概念:

1. 群體

群體為自然選擇提供了豐富的基因庫,保證了個體的多樣性,群體越大越容易產生更具適應性的個體。

2. 基因型和表現型

我們看到的個體的外貌和行為等外在表現是由內部的基因決定的。基因型即為決定個體表現的內部數據,表現型為個體的外觀和行為。比如數字125,即可以表示個體的顏色,也可以表示個體的高度。數字125即為個體的基因型,顏色、高度等為個體的表現型。 在設計遺傳算法時,要着重設計個體的基因型和表現型。

3. 遺傳、突變和選擇

遺傳、突變和選擇是達爾文進化論中的3個基本法則。遺傳保證了子代能夠繼承父代的特性。突變保證了個體的多樣性,如果沒有突變子代和父代會永遠保持一致,新的特性就永遠不會出現,種群也不會進化。選擇保證了群體向更具適應性的方向進化,使群體中某些個體能夠繁殖而另一些個體卻沒有機會或有很少的機會繁殖。這就是通常說的“適者生存”。在遺傳算法中,在每一代都會計算個體的適應性,適應性高的個體的基因會在下一代遺傳下去,適應性低的個體的基因會被淘汰。

二、智能鳥群的實現

下面借助白鷺引擎實現智能鳥群

1. 小鳥的基因型和表現型

為了使小鳥不斷飛行,在小鳥的生命周期的每一時刻都會為小鳥賦予一個推力,通過推力改變小鳥的加速度,進而影響到小鳥的速度和位置。在小鳥的整個生命周期中(假設為200幀),可以把每一幀上的推力所組成的數組作為小鳥的基因型。小鳥在一系列的推力作用下所形成的飛行路線,為小鳥的表現型。可以用DNA類來定義小鳥的基因,其中推力用一個二維向量來定義。

 

 1 class DNA {
 2     private _genes=[];
 3     private _fitness:number;
 4     private _maxforce=0.5;//最大推力為0.5
 5     private _lifetime=200;//小鳥的生命周期為200幀
 6 
 7     public constructor() {
 8         for(let i=0;i<this._lifetime;i++){
 9             let force=Vector2D.random2D();
10             force.mult(Math.random()*this._maxforce);//初始狀態,每一時刻的推力是隨機的。
11             this._genes.push(force);
12         }
13     }
14 
15     public get genes(){
16         return this._genes;
17     }
18

2. 小鳥的基因如何影響小鳥的飛行路線

在小鳥的類中,我們定義了一個applyForce方法,會根據生命周期的不同時刻將DNA上對應的推力應用在小鳥身上,從而使小鳥的位置發生改變。下面例子中我們使用egret畫了一個三角形代表小鳥。代碼如下:

 1 class Bird extends egret.Sprite {
 2 
 3     public location:Vector2D;
 4     public velocity:Vector2D;
 5     public acceleration:Vector2D;
 6     public mass:number;
 7     private shape:egret.Shape;
 8     
 9     public target:Vector2D;
10     public dna:DNA;
11     private geneCounter=0;
12 
13     public constructor(mass:number,x:number,y:number,target:Vector2D=new Vector2D(0,0)) {
14         super();
15         this.dna=new DNA();
16         this.mass=mass;
17         this.location=new Vector2D(x,y);//小鳥位置
18         this.velocity=new Vector2D(0,0);//小鳥的速度
19         this.acceleration=new Vector2D(0,0);//小鳥加速度
20         this.target=target;
21 
22         this.shape=new egret.Shape();
23         let g=this.shape.graphics;
24         g.clear();
25         g.beginFill(0xff0000);
26         g.moveTo(0,0);
27         g.lineTo(-10,-5);
28         g.lineTo(-10,5);
29         g.lineTo(0,0);
30         this.addChild(this.shape);
31         this.shape.x=this.location.x;
32         this.shape.y=this.location.y;
33     }
34 
35     public run(){
36         this.geneCounter++;
37         this.applyForce(this.dna.genes[this.geneCounter]);//將基因對應時刻的力作用在小鳥上
38         this.update();
39         this.display();
40     }
41 
42     public applyForce(force:Vector2D){
43         let f:Vector2D=Vector2D.div(force,this.mass);//力除以質量為加速度
44         this.acceleration.add(f);//改變加速度
45     }
46 
47     public update(){
48         this.velocity.add(this.acceleration);
49         this.location.add(this.velocity);
50         this.acceleration.mult(0);
51     }
52 
53     public display(){
54         this.shape.x=this.location.x;
55         this.shape.y=this.location.y;
56         let angle=this.velocity.heading2D()*180/Math.PI;
57         this.shape.rotation=angle;
58     }
59 }

3. 小鳥的適應性

在小鳥的生命周期結束時,我們通過判斷小鳥離目標點的距離來判斷小鳥的適應性,離目標點越近的小鳥適應性越高,否則適應性越低。可以通過小鳥離目標點的距離的倒數的平方作為小鳥的適應度。計算小鳥的適應度函數如下(在Bird類中定義):

1 /*適應度計算*/
2 public fitness(){
3     let d=Vector2D.dist(this.location,this.target);//計算小鳥離目標點的距離,this.target為小鳥的目標點
4     this._fitness=Math.pow(1/d,2);
5 }
6 
7 public getFitness(){
8     return this._fitness;
9 }

4. 選擇:選擇適應度更高的小鳥

我們定義一個Population類來管理所有的小鳥以及負責小鳥的選擇和遺傳。在Population類中定義了一個population數組來存儲所有的小鳥,另外定義了一個matingPool數組作為交配池,我們根據小鳥適應性的強弱來將其放入交配池中,適應性越強的小鳥放入交配池中的數量越多,否則就越少。最后我們從交配池中隨機的選擇小鳥進行交配遺傳,這樣就保證了適應性強的小鳥選到的概率就越大。Population類中的選擇函數如下:

 1 public selection(){
 2     this.matingPool=[];
 3     let totalFitness=0;
 4     for(let i=0;i<this.totalPopulation;i++){
 5         totalFitness+=this.population[i].getFitness();
 6     }
 7 
 8     for(let i=0;i<this.totalPopulation;i++){
 9         let n=this.population[i].getFitness()/totalFitness*200;//適應性越大的小鳥,存入交配池中的數量就越多
10         if(n<1){
11             continue;
12         }
13         for(let j=0;j<n;j++){
14             this.matingPool.push(this.population[i]);
15         }
16     }
17 }

 上面的方法,先計算所有小鳥的適應度之和,最后計算每個小鳥的適應度在所有小鳥的適應度中所占比例,然后將這個比例換算為相應的個數存入交配池。可以把這種選擇方法想象成一個輪盤,某個個體的適應度所占的比例約大,它被選中的概率就越高。

5. 突變:增加基因的豐富性

為了增加基因的豐富性,從而產生適應性更強的個體,我們需要在每一代使小鳥的基因有一定的幾率產生突變,我們在小鳥的DNA類中加入突變函數:

1 public mutate(mutationRate:number){
2     for(let i=0;i<this._genes.length;i++){
3         if(Math.random()<mutationRate){
4             let force=Vector2D.random2D();
5             force.mult(Math.random()*this._maxforce);
6             this._genes[i]=force;
7         }
8     }
9 }

並且為Bird類添加mutate接口

1 public mutate(mutationRate:number){
2     this.dna.mutate(mutationRate);
3 }

 6. 繁殖:產生新的DNA

在DNA類中,添加crossover方法,它接受另一個DNA實例,通過交叉組合生成新的DNA:

 1 public crossover(partner:DNA):DNA{
 2     let child=new DNA();
 3     for(let i=0;i<this._genes.length;i++){
 4         let random=Math.random();
 5         if(random>0.5){
 6             child._genes[i]=this._genes[i];
 7         }else{
 8             child._genes[i]=partner._genes[i];
 9         }
10     }
11     return child;
12 }

在上面方法中,在生命周期的每一幀中隨機選擇雙親對應節點的數據作為子代的基因。下面為Bird類添加繁殖的方法:

/*小鳥的繁殖*/
public crossover(b:Bird):Bird{
    let bird=new Bird(1,initX,initY);//小鳥質量為1,初始位置為(initX,initY)
    let dna=this.dna.crossover(b.dna);
    bird.dna=dna;
    return bird;
}

7. 群體的繁殖

接下來在Population中添加reproduction方法,用來產生下一代。在reproduction方法中,我們從交配池中隨機的選擇兩個小鳥作為雙親,產生新的小鳥,並使小鳥發生突變,最后把新產生的小鳥加入數組population中。

 1 public reproduction(){
 2 
 3     for(let i=0;i<this.population.length;i++){
 4         let a=Math.floor(Math.random()*this.matingPool.length);
 5         let b=Math.floor(Math.random()*this.matingPool.length);
 6         let partnerA=this.matingPool[a];
 7         let partnerB=this.matingPool[b];
 8         let child=partnerA.crossover(partnerB);
 9         child.target=this.target;//為小鳥設定目標
10 
11         child.mutate(this.mutationRate);
12         this.removeChild(this.population[i]);
13         this.population[i]=child;
14         this.addChild(child);
15     }
16 }

、總結和優化

總結

通過上面的示例,我們可以總結出使用遺傳算法時的幾個關鍵步驟:

1. 定義個體的基因型和表現型,基因型發生改變表現型也會隨之變化。

2. 計算群體中每個個體的適應性;

3. 選擇適應性更高的個體作為下一代的雙親(可以通過交配池實現);

4. 通過雙親繁殖下一代,產生的下一代會發生基因突變;

5. 返回2進行下下一代的繁殖;

優化

上例中,小鳥在進行多代繁殖后,最終會沿直線朝目標飛去。為了體現遺產算法的強大,可以在小鳥和目標之間加入障礙物,當小鳥碰到障礙物時會停止,並且碰到障礙物的小鳥的適應性會急速下降。可以看看經過幾代的進化后聰明的小鳥會如何繞過障礙物到達目標點。

上面只貼出了整個項目中關鍵幾步的代碼,整個項目可以訪問我的GitHub,歡迎提交issues交流。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM