由於遺傳算法是由進化論和遺傳學機理而產生的搜索算法,所以在這個算法中會用到很多生物遺傳學知識,下面是我們將會用來的一些術語說明:
一、染色體(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,它的基因特征值為8;基因位置1中的1,它的基因特征值為2。
五、適應度(Fitness)
各個個體對環境的適應程度叫做適應度(fitness)。為了體現染色體的適應能力,引入了對問題中的每一個染色體都能進行度量的函數,叫適應度函數. 這個函數是計算個體在群體中被使用的概率。
霍蘭德(Holland)教授最初提出的算法也叫簡單遺傳算法,簡單遺傳算法的遺傳操作主要有三種:選擇(selection)、交叉(crossover)、變異(mutation)這也是遺傳算法中最常用的三種算法:
1.選擇(selection)
選擇操作也叫復制操作,從群體中按個體的適應度函數值選擇出較適應環境的個體。一般地說,選擇將使適應度高的個體繁殖下一代的數目較多,而適應度較小的個體,繁殖下一代的數目較少,甚至被淘汰。最通常的實現方法是輪盤賭(roulette wheel)模型。令Σfi表示群體的適應度值之總和,fi表示種群中第i個染色體的適應度值,它被選擇的概率正好為其適應度值所占份額fi/Σfi。
如果適應度值存在負數,我的方法是采用以下公式:
再計算概率和累計概率。
設想群體全部個體的適當性分數由一張餅圖來代表 (見圖)。
群體中每一染色體指定餅圖中一個小塊。塊的大小與染色體的適應性分數成比例,適應性分數愈高,它在餅圖中對應的小塊所占面積也愈大。為了選取一個染色體,要做的就是旋轉這個輪子,直到輪盤停止時,看指針停止在哪一塊上,就選中與它對應的那個染色體。
若產生隨機數為0.81,則6號個體被選中。
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的低二位交換,后代P1為1100 1111,P2為10101100。在這里高二位與低二位是按從左到右的順序還是從右到左的順序,自我感覺效果是一樣的,不過也沒具體證明,網上是按從右到左的順序,在這篇文章中的定義都是從左到右的。
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 1 0 1 0 0 0
4.精英主義 (Elitism)
僅僅從產生的子代中選擇基因去構造新的種群可能會丟失掉上一代種群中的很多信息。也就是說當利用交叉和變異產生新的一代時,我們有很大的可能把在某個中間步驟中得到的最優解丟失。在此我們使用精英主義(Elitism)方法,在每一次產生新的一代時,我們首先把當前最優解原封不動的復制到新的一代中,其他步驟不變。這樣任何時刻產生的一個最優解都可以存活到遺傳算法結束。不過這篇文章的代碼中並沒實現這點,以后有機會有時間再貼上代碼。
說簡單點遺傳算法就是遍歷搜索空間或連接池,從中找出最優的解。搜索空間中全部都是個體,而群體為搜索空間的一個子集。並不是所有被選擇了的染色體都要進行交叉操作和變異操作,而是以一定的概率進行,一般在程序設計中交叉發生的概率要比變異發生的概率選取得大若干個數量級。大部分遺傳算法的步驟都很類似,常使用如下參數:
Fitness函數:在這篇文章中為:
Fitnessthreshold(適應度閾值):適合度中的設定的閥值,當最優個體的適應度達到給定的閥值,或者最優個體的適應度和群體適應度不再上升時(變化率為零),則算法的迭代過程收斂、算法結束。否則,用經過選擇、交叉、變異所得到的新一代群體取代上一代群體,並返回到選擇操作處繼續循環執行。這篇文章中沒使用這個閾值。
P:種群的染色體總數叫種群規模,它對算法的效率有明顯的影響,其長度等於它包含的個體數量。太小時難以求出最優解,太大則增長收斂時間導致程序運行時間長。對不同的問題可能有各自適合的種群規模,通常種群規模為 30 至 160。這篇文章中使用的群體規模為20。
pc:在循環中進行交叉操作所用到的概率。交叉概率(Pc)一般取0.6至0.95之間的值,Pc太小時難以向前搜索,太大則容易破壞高適應值的結構。這篇文章中的交叉概率為0.65。
Pm:變異概率,從個體群中產生變異的概率,變異概率一般取0.01至0.03之間的值變異概率Pm太小時難以產生新的基因結構,太大使遺傳算法成了單純的隨機搜索。這篇文章中的編譯概率為0.01。
另一個系統參數是個體的長度,有定長和變長兩種。它對算法的性能也有影響。由於GA是一個概率過程,所以每次迭代的情況是不一樣的,系統參數不同,迭代情況也不同。這篇文章中並沒用到這個。
了解了上面的基本參數,下面我們來看看遺傳算法的基本步驟。
基本過程為:
- 對待解決問題進行編碼,我們將問題結構變換為位串形式編碼表示的過程叫編碼;而相反將位串形式編碼表示變換為原問題結構的過程叫譯碼。
- 隨機初始化群體P(0):=(p1, p2, … pn);
- 計算群體上每個個體的適應度值(Fitness)
- 評估適應度,對當前群體P(t)中每個個體Pi計算其適應度F(Pi),適應度表示了該個體的性能好壞
- 按由個體適應度值所決定的某個規則應用選擇算子產生中間代Pr(t)
- 依照Pc選擇個體進行交叉操作
- 仿照Pm對繁殖個體進行變異操作
- 沒有滿足某種停止條件,則轉第3步,否則進入9
- 輸出種群中適應度值最優的個體
程序的停止條件最簡單的有如下二種:完成了預先給定的進化代數則停止;種群中的最優個體在連續若干代沒有改進或平均適應度在連續若干代基本沒有改進時停止。
代碼實現
#include <iostream> #include <time.h> #include <stdlib.h> #include <cmath> using namespace std; const int M = 8; //染色體的數目,群體的大小 const int T = 7; //終止代數 const double pc = 0.6; //交叉概率 const double pm = 0.01; //變異概率 struct Population { int g[20]; //將x,y的取值區間分為(2^10-1)份,用20位二進制位表示份數 double x,y; double fit; //適應函數值 double nfit; double sumfit; //適應值總和 }; class gaModel{ public: int rand01() const; //隨機生成01 double randab(double,double) const; //隨即生成[a,b)的一個數 void initial(Population *); // 初始化函數 void evalueFitness(Population *); // 計算適應度 void select(Population *); // 選擇復制函數 void crossover(Population *); // 交叉函數 void mutation(Population *); // 變異函數 void decoding(Population *); // 解碼函數 void print(Population *); // 顯示函數 Population p[M]; };
#include "GaModel.h" int gaModel::rand01() const{ int r = 0; double q = 0.0; q = rand()/(RAND_MAX + 0.0); if(q < 0.5) r = 0; else r = 1; return r; } double gaModel::randab(double a,double b) const{ double c,r; c = b - a; r = a + c * rand()/(RAND_MAX+1.0); return r; } void gaModel::initial(Population *t){ Population *po; srand(time(0)); for(po = t; po < t + M; po++) //初始化群體 for(int j = 0; j < 20; j++) (*po).g[j] = rand01(); } void gaModel::evalueFitness(Population *t){ double f,xx,yy,temp = 0.0; Population *po1,*po2; double fmax = 0,fmin = 9999999999; for(po1 = t; po1 < t + M; po1++){ //計算適應值 xx = (*po1).x; yy = (*po1).y; f = xx * sin(6 * yy) + yy * cos(8 * xx);//優化函數 (*po1).fit = f; //cout << (*po1).fit << "測試" << endl; } for(po1 = t; po1 < t + M; po1++){ if((*po1).fit > fmax) fmax = (*po1).fit; if((*po1).fit < fmin) fmin = (*po1).fit; } for(po1 = t; po1 < t + M; po1++){ (*po1).nfit = ((*po1).fit - fmin) / (fmax - fmin + 1); } for(po1 = t; po1 < t + M; po1++){ //計算累計適應值,為的是之后的輪盤賭模型 for(po2 = t; po2 <= po1; po2++) temp = temp + (*po2).nfit; (*po1).sumfit = temp; temp = 0.0; } } void gaModel::select(Population *t){ //輪盤賭選擇 int i = 0; Population pt[M],*po; double s; srand(time(0)); while(i < M){ s = randab((*t).sumfit,(*(t + M - 1)).sumfit); for(po = t; po < t + M; po++){ if((*po).sumfit >= s){ pt[i] = (*po); break; } else continue; } i++; } for(i = 0; i < M; i++) for(int j = 0; j < 20; j++) t[i].g[j] = pt[i].g[j]; } void gaModel::crossover(Population *t){ //交叉函數 Population *po; double q; //存放一個0到1的隨機數,判定是否交叉 int d,e,tem[20]; //d存放一個從1到19的一個隨機整數,用來確定交叉的位置 int k = 0,rpo[5] = {99,99,99,99,99};//e存放從0到M的一個隨機且與當前t[i]中i不同的整數,用來確定交叉的對象 bool tf = true; //rpo存放每次的e,確保所有的染色體都會交叉 srand(time(0)); for(po = t; po < t + M/2; po++){ q = rand() / (RAND_MAX + 0.0); while(true){ //避免自己與自己交叉和交叉過的染色體繼續交叉 tf = true; e = rand() % M; for(int j = 0; j < M; j++){ if(e == rpo[j]) tf = false; } if(t + e != po && tf) break; } if(q < pc){ d = 1 + rand() % 19; //確定交叉位置 for(int i = d; i < 20; i++){ tem[i] = (*po).g[i]; (*po).g[i] = (*(t+e)).g[i]; (*(t+e)).g[i] = tem[i]; } } rpo[k] = e; k++; } } void gaModel::mutation(Population *t){ //變異函數 double q; Population *po; srand(time(0)); for(po = t; po < t + M; po++){ int i = 0; while(i < 20){ q = rand() / (RAND_MAX + 0.0); if(q < pm) (*po).g[i] = 1 - (*po).g[i]; i++; } } } void gaModel::decoding(Population *t){ //解碼函數 Population *po; int temp,s1 = 0, s2 = 0; float m,n,dit; n = ldexp(1.0,10); dit = (2 - 1) / (n - 1); for(po = t; po < t + M; po++){ for(int i = 0; i < 10; i++){ temp = ldexp((*po).g[i]*1.0,i); s1 = s1 + temp; } m = 1 + s1 * dit; (*po).x = m; s1 = 0; for(int j = 10; j < 20; j++){ temp = ldexp((*po).g[j]*1.0,j-10); s2 = s2 + temp; } m = 1 + s2 * dit; (*po).y = m; s2 = 0; } } void gaModel::print(Population *t){ //顯示函數 Population *po; for(po = t; po < t + M; po++){ for(int j = 0; j < 20; j++){ cout << (*po).g[j]; if((j+1)%5 == 0) cout << ' '; } cout << endl; cout << "x=" << (*po).x << ' ' << "y=" << (*po).y << ' ' << "fit=" << (*po).fit << ' ' << "nfit=" << (*po).nfit << ' ' << "sumfit=" << (*po).sumfit << endl; } }
#include "GaModel.h" int main(){ int kkk = 0; gaModel g1; int gen = 0; g1.initial(&(g1.p[0])); g1.print(&(g1.p[0])); cout << endl; g1.decoding(&(g1.p[0])); g1.print(&(g1.p[0])); cout << endl; g1.evalueFitness(&(g1.p[0])); g1.print(&(g1.p[0])); while(gen < T){ cout << "gen=" << gen + 1 << endl; g1.select(&(g1.p[0])); g1.decoding(&(g1.p[0])); g1.evalueFitness(&(g1.p[0])); cout << "selected!" << endl; g1.print(&(g1.p[0])); g1.crossover(&(g1.p[0])); g1.decoding(&(g1.p[0])); g1.evalueFitness(&(g1.p[0])); cout << "crossovered!" << endl; g1.print(&(g1.p[0])); g1.mutation(&(g1.p[0])); g1.decoding(&(g1.p[0])); g1.evalueFitness(&(g1.p[0])); cout << "mutated" << endl; g1.print(&(g1.p[0])); gen++; } cin >> kkk; }
結果顯示
BUG已經修改。
參考:
1.http://www.madio.net/thread-170773-1-1.html
2.http://hi.baidu.com/wangxiaoliblog/item/562dfddd98d34e57d73aaee9
3.http://www.cppblog.com/twzheng/articles/21339.html