数学建模方法-遗传算法(实战篇part 1)

一、引言

  在上一篇中我们详细介绍了什么是遗传算法,但是光说不练是不行的,因此,在这一篇中,我们将举一个例子,并且利用遗传算法来解决我们的例子。

二、问题

  已知:$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次的时候,整个种群的平均函数最大值就已经接近真正最大值了。

 

posted @ 2018-08-22 14:56  Qling的随笔  阅读(7784)  评论(0编辑  收藏  举报