瞭解遺傳算法

遺傳算法是一種最優化算法,所謂最優化問題,就是這樣一類問題,滿足它的解(稱爲可行解)有很多(通常是極多)對於每一種解有一個評價函數得到一個評價值,也就確定瞭解集的一個偏序關係,在這個偏序關係的求最小值(或最大值)或者近似最小值(或最大值)。因爲通常可行解非常之多,所以確定性算法很難做到這一點,而遺傳算法是模擬了生物學中物種進化的過程的一種最優化算法,簡單來說,遺傳算法=遺傳操作+遺傳選擇。

 

在算法開始之前要做一下準備工作:編碼。
對於不同的問題有不同的解的形式,但要運行遺傳算法就要對其進行抽象的編碼,也就是確定染色體的形式,這裏的染色體就是用某種特定的編碼方式描述一個解,通常一個具體的解也稱爲個體。而多個不同的個體就組成一個種羣,一個種羣內有統一的編碼方式。這些概念都完全等同於生物學中的概念,它們是平行的。
例如,N個城市的旅行商問題(TSP),假如用1,2,...N表示這N個城市,那對於任意這樣的一個排列1p2p3...pN就表示了一個解,這個串就可以認爲是一個染色體,它表示一個個體的基因。

 

遺傳算法有一些基本的操作,如選擇、交叉和變異等。

選擇操作:
首先要知道適應度函數,所謂的適應度函數就是評價函數,通常是問題的目的函數(或它的倒數),它描述了個體的優劣程度同時也決定了選擇操作的概率,設fi表示第i個個體的適應度值,那選擇第i個個體的概率就是fi/∑fj,簡單來說,這個概率的大小就決定了該個體是被淘汰還是被保留。通常的具體做法是用類似賭盤的方法,每個個體佔它的適應度那麼寬的轉盤大小,每次擲色子,落到哪一格就選哪一格對應的個體。

交叉操作:
交叉操作就是讓2個以上的染色體進行交叉產生後代的過程,具體的交叉操作要看具體的問題。不過我覺得有一個原則,就是要有對稱性,交叉得到的後代中的基因要來源於父代的所有個體中,也就是說n個個體進行交叉是和它們的排列沒關係,這樣子代纔有機會得到更優秀的基因。交叉操作是遺傳算法中最重要的操作。最簡單的基本方式是交換父代中染色體片段。

變異操作:
生物可以突變,有時候突變是好的,有時候卻是壞的,但正是因爲有了突變才讓有限的種羣中基因庫可以非常豐富,也保證了種羣的適應能力。變異操作通常是翻轉個體中某段染色體,編碼後的染色體在計算機中都是01串,也就可以隨機的翻轉某個(或多個)bit上的值。

交叉和變異不是一定要發生在選擇了的個體上,而是按一定控制概率發生的,交叉概率比較高通常是0.6~0.95,而變異概率比較低通常是0.001~0.01。

 

這些操作就確定了子代,就構成了種羣進化的方式。於是遺傳算法一般的結構是:

1,產生初始種羣G
2,選擇G,交叉、變異->G'
3,是否滿足某個終止條件,如果沒有重複上面過程。

 

遺傳算法的收斂:
一般的遺傳算法不一定收斂,但給一個條件,讓每代中最優秀的個體直接進入到子代,就能保證一定收斂。

 

遺傳算法的性能:
可以收斂和收斂得有多快是兩碼事,這方面的研究還在進行中,也是當今一大課題。但可以知道和哪些因素有關,首先是種羣規模,也就是種羣中個體的數目,如果太小,顯然不利於快速收斂,如果太大又會花更多的時間計算,很矛盾,通常選擇100左右的種羣規模。其次,父代可不可以有機會進入子代?如果某次交叉產生的子代比父代還差,那是否還要這個新個體?當然不,父代也要有機會進入子代。然後就是終止條件,一般運用遺傳算法時,對最優解是不瞭解的,那我們就不知道算法何時停止,一般使用的終止條件是在進化m代後,每代中的最優個體沒有提高時停止,這裏的m的選取有時候會誤導你,如果種羣中有一個個體早熟,而其它個體沒有,那它將作爲最優個體保持m代,而事實上它卻不是最優解,改進的方法可以考慮種羣中適應度平均值和最大值兩個方面,而m的選取,不能太小,太大又浪費時間,適中選取。

 

