智能算法之Genetic Algorithm遺傳算法

智能算法之Genetic Algorithm遺傳算法


前言:本文主要圍繞 Matlab 的實現展開,Java版本以及Python版本參考文章最後的源碼地址,MatLab和python實現大致相同,Java較爲不同。

1、什麼是遺傳算法

  我們瞭解過深度學習的都知道,我們在進行網絡優化的過程都是通過反向傳播求導進行參數的不斷優化,而這種類型的優化參數採用前向傳播的方式繼續優化網絡,不斷找出最優解,或者最優的參數。很多的優化算法都來自於大自然的啓發,來一種算法叫做蟻羣算法,靈感就是來自於螞蟻,所以觀察大自然有時也是靈感的來源。

  遺傳算法,也叫Genetic Algorithm,簡稱 GA 算法他既然叫遺傳算法,那麼遺傳之中必然有基因,那麼基因染色體(Chromosome)就是它的需要調節的參數。我們在生物中瞭解到,大自然的法則是“物競天擇,適者生存”,我覺得遺傳算法更適用於“優勝劣汰”。

  • 優:最優解,
  • 劣:非最優解。

2、遺傳算法名詞解釋

  下面主要通過疑問提問題的方式進行解釋遺傳算法當中的適應度函數、選擇、交叉、變異這幾個名詞。

1. 都知道優勝劣汰,那怎麼實現優勝劣汰呢?
  很重要的一個環節就是選擇,也稱之爲“大自然的選擇”,大自然怎麼選擇呢,大自然會拋棄掉一些適應能力差的,在程序當中就是離最優解較遠的解,會被拋棄掉。

2. 如何實現大自然的選擇呢?
  這裏我們會引入輪盤賭法,進行大自然的選擇。選擇一些離最優解較近的個體。還有一些其他的經典的選擇辦法,例如錦標賽法進行選擇。

3. 只靠選擇就可以實現嗎?那樣會不會陷入局部最優解?
  如果只靠選擇進行調優,那麼最終的結果會受到初始種羣的影響,只是在初始種羣的羣體中進行選擇,得出的最優解也是在初始羣體中的最優解。所以就需要引入大自然當中的“啪啪啪”,也叫交叉。正所謂“龍生龍,鳳生風,老鼠生下來就會打洞”,所以說兩個優秀的基因進行交叉可以將兩者優秀的基因遺傳給一代,也增加了羣體的基因多樣性,但這種不一定就是最好的,也可能發生“夭折”。

4. 大自然的選擇好像不僅如此,還有變異吧?
  爲了更好的模擬大自然的選擇規律,來需要進入變異,變異發生的概率很低,但也是增加羣體多樣性的條件,和交叉相同,變異求解出來的不一定是最好的解,也會出現“夭折”。

5. 上面只是大自然的規則,那麼大自然的環境又是什麼呢?
  優秀的基因並不是獨立的,就像北極熊不會存活在熱帶雨林一樣。只有適合環境的基因纔是優秀的,所以說基因具有相對性,環境是挑選基因的先決條件,這裏的環境就是適應度函數。個體用過適應度函數後得到的結果越大,表明更加適合這裏的環境,那麼保留下來的概率越大。反之則越小。

3、遺傳算法的程序實現

  正所謂 “不結合代碼的解釋都是** ” 。下面結合代碼來梳理遺傳算法的實現。

基本結構

  涉及到還是適應度函數、選擇、交叉、變異這幾個模塊。下面就這幾個模塊展開說明。具體的流程圖解釋如下:

  1. 需要先對初始種羣進行一次適應度函數進行計算,這樣方便我們對個體進行選擇,適應度值越大的越容易被保留;
  2. 對羣體進行選擇,選擇出適應度值較大的一部分優勢羣體;
  3. 對優勢種羣進行 “交配”,更容易產生優秀的個體;
  4. 模擬大自然變異操作,對染色體個體進行變異操作;

下面以計算函數最大值
f(x)=10×sin(5×x)+7×x5+10;x[0,10] f(x)=10\times sin(5\times x)+7\times \left|x-5\right|+10; x\in[0, 10]

3.1、種羣初始化

  種羣的初始化相對簡單,只需要隨機生成一個二維矩陣,矩陣的size=(種羣大小,染色體編碼長度)。染色體編碼採用二進制編碼的方式。染色體編碼並不是直接採用將[0, 10]直接轉換爲二進制,原因如下:

  • 並非均勻分佈,10的二進制表示爲1010,會導致出現空餘位置,例如11、12等沒有意義的數字出現
  • 精度低,如果直接編碼最小增量單位變成了 1 ,沒有了浮點數的表示,最優解很多情況都會出現在浮點數的表示範圍。n:代表染色體編碼的長度

x=2n1 x = \dfrac{染色體編碼對應的十進制值}{2^n - 1}
  此時x的範圍爲[0, 1],我們可以根據待測得x軸的範圍進行偏移計算。例如:x得範圍爲[2, 10],則設計:

x=2n1×8+2 x = \dfrac{染色體編碼對應的十進制值}{2^n - 1} \times 8 + 2
  種羣初始化基本結構如下,實數範圍還需要進一步計算得到真正得x軸的浮點值。

基本結構
```matlab % popsize: 種羣個數 % chromlength: 染色體長度 function pop=initpop(popsize,chromlength) % round:產生的隨機數進行四捨五入操作就是0或者1 pop = round(rand(popsize,chromlength)); ```

3.2、適應度函數設計

  適應度函數得出的值越大表明個體越優秀,所以一般情況下,在求解函數最大值的時候,適應度函數就是求解函數本身,求解最小值的時候適應度函數就是函數的倒數。在本例中求取最大值,所以適應度函數就是函數本身。

function [objvalue] = cal_objvalue(pop)
x = binary2decimal(pop);
objvalue=10*sin(5*x)+7*abs(x-5)+10;

% 二進制轉10進制並轉換到對應的x軸浮點值
function pop2 = binary2decimal(pop)
[px,py]=size(pop);
for i = 1:py
    pop1(:,i) = 2.^(py-i).*pop(:,i);
end
temp = sum(pop1,2);
pop2 = temp*10/1023; % 限制到[0, 10]

3.3、選擇

  我們在前面已經說明了選擇的原因(挑選優秀個體),挑選的算法有很多,我們這裏選擇“輪盤賭法”。輪盤賭法就是類似於我們玩的轉盤,圓心角度越大的我們越容易選中。
注: 選擇後後的個體數目和原種羣個數相同。

輪盤賭法

  大自然就像那個指針,適應度值越大,表明這個體約適應這個環境,那麼被選擇的概率越大,與此同時,適應度值小的個體並不代表不會被選中,只是選中的概率小,一方面也保證了正負樣本的一個均衡。

% pop: 遺傳算法的種羣
% fitvalue: 種羣的適應度值
% newpop: 返回篩選過後的新的種羣
function [newpop] = selection(pop,fitvalue)
[px,py] = size(pop);
totalfit = sum(fitvalue);
% 將適應度值轉換爲概率,適應度值越大表明概率越大
p_fitvalue = fitvalue/totalfit;
% 沿梯度進行求和運算 [0.1, 0.3, 0.6] ->[0.1, 0.4, 1]
p_fitvalue = cumsum(p_fitvalue); 
% ms相當於指針,得出落在哪一個區域,就表明這個基因被選中
ms = sort(rand(px,1)); % 先進行排序,減小時間複雜度
fitin = 1; % 適應度矩陣的索引
newin = 1; % 新種羣索引
while newin<=px
    if(ms(newin))<p_fitvalue(fitin) % 表明落在了這一個區域
        newpop(newin,:)=pop(fitin,:);
        newin = newin+1;
    else
        fitin=fitin+1;
    end
end

3.4、交叉

  交叉相對來說,比較簡單,就是將兩個染色體進行基因片段的交叉。交叉的方式有很多,可以單個點進行交叉,隨機基因片段交叉,隨機長度的交叉方式,在這裏僅實現隨機長度的交叉方式。
隨機長度交叉:

交叉-隨機片段

隨機距離交叉(本例實現):

交叉-隨機片段

程序實現:

% pop:總種羣
% pc:交叉概率
function [newpop] = crossover(pop,pc)
[px,py] = size(pop);
newpop = ones(size(pop));
% 每兩個進行一次交叉
for i = 1:2:px-1
    if(rand<pc)
        % 隨機產生分割點
        cpoint = round(rand*py); 
        newpop(i,:) = [pop(i,1:cpoint),pop(i+1,cpoint+1:py)];
        newpop(i+1,:) = [pop(i+1,1:cpoint),pop(i,cpoint+1:py)];
    else
        newpop(i,:) = pop(i,:);
        newpop(i+1,:) = pop(i+1,:);
    end
end

3.5、變異

  變異就變得更加簡單,對於二進制編碼,只需要將隨機一個基因位置進行取反操作。

變異操作

程序實現:

% pop: 總種羣
% pm: 變異概率
function [newpop] = mutation(pop,pm)
[px,py] = size(pop);
newpop = ones(size(pop));
for i = 1:px
    if(rand<pm)
        mpoint = round(rand*py); 
        if mpoint <= 0 % matlab數組索引從 1 開始
            mpoint = 1;
        end
        newpop(i,:) = pop(i,:);
        if newpop(i,mpoint) == 0 
            newpop(i,mpoint) = 1;
        else newpop(i,mpoint) == 1
            newpop(i,mpoint) = 0;
        end
    else newpop(i,:) = pop(i,:);
    end
end

3.6、主函數

主函數主要是圍繞下面的流程圖進行計算。

基本結構

程序實現:

clear;
clc;
% 總種羣數量
popsize=100;
% 染色體長度
chromlength=10;
% 交叉概率
pc = 0.6;
% 變異概率
pm = 0.001;
%初始化種羣
pop = initpop(popsize,chromlength);  % 100 * 10

for i = 1:100
    objvalue = cal_objvalue(pop);
    fitvalue = objvalue;
    newpop = selection(pop,fitvalue);
    newpop = crossover(newpop,pc);
    newpop = mutation(newpop,pm);
    pop = newpop;
    [bestindividual,bestfit] = best(pop,fitvalue);
    x2 = binary2decimal(bestindividual);  % 最優解 x 值
    x1 = binary2decimal(newpop);
    y1 = cal_objvalue(newpop);
    if mod(i,20) == 0
        figure;
        fplot(@(x)10*sin(5*x)+7*abs(x-5)+10,[0 10]);
        hold on;
        plot(x1,y1,'*');
        title(['迭代次數:%d' num2str(i)]);
    end
end
fprintf('The best X is --->>%5.2f\n',x2);
fprintf('The best Y is --->>%5.2f\n',bestfit);

4、運行結果展示

運行結果

總結

遺傳算法同樣會陷入局部最優解的情況,例如下面的情況:

運行結果

解決局部最優解的方法有很多,小夥伴可以自行百度,我這裏提供一種方法叫做非線性尋優,利用matlab自帶函數fmincon進行非線性尋優。


最後

更多精彩內容,大家可以轉到我的主頁:曲怪曲怪的主頁
源碼地址github地址

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