遗传算法的基本概念

遗传算法(Genetic Algorithm, GA)是由Holland提出来的,是受遗传学中的自然选择和遗传机制启发发展起来的一种优化算法,它的基本思想是模拟生物和人类进化的方法求解复杂的优化问题。

基本定义

  1. 个体(individual):在遗传学中表示的是基因编码,在优化问题中指的是每一个解。
  2. 适应值(fitness):评价个体好坏的标准,在优化问题中指的是优化函数。
  3. 群体(population): 由个体组成的集合
  4. 遗传操作:遗传操作的主要目的是用于在当前的群体中产生新的群体,主要的操作包括:选择(selection)、交叉(crossover)、变异(mutation)。

遗传算法的基本流程

遗传算法的过程中主要包括这样几个要素:1、参数的编码。2、初始群体的设定。3、适应度函数的设计。4、遗传操作设计。5、控制参数的设定。 

基本遗传算法的具体过程如下: 

遗传算法对公式中的权重进行调整 遗传算法的主要参数_Group

遗传算法过程中的具体操作

参数的编码

遗传算法中的参数编码的方式主要有:1、二进制编码。2、Gray编码。3、实数编码。4、有序编码。

二进制编码

0和1对问题的解向量进行编码。例如,对于如下的优化问题: 


maxf(x1,x2)=21.5+x1sin(4πx1)+x2sin(20πx2)






−3.0≤x1≤12.1;4.1≤x2≤5.8




其三维图像如下图所示: 


遗传算法对公式中的权重进行调整 遗传算法的主要参数_遗传算法对公式中的权重进行调整_02

 


在对这样的优化问题进行二进制编码的过程中,是将问题的可能解编码为二进制位串,例如问题的可能解为实数对

(x1,x2) [ ,首先必须将 x1 和 x2 分别使用二进制位串表示,然后将他们的二进制位串组合在一起。对于每一个变量的二进制位串的长度取决于变量的定义域所要求的精度。


二进制位串的长度的计算方法如下: 
假设aj≤xj≤bj,所要求的精度是小数点后t位。这要求将区间划分为至少(bj−aj)10t份。假设表示变量xj的位串的长度用lj表示,则lj可取为满足下列不等式的最小数m: 


(bj−aj)10t≤2m−1



即有: 



2lj−1−1<(bj−aj)10t≤2lj−1




4位,则: 


(12.1−(−3.0))×10000=151000




217−1<151000≤218−1



那么表示变量

x1 的二进制位串的长度为 l1=18 。 


同理,对于变量

x2 : 



(5.8−4.1)×10000=17000




214−1<17000≤215−1



表示变量

x2 的二进制位串的长度为 l2−15 。 


此时,个体可以表示为: 


遗传算法对公式中的权重进行调整 遗传算法的主要参数_遗传算法_03



其中,前

18 位表示的是 x1 ,后 15 位表示的是 x2 。

Gray编码

这种编码方式在求解优化问题时,本人基本上没做过任何研究。

实数编码

(x1,x2)这样的编码方式。

有序编码

在本文中主要涉及二进制编码和实数编码

初始群体的设定

N=20。对于二进制编码方式与实数编码方式产生20个初始解。如: 


v1=(010001001011010000111110010100010)



对应的实数编码的方式则为: 



v1=(1.052426,5.755330)



对于二进制编码则是随机初始化

20 组这样的初始解,每组初始解随机初始化 33 位的


码。而对于实数编码方式,则是在区间上随机初始化

20 组初始解。

适应度函数的计算

适应度函数的目的是评价个体的好坏,如上面的优化问题中,即为最终的优化目标函数。对于二进制编码,则需要先将二进制编码转换成实数编码,再进行适应值函数的计算,对于实数编码方式,则直接进行适应值函数的计算。

遗传操作设计

遗传操作主要包括:选择(selection)、交叉(crossover)、变异(mutation),遗传操作的主要目的是从当前的群体中产生新的群体,这样便能使得产生新的更优的个体。

选择(selection)

选择操作的目的是选择出父体,用于参加交叉(crossover)和变异(mutation)操作。一般使用较多的方式是轮盘赌的选择策略(Roulette Wheel Selection)。根据每个个体的适应值,计算出相对适应值大小,即: 


pi=fi∑fi


相对适应值又称为选择概率,将一个圆盘划分成 N 份,即群体的大小。每个扇面的面积与其选择概率成正比。轮盘如下图所示: 


遗传算法对公式中的权重进行调整 遗传算法的主要参数_编码方式_04

 


现在在

[0,1]   上产生一个随机数 r ,若: 



p1+p2+⋯+pi−1<r≤p1+p2+⋯+pi



则选择第

i  个个体(适应值大的被选择的概率大)。 


重复此操作

N 次,选择出 N 个父体。轮盘赌的算法过程如下所示: 


遗传算法对公式中的权重进行调整 遗传算法的主要参数_遗传算法对公式中的权重进行调整_05

交叉(crossover)

交叉操作也称为杂交,其目的是产生新的个体。 

对于二进制编码方式,主要有单点杂交和多点杂交。单点杂交是指在二进制串中随机选择一位,交换两个父体中该位以后的二进制串,用以产生新的个体,操作如下图所示: 

遗传算法对公式中的权重进行调整 遗传算法的主要参数_遗传算法对公式中的权重进行调整_06


多点杂交是指在二进制串中选择某几位进行杂交,其中以两点杂交最为常见,其过程如下图所示: 

遗传算法对公式中的权重进行调整 遗传算法的主要参数_遗传算法_07


具体的操作过程为:设定一个杂交的概率pc,对选择操作中产生的N个父体,每个父体产生一个[0,1]   区间上的随机数rk,若rk<pc,则将第k个个体用于杂交,若选择出来的个体数目是奇数,则在父体集合中再随机挑选一个,以保证挑选出的是偶数个,之后进行两两杂交操作。 

对于实数编码形式,可以将实数转换成二进制编码的形式进行杂交运算,但是这样同样存在效率的问题,在实数编码中,主要采用的是算术杂交方式,算术杂交分为:部分算术杂交和整体算术杂交。部分算术杂交是指在父体向量中选择一部分分量进行算术运算,而整体算术杂交是指全部的分量都进行算术运算。我们以整体算术杂交为例:先在[0,1]  生成n个随机数a1,a2,⋯,an,经杂交算子后,所得到的两个后代为: 

x′=(a1x1+(1−a1)y1,a2x2+(1−a2)y2,⋯,anxn+(1−an)yn)

 



y′=(a1y1+(1−a1)x1,a2y2+(1−a2)x2,⋯,anyn+(1−an)xn)



变异(mutation)

变异操作的目的是使得基因突变,在优化算法中,可以防止算法陷入局部最优,从而跳出局部最优,帮助算法找到全局最优解。 
二进制编码时的变异算子非常简单,只是依一定的概率(称为变异概率)将所选个体的位取反。即若是1,则取0;若是0,则取1 。 具体的操作为:设定一个变异概率pm,对杂交操作后的N个父体,对父体中的每一个位产生一个[0,1]   区间上的随机数rk,j,若rk,j<pm,则该位变异。 
对于实数编码方式,可以采用均匀变异和非均匀变异方式,在均匀变异中,假设x=(x1,x2,⋯,xn)是要变异的个体,随机产生一个随机整数k∈[1,n]   ,产生新的后代x=(x1,x2,⋯,x′k,⋯,xn),其中x′k是[lk,uk]   中服从均匀分布的一个随机数。 
另一种是非均匀变异,,假设x=(x1,x2,⋯,xn)是要变异的个体,随机产生一个随机整数k∈[1,n]   ,产生新的后代x=(x1,x2,⋯,x′k,⋯,xn),其中: 


x′k={xk+Δ(t,uk−xk)xk−Δ(t,xk−lk) if Random(2)=0 if Random(2)=1

 


这里

t   是当前演化代数,函数 Δ(t,y)

    返回

[0,y]

    中的一个值,并且

Δ(t,y)

    

t   的增加而趋于 0 的概率增大。函数 Δ(t,y)

    具体形式为: 



Δ(t,y)=y⋅(1−r(1−t/T)b)



或者 



Δ(t,y)=y⋅r⋅(1−tT)b



其中,

r 是 [0,1]

   上的一个随机数,

T 表示最大演化代数。 b 是确定非均匀度的一个参数,通常取 2∼5 。

控制参数的设定

N,演化代数T,杂交概率pc,变异概率pm等等。


在实现遗传算法时,一个常用的方法是将到当前代为止演化的最好个体单独存放起来,在遗传算法结束后,将演化过程中发现的最好个体作为问题的最优解或近似最优解。


求解优化问题的实例

问题描述



minf(x1,x2,⋯,xn)=−20⋅exp⎛⎝−0.21n∑i=1nx2i−−−−−−−√⎞⎠−exp(1n∑i=1ncos(2π⋅xi))+20+e



其中,

−30≤xi≤30,i=1,2,⋯,n;e=2.71828



问题分析

这是一道不带约束条件的函数优化的问题,既可以采用二进制编码方式,也可以采用十进制的编码方式,在本题的解决过程中,采用十进制的编码方式。首先通过Matlab得到的函数图像大致如下,从图像中可以观察到当n=2时,我们可以在(0,0)  附近取得函数的最小值。 

遗传算法对公式中的权重进行调整 遗传算法的主要参数_Group_08

算法设计

n=2时,以下分别从个体的编码、适应值函数、选择策略、杂交算子、变异算子、参数设置、初始化以及终止条件这八个方面对程序的设计作简要的描述:

个体编码

采用实数向量编码,每一个个体是一实数对(x1,x2)  

适应值函数

F=30−f(x1,x2),这里的30是取的一个上界。这样,既保证了变换后的适应值函数式中为正,而且我们可以将极小化问题转换成一个极大值问题考虑。

选择策略

采用轮盘赌的选择策略,因为在计算适应值时已经作了处理,即适应值始终为正,这样就可以使用轮盘赌的选择策略。轮盘赌的选择策略是一种基于适应值比例的选择策略,适应值越大被选择到下一代的概率也会越大。

杂交算子

采用整体算术杂交,即给定两个父体,产生一个随机数,经杂交后得到两个后代个体,v1=(x1,x2),v 2=(y1,y2),产生一个随机数α∈[0,1]     ,经杂交后得到两个后代个体:v′1=(αx1+(1−α)y1,αx2+(1−α)y2)      ,v′2=(αy1+(1−α)x1,αy2+(1−α)x2)       

变异算子

采用非均匀变异,即对于要变异的个体x=(x1,x2),随机产生整数k∈{1,2}   ,例如k=1,然后产生后代x′=(x′1,x2),其中 


x′1={x1+Δ(t,u1−x1)x1−Δ(t,x1−l1) if Random(2)=0 if Random(2)=1



参数设置

  • 种群规模N=100
  • 个体长度Size=2
  • 演化代数T=3000
  • 杂交概率pc=0.7
  • 变异概率pm=0.1
  • 函数上界Upp=30.0

初始化

0。

终止条件

采用代数作为终止条件,当算法运行到指定的最大代数时,程序停止。

实验代码


#include<iostream>
#include<time.h>
#include<stdlib.h>
#include<cmath>
#include<fstream>

using namespace std;


const int COLONY_SIZE=100;  //个体数目
const int Size=2;//个体的长度
const int Generation=3000;//代数
const double OVER=0.7;//杂交的概率
const double MUTATE=0.1;//变异的概率
const double UPPER=30.0;//函数的上界

struct Indival
{
    double code[Size];
    double fitness;
    double cfitness;
    double rfitness;
}Group[COLONY_SIZE];

Indival newGroup[COLONY_SIZE];

Indival bestChrom;//记录最好的个体

int GenNum=0;

double random(double, double);
void initiate();
void calvalue();
void select();
void crossOver();
void xOver(int,int);
void mutate();
double delta(int,double,double,double);
void sort();

/*****************主函数***************/
int main()
{
    ofstream output;
    srand((unsigned)time(NULL));
    initiate();
    calvalue();
    output.open("data.txt");
    while(GenNum<=Generation)
    {
        GenNum++;
        select();   
        crossOver();
        mutate();   
        calvalue(); 
        sort();
        if (bestChrom.fitness<Group[0].fitness)
        {
            bestChrom.code[0]=Group[0].code[0];
            bestChrom.code[1]=Group[0].code[1];
            bestChrom.fitness=Group[0].fitness;
        }
//      output<<"gen: "<<GenNum<<"最优解为:"<<endl;
//      output<<"x1: "<<bestChrom.code[0]<<"  x2: "<<bestChrom.code[1]<<"   函数值为: "<<(30-bestChrom.fitness)<<endl;
        output<<GenNum<<"   "<<(30-bestChrom.fitness)<<endl;
    }
    output.close();
    cout<<"运行结束!"<<endl;//提示运行结束
    return 0;
}


/******************************函数的实现*****************************************/


double random(double start, double end){//随机产生区间内的随机数   
    return start+(end-start)*rand()/(RAND_MAX + 1.0);
}

void initiate()//初始化
{
    for(int i=0;i<COLONY_SIZE;i++)
    {
        Group[i].code[0]=random(-30,30);
        Group[i].code[1]=random(-30,30);
        Group[i].fitness=0;//适应值
        Group[i].cfitness=0;//相对适应值比例之和
        Group[i].rfitness=0;//相对适应值比例
    }
}

void calvalue()//计算适应值
{
    double x1,x2;
    double sum=0;
    double part1,part2;//将函数分成几个部分
    for(int i=0;i<COLONY_SIZE;i++)
    {
        x1=Group[i].code[0];
        x2=Group[i].code[1];
        part1=-0.2*sqrt((x1*x1+x2*x2)/Size);
        part2=(cos(2*3.1415926*x1)+cos(2*3.1415926*x2))/Size;
        Group[i].fitness=UPPER-(-20*exp(part1)-exp(part2)+20+2.71828);//适应值
        sum+=Group[i].fitness;//计算适应值之和
    }
    for(int mem=0;mem<COLONY_SIZE;mem++)//轮盘赌选择机制里所要求的几个参数
    {
        Group[mem].rfitness=Group[mem].fitness/sum;//适应值的比例
    }
    Group[0].cfitness=Group[0].rfitness;
    for(mem=1;mem<COLONY_SIZE;mem++)
    {
        Group[mem].cfitness=Group[mem-1].cfitness+Group[mem].rfitness;//模拟轮盘
    }


}
void select()
{
    double p;
    for(int i=0;i<COLONY_SIZE;i++)//挑选出N个个体
    {
        p=random(0,1);//随机产生0到1之间的随机数
        if(p<Group[0].cfitness)
            newGroup[i]=Group[0];
        else
        {
            for(int j=1;j<COLONY_SIZE;j++)//往轮盘后走
            {
                if(p>=Group[j-1].cfitness&&p<Group[j].cfitness)
                {
                    newGroup[i]=Group[j];
                    break;
                }
            }
        }
    }
    for(i=0;i<COLONY_SIZE;i++)//从newGroup复制到Group中
        Group[i]=newGroup[i];
}
void crossOver()
{
    int mem,one;
    int first=0;//记录杂交的数目
    double x;
    for(mem=0;mem<COLONY_SIZE;mem++)
    {
        x=random(0,1);
        if(x<OVER)
        {
            ++first;
            if(first%2==0)//若为偶数
                xOver(one,mem);
            else 
                one=mem;
        }
    }
}
void xOver(int one,int two)
{
    double point;
    point=random(0,1);
    Group[one].code[0]=Group[one].code[0]*point+Group[two].code[0]*(1-point);
    Group[one].code[1]=Group[one].code[1]*point+Group[two].code[1]*(1-point);
    Group[two].code[0]=Group[one].code[0]*(1-point)+Group[two].code[0]*point;
    Group[two].code[1]=Group[one].code[1]*(1-point)+Group[two].code[1]*point;
}
void mutate()
{
    double x;
    for(int i=0;i<COLONY_SIZE;i++)
    {
        for(int j=0;j<Size;j++)
        {
            x=random(0,1);
            if (x<MUTATE)
            {
                Group[i].code[j]=delta(GenNum,Group[i].code[j],30,-30);
            }
        }
    }
}


double delta(int t,double x,double u,double l)
{
    double temp1;
    double temp2;
    double y;
    double r=random(0,1);
    temp1=pow((1-t/Generation),4);
    temp2=pow(r,temp1);   //变异函数可另选
    int a=(int)random(0,2);
    if(a==0)
    {
        y=u-x;
        return (x+y*(1-temp2));
    }else
    {
        y=x-l;
        return (x-y*(1-temp2));
    }
}

void sort()//排序
{
    Indival temp;
    for(int i=0;i<COLONY_SIZE-1;i++)
    {
        for(int j=i+1;j<COLONY_SIZE;j++)
        {
            if(Group[i].fitness<Group[j].fitness)
            {
                temp=Group[i];
                Group[i]=Group[j];
                Group[j]=temp;
            }
        }
    }
}



最终结果


遗传算法对公式中的权重进行调整 遗传算法的主要参数_编码方式_09