演化策略应用:多维实值函数的优化C++

本文介绍了一个使用演化策略求解多维实值函数优化问题的C++实现,特别是针对Ackley函数的极小化问题。算法中,个体由目标变量和变异步长两部分组成,种群初始化时变量部分随机产生并约束在指定范围内。通过离散重组和中值重组算子进行重组,结合(μ,λ)选择策略进行存活选择。经过200000次函数值计算或找到最优解后终止算法,实验结果显示最后一代最好解的平均函数值达到7.48×10^-8。
摘要由CSDN通过智能技术生成

1 考虑下面的Ackley函数极小化问题

(详见黄竞伟-计算智能教材P101)
m i n f ( x 1 , x 2 , . . . , x 30 ) = − 20 ⋅ e x p { − 0.2 1 30 ∑ i = 0 30 x i 2 } − e x p { 1 30 ∑ i = 0 30 c o s ( 2 π ⋅ x i ) } + 20 + e , minf(x_1,x_2,...,x_{30})=-20\cdot exp \lbrace-0.2\sqrt{\frac{1}{30}\sum_{i=0}^{30} x_i^2} \rbrace -exp \lbrace\frac{1}{30}\sum_{i=0}^{30} cos(2\pi\cdot x_i) \rbrace +20+e, minf(x1,x2,...,x30)=20exp{0.2301i=030xi2 }exp{301i=030cos(2πxi)}+20+e,
− 30 ≤ x i ≤ 30 , i = 1 , 2 , . . . , 30 , e = 2.718282. -30≤x_i≤30, i=1,2,...,30, e = 2.718282. 30xi30,i=1,2,...,30,e=2.718282.
        求解该问题的演化策略设计如下:
(1)表示:个体表示的变量部分是直接的,而表示的形式是与变异算子的选择密切相关。若决定用不相关变异算子,则表示中可省略旋转角度α,并对每个分量使用不同的变异步长。这样,个体的表示为如下形式:
( x 1 , x 2 , ⋅ ⋅ ⋅ , x 30 , σ 1 , σ 2 , ⋅ ⋅ ⋅ , σ 30 ) . (x_1,x_2,\cdot\cdot\cdot,x_{30},\sigma_1,\sigma_2,\cdot\cdot\cdot,\sigma_{30}). (x1,x2,,x30,σ1,σ2,,σ30).
(2)适应函数:适应函数取为目标函数。
(3)重组算子:对变量部分使用离散重组,对策略参数部分使用全局中值重组。
(4)存活选择:使用(μ,λ)选择,其中,μ=30,λ=200.
(5)终止准则:当进行200000次函数值计算或发现最优解后终止算法。
(6)种群初始化:初始种群中每个个体的变量部分随机地产生,每个分量均匀地分布在区间【-30,30】内,每个个体的变异步长都相同,设为σ=3.
运行上述算法10次,每次找到的最好解都位于全局最优峰上,最后一代最好解的平均函数值为7.48 10-8.

2 所需类如下:个体类、种群类

类名头文件源文件
GetiGeti.hGeti.cpp
ZhongqunZhongqun.hZhongqun.cpp
\\main.cpp

个体类头文件

// Geti.h
#ifndef GETI_H
#define GETI_H
#include <iostream>
using namespace std;
#define N 30
class Geti
{
public:
	double x[N], σ[N];
	Geti();//初始化函数
	void shuchu();//打印函数
};
#endif

个体类源文件

// Geti.cpp
#include <iostream>
#include "Geti.h"
using namespace std;

Geti::Geti()
{
	for (int i = 0; i < N; i++)
	{
		x[i] = 0; σ[i] = 0;
	}
}

void Geti::shuchu()
{
	cout << "(";
	for (int i = 0; i < N; i++)
	{
		if (i == N - 1) std::cout << x[i] << "; ";
		else std::cout << x[i]<<",";
	}
	for (int i = 0; i < N; i++)
	{
		if (i == N - 1) std::cout << σ[i] << ")"<<endl;
		else std::cout << σ[i] << ",";
	}
}

种群类头文件

// Zhongqun.h
#ifndef ZHONGQUN_H
#define ZHONGQUN_H
#include "Geti.h"
const int M = 30;
class Zhongqun
{
public:
	Geti geti[M];
	void  shuchu();//打印函数
};
#endif