舉個簡單的例子,用遺傳算法求根號2,(是不是有種殺雞焉用牛刀的感覺,呵呵~)

求根號2,也就是求方程f(x)=x*x-2=0的正整數解,x=1時f(1)<0,x=2時f(2)>0,由介值定理,則1到2中間存在一個根,根據代數基本定理和根的對稱性知這就是我們要找的根(廢話,初中生都知道是1.414左右),由目標函數得到適應度函數,我們選擇個體都在[1,2]之間,那適應度函數我可以取
j(x)=40/(2+|x*x-2|)-10,由x的取值範圍知j的範圍是(0,10)
x和y交叉就用取平均(x+y)/2,交叉概率取0.9,變異概率爲0,最後得到的C++程序:

#include<stdio.h>
#include<stdlib.h>
typedef struct _indi
{
 double code;//染色體
 double degree;//適應度
}Indi;
Indi group[40];//種羣規模爲40
void Judge(Indi &x)
{
 double tmp=x.code*x.code-2.0;
 if(tmp>=0)
  x.degree=40.0/(2.0+tmp)-10.0;
 else
  x.degree=40.0/(2.0-tmp)-10.0;
}
int happened(double p)//發生一個p=0~1間概率的事件
{
 return rand()<(int)(p*RAND_MAX);
}
void Cross(Indi &x,Indi &y)//交叉操作,產生一個子代取代父代中最次的一個
{
 Indi z;
 z.code=(x.code+y.code)/2.0;
 Judge(z);
 if(x.degree<y.degree)
 {
  if(z.degree<=x.degree)
   return;//如果新個體不如雙親,淘汰之
  x=z;
 }
 else
 {
  if(z.degree<=y.degree)
   return;
  y=z;
 }
}
void main()
{
 int i,j,best,x,y,c;
 double sum,strick;
 for(i=0;i<40;++i)//隨機得到初始種羣
 {
  group[i].code=1.0+(double)rand()/RAND_MAX;
  Judge(group[i]);
 }
 for(i=1;i<=10;++i)//固定進化10代
 {
  for(sum=0.0,best=0,j=0;j<40;++j)
  {
   sum+=group[j].degree;//求總的適應度sum
   if(group[j].degree>group[best].degree)
    best=j;//求當前最優個體
  }
  printf("第%2d代中 最優個體爲 %10f(%10f) 平均適應度爲 %10f/n",
   i,group[best].code,group[best].degree,sum/40.0);
  for(c=40;c;--c)
  {
   strick=(double)rand()/RAND_MAX*sum;//賭盤中的色子,選擇個體x,y
   for(x=0;x<40&&strick>=group[x].degree;++x)
    strick-=group[x].degree;
   strick=(double)rand()/RAND_MAX*sum;
   for(y=0;y<40&&strick>=group[y].degree;++y)
    strick-=group[y].degree;
   if(happened(0.9))
    Cross(group[x],group[y]);//交叉
  }
 }
}

運行結果:
第 1代中 最優個體爲 1.445692(適應度 9.138515) 平均適應度爲 5.201076
第 2代中 最優個體爲 1.414396(適應度 9.994853) 平均適應度爲 6.789277
第 3代中 最優個體爲 1.414396(適應度 9.994853) 平均適應度爲 7.726777
第 4代中 最優個體爲 1.414396(適應度 9.994853) 平均適應度爲 8.267572
第 5代中 最優個體爲 1.414396(適應度 9.994853) 平均適應度爲 8.803805
第 6代中 最優個體爲 1.414396(適應度 9.994853) 平均適應度爲 9.147720
第 7代中 最優個體爲 1.414113(適應度 9.997158) 平均適應度爲 9.280602
第 8代中 最優個體爲 1.414205(適應度 9.999755) 平均適應度爲 9.356507
第 9代中 最優個體爲 1.414205(適應度 9.999755) 平均適應度爲 9.465438
第10代中 最優個體爲 1.414211(適應度 9.999931) 平均適應度爲 9.553306 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章