1、遺傳算法概述html
1.1 羣體git
1.2 基因型和表現型 github
1.3 突變算法
1.4 選擇數組
1.5 遺傳app
2、智能鳥羣的實現dom
2.1 小鳥的基因型和表現型函數
2.2 小鳥的基因如何影響小鳥的飛行路線優化
2.3 小鳥的適應性this
2.4 選擇:選擇適應度更高的小鳥
2.5 突變:增長基因的豐富性
2.6 繁殖:產生新的DNA
2.7 羣體的繁殖
三
、總結和優化
本文經過使用遺傳算法實現了一個智能鳥羣。場景以下圖,小鳥從屏幕右側開始,越過障礙物最終到達屏幕左側的目標點。鳥羣在初始狀態時會在屏幕中亂飛(下圖1),隨着不斷的進化,最終鳥羣會直接飛向目標點(下圖2),可點擊這裏看效果。下面先介紹下遺傳算法。
遺傳算法借用了達爾文天然選擇的思想,即在一個羣體中,適應性更高的個體,會更有可能將本身的基因(特性)遺傳下去。下面是天然選擇中的一些關鍵概念:
羣體爲天然選擇提供了豐富的基因庫,保證了個體的多樣性,羣體越大越容易產生更具適應性的個體。
咱們看到的個體的外貌和行爲等外在表現是由內部的基因決定的。基因型即爲決定個體表現的內部數據,表現型爲個體的外觀和行爲。好比數字125,便可以表示個體的顏色,也能夠表示個體的高度。數字125即爲個體的基因型,顏色、高度等爲個體的表現型。 在設計遺傳算法時,要着重設計個體的基因型和表現型。
遺傳、突變和選擇是達爾文進化論中的3個基本法則。遺傳保證了子代可以繼承父代的特性。突變保證了個體的多樣性,若是沒有突變子代和父代會永遠保持一致,新的特性就永遠不會出現,種羣也不會進化。選擇保證了羣體向更具適應性的方向進化,使羣體中某些個體可以繁殖而另外一些個體卻沒有機會或有不多的機會繁殖。這就是一般說的「適者生存」。在遺傳算法中,在每一代都會計算個體的適應性,適應性高的個體的基因會在下一代遺傳下去,適應性低的個體的基因會被淘汰。
下面藉助白鷺引擎實現智能鳥羣
爲了使小鳥不斷飛行,在小鳥的生命週期的每一時刻都會爲小鳥賦予一個推力,經過推力改變小鳥的加速度,進而影響到小鳥的速度和位置。在小鳥的整個生命週期中(假設爲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 }
在小鳥的類中,咱們定義了一個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 }
在小鳥的生命週期結束時,咱們經過判斷小鳥離目標點的距離來判斷小鳥的適應性,離目標點越近的小鳥適應性越高,不然適應性越低。能夠經過小鳥離目標點的距離的倒數的平方做爲小鳥的適應度。計算小鳥的適應度函數以下(在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 }
咱們定義一個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 }
上面的方法,先計算全部小鳥的適應度之和,最後計算每一個小鳥的適應度在全部小鳥的適應度中所佔比例,而後將這個比例換算爲相應的個數存入交配池。能夠把這種選擇方法想象成一個輪盤,某個個體的適應度所佔的比例約大,它被選中的機率就越高。
爲了增長基因的豐富性,從而產生適應性更強的個體,咱們須要在每一代使小鳥的基因有必定的概率產生突變,咱們在小鳥的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 }
在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; }
接下來在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交流。