加了个小目录~方便定位查看~
前言
网上有很多博客讲解遗传算法,但是大都只是“点到即止”,虽然给了一些代码实现,但也是“浅尝辄止”,没能很好地帮助大家进行扩展应用,抑或是进行深入的研究。
这是我的开篇之作~之前没有写博客的习惯,一般是将笔记存本地,但久而久之发现回看不便,而且无法与大家交流和学习。现特此写下开篇之作,若有疏漏之处,敬请指正,谢谢!
本文对遗传算法的原理进行梳理,相关代码是基于国内高校学生联合团队开源的高性能实用型进化算法工具箱geatpy来实现,部分教程引用了geatpy的官方文档:
geatpy官网:Geatpy
若有错误之处,恳请同志们指正和谅解,谢谢!
因为是基于geatpy遗传和进化算法工具箱,所以下文的代码部分在执行前,需要安装geatpy:
pip install geatpy
安装时会自动根据系统版本匹配下载安装对应的版本。这里就有个小坑:如果最新版Geatpy没有与当前版本相匹配的包的话,会自动下载旧版的包。而旧版的包在Linux和Mac下均不可用。
安装好后,在Python中输出版本检查是否是最新版(version 2.6.0):
import geatpy as ea
print(ea.__version__)
下面切入主题:
自然界生物在周而复始的繁衍中,基因的重组、变异等,使其不断具有新的性状,以适应复杂多变的环境,从而实现进化。遗传算法精简了这种复杂的遗传过程而抽象出一套数学模型,用较为简单的编码方式来表现复杂的现象,并通过简化的遗传过程来实现对复杂搜索空间的启发式搜索,最终能够在较大的概率下找到全局最优解,同时与生俱来地支持并行计算。
下图展示了常规遗传算法(左侧) 和某种在并行计算下的遗传算法(右侧) 的流程。
本文只研究经典的遗传算法,力求最后能够解决各种带约束单目标优化问题,并能够很轻松地进行扩展,让大家不仅学到算法理论,还能轻松地通过“复制粘贴”就能够将相关遗传算法代码结合到各类不同的现实问题的求解当中。
从上面的遗传算法流程图可以直观地看出,遗传算法是有一套完整的“固定套路”的,我们可以把这个“套路”写成一个“算法模板”,即把:种群初始化、计算适应度、选择、重组、变异、生成新一代、记录并输出等等这些基本不需要怎么变的“套路”写在一个函数里面,而经常要变的部分:变量范围、遗传算法参数等写在这个函数外面,对于要求解的目标函数,由于在遗传进化的过程中需要进行调用目标函数进行计算,因此可以把目标函数、约束条件写在另一个函数里面。
另外我们还可以发现,在遗传算法的“套路”里面,执行的“初始化种群”、“选择”、“重组”、“变异”等等,其实是一个一个的“算子”,在geatpy工具箱里,已经提供现行的多种多样的进化算子了,因此直接调用即可。
Geatpy工具箱提供一个面向对象的进化算法框架,因此一个完整的遗传算法程序就可以写成这个样子:
关于算法框架的详细介绍可参见:Geatpy教程 – Geatpy
下面就来详细讲一下相关的理论和代码实现:
正文
一. 基础术语:
先介绍一下遗传算法的几个基础的术语,分别是:”个体“、”种群“、”编码与解码“、”目标函数值“、”适应度值“。
1.个体:“个体”其实是一个抽象的概念,与之有关的术语有:
(1)个体染色体:即对决策变量编码后得到的行向量。
比如:有两个决策变量x1=1,x2=2,各自用3位的二进制串去表示的话,写成染色体就是:
(2)个体表现型:即对个体染色体进行解码后,得到的直接指代各个控制变量的值的行向量。
比如对上面的染色体“0 0 1 0 1 0”进行解码,得到 “1 2”,它就是个体的表现型,可看出该个体存储着两个变量,值分别是1和2。
注意:遗传算法中可以进行“实值编码”,即可以不用二进制编码,直接用变量的实际值来作为染色体。这个时候,个体的染色体数值上是等于个体的表现型的。
(3)染色体区域描述器:用于规定染色体每一位元素范围,详细描述见下文。
2. 种群:“种群”也是一个抽象的概念,与之有关的术语有:
(1)种群染色体矩阵(Chrom):它每一行对应一个个体的染色体。此时会发出疑问:一个个体可以有多条染色体吗?答:可以有,但一般没有必要,一条染色体就可以存储很多决策变量的信息了,如果要用到多条染色体,可以用两个种群来表示。
例如:
它表示有3个个体(因为有3行),因此有3条染色体。此时需要注意:这些染色体代表决策变量的什么值,我们是不知道的,我们也不知道该种群的染色体采用的是什么编码。染色体具体代表了什么,取决于我们采用什么方式去解码。假如我们采用的是二进制的解码方式,并约定上述的种群染色体矩阵中前3列代表第一个决策变量,后3列代表第二个决策变量,那么,该种群染色体就可以解码成:
(2)种群表现型矩阵(Phen):它每一行对应一个个体的表现型。比如上图就是根据Chrom种群染色体矩阵解码得到的种群表现型矩阵。同样地,当种群染色体采用的是“实值编码”时,种群染色体矩阵与表现型矩阵实际上是一样的。
(3)种群个体违反约束程度矩阵(CV):它每一行对应一个个体,每一列对应一种约束条件(可以是等式约束或不等式约束)。CV矩阵中元素小于或等于0表示对应个体满足对应的约束条件,大于0则表示不满足,且越大表示违反约束条件的程度越高。比如有两个约束条件:
如何计算CV矩阵?可以创建两个列向量CV1和CV2,然后把它们左右拼合而成一个CV矩阵。
假设x1、x2、x3均为存储着种群所有个体的决策变量值的列向量(这里可以利用种群表现型矩阵Phen得到,比如x1=Phen[:, [0]];x2=Phen[:, [1]]);x3=Phen[:, [2]]),这样就可以得到种群所有个体对应的x1、x2和x3)。
那么:
比如在某一代中,种群表现型矩阵Phen为:
则有:
此时CV矩阵的值为:
由此可见,第一个个体满足两个约束条件;第二个个体违反了2个约束条件;第三和第四个个体满足第一个约束条件但违反了第二个约束条件。
下面看下如何用代码来生成一个种群染色体矩阵:
代码1. 实整数值种群染色体矩阵的创建:
import numpy as np
from geatpy import crtpc
help(crtpc) # 查看帮助
# 定义种群规模(个体数目)
Nind = 4
Encoding = 'RI' # 表示采用“实整数编码”,即变量可以是连续的也可以是离散的
# 创建“区域描述器”,表明有4个决策变量,范围分别是[-3.1, 4.2], [-2, 2],[0, 1],[3, 3],
# FieldDR第三行[0,0,1,1]表示前两个决策变量是连续型的,后两个变量是离散型的
FieldDR=np.array([[-3.1, -2, 0, 3],
[ 4.2, 2, 1, 5],
[ 0, 0, 1, 1]])
# 调用crtri函数创建实数值种群
Chrom=crtpc(Encoding, Nind, FieldDR)
print(Chrom)
代码1的运行结果:
这里要插入讲一下“区域描述器”(见代码1中的FieldDR),它是用于描述种群染色体所表示的决策变量的一些信息,如变量范围、连续/离散性。另外还有一种区域描述器(FIeldD),用于描述二进制/格雷码的种群染色体。FieldDR和FieldD两个合称“Field”,又可以认为它们是“译码矩阵”。FieldDR具有以下的数据结构:
代码1中的FieldDR矩阵的第三行即为这里的varTypes。它如果为0,表示对应的决策变量是连续型的变量