种群类源文件

// Zhongqun.cpp
#include "Zhongqun.h"
#include <iostream>
using namespace std;
void Zhongqun::shuchu()
{
	for (int i = 0; i < M; i++)
	{
		cout << "第" << i + 1 << "个个体为:  " ;
		geti[i].shuchu();
	}
}

3 个体表示

       由于演化策略通常是用于求解连续参数优化问题,问题的解是一个实数向量。然而,在当今的演化策略中,一般策略的参数都用自适应的方式来改变,所以实数向量x=(x1,x2,…,xn)仅仅形成个体表示的一部分,在个体表示中,通常含有策略参数。特别地,含有变异步长σ。当策略参数的个数与实数向量的维数相同时,则个体将采用以下二元表示:在这种表示中,个体由目标变量x=(x1,x2,…,xn)和标准差σ=(σ12,…,σn)两部分组成,即个体的二元表示为
( x , σ ) = ( x 1 , x 2 , ⋅ ⋅ ⋅ , x n , σ 1 , σ 2 , ⋅ ⋅ ⋅ , σ n ) (x,\sigma) = (x_1,x_2,\cdot\cdot\cdot,x_{n},\sigma_1,\sigma_2,\cdot\cdot\cdot,\sigma_{n}) (x,σ)=(x1,x2,,xn,σ1,σ2,,σn)

4 种群初始化

       由于要对种群内M个个体进行初始化,每个个体要求对变量部分xi(i=1,2,…,30)随机均匀产生且分布在区间【-30,30】内,同时每个个体的变异步长都相同,即 σi=3(i=1,2,…,30),
C++提供的均匀分布随机函数如下:
std::default_random_engine random(time(NULL));
static std::uniform_real_distribution distribution(Xmin, std::nextafter(Xmax, DBL_MAX));// C++11提供的实数均匀分布模板类
distribution(random)

