數學建模方法-遺傳算法(實戰篇part 1)

1、引言

  在上一篇中咱們詳細介紹了什麼是遺傳算法,可是光說不練是不行的,所以,在這一篇中,咱們將舉一個例子,而且利用遺傳算法來解決咱們的例子。算法

2、問題

  已知:$f(x) = x + 10sin5x + 7cos4x, x \in [0, 9]$函數

  求:函數$f(x)$的最大值spa

3、通常求解

  在MATLAB中輸入以下代碼:ssr

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)。code

  好,接下來,咱們利用遺傳算法來設計代碼,對咱們這個問題進行求解,看看會是怎樣。blog

3、遺傳算法求解

  咱們來回顧下上個篇章所講的,遺傳算法的步驟,以下:
內存

  1.  種羣初始化
  2. 計算每一個種羣的適應度值
  3. 選擇(Selection)
  4. 交叉(Crossover)
  5. 變異(Mutation)
  6. 重複2-5步直至達到進化次數
ci

  咱們從第一步開始編寫咱們的代碼,爲了讓咱們的遺傳算法的代碼具備更好的包容性,即針對不一樣的題目,咱們不想每一次都要大幅度的重寫咱們的代碼,所以,咱們但願可以把一些步驟的功能編寫成函數,這樣針對不一樣的題目,咱們只須要調用咱們事先編寫好的函數,輸入不一樣的參數和數據便可。好了,廢話很少說,咱們開始吧。get

  (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次的時候,整個種羣的平均函數最大值就已經接近真正最大值了。

相關文章
相關標籤/搜索