遺傳算法

遺傳算法(Genetic Algorithm)又叫基因進化算法,或進化算法。屬於啓發式搜索算法一種,這個算法比較有趣,並且弄明白後很簡單,寫個100-200行代碼就可以實現。在某些場合下簡單有效。本文就花一些篇幅,儘量白話方式講解一下。  

 

    首先說一下問題。在我們學校數據結構這門功課的時候,時常會有一些比較經典的問題(而且比較複雜問題)作爲學習素材,如八皇后,揹包問題,染色問題等等。上面列出的幾個問題都可以通過遺傳算法去解決。本文列舉的問題是TSP(Traveling Salesman Problem)類的問題。

 

      TSP問題實際上是”哈密頓迴路問題”中的”哈密頓最短迴路問題”.如下圖,就是要把下面8個城市不重複的全部走一遍。有點像小時候玩的畫筆畫遊戲,一筆到底不能重複。TSP不光是要求全部走一遍,並且是要求路徑最短。就是有可能全部走一遍有很多走法,要找出其中總路程最短的走法。 
    

 


      和這個問題有點相似的是歐拉回路(下圖)問題,它不是要求把每個點都走一遍,而是要求把每個邊都不重複走一遍(點可以重複),當然歐拉回路不是本算法研究的範疇。



clip_image003

本文會從TSP引申出下面系列問題

1、  TSP問題:要求每個點都遍歷到,而且要求每個點只被遍歷一次,並且總路程最短。

2、  最短路徑問題:要求從城市1 到城市8,找一條最短路徑。

3、  遍歷m個點,要求找出其距離最短的路線。(如果m=N總數,其實就是問題1了,所以問題1可以看成是問題3的特例 )。 

遺傳算法的理論是根據達爾文進化論而設計出來的算法: 人類是朝着好的方向(最優解)進化,進化過程中,會自動選擇優良基因,淘汰劣等基因。

在上面tsp問題中,一個城市節點可以看成是一個基因,一個最優解就是一條路徑,包含若干個點。就類似一條染色體有若干基因組成一樣。所以求最短路徑問題,可以抽象成求最優染色體的問題。

遺傳算法很簡單,沒有什麼分支判斷,只有兩個大循環,流程大概如下 

   流程中有幾個關鍵元素:

 

 

       1、  適度值評估函數。這個函數是算法的關鍵,就是對這個繁衍出來的後代進行評估打分,是優秀,還是一般,還是很差的畸形兒。用這個函數進行量化。在tsp中,路徑越短,分數越高。函數可以可以這樣 fitness = 1/total_distance.  或者 fitness = MAX_DISTANCE – total_distance. 不同的計算方法會影響算法的收斂速度,直接影響結果和性能。 

 

       2、  選擇運算規則: 又稱選擇算子。對應着達爾文理論中適者生存,也有地方叫着精英主義原則,意思就是隻有優秀的人才有更大的機率存活下來,擁有交配權。有權利擁有更多後代,傳承下自己血脈基因。和現實中很相像,皇帝權臣遺留下來的子孫後代比較多。選擇方法比較多。最常見的是round robin selection 算法,即輪盤賭算法, 這個算法比較簡單有效。選擇算法目前已有的有10來種之多。各種不同業務可以按需選擇。 

clip_image007

           選擇公式如下:

