一、引言
在上一篇中我們詳細介紹了什么是遺傳算法,但是光說不練是不行的,因此,在這一篇中,我們將舉一個例子,並且利用遺傳算法來解決我們的例子。
二、問題
已知:$f(x) = x + 10sin5x + 7cos4x, x \in [0, 9]$
求:函數$f(x)$的最大值
三、一般求解
在MATLAB中輸入如下代碼:
x = 0: 0.0001: 9; y = x + 10*sin(5*x) + 7*cos(4*x); [maxY, index] = max(y) maxX = x(index)
則會輸出以下結果:
maxY = 24.8554 index = 78568 maxX = 7.8567
對此,我們得到$f(x)$在其定義域內的最大坐標值為(7.8567, 24.8554)。
好,接下來,我們利用遺傳算法來設計代碼,對我們這個問題進行求解,看看會是怎樣。
三、遺傳算法求解
我們來回顧下上個篇章所講的,遺傳算法的步驟,如下:
1. 種群初始化
2. 計算每個種群的適應度值
3. 選擇(Selection)
4. 交叉(Crossover)
5. 變異(Mutation)
6. 重復2-5步直至達到進化次數
我們從第一步開始編寫我們的代碼,為了讓我們的遺傳算法的代碼具有更好的包容性,即針對不同的題目,我們不想每一次都要大幅度的重寫我們的代碼,因此,我們希望能夠把一些步驟的功能編寫成函數,這樣針對不同的題目,我們只需要調用我們事先編寫好的函數,輸入不同的參數和數據即可。好了,廢話不多說,我們開始吧。
(1)種群初始化函數popInit(),能根據提供的種群大小和染色體長度產生初始的種群。代碼如下:
% [name] -- popInit(種群初始化函數) % [function]-- 構建種群矩陣,其中行數為種群個數,列數為染色體長度(即基因的個數),並隨機分配染色體的樣式 % [input] -- 1. popSize(種群大小/個數) % -- 2. cLength(染色體長度) % [output] -- popMat(種群矩陣) function popMat = popInit(popSize, cLength) popMat = zeros(popSize, cLength); % 預分配內存 for i = 1: popSize for j = 1: cLength popMat(i, j) = round(rand); % rand產生(0,1)之間的隨機數,round()是四舍五入函數 end end clear i; clear j;
(2)計算每個種群的適應度值函數getfitnessValue(),這個函數,針對不同的題目有不同的適應度值,因此,對於不同的題目,這個函數需要更改。在基於本章的題目中,該函數可以實現對種群的適應度值的計算。代碼如下:
% [name] -- getfitnessValue(計算種群個體適應度值) % [function]-- 根據不同的題目要求,設計適應度方程計算的算法,本例中,適應度函數為f(x) = x + 10*sin(5*x) + 7*cos(4*x),x ∈[0, 9],其解碼規則為: % x = lower_bound + decimal(chromosome) * (upper_bound - lower_bound) / (2 ^ chromosome_length - 1) % [input] -- popMat (種群矩陣) % [output] -- fitValMat(每個個體的適應度值) function fitnessValueMatrix = getfitnessValue(popMat) [popSize, cLength] = size(popMat); % 獲取種群的數目(行)和染色體長度(列) fitnessValueMatrix = zeros(popSize, 1); % 初始化適應度矩陣 X = zeros(popSize, 1); % 預分配內存 lower_bound = 0; % 自變量區間下限 upper_bound = 9; % 自變量區間上限 % 1. 首先先將種群中個體的染色體所對應的十進制求出來 for i = 1: popSize for j = 1: cLength if popMat(i, j) == 1 X(i) = X(i) + 2 ^ (j - 1); end end % 2. 根據十進制值進行解碼 X(i) = lower_bound + X(i) * (upper_bound - lower_bound) / (2 ^ cLength - 1); % 3. 獲取適應度值 fitnessValueMatrix(i) = X(i) + 10 * sin(5 * X(i)) + 7 * cos(4 * X(i)); end clear i; clear j;
(3)選擇函數selection(),可以對種群進行選擇,具體算法和代碼如下:
% [name] -- selection(選擇操作) % [function]-- 采用輪盤賭的一個形式來進行選擇,增加不確定性,這樣種群就不容易趨向局部最優 % [input] -- 1. fitnessValueMatrix (適應度值矩陣) % -- 2. popMat(未選擇的種群矩陣) % -- 3. type -- 1: 保留親代最優個體 % 0: 不保留親代最優個體 % [output] -- updatedPopMat(經選擇后的種群矩陣) function updatedPopMat = selection(fitnessValueMatrix, popMat, type) [popSize, cLength] = size(popMat); updatedPopMat = zeros(popSize, cLength); % 剔除適應值為負的種群,使其適應值為0 for i = 1: popSize if fitnessValueMatrix(i, 1) < 0 fitnessValueMatrix(i, 1) = 0; end end % 輪盤賭算法 P = fitnessValueMatrix / sum(fitnessValueMatrix); Pc = cumsum(P); for i = 1: popSize index = find(Pc >= rand); updatedPopMat(i, :) = popMat(index(1), :); end % 是否要保留親本適應度值最高的,若是,則type = 1,否則為0 if type [~, bestIndex] = max(fitnessValueMatrix); updatedPopMat(popSize, :) = popMat(bestIndex, :); % 將親代最優染色體放到子代的最后一個個體中 end clear i; clear j;
(4)交叉函數crossover(),可以對種群進行交叉,交叉的方式又分為單點交叉和多點交叉,根據輸入不同的參數來選擇不同的實現方式,具體算法和代碼如下:
% [name] -- crossover(交叉操作) % [function]-- 選定交叉點並進行互換 % [input] -- 1. popMat (未交叉的種群矩陣) % -- 2. type -- 1: 單點交叉 % -- 2: 多點交叉 % -- 3. crossrate (交叉率) -- 建議值為0.6 % [output] -- updatedPopMat(經交叉后的種群矩陣) function updatedPopMat = crossover(popMat, type, crossrate) [popSize, cLength] = size(popMat); if type == 1 % 單點交叉 for i = 1: 2: popSize if crossrate >= rand crossPosition = round(rand() * (cLength - 2) + 2); % 隨機獲取交叉點,去除0和1 % 對 crossPosition及之后的二進制串進行交換 for j = crossPosition: cLength temp = popMat(i, j); popMat(i, j) = popMat(i + 1, j); popMat(i + 1, j) = temp; end end end updatedPopMat = popMat; elseif type == 2 % 多點交叉 for i = 1: 2: popSize if crossrate >= rand crossPosition1 = round(rand() * (cLength - 2) + 2); % 第一個交叉點 crossPosition2 = round(rand() * (cLength - 2) + 2); % 第二個交叉點 first = min(crossPosition1, crossPosition2); last = max(crossPosition1, crossPosition2); for j = first: last temp = popMat(i, j); popMat(i, j) = popMat(i + 1, j); popMat(i + 1, j) = temp; end end end updatedPopMat = popMat; else h = errordlg('type的類型只能為1(單點交叉)或者2(多點交叉)', '進行交叉時發生錯誤'); end clear i; clear j;
(5)變異函數mutation(),可以對種群進行變異,具體算法和代碼如下:
% [name] -- mutation(變異操作) % [function]-- 單點變異:隨機選擇變異點,將其變為0或1 % [input] -- 1. popMat (未交叉的種群矩陣) % -- 2. mutateRate (交叉率) -- 建議值為0.1 % [output] -- updatedPopMat(經交叉后的種群矩陣) function updatedPopMat = mutation(popMat, mutateRate) [popSize, cLength] = size(popMat); for i = 1: popSize if mutateRate >= rand mutatePosition = round(rand() * (cLength - 1) + 1); % 隨機獲取交叉點,去除0 % 對mutatePosition點進行變異 popMat(i, mutatePosition) = 1 - popMat(i, mutatePosition); end end updatedPopMat = popMat; clear i; clear j;
(6)另外,針對本章的題目,我們需要將二進制的染色體與題目十進制的自變量x值關聯起來,因此,我們需要編寫一個函數getDecimalValue()來實現這樣的工作。代碼如下:
% [name] -- getDecimalValue(根據染色體個體(二進制)算出所對應的x值(十進制)) % [function]-- 根據不同的題目要求,設計適應度方程計算的算法,本例中,適應度函數為f(x) = x + 10*sin(5*x) + 7*cos(4*x),x ∈[0, 9],其解碼規則為: % x = lower_bound + decimal(chromosome) * (upper_bound - lower_bound) / (2 ^ chromosome_length - 1) % [input] -- chromosome (染色體) % [output] -- decimalValue(每個個體的物理意義值) function decimalValue = getdecimalValue(chromosome) decimalValue = 0.0; % 初始化 cLength = size(chromosome, 2); % 獲取種群的數目(行)和染色體長度(列) lower_bound = 0; % 自變量區間下限 upper_bound = 9; % 自變量區間上限 % 1. 首先先將種群中個體的染色體所對應的十進制求出來 for i = 1: cLength if chromosome(1, i) == 1 decimalValue = decimalValue + 2 ^ (i - 1); end end % 2. 解碼 decimalValue = lower_bound + decimalValue * (upper_bound - lower_bound) / (2 ^ cLength - 1); clear i;
(7)另外,我們還編寫了一個畫圖的函數,用於直觀的顯示在進化的過程中,種群平均適應度值的變化。代碼如下:
% [name] -- plotGraph(畫圖) % [function]-- 直觀的展示在進化過程中,平均適應度值的趨勢變化 % [input] -- 1. generationTime(進化次數) % 2. fitnessAverageValueMatrix(平均適應度值矩陣) % [output] -- none function plotGraph(fitnessAverageValueMatrix, generationTime) x = 1: 1: generationTime; y = fitnessAverageValueMatrix; plot(x, y); xlabel('迭代次數'); ylabel('平均函數最大值'); title('種群平均函數最大值的進化曲線');
好了,至此,我們就完成了解決本題目需要的函數塊。接下來,我們只需要編寫主函數main.m,針對本章的題目,對其進行求解即可。代碼如下:
%% I. 清空變量 clc clear all %% II. 參數初始化 popSize = 100; % 種群大小 cLength = 17; % 染色體的長度 generationTime = 200; % 進化次數 crossRate = 0.6; % 交叉概率 mutateRate = 0.01; % 變異概率 fitnessAverageValueMatrix = zeros(generationTime, 1); % 每代平均適應度值 fitnessBestValueMatrix = zeros(generationTime, 1); % 每代最優適應度值 bestIndividual = zeros(generationTime, 1); % 每代最佳自變量(十進制) %% III. 初始化種群 popMat = popInit(popSize, cLength); %% IV. 迭代繁衍獲取更好的個體(選擇、交叉、變異) for currentTime = 1: generationTime % 求適應度值,返回適應度值矩陣 fitnessValueMatrix = getfitnessValue(popMat); % 記錄當前最好的數據 if currentTime == 1 [bestValue, bestIndex] = max(fitnessValueMatrix); fitnessBestValueMatrix(currentTime) = bestValue; fitnessAverageValueMatrix(currentTime) = mean(fitnessValueMatrix); else [bestValue, bestIndex] = max(fitnessValueMatrix); fitnessBestValueMatrix(currentTime) = max(fitnessBestValueMatrix(currentTime - 1), bestValue); fitnessAverageValueMatrix(currentTime) = mean(fitnessValueMatrix); end bestChromosome = popMat(bestIndex, :); % 最佳適應度值所對應的個體(二進制) bestIndividual(currentTime) = getdecimalValue(bestChromosome); % 解碼,將二進制轉為十進制,得到最佳適應度值所對應的x值 if currentTime ~= generationTime % 選擇 popMat = selection(fitnessValueMatrix, popMat, 1); % 保留親代最佳個體 % 交叉 popMat = crossover(popMat, 1, crossRate); % 單點交叉 % 變異 popMat = mutation(popMat, mutateRate); end end %% V. 畫圖並展示結果 % 畫圖 plotGraph(fitnessAverageValueMatrix, generationTime); % 展示數據 [fitnessBestValue, index] = max(fitnessBestValueMatrix); disp 最優適應度 fitnessBestValue disp 最優個體對應的自變量值 bestIndividual(index)
運行上述程序,可以得到:
最優適應度 fitnessBestValue = 24.8554 最優個體對應的自變量值 ans = 7.8567
將上述結果跟我們第二步用一般求解的對比發現,答案一致。因此,我們的遺傳算法是可行的。最后po一張圖,可以明顯看到,差不多迭代到30次的時候,整個種群的平均函數最大值就已經接近真正最大值了。