此文承接上篇遺傳算法入門到掌握(一)
http://blog.csdn.net/leonis_v/article/details/53673254遺傳算法引擎――GenAlg
- <span style="font-size:16px;">/遺傳算法
- class GenAlg
- {
- public:
- //這個容器將儲存每一個個體的染色體
- vector <Genome> vecPop;
- //人口(種羣)數量
- int popSize;
- //每一條染色體的基因的總數目
- int chromoLength;
- //所有個體對應的適應性評分的總和
- double totalFitness;
- //在所有個體當中最適應的個體的適應性評分
- double bestFitness;
- //所有個體的適應性評分的平均值
- double averageFitness;
- //在所有個體當中最不適應的個體的適應性評分
- double worstFitness;
- //最適應的個體在m_vecPop容器裏面的索引號
- Genome fittestGenome;
- //基因突變的概率,一般介於0.05和0.3之間
- double mutationRate;
- //基因交叉的概率一般設爲0.7
- double crossoverRate;
- //代數的記數器
- int generation;
- //最大變異步長
- double maxPerturbation;
- double leftPoint;
- double rightPoint;
- //構造函數
- GenAlg();
- //初始化變量
- void Reset();
- //初始化函數
- void init(int popsize, double MutRate, double CrossRate, int GenLenght,double LeftPoint,double RightPoint);
- //計算TotalFitness, BestFitness, WorstFitness, AverageFitness等變量
- void CalculateBestWorstAvTot();
- //輪盤賭選擇函數
- Genome GetChromoRoulette();
- //基因變異函數
- void Mutate(vector<double> &chromo);
- //這函數產生新一代基因
- void Epoch(vector<Genome> &vecNewPop);
- Genome GetBestFitness();
- double GetAverageFitness();
- };</span>
其中Reset()函數,init()函數和CalculateBestWorstAvTot()函數都比較簡單,讀者查看示例程序的代碼就能明白了。而下面分別介紹init函數和Epoch函數。
類的初始化函數――init函數
init函數主要充當CGenAlg類的初始化工作,把一些成員變量都變成可供重新開始遺傳算法的狀態。(爲什麼我不在構造函數裏面做這些工作呢?因爲我的程序裏面CGenAlg類是View類的成員變量,只會構造一次,所以需要另外的初始化函數。)下面是init函數的代碼:
- void GenAlg::init(int popsize, double MutRate, double CrossRate, int GenLenght,double LeftPoint,double RightPoint)
- {
- popSize = popsize;
- mutationRate = MutRate;
- crossoverRate = CrossRate;
- chromoLength = GenLenght;
- totalFitness = 0;
- generation = 0;
- //fittestGenome = 0;
- bestFitness = 0.0;
- worstFitness = 99999999;
- averageFitness = 0;
- maxPerturbation=0.004;
- leftPoint=LeftPoint;
- rightPoint=RightPoint;
- //清空種羣容器,以初始化
- vecPop.clear();
- for (int i=0; i<popSize; i++)
- {
- //類的構造函數已經把適應性評分初始化爲0
- vecPop.push_back(Genome());
- //把所有的基因編碼初始化爲函數區間內的隨機數。
- for (int j=0; j<chromoLength; j++)
- {
- vecPop[i].vecGenome.push_back(random() *
- (rightPoint - leftPoint) + leftPoint);
- }
- }
- }
恩,正如我之前說的,我們這個程序不但要應付基因編碼只有一個浮點數的“袋鼠跳”問題的情況,還希望以後在處理一串浮點數編碼的時候也一樣適用,所以從這裏開始我們就把基因當成串來對待。
開創新的紀元――Epoch函數
現在萬事具備了,只差把所有現成的“零件”裝配起來而已。而Epoch函數就正好充當這個職能。下面是這個函數的實現:
- void GenEngine:: OnStartGenAlg()
- {
- //產生隨機數
- srand( (unsigned)time( NULL ) );
- //初始化遺傳算法引擎
- genAlg.init(g_popsize, g_dMutationRate, g_dCrossoverRate, g_numGen,g_LeftPoint,g_RightPoint);
- //清空種羣容器
- m_population.clear();
- //種羣容器裝進經過隨機初始化的種羣
- m_population = genAlg.vecPop;
- //定義兩個容器,以裝進函數的輸入與及輸出(我們這個函數是單輸入單輸出的,但是以後往往不會那麼簡單,所以我們這裏先做好這樣的準備。)
- vector <double> input;
- double output;
- input.push_back(0);
- for(int Generation = 0;Generation <= g_Generation;Generation++)
- {
- //裏面是對每一條染色體進行操作
- for(int i=0;i<g_popsize;i++)
- {
- input = m_population[i].vecGenome;
- //爲每一個個體做適應性評價,如之前說的,評價分數就是函數值。其
- //Function函數的作用是輸入自變量返回函數值,讀者可以參考其代碼。
- output = (double)curve.function(input);
- m_population[i].fitness = output;
- }
- //由父代種羣進化出子代種羣(長江後浪退前浪。)
- genAlg.Epoch(m_population);
- //if(genAlg.GetBestFitness().fitness>=bestFitness)
- bestSearch=genAlg.GetBestFitness().vecGenome[0];
- bestFitness=genAlg.GetBestFitness().fitness;
- averageFitness=genAlg.GetAverageFitness();
- //cout<<bestSearch<<endl;
- report(Generation+1);
- }
- //return bestSearch;
- }
恩,到這裏“袋鼠跳”的主要代碼就完成了。(其它一些代碼,比如圖形曲線的顯示,和MFC的相關代碼在這就不作介紹了,建議初學者不必理會那些代碼,只要讀懂算法引擎部分就可以了。)下面就只等着我們下達命令了!
讓袋鼠在你的電腦裏進化――程序的運行
我想沒有什麼別的方法比自己親手寫一個程序然後通過修改相關參數不斷調試程序,更能理解並且掌握一種算法了。不知道你還記不記得你初學程序的日子,我想你上機動手寫程序比坐在那裏看一本厚厚的程序開發指南效率不知高上多少倍,興趣也特命濃厚,激情也特別高漲。恩,你就是需要那樣的感覺,學遺傳算法也是一樣 的。你需要把自己的代碼運行起來,然後看看程序是否按照你所想象的去運行,如果沒有,你就要思考原因,按照你的想法去改善代碼,試着去弄清其中的內在聯繫。這是一個思維激活的過程,你大腦中的神經網絡正在劇烈抖動(呵呵,或許學到後面你就知道你大腦的神經網絡是如何“抖動”的。),試圖去接受這新鮮而有 趣的知識。遺傳算法(包括以後要學到的人工神經網絡)包含大量的可控參數,比如進化代數、人口數目、選擇概率、交叉概率、變異概率、變異的步長還有以後學到的很多。這些參數之間的搭配關係,不能指望別人用“灌輸”的方式讓你被動接受,這需要你自己在不斷的嘗試,不斷的調整中去形成一種“感覺”的。很多時候 一個參數的量變在整個算法中會表現出質的變化。而算法的效果又能從宏觀上反映參數的設置。
現在就讓我們來對這個程序做簡單的說明。
參數的設置:
這個程序有很多的需要預先設置好的參數,爲了方便修改,我把它們都定義爲全局變量,定義和初始化都放在Parameter.h的頭文件裏面。下面對幾個主要參數的說明:
- //目標函數的左右區間,目前的設置是[-1,2]
- double g_LeftPoint = -1;
- double g_RightPoint = 2;
- ////遺傳算法相關參數////
- int g_numGen = 1; //每條染色體的編碼個數,這裏是1個
- int g_Generation = 1000; //進化的代數
- int g_popsize = 50; //種羣的人口數目(就是說你要放多少隻袋鼠到山上)
- double g_dMutationRate = 0.8; //基因變異的概率
- double g_dMaxPerturbation = 0.005; //基因變異的步長(袋鼠跳的最大距離)
當然,一些主要的參數在程序運行後可以通過參數設置選項進行設置。(其中緩衝時間是每進化一代之後,暫停的時間,單位爲毫秒)如圖2-6。
程序運行後請選擇菜單項:控制->讓袋鼠們開始跳吧,開始遺傳算法的過程。其中藍色的線條是函數曲線(恩,那就是喜瑪拉雅山脈。其中最高的波峯,就是珠穆朗瑪峯。)綠色的點是一隻只袋鼠。上方的黑色曲線圖表是對每一代最優的個體的適應性評分的統計圖表。下方的黑色曲線圖表是對每一代所有個體的平均適應性評分的統計圖表。(如果你認爲它們阻礙了你的視線,你可以在參數設置裏面取消掉。)如圖2-7所示。另外還可以用鍵盤的上下左右鍵來控制視窗的移動,加減鍵控制函數曲線的放縮。
剛開始的時候,袋鼠分佈得比較分散它們遍佈了各個山嶺,有的在高峯上,有的在深谷裏。(如圖2-8)
經過了幾代的進化後,一些海拔高度比較低的都被我們射殺了,而海拔較高的袋鼠卻不斷的生兒育女。(如圖2-9)
最後整個袋鼠種羣就只出現在最高峯上面(最優解上)。(如圖2-10)
當然,袋鼠不是每一次都能跳到珠穆朗瑪峯的,如圖2-11所 示。(就是說不是每次都能收斂到最優解)也許它們跳到了某一個山峯,就自大的認爲它們已經“會當凌絕頂”了。(當然,事實上是因爲不管它們向前還是向後跳都只能得到更小的適應度,所以不等它們跳過山谷,再跳到旁邊更高的山峯,就被我們射殺了。)所以,我們爲了使到袋鼠每次都儘可能的攀到珠穆朗瑪峯,而不是 留戀在某一個低一些的山峯,我們有兩個改進的辦法,其一是初始人口數目更多一些,以使最好有一些袋鼠一開始就降落到最高峯的附近,但是這種方法對於搜索空間非常大的問題往往是無能爲力的。我們常常採用的方法是使袋鼠有一定的概率跳一個很大的步長,以使袋鼠有可能跳過一個山谷到更高的山峯去。這些改進的方法 留給讀者自己去實現。
另外,如果把變異的機率調得比較高,那麼就會出現袋鼠跳得比較活躍的局面,但是很可能丟失了最優解;而如果變異的機率比較低的話,袋鼠跳得不太活躍,找到最優解的速度就會慢一些,這也留給讀者自己去體驗。
作爲一個尋找大值的程序,這個的效率還 很低。我希望留給初學者更多改進的空間,大家不必受限於現有的方法,大可以發揮豐富的想象力,自己想辦法去提高程序的效率,然後自己去實現它,讓事實去驗證你的想法是否真的能提高效率,抑或剛好相反。恩,在這個過程當中,大家不知不覺地走進了遺傳算法的聖殿了,勝於一切繁複公式的擺設和教條式的講解。
博主後記:由於原作者未知加上本人學習繁忙,所以MFC版本源碼未整理,以下爲自己整理實現的控制檯演示版本:
最後結果是一樣的,附上下載:
http://download.csdn.net/detail/emiyasstar__/3759299