clip_image008  

 

 

  1. //選擇運算---輪盤賭,此算法要求不能有負數.
     
  2.     int32_t Genetic::Selection(Genome & selGenome)
  3.     {
  4.         //生成一個隨機浮點數
            //本算法在輪盤賭算法上加上了選擇概率,提高最大可行解入圍概率
  5.         double ftmp = (((random())%100001)/(100000 + 0.0000001));
  6.         if( ftmp > 0.)
  7.         {
  8.             GetBestGenome(selGenome);
  9.             return ESUCCESS;
  10.         }
  11.         //生成一個【0, m_dTotalFitness】之間的隨機浮點數
  12.         double dRange     = (((random()+ random())%100001)/(100000 + 0.0000001)) * m_dTotalFitness;
  13.         double dCursor    = 0.0;
  14.         size_t i         = 0;
  15.         
  16.         for(= 0; i < m_vGenome.size(); ++i)
  17.         {
  18.             dCursor += m_vGenome[i].dFitness;
  19.             
  20.             if (dCursor > dRange) 
  21.             {
  22.                 break;
  23.             }
  24.         }

  25.         selGenome = m_vGenome[i];
  26.         
  27.         return ESUCCESS;
  28.     }

 

      3、  交叉運算規則:又稱交配規則,交叉算子。對應遺傳學中的精子和卵子產生的受精卵含有精子的部分基因,也含有卵子的部分基因的現象。就像孩子有點像父親,又有點像母親的規律。交叉運算算法更多。作者可以天馬行空的自己去想象。只要達到交叉結果中含有父母的基因就可以。最常見的是k-opt 交換。其中k可以是 1,2,3….等等。簡稱單點交換,兩點交換,3點交換等等:

單點交換

  

           其中修復重複基因根據業務需要看是否需要。 
       兩點交換

 

 

 

       4、  變異運算規則:又叫變異算子。在人類遺傳進化過程中。會發生一些基因突變。這些突變有可能是好的突變,有可能是壞的突變。像癌細胞就是壞的突變。愛因斯坦的大腦估計是好的突變。突變方法也是可以天馬行空的自己去發揮創造。 
 

這裏討論一下,爲什麼要有突變這道流程呢。從人類進化角度來說。人類基因有數十萬種,在遠古交流比較少的年代。都是部落內部通婚,但是整個部落內部居民可能都缺少某種好的基因,這樣無論他們怎麼交配,都不會產生好的基因,那麼他們需要引入好的基因,於是和其他部落通婚。引入其他自己沒有的基因,其實對於這個種羣來說這就是一次基因變異。如果是好的變異,那麼這個後代就很優秀,結果就是會產生更多子孫,把這個好的變異基因傳承下去,如果不是好的變異基因,自然而然會在前面選擇算子下淘汰,就是現實生活中的所謂的年幼夭折,癡呆無後,或先天畸形被淘汰,不會傳承下去。 

從計算機算法角度看:所有的啓發式算法無外乎2種手段結合。局域搜索和全域搜索。局域搜索是在鄰域範圍內找出最優解。對應的是選擇算子和交叉算子。在自己部落裏面找最優秀的人。如果只有局域搜索的話,就容易陷入局域最優解。算法結果肯定是要找出全域最優解。這就要求跳出局域搜索。我們稱之爲“創新”。創新就是一次打破常規的突破——就是我們的“變異”算子。 

這裏拿最短路徑路徑舉例子,求點1到點8之間的最短路徑, 初始解是1——2——3——6——8

  

內變異:所謂內變異就是在自己內部發生變異。嚴格來說其實不是一種變異。但是它又是一種比較有效的手段。
  

 

外變異:外變異是引入創新,突破傳統的質的飛躍, 也是啓發算法中所謂的全域搜索。下面是充當前基因中引入外部基因(當前集合的補集)。 

  

結尾:遺傳算法除了上述這些幾個主要算子之外,還有一些細節。如交叉概率pc,變異概率pm,這些雖然都是輔助手段,但是有時候對整個算法結果和性能帶來截然不同的效果。這也是啓發式算法的一個缺點。參數需要不停的在實踐中摸索,沒有萬能的推薦參數。 

還有細心的讀者可能發現幾個疑問,就是最短路徑中變異或交叉結果可能產生無效解,如前面最短路徑 1——6——3——2——8.  其中1和6之間根本沒有通路。碰到這種情況,可以拋棄這條非法解,重新生成一條隨機新解(其實這也是一次變異創新)。或者自己修復成可行解。反正框框在那裏。具體手段可以自己天馬行空發揮。 

另一個比較實際的問題是:在最短路徑中並不知道染色體長度是多少,不錯。大部分人還是用定長染色體去解決問題,這樣性能低下。算法不直觀。這時候可以使用變長染色體來解決。其實我建議不管何種情況,都設計變長染色體模式。因爲定長也是變長的一種特例。使用變長可以解決任何問題。不管是tsp還是最短路徑問題。 

還有一個編解碼問題,就是把現實問題轉換成基因,這些問題都比較容易解決,最簡單的就是直接用數組下標表示。


最後附上遺傳算法簡單參考程序MATLAB版本:

該程序是遺傳算法優化BP神經網絡函數極值尋優:
%% 該代碼爲基於神經網絡遺傳算法的系統極值尋優
%% 清空環境變量
clc
clear

%% 初始化遺傳算法參數
%初始化參數
maxgen=100; %進化代數,即迭代次數
sizepop=20; %種羣規模
pcross=[0.4]; %交叉概率選擇,0和1之間
pmutation=[0.2]; %變異概率選擇,0和1之間

lenchrom=[1 1]; %每個變量的字串長度,如果是浮點變量,則長度都爲1
bound=[-5 5;-5 5]; %數據範圍

individuals=struct('fitness',zeros(1,sizepop), 'chrom',[]); %將種羣信息定義爲一個結構體
avgfitness=[]; %每一代種羣的平均適應度
bestfitness=[]; %每一代種羣的最佳適應度
bestchrom=[]; %適應度最好的染色體

%% 初始化種羣計算適應度值
% 初始化種羣
for i=1:sizepop
%隨機產生一個種羣
individuals.chrom(i,:)=Code(lenchrom,bound); 
x=individuals.chrom(i,:);
%計算適應度
individuals.fitness(i)=fun(x); %染色體的適應度
end
%找最好的染色體
[bestfitness bestindex]=min(individuals.fitness);
bestchrom=individuals.chrom(bestindex,:); %最好的染色體
avgfitness=sum(individuals.fitness)/sizepop; %染色體的平均適應度
% 記錄每一代進化中最好的適應度和平均適應度
trace=[avgfitness bestfitness]; 

%% 迭代尋優
% 進化開始
for i=1:maxgen
i
% 選擇
individuals=Select(individuals,sizepop); 
avgfitness=sum(individuals.fitness)/sizepop;
%交叉
individuals.chrom=Cross(pcross,lenchrom,individuals.chrom,sizepop,bound);
% 變異
individuals.chrom=Mutation(pmutation,lenchrom,individuals.chrom,sizepop,[i maxgen],bound);

% 計算適應度 
for j=1:sizepop
x=individuals.chrom(j,:); %解碼
individuals.fitness(j)=fun(x); 
end

%找到最小和最大適應度的染色體及它們在種羣中的位置
[newbestfitness,newbestindex]=min(individuals.fitness);
[worestfitness,worestindex]=max(individuals.fitness);
% 代替上一次進化中最好的染色體
if bestfitness>newbestfitness
bestfitness=newbestfitness;
bestchrom=individuals.chrom(newbestindex,:);
end
individuals.chrom(worestindex,:)=bestchrom;
individuals.fitness(worestindex)=bestfitness;

avgfitness=sum(individuals.fitness)/sizepop;

trace=[trace;avgfitness bestfitness]; %記錄每一代進化中最好的適應度和平均適應度
end
%進化結束

%% 結果分析
[r c]=size(trace);
plot([1:r]',trace(:,2),'r-');
title('適應度曲線','fontsize',12);
xlabel('進化代數','fontsize',12);ylabel('適應度','fontsize',12);
axis([0,100,0,1])
disp('適應度 變量');
x=bestchrom;
% 窗口顯示
disp([bestfitness x]);

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