5 变异

       由于个体的二元表示为(x,σ) = (x1,x2,…,xn12,…,σn),这时有n个策略参数 σi(i=1,2,…,n),变异算子由下式所实现:
{ σ i ′ = σ i ⋅ e x p ( τ ′ ⋅ N ( 0 , 1 ) + τ ⋅ N i ( 0 , 1 ) ) , x i ′ = x i + σ i ′ ⋅ N i ( 0 , 1 ) , \begin{cases} {σ_i}^{'} = σ_i\cdot exp({\tau}^{'} \cdot N(0,1)+ \tau \cdot N_i(0,1)), \\ {x_i}^{'}=x_i+{σ_i}^{'} \cdot N_i(0,1), \end{cases} {σi=σiexp(τN(0,1)+τNi(0,1)),xi=xi+σiNi(0,1),
       其中,τ∝ 1/ √2n ,τ∝ 1 / √(2√n) ,经变异后所得到的后代为(x1,x2,…,xn12,…,σn),另外还要对变异后的个体进行约束处理,-30≤xi≤30(i=1,2,…,n)

Geti Bianyi(Geti g1)//变异函数
{
	Geti g;
	int n = N;
	double τ1 = 1/sqrt(2*N),τ=1/sqrt(2*sqrt(n));
	//normal(0,1)中0为均值,1为方差
	// construct a random generator engine from a time-based seed
	unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
	std::default_random_engine gen(seed);
	std::normal_distribution<double> normal(0, 1);
	for (int j = 0; j < N; j++)//对变异部分进行中值重组
	{
		while (normal(gen) >= 0.0 && normal(gen) <= 1.0)
		{
			g.σ[j] = g1.σ[j] * exp(τ1*normal(gen) + τ*normal(gen));
			g.x[j] = g1.x[j] + g.σ[j] * normal(gen);
			//if (g.σ[j] < ε0) g.σ[j] = ε0;//对变异步长进行约束 0.01<=σ<=3.0
			//else if (g.σ[j]> θ)g.σ[j] = ε0;
		}
		if (g.x[j] > Xmax) g.x[j] = Xmax;//边界约束 -30<==Xi<=30
		else if (g.x[j] < Xmin) g.x[j] = Xmin;
	}
	return g;
}

6 重组

       与其他的演化算法不同,演化策略中的重组算子由两个或多个父体得到一个后代,所以,为了得到λ个后代,需要运用重组算子λ次。最基本的重组算子对两个父体进行重组得到一个后代。下面以这种重组算子来说明实现重组算子的方式。为简单起见,在个体的表示中,省略了策略参数。演化策略通常使用全局重组,而且对表示的不同部分使用不同的重组策略,Schwefel建议,对变量部分宜用离散重组,而对策略参数部分宜用中值重组

6.1 离散重组

       假设两个附体分别为x=(x1,x2,…,xn)和y=(y1,y2,…,yn),则由这两个父体重组得到的后代为z=(z1,z2,…,zn),其中,
z ( i ) = { x i , Random(2)=0, y i , Random(2)=1, i = 1 , 2 , . . . , n z(i)= \begin{cases} x_i, & \text {Random(2)=0,} \\ y_i, & \text{Random(2)=1,} \end{cases} i=1,2,...,n z(i)={xi,yi,Random(2)=0,Random(2)=1,i=1,2,...,n
上式中,Random(2)表示在[0,2]上随机地取一个整数所得的结果。

6.2 中值重组

       假设两个附体分别为x=(x1,x2,…,xn)和y=(y1,y2,…,yn),则由这两个父体重组得到的后代为z=(z1,z2,…,zn),其中,
z i = x i + y i 2 , i = 1 , 2 , . . . , n z_i = \frac{x_i+y_i}{2}, i=1,2,...,n zi=2xi+yi,i=1,2,...,n

7(μ,λ)存活选择

       演化策略通常使用(μ,λ)选择。演化策略中的(μ,λ)选择策略为:在从μ个父代个体产生λ个个体并计算其适应值后,(μ,λ)选择从λ(λ > μ)个后代中择优选择μ个个体作为下一代种群。

		while (caltime < Xunhuantime)
		{
			for (int i = 0; i < λ; i++)
			{
				Geti gt;
				int a = 0, b = μ - 1;
				int ρ1 = (rand() % (b - a + 1)) + a, ρ2 = (rand() % (b - a + 1)) + a;//要取得[a,b]的随机整数
				gt = Chongzu(zq.geti[ρ1], zq.geti[ρ2]);//杂交重组
				gt = Bianyi(gt);//个体变异
				houdai[i] = gt;
				eval[i] = Adopt_function(houdai[i].x);
				eval_index[i] = i;
				caltime++;//函数值计算次数加一
			}
			flag = caltime / λ;
			//接下来,对λ个个体评估值进行排序
			for (int i = 0; i < λ - 1; i++)
			{
				for (int j = 0; j < λ - i - 1; j++)
				{
					if (eval[j] > eval[j + 1])
					{
						std::swap(eval[j], eval[j + 1]);
						std::swap(eval_index[j], eval_index[j + 1]);
					}
				}
			}
			for (int i = 0; i < μ; i++)	//(u,λ)演化策略,从含有u个个体的种群,并通过重组和变异产生λ个后代,并在λ个个体中择优选择u个个体作为下一代种群
			{
				zq.geti[i] = houdai[eval_index[i]];
			}
		}

8 演化策略求解Ackley函数极小化问题的主程序

主函数源文件

// main.cpp
#include <iostream>
using namespace std;
#define _USE_MATH_DEFINES
#include <math.h>
#include <random>
#include <chrono>
#include <ctime>
#include"Geti.h"
#include "Zhongqun.h"
# define e 2.718282
const int μ = 30, λ = 200, ρ = 2, Xmax = 30, Xmin = -30;
const double ε0 = 0.01, θ = 3.0;;//认为设定的变异步长阈值
int flag = 0;
double Adopt_function(double x[N])
{
	double f = 0,value1=0,value2=0;
	for (int i = 0; i < N; i++)
	{
		value1 += x[i]*x[i];
		value2 += cos(2 * M_PI*x[i]);
	}
	f = -20 * exp(-0.2*sqrt(value1 / 30.0)) - exp(value2 / 30.0) + 20 + e;
	return f;
}

Geti Chongzu(Geti g1,Geti g2)//重组函数
{
	Geti g;
	for (int j = 0; j < N; j++)//对变量部分进行离散重组
	{
		int k = rand() % 2;
		if (k == 0)  g.x[j] = g1.x[j];
		else if(k ==1) g.x[j] = g2.x[j];
	}
	for (int j = 0; j < N; j++)//对变异部分进行中值重组
		g.σ[j] = (g1.σ[j] + g2.σ[j])/2.0;
	return g;
}

Geti Bianyi(Geti g1)//变异函数
{
	Geti g;
	int n = N;
	double τ1 = 1/sqrt(2*N),τ=1/sqrt(2*sqrt(n));
	//normal(0,1)中0为均值,1为方差
	// construct a random generator engine from a time-based seed
	unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
	std::default_random_engine gen(seed);
	std::normal_distribution<double> normal(0, 1);
	for (int j = 0; j < N; j++)//对变异部分进行中值重组
	{
		while (normal(gen) >= 0.0 && normal(gen) <= 1.0)
		{
			g.σ[j] = g1.σ[j] * exp(τ1*normal(gen) + τ*normal(gen));
			g.x[j] = g1.x[j] + g.σ[j] * normal(gen);
			//if (g.σ[j] < ε0) g.σ[j] = ε0;//对变异步长进行约束 0.01<=σ<=3.0
			//else if (g.σ[j]> θ)g.σ[j] = ε0;
		}
		if (g.x[j] > Xmax) g.x[j] = Xmax;//边界约束 -30<==Xi<=30
		else if (g.x[j] < Xmin) g.x[j] = Xmin;
	}
	return g;
}


int main()
{
	cout << "Hello World!" <<M_PI<< endl;
	int iteratortime = 1,Xunhuantime = 200000;
	Zhongqun zq;
	//第一步,进行种群的初始化
	std::default_random_engine random(time(NULL));
	static std::uniform_real_distribution<double> distribution(Xmin, std::nextafter(Xmax, DBL_MAX));// C++11提供的实数均匀分布模板类
	for (int i = 0; i < M; i++)
	{
		for (int j = 0; j < N; j++)
		{
			zq.geti[i].x[j] = distribution(random);//每个个体的变量部分随机产生
			zq.geti[i].σ[j] = θ;//每个个体的变异部分都相同,为3
		}
	}
	std::cout << "第一代种群个体如下:" << endl;
	zq.shuchu();
	int caltime = 0,eval_index[λ];
	double eval[λ];
	Geti houdai[λ];
	for (int cishu = 0; cishu < iteratortime; cishu++)
	{
		while (caltime < Xunhuantime)
		{
			for (int i = 0; i < λ; i++)
			{
				Geti gt;
				int a = 0, b = μ - 1;
				int ρ1 = (rand() % (b - a + 1)) + a, ρ2 = (rand() % (b - a + 1)) + a;//要取得[a,b]的随机整数
				gt = Chongzu(zq.geti[ρ1], zq.geti[ρ2]);//杂交重组
				gt = Bianyi(gt);//个体变异
				houdai[i] = gt;
				eval[i] = Adopt_function(houdai[i].x);
				eval_index[i] = i;
				caltime++;//函数值计算次数加一
			}
			flag = caltime / λ;
			//接下来,对λ个个体评估值进行排序
			for (int i = 0; i < λ - 1; i++)
			{
				for (int j = 0; j < λ - i - 1; j++)
				{
					if (eval[j] > eval[j + 1])
					{
						std::swap(eval[j], eval[j + 1]);
						std::swap(eval_index[j], eval_index[j + 1]);
					}
				}
			}
			for (int i = 0; i < μ; i++)	//(u,λ)演化策略,从含有u个个体的种群,并通过重组和变异产生λ个后代,并在λ个个体中择优选择u个个体作为下一代种群
			{
				zq.geti[i] = houdai[eval_index[i]];
			}
		}
	}
	std::cout << "*********************演化策略计算后的种群个体如下:" << endl;
	zq.shuchu();
	std::cout << "*********************最后一代最好解对应的个体为:" << endl;
	zq.geti[0].shuchu();
	std::cout << "*********************最后一代最好解的平均函数值为: " << Adopt_function(zq.geti[0].x)<< endl;
	system("pause");
	system("pause");
	return 0;
}

运行结果如下:
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值