演化策略求解多维实值函数优化问题C++
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)=−20⋅exp{−0.2301i=0∑30xi2}−exp{301i=0∑30cos(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.
−30≤xi≤30,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 所需类如下:个体类、种群类
类名 | 头文件 | 源文件 |
---|---|---|
Geti | Geti.h | Geti.cpp |
Zhongqun | Zhongqun.h | Zhongqun.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)和标准差σ=(σ1,σ2,…,σ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,…,xn,σ1,σ2,…,σ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′=σi⋅exp(τ′⋅N(0,1)+τ⋅Ni(0,1)),xi′=xi+σi′⋅Ni(0,1),
其中,τ’∝ 1/ √2n ,τ∝ 1 / √(2√n) ,经变异后所得到的后代为(x1’,x2’,…,xn’,σ1’,σ2’,…,σ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;
}
运行结果如下: