基本概念
- 染色體:待解決的數學問題的一個可行解成為染色體。
- 基因:一個可行解一般由多個元素構成,那么這每一個元素就被稱為染色體上的一個基因。
- 適應度函數:執行優勝劣汰的函數。將適應度高的染色體留下,將適應度低的染色體淘汰掉。從而經過若干次迭代后染色體的質量將越來越優良。
- 交叉:兩個染色體生成一個新的染色體,新染色體上的基因由輪盤賭算法完成。在每完成一次進化后,都要計算每一條染色體的適應度,然后采用公式計算每一條染色體的適應度概率。那么在進行交叉過程時,就需要根據這個概率來選擇父母染色體。適應度比較大的染色體被選中的概率就越高。這也就是為什么遺傳算法能保留優良基因的原因。(染色體i被選擇的概率 = 染色體i的適應度 / 所有染色體的適應度之和)
- 變異:交叉能保證每次進化留下優良的基因,但它僅僅是對原有的結果集進行選擇,基因還是那么幾個,只不過交換了他們的組合順序。這只能保證經過N次進化后,計算結果更接近於局部最優解,而永遠沒辦法達到全局最優解,為了解決這一個問題,我們需要引入變異。變異很好理解。當我們通過交叉生成了一條新的染色體后,需要在新染色體上隨機選擇若干個基因,然后隨機修改基因的值,從而給現有的染色體引入了新的基因,突破了當前搜索的限制,更有利於算法尋找到全局最優解。
- 選擇:淘汰fw個體,選出優秀個體傳給下一代。
- 表現型:輸入的值。
- 基因型:將輸入值編碼后的值。故表現型通過編碼變成基因型,基因型通過解碼變成表現型。
- 編碼方式:分為二進制編碼,實數編碼和符號編碼。
算法
流程
- 在算法初始階段,它會隨機生成一組可行解,也就是第一代染色體。
- 然后采用適應度函數分別計算每一條染色體的適應程度,並根據適應程度計算每一條染色體在下一次進化中被選中的概率。
- 通過“交叉”,生成N-M條染色體;
- 再對交叉后生成的N-M條染色體進行“變異”操作;
- 然后使用“復制”的方式生成M條染色體;
流程圖如下:

遺傳算法工具箱
從界面運行 APP - Optimization app,然后從 Solver 菜單選擇ga - Genetic Algorithm。
遺傳算法工具箱是求最小值的,最大值的話就添負號。
代碼
求 $f(x)=x+10sin(5x)+7cos(4x),x∈[0,10] $ 的最大值。
步驟
- 初始化種群
- 計算目標函數值和適應度值:目標函數的結果可以是負數,但是適應度值不能為負數。因為后面會用輪盤賭法算概率,如果適應度值為負數的話,概率也為負數。母豬會上樹概率都不可能為負數。所以,要進行處理,當目標函數為負數時候適應度值要為0。
- 選擇,交叉,變異
- 求最優個體,並記錄
- 繼續下一次迭代,直到迭代結束
- 根據每一次迭代的最優個體,再找出其中的最優個體,即為最優解
主程序
clear
clc
popsize=20; %群體大小
chromlength=10; %字符串長度(個體長度)
pc=0.6; %交叉概率,只有在隨機數小於pc時,才會產生交叉
pm=0.001; %變異概率
times = 2000 % 遺傳次數
pop=initpop(popsize,chromlength); %隨機產生初始群體
for i=1:times
[objvalue]=calobjvalue(pop); %計算目標函數
fitvalue=calfitvalue(objvalue); %計算群體中每個個體的適應度
[newpop]=selection(pop,fitvalue); % 選擇
[newpop1]=crossover(newpop,pc); % 交叉
[newpop2]=mutation(newpop1,pm); % 變異
[objvalue]=calobjvalue(newpop2); %計算目標函數
fitvalue=calfitvalue(objvalue); %計算群體中每個個體的適應度
[bestindividual,bestfit]=best(newpop2,fitvalue); %求出群體中適應值最大的個體及其適應值
y(i)=bestfit; %返回的 y 是自適應度值,而非函數值
x(i)=decodebinary(bestindividual)*10/1023; %將自變量解碼成十進制
pop=newpop2;
end
fplot('x+10*sin(5*x)+7*cos(4*x)',[0 10])
hold on
plot(x,y,'r*')
hold on
[z ,index]=max(y); % 計算所有最優良個體中的最優良個體
x5=x(index) % 計算最大值對應的x值
ymax=z
初始化 initpop.m
根據種群大小和個體長度構造個全為0和1的隨機二進制編碼基因的種群,同時這個種群每一橫行表示一個個體。此例的矩陣為20*10。
function pop=initpop(popsize,chromlength)
pop=round(rand(popsize,chromlength));
計算目標函數值 calobjvalue.m
將基因型轉換為表現型。
function [objvalue]=calobjvalue(pop)
temp1=decodebinary(pop); % 將pop每行轉化成十進制數
x=temp1*10/1023; % 得到十進制數在0~1023之間,通過變換得到x屬於0~10的值
objvalue=x+10*sin(5*x)+7*cos(4*x); % 通過x的值計算目標函數值
如果是和神經網絡連用的話,神經網絡輸入值歸一化的值就在0到1之間,所以只用將轉換的十進制數除以1023然后就可以代入網絡中。
除了把二進制數看作是二進制整數外,我還有個思路就是把二進制數看成小數,這樣的話轉換成十進制數的時候本來就在0~1之間,這樣就不用再處理了。
二進制轉換為十進制 decodebinary.m
sum
函數第二個參數不寫默認是1,表示對每一列求和得到行向量。如果第二個參數輸入2,則表示對每一行求和得到列向量。
function pop2=decodebinary(pop)
[px,py]=size(pop); %求pop行和列數
for i=1:py
pop1(:,i)=2.^(py-i).*pop(:,i);
end
pop2=sum(pop1,2); %求pop1的每行之和
適應度函數 calfitvalue.m
如果目標函數值大於等於0,適應度是目標函數值。如果目標函數值小於0,適應度就是0。
function fitvalue=calfitvalue(objvalue)
[px,py]=size(objvalue); %目標值有正有負
for i=1:px
if objvalue(i)>0
temp=objvalue(i);
else
temp=0.0;
end
fitvalue(i)=temp;
end
fitvalue=fitvalue';
選擇 selection.m
這坨代碼的思路是除掉fw個體,重復添加優秀個體,越優秀的個體添加的次數可能性越大。
function [newpop]=selection(pop,fitvalue)
totalfit=sum(fitvalue); %求適應值之和
fitvalue=fitvalue/totalfit; %單個個體被選擇的概率,輪盤賭算法的公式
fitvalue=cumsum(fitvalue); % 累加是為了將分布律轉換為分布函數然后可以比較
[px,py]=size(pop); %20*10
ms=sort(rand(px,1)); % 將隨機數從小到大排列
fitin=1;
newin=1;
while newin<=px % 直到20個隨機數被走完才跳出循環
if(ms(newin))<fitvalue(fitin) % 概率小於分布函數,優良個體被選擇
newpop(newin,:)=pop(fitin,:);
newin=newin+1;
else % 概率大於分布函數,個體被淘汰,繼續選擇下一個個體
fitin=fitin+1;
end
end
如果這群個體中某個概率較大,則累加的數在累加到它的時候增量較大,它的增量較大幾率大於隨機數的增量,這個增量較大的個體會有更大的幾率多次被選中。
除了上面的代碼外,
交叉 crossover.m
交叉我還以為是基因的交叉,沒想到是鹼基對的交叉。將原來兩個個體交叉出現兩個新個體,然后用這兩個新個體來替換掉原來的兩個個體放入新的種群中。
function [newpop]=crossover(pop,pc) % pc=0.6 pc是交叉概率
[px,py]=size(pop); % px是種群數量,py是編碼長度
newpop=ones(size(pop)); % 生成一群全為1的種群
for i=1:2:px-1 % 步長為2,是將相鄰的兩個個體進行交叉
if(rand<pc) % 如果隨機數小於交叉概率,則發生了交叉
cpoint=round(rand*py); % cpoint: 0-10的隨機數
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保持原有pop的個體
newpop(i,:)=pop(i,:);
newpop(i+1,:)=pop(i+1,:);
end
end
變異 mutation.m
隨機選取一個位置,將該位置的0變成1或1變成0。
function [newpop]=mutation(pop,pm) % pop是種群,pm是變異概率
[px,py]=size(pop);
newpop=ones(size(pop));
for i=1:px
if(rand<pm) % 隨機數小於變異概率,發生了變異
mpoint=round(rand*py); % 產生的變異點在1-10之間
if mpoint<=0 % 可以寫成 mpoint == 0 ,因為這個值不可能小於0,但是可以等於0
mpoint=1; % mpoint表示變異位置
end
newpop(i,:)=pop(i,:);
if any(newpop(i,mpoint))==0% 這個any意義不大,因為傳入的是一個值,不是矩陣
newpop(i,mpoint)=1; % 把0變異成1
else
newpop(i,mpoint)=0; % 或者把1變異成0
end
else % 不發生變異,原來的個體還是原來的個體
newpop(i,:)=pop(i,:);
end
end
求最大適應度個體 best.m
目的:將最大適應度個體保留在一個矩陣中,然后在最大適應度個體矩陣中找出其中最大適應度個體,從而獲得它的x值和y值,於是它就是最大值。
function [bestindividual,bestfit]=best(pop,fitvalue)
[px,py]=size(pop);
bestindividual=pop(1,:);
bestfit=fitvalue(1);
for i=2:px
if fitvalue(i)>bestfit
bestindividual=pop(i,:);
bestfit=fitvalue(i);
end
end
自己的一些感悟
-
關於交叉:0-1編碼的交叉是截取染色體部分進行交叉,實數編碼的交叉是所有染色體進行隨機權值相乘交叉。
-
關於選擇:如果適應度是越小越好可以將適應度取倒數(或者用一個常數除以這個數,比如10)然后再用輪盤賭算法。
-
關於變異:0-1編碼的變異是將1變成0,將0變成1。實數編碼就是將該數加上或減去該數與上界或下界的差或和(這個隨機判定)再乘以一個比較小的數,記得實數編碼要檢驗是否在規定邊界范圍內。0-1編碼不用檢測。關於變異有一套代碼:
fg = (rand*(1-num/maxgen))^2; % num:當前迭代次數。maxgen:最大迭代次數
if pick > 0.5
% chrom:種群。i:第i個個體索引。pos:變異的位置
chrom(i, pos) = chrom(i, pos)+(chrom(i, pos)-bound(pos, 2))*fg;
else
chrom(i, pos) = chrom(i, pos)+(bound(pos, 1)-chrom(i, pos))*fg;
end
參考文章
-
十分鍾搞懂遺傳算法:https://zhuanlan.zhihu.com/p/33042667
-
遺傳算法介紹並附上matlab代碼:https://www.cnblogs.com/LoganChen/p/7509702.html
-
matlab遺傳工具包:https://zhuanlan.zhihu.com/p/158600868
-
一個非常好的理解遺傳算法的例子 強烈推薦入門:https://blog.csdn.net/u012422446/article/details/68061932
-
【算法】超詳細的遺傳算法(Genetic Algorithm)解析:https://www.jianshu.com/p/ae5157c26af9
-
遺傳算法(Genetic Algorithm)原理詳解和matlab代碼解析實現及對應gaot工具箱實現代碼:https://blog.csdn.net/qq_35608277/article/details/83785678