重要参考:
1,安装(Anaconda+geatpy)
Anaconda安装遗传和进化算法库函数安装——geatpy
输入:pip install geatpy
Python中输出版本检查是否是最新版:
import geatpy as ea
print(ea.__version__)
2,Geatpy官网
3,遗传算法
遗传算法中每一条染色体,对应着遗传算法的一个解决方案,一般我们用适应性函数(fitness function)来衡量这个解决方案的优劣
3.1基础数据结构
1,个体
种群中的单个元素:由一个用于描述起基本遗传结构的数据结构表示
(1)个体染色体:即对决策变量编码后得到的行向量。
比如:二进制编码下:有两个决策变量x1=1,x2=2,各自用3位的二进制串去表示的话,写成染色体就是:001010,个体[x1,x2]
染色体编码:
二进制编码:
格雷编码:
注:遗传算法中可以进行“实值编码”,即可以不用二进制编码,直接用变量的实际值来作为染色体。这个时候,个体的染色体数值上是等于个体的表现型的。
(2)个体表现型:即对个体染色体进行解码后,得到的直接指代各个控制变量的值的行向量。
比如:”染色体“0 0 1 0 1 0”进行解码得到x1=2,x2=2,; “1 2” 就是个体的表现型,可看出该个体存储着两个变量,值分别是1和2。
(3)染色体区域描述器:
来描述种群染色体的特征
用于规定染色体每一位元素范围(译码矩阵)
译码矩阵可以与一个存储着种群染色体编码方式的字符串Encoding来配合使用
目前Geatpy中有三种Encoding,分别为:
- ’BG’ (二进制/格雷码)
- ’RI’ (实整数编码,即实数和整数的混合编码)
- •’P’ (排列编码,即染色体每一位的元素都是互异的)
注意:’RI’和’P’编码的染色体都不需要解码,染色体上的每一位本身就代表着决策变量的真实值,因此“实整数编码”和“排列编码”可统称为“实值编码”。
FieldDR:用于描述种群染色体所表示的决策变量的一些信息,如变量范围、连续/离散性。
对于实值编码(即前面所说的不需要解码的编码方式)的种群,使用3行n列的矩阵FieldDR
来作为译码矩阵,n是染色体所表达的控制变量个数。
FieldDR的结构如下:
varTypes:0表示对应的决策变量是连续型的变量;1表示对应的是离散型变量
FIeldD:描述二进制/格雷码的种群染色体
译码矩阵FieldD具有以下的数据结构:
对于Encoding=′BG′的种群,使用8行n列的矩阵FieldD来作为译码矩阵,n是染色体所表达的决策变量个数
lens, lb, ub, codes, scales, lbin, ubin, varTypes均为长度等于决策变量个数的行向量
lens: 包含染色体的每个子染色体的长度。sum(lens) 等于染色体长度
lb: 指明每个变量使用的下界
ub:指明每个变量使用的下界。
codes[i] = 0 表示第i 个变量使用的是标准二进制编码;codes[i] = 1 表示使用格雷编码
scales从2.5.0版本开始,取消了对对数刻度的支持,该参数暂时保留,但不在起作用
lbin 和ubin 指明了变量是否包含其范围的边界。0 表示不包含边界;1 表示包含边界。
varTypes 指明了决策变量的类型,元素为0 表示对应位置的决策变量是连续型变量;1 表示对应的是离散型变量。
注:对于二进制编码,二进制种群的染色体具体代表决策变量的什么含义是不由染色体本身决定的,而是由解码方式决定的。因此在创建二进制种群染色体之初就要设定好译码矩阵(又称“区域描述器”)。
mport numpy as np from geatpy import crtpc # help(crtpc) # 查看帮助 # 定义种群规模(个体数目) Nind = 4 Encoding = 'BG' # 表示采用“二进制/格雷”编码 # 创建“译码矩阵” FieldD_2 = np.array([[3, 2,3], # 各决策变量编码后所占二进制位数,3个决策变量,长度3,2,3 [0, 0,0], # 各决策变量的范围下界 [7, 3,7], # 各决策变量的范围上界 [0, 0,0], # 各决策变量采用什么编码方式(0为二进制编码,1为格雷编码) [0, 0,0], # 各决策变量是否采用对数刻度(0为采用算术刻度) [1, 1,1], # 各决策变量的范围是否包含下界(对bs2int实际无效,详见help(bs2int)) [1, 1,1], # 各决策变量的范围是否包含上界(对bs2int实际无效) [0, 0,0]])# 表示两个决策变量都是连续型变量(0为连续1为离散) # 调用crtpc函数来根据编码方式和译码矩阵来创建种群染色体矩阵 Chrom_2=crtpc(Encoding, Nind, FieldD_2) print("二进制染色体矩阵:\n", Chrom_2)
结果
FieldDR和FieldD两个合称“Field”译码矩阵
2.种群
(1)种群染色体矩阵(Chrom):矩阵每一行代表一个个体的染色体
一个个体可以有多条染色体,但一般没有必要。
一条染色体可以存储很多决策变量,如果要用到多条染色体,可以用两个种群来表示。
三个个体(因为有3行):三条染色体
此时:染色体代表的决策变量并不确定,未知采用的编码方式和位数。
假如:采用的是二进制的解码方式,并约定位数为3(三位代表一个决策变量:前3列代表第一个决策变量,后3列代表第二个决策变量),可以解码成:
(2)种群表现型矩阵(Phen):每一行对应一个个体的表现型,对种群染色体矩阵解码
代码. 实整数值种群染色体矩阵的创建:
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("实整数值种群染色体矩阵:\n",Chrom)
代码1的运行结果:
3)种群个体违反约束矩阵(CV):它每一行对应一个个体,每一列对应一种约束条件(可以是等式约束或不等式约束)。
(类似与对偶的概念)
CV矩阵中元素小于或等于0表示对应个体满足对应的约束条件,大于0则表示不满足,且越大表示违反约束条件的程度越高。
比如有两个约束条件:
1,创建两个列向量CV1和CV2,然后左右拼合而成一个CV矩阵
假定某一次迭代中,种群表现型矩阵Phen为(此时有四行,即有四个个体)
x1、x2、x3均为存储着种群所有个体的决策变量值的列向量(可以利用种群表现型矩阵Phen得到)
x1=Phen[:, 0];%phen的第1列
x2=Phen[:, 1]);%phen的第2列
x3=Phen[:, 2]),%phen的第3列
得到
此时CV矩阵为
由此可见,第一个个体满足两个约束条件(第一行所有元素小于或等于0);第二个个体违反了2个约束条件(第2行所有元素均大于0);第三和第四个个体满足第一个约束条件但违反了第二个约束条件(第三第四行,前一个元素小于或等于0,后一个元素大于0
import numpy as np """phen为种群表现性矩阵,由4个个体组成""" phen = np.array([[1, 0, 0], [1.1, 1, 2], [2, -3.1, 2.1], [1, -1, 0]]) """x1,x2,x3为决策变量""" x1 = phen[:, 0] x2 = phen[:, 1] x3 = phen[:, 2] """计算CV矩阵""" cv1 = x1 + 2*x2 - 2 # 不等式约束 cv2 = np.abs(x1 + x2 + x3 - 1) # 等式约束,加绝对值,就可以保证CV矩阵的值只有等于0满足约束 CV = np.vstack((cv1,cv2)).T # 上下合并两个行向量,并转置 print("种群约束矩阵:\n",CV)
CV矩阵作用
1,CV矩阵可用于标记非可行解,在含约束条件的优化问题中有用,
2,CV矩阵可用于度量种群个体违反各个约束条件的程度的高低。
对于含约束条件的优化问题,我们可以采用罚函数或者是可行性法则来进行处理。
罚函数法:借助罚函数把约束问题转化为无约束问题,进而用无约束最优化方法求解。最简单的罚函数可以是直接找到非可行解个体的索引,然后修改其对应的ObjV的目标函数值即可。
对于可行性法则:计算每个个体违反约束的程度,并把结果保存在种群类的CV矩阵中。
CV矩阵的每一行对应一个个体、每一列对应一个约束条件(可以是等式约束也可以是不等式约束),CV矩阵中元素小于或等于0表示对应个体满足对应的约束条件,否则是违反对应的约束条件,大于0的值越大,表示违反约束的程度越高。
生成CV标记之后,在后面调用适应度函数计算适应度时,只要把CV矩阵作为函数传入参数传进函数体,就会自动根据CV矩阵所描述的种群个体违反约束程度来计算出合适的种群个体适应度。
3、编码和解码
实值染色体矩阵未编码,不需要解码
二进制编码染色体矩阵需要解码
from geatpy import bs2ri
# help(bs2ri)
Phen = bs2ri(Chrom_2, FieldD_2) #调用已经设置好的染色体矩阵Chrom_2和FieldD_2译码矩阵
print('表现型矩阵 = \n', Phen)
4、目标函数值
种群的目标函数值存在一个矩阵里面(一般命名为ObjV):每一行对应一个个体的目标函数值
对于单目标而言,这个目标函数值矩阵只有1列,而对于多目标而言,就有多列了,比如下面就是一个含两个目标的种群目标函数值矩阵:
(这里Nind表示种群的规模,即种群含多少个个体;Nvar表示决策变量的个数)
'''
主体代码
二进制染色体解码成整数值种群
目标函数值:f(x,y)=x+y
等式约束:x + y = 3
'''
import numpy as np
from geatpy import crtpc
from geatpy import bs2ri
#定义目标函数值
def aim(Phen):
x = Phen[:, [0]] # 取出种群表现型矩阵的第一列,得到所有个体的决策变量x
y = Phen[:, [1]] # 取出种群表现型矩阵的第二列,得到所有个体的决策变量y
CV = np.abs(x + y - 3) # 生成种群个体违反约束程度矩阵CV,以处理等式约束:x + y == 3看,np.abs求绝对值
f = x + y # 计算目标函数值
return f, CV # 返回目标函数值矩阵
# 定义种群规模(个体数目)
Nind = 4
Encoding = 'BG' # 表示采用“实整数编码”,即变量可以是连续的也可以是离散的
# 创建“译码矩阵”
FieldD = np.array([[3, 2], # 各决策变量编码后所占二进制位数,有两个变量x(占3位),y(占2位),此时染色体长度为3+2=5:xy的取值范围决定了位数
[0, 0], # 各决策变量的范围下界:x,y下届0
[7, 3], # 各决策变量的范围上界:x上界7,y上界3
[0, 0], # 各决策变量采用什么编码方式codes(0为二进制编码,1为格雷编码)
[0, 0], # 各决策变量是否采用对数刻度scales(0为采用算术刻度),取消了
[1, 1], # 各决策变量的范围是否包含下界(对bs2int实际无效,详见help(bs2int)),(0 表示不包含边界;1 表示包含边界)
[1, 1], # 各决策变量的范围是否包含上界(对bs2int实际无效)(0 表示不包含边界;1 表示包含边界)
[0, 0]])# 表示两个决策变量都是连续型变量(0为连续1为离散)
# 调用crtri函数创建实数值种群(本质随机生成,实整数编码,4行,5列初始解,每次运行结果都会不同)
Chrom=crtpc(Encoding, Nind, FieldD)
print('二进制染色体矩阵 = \n', Chrom)
# 解码
Phen = bs2ri(Chrom, FieldD)
print('表现型矩阵 = \n', Phen)
# 计算目标函数值矩阵
ObjV, CV = aim(Phen)
print('目标函数值矩阵 = \n', ObjV)
print('CV矩阵 = \n', CV)
结果:
随机生成二进制染色体矩阵,初始种群
初始种群解码
根据CV矩阵可知,第1个个体的CV值为0,表示第1个个体满足x+y=3这个等式约束。其他都大于0,表示不满足该约束。
结果:目标函数为3,x=0.y=3,其他不满足约束
5.适应度值
适应度值通俗来说就是对种群个体的”生存能力的评价“。
对于简单的单目标优化,我们可以简单地把目标函数值直接当作是适应度值
(注意:当用geatpy遗传和进化算法工具箱时,则需要对目标函数值加个负号才能简单地把它当作适应度值,因为geatpy遵循的是”目标函数值越小,适应度值越大“的约定。)
对于多目标优化,则需要根据“非支配排序”或是其他方法来确定种群个体的适应度值
种群适应度(FitnV):它是一个列向量,每一行代表一个个体的适应度值:
geatpy遗传和进化算法工具箱里面有几个函数可以计算种群个体的适应度 ,分别是:
ranking、indexing、powing、scaling。
具体的用法,可以用help命令查看,如help(ranking)。
和上面主体代码一起用:
'''
ranking(基于目标函数值排序的适应度分配)计算种群的适应度
Nind表示种群含多少个个体
'''
from geatpy import ranking
help(ranking)
FitnV = ranking(ObjV, CV)
print('种群适应度矩阵 = \n', FitnV)
运行结果:
分析这个结果我们发现,由于第、3、4个体违反约束条件,而第1个个体满足约束条件,因此第3个个体的适应度最高。
而在第2、3、4个体中,个体3的目标函数值最大,因此适应度最低。可见遵循“最小化目标”的约定,即目标函数值越小,适应度越大。
6、进化追踪器
在使用Geatpy进行进化算法编程时,常常建立一个进化追踪器(如pop_trace)来记录种群在进化的过程中各代的最优个体,尤其是采用无精英保留机制时,进化追踪器帮助我们记录种群在进化的“历史长河”中产生过的最优个体。待进化完成后,再从进化追踪器中挑选出“历史最优”的个体。
进化记录器有多种,其中一种是numpy的array类型的,结构如下:
MAXGEN是种群进化的代数
trace的每一列代表不同的指标:第一列记录各代种群的最佳目标函数值,第二列记录各代种群的平均目标函数值......
trace的每一行对应每一代,如第一行代表第一代,第二行代表第二代.....
3.2遗传算法基本算子
1、选择
在进化算法中存在两个阶段的选择。
第一次是参与进化操作的个体的选择。这个阶段的选择可以是基于个体适应度的、也可以是完全随机地选择交配个体。一旦个体被选中,那么它们就会参与交叉、变异等进化操作。未被选中的个体不会参与到进化操作中。
第二次:“重插入”或“环境选择”的选择: 指在个体经过交叉、变异等进化操作所形成的子代(或称“育种个体”)后用某种方法来保留到下一代从而形成新一代种群的过程。这个选择过程对应的是生物学中的” 自然选择”。它可以是显性地根据适应度(再次注意:适应度并不等价于目标函数值)来进行选择的,也可以是隐性地根据适应度(即不刻意去计算个体适应度)来选择。例如在多目标优化的NSGA-II 算法中,父代与子代合并后,处于帕累托分层中第一层级的个体以及处于临界层中的且拥挤距离最大的若干个个体被保留到下一代。这个过程就没有显性地去计算每个个体的适应度。
geatpy工具箱提供丰富的进化算子(仅限于遗传算子):
经典的选择算子有:“轮盘赌选择”、“随机抽样选择”、“锦标赛选择”、“本地选择”、“截断选择”、“一对一生存者竞争选择”等等
注:遗传算法选择出的后代是可以有重复的
import numpy as np
from geatpy import tour
help(tour)
FitnV = np.array([[1.2],[0.8],[2.1], [3.2],[0.6],[2.2],[1.7],[0.2]])
chooseIdx = tour(FitnV, 6)
print('个体的适应度为:\n', FitnV)
print('选择出的个体的下标为:\n', chooseIdx)
这里只是得出了选择个体的下标
如果我们需要得到被选中个体的染色体,同时尝试改用高级选择函数“selecting”来调用低级选择算子“tour”来进行选择,则可以如下操作:
import numpy as np
from geatpy import selecting #高级选择函数
help(selecting)
#创建了给定元素的数组
Chrom=np.array([[1,11,21],
[2,12,22],
[3,13,23],
[4,14,24],
[5,15,25],
[6,16,26],
[7,17,27],
[8,18,28]])
#种群适应度,越小越好,大于0,为测试selecting——tour,这里给定
FitnV = np.array([[1.2],[0.8],[2.1], [3.2],[0.6],[2.2],[1.7],[0.2]])
SelCh = Chrom[selecting('tour', FitnV, 6), :] # 使用'tour'锦标赛选择算子,同时片取Chrom得到所选择个体的染色体
print('个体的适应度为:\n', FitnV)
print('选择后得到的种群染色矩阵为:\n', SelCh)
2、交叉重组
进化算法中的重组有时俗称”交叉”,但系统地看,重组包含了交叉。
重组算法是改进进化算法最有效的环节,它通过结合交配群体中包含的遗传信息产生新的个体
重组概率:
染色体重组概率,即对于任意一组配对的染色体而言,其发生重组的概率
条件重组概率,又称片段重组概率,它是指在满足染色体重组概率的条件下,重组算子在染色体上发生作用的最小片段(“最小作用片段”)将要发生重组的概率。
from geatpy import xovdp # 两点交叉
import numpy as np
# help(xovdp)
OldChrom=np.array([[1,1,1,1,1],[1,1,1,1,1],[0,0,0,0,0],[0,0,0,0,0]]) #创建一个二进制种群染色体矩阵
print('交叉前种群染色矩阵为:\n', OldChrom)
NewChrom = xovdp(OldChrom, XOVR=0.7) # 设交叉概率为1
print('交叉后种群染色矩阵为:\n', NewChrom)
3、变异
变异是指通过改变染色体中的一部分元素来形成新的染色体的过程。
降低进化算法陷入局部最优解的风险
from geatpy import mutuni # 均匀变异
import numpy as np
help(mutuni)
# 自定义种群染色体矩阵,表示有3个个体,且染色体元素直接表示变量的值(即实值编码)
OldChrom = np.array([[9,10],
[10,10],
[10,10]])
# 创建区域描述器(又称译码矩阵)
FieldDR = np.array([[7,8],
[10,10],
[1, 1]])
# 此处设编码方式为实值编码中的“实整数编码”RI,表示染色体可代表实数和整数
NewChrom = mutuni('RI', OldChrom, FieldDR, 0.7)
print(NewChrom)
4、重插入
经典遗传算法通过选择、重组和变异后,我们得到的是育种后代,此时育种后代的个体数有可能会跟父代种群的个体数不相同
为了保持种群的规模,这些育种后代可以重新插入到父代中,替换父代种群的一部分个体,或者丢弃一部分育种个体,最终形成新一代种群
重插入”也是一种选择,但它是环境选择,是用于生成新一代种群的;
前面在交叉变异之前的选择是用于选择个体参与交叉变异,那个选择常被称作“抽样”。
'''
精英保留策略(EGA)
假设父代种群规模为N,则选择出(N-1)个个体进行交叉变异,然后找出父代的精英个体
用该精英个体和交叉变异得到的子代个体进行“拼合”,得到新一代种群。
'''
from geatpy import mutuni #均匀变异
import numpy as np
#自定义种群染色体矩阵,表示有4个个体,且染色体元素直接表示变量的值(即实值编码)
Chrom = np.array([[1.1, 1.3],
[2.4, 1.2],
[3, 2.1],
[4, 3.1]])
# 若父代个体的适应度为:
FitnV = np.array([[1],
[2],
[3],
[4]])
# 考虑采用“精英保留策略”的遗传算法,此时从父代选择出4-1=3个个体,经过交叉变异后假设子代的染色体为:
offChrom = np.array([[2.1, 2.3],
[2.3, 2.2],
[3.4, 1.1]])
# 假设直接把目标函数值当作适应度,且认为适应度越大越好。则通过以下代码重插入生成新一代种群:
bestIdx = np.argmax(FitnV) # 得到父代精英个体的索引
NewChrom = np.vstack([Chrom[bestIdx, :], offChrom]) # 得到新一代种群的染色体矩阵
print('新一代种群的染色体矩阵为:\n', NewChrom)