一、Geatpy 总览
1. Geatpy 结构分析
Geatpy 提供面向对象的简单封装的开放式进化算法框架,可以方便、自由地与其他
算法或项目相结合。Geatpy 的文件结构如下图所示:
图 1 Geatpy 文件结构一览
其中“core”文件夹里面全部为 Geatpy 工具箱的内核函数;“templates”文件夹存
放的是 Geatpy 的进化算法模板;“testbed”是进化算法的测试平台,内含多种单目标优
化、多目标优化、组合优化的测试集。“demo”文件夹中包含了应用 Geatpy 工具箱求解
问题的案例。“operators”是 2.2.2 版本之后新增的,里面存放着面向对象的重组和变异
算子类,这些重组和变异算子类通过调用“core”文件夹下的重组和变异算子来执行相
关的操作。
Geatpy 的面向对象进化算法框架有四个大类:Algorithm(算法模板顶级父类)、Pop-
ulation(种群类)、PsyPopulation(多染色体种群类) 和 Problem(问题类),分别存放在“Al-
gorithm.py”、“Population.py”、“Problem.py”文件中。其 UML 类图如下所示:
图 2 Geatpy UML 类图
Problem 类定义了与问题相关的一些信息,如问题名称 name、优化目标的维数 M、
决策变量的个数 Dim、决策变量的范围 ranges、决策变量的边界 borders 等。maxormins
是一个记录着各个目标函数是最小化抑或是最大化的 list 类型列表,其中元素为 1 表示
对应的目标是最小化目标;为-1 表示对应的是最大化目标。例如 M=3,maxormins=[1,-1
,1],此时表示有三个优化目标,其中第一、第三个是最小化目标,第二个是最大化目标
。varTypes 是一个记录着决策变量类型的行向量,其中的元素为 0 表示对应的决策变量
是 连续型变量;为 1 表示对应的是离散型变量。待求解的目标函数定义在 aimFunc() 的
函 数中。calBest() 函数则用于从理论上计算目标函数的理论最优值。在实际使用时,不
是 直接在 Problem 类的文件中修改相关代码来使用的,而是通过定义一个继承 Problem
的 子类来完成对问题的定义的。这些在后面的章节中会详细讲述。对于 Problem 类中各
属 性的详细含义可查看 Problem.py 源码。
Population 类是一个表示种群的类。一个种群包含很多个个体,而每个个体都有一
条染色体 (若要用多染色体,则使用 PsyPopulation 类)。除了染色体外,每个个体都有
一个译码矩阵 Field(或俗称区域描述器) 来标识染色体应该如何解码得到表现型,同时
也有其对应的目标函数值以及适应度。种群类就是一个把所有个体的这些数据统一存
储起来的一个类。比如里面的 Chrom 是一个存储种群所有个体染色体的矩阵,它的每
一行对应一个个体的染色体;ObjV 是一个目标函数值矩阵,每一行对应一个个体的所
有目标函数值,每一列对应一个目标。对于 Population 类中各属性的详细含义可查看
Population.py 源码以及下一章“Geatpy 数据结构”。
PsyPopulation 类是继承了 Population 的支持多染色体混合编码的种群类。一个种群
包含很多个个体,而每个个体都有多条染色体。用 Chroms 列表存储所有的染色体矩阵
(Chrom);Encodings 列表存储各染色体对应的编码方式 (Encoding);Fields 列表存储各
染色体对应的译码矩阵 (Field)。
Algorithm 类是进化算法的核心类。它既存储着跟进化算法相关的一些参数,同时
也在其继承类中实现具体的进化算法。比如 Geatpy 中的 moea_NSGA3_templet.py 是实
现了多目标优化 NSGA-III 算法的进化算法模板类,它是继承了 Algorithm 类的具体算
法的模板类。关于 Algorithm 类中各属性的含义可以查看 Algorithm.py 源码。这些算法
模板通过调用 Geatpy 工具箱提供的进化算法库函数实现对种群的进化操作,同时记录
进化过程中的相关信息,其基本层次结构如下图:
图 3 Geatpy 层次结构
其中“算子类”是 2.2.2 版本之后增加的新特性,通过实例化算子类来调用低级操
作函数,可以利用面向对象编程的优势使得对传入低级操作函数的一些参数的修改变得
更加方便。目前内置的“算子类”有“重组算子类”和“变异算子类”。用户可以绕开
对底层的低级操作函数的修改而直接通过新增“算子类”来实现新的算子例如自适应的
重组、变异算子等。
对于未接触过面向对象编程的用户而言,可以不采用上面所说的 Geatpy 提供的面
向对象进化算法框架,可以通过直接调用工具箱的库函数来解决实际问题 (详见第三章
“快速入门”)。这样做的不好之处是需要比较熟悉工具箱的内核用法。
Geatpy 工具箱提供了大量的跟进化算法有关的内核函数,涵盖了单目标优化、多目
标优化、组合优化等方面的众多算子,同时提供指标评价、绘图等功能性函数。下图展
示了 Geatpy 的函数调用关系。中心是进化算法模板,它调用高级的运算函数 (selecting,
recombin, mutate) 来实现进化优化。高级函数进一步调用相关的低级运算函数 (即实现
选择、交叉、变异等底层算法的函数)。这种层级调用关系使 Geatpy 的结构十分清晰,更
重要的是,你可以自定义更多低级运算函数来轻松自由地扩展 Geatpy。
特别注意:自定义的进化算法模板最好不要与 Geatpy 自带的模板名称重复,否则
会出现一些难以解决的冲突问题。
2. Geatpy 调用关系
图 4 Geatpy 调用关系
图中黑色文字的方框 Geatpy 内置的函数 (可以添加自定义的函数)。其中,crt* 用于
创建种群染色体矩阵 (* 表示名字是多样的);bs2* 用于二进制/格雷码种群染色体矩阵的
解码;ranking, powing, scaling, indexing 均是用于计算单目标优化种群个体适应度的函
数;selecting、recombin、mutate 分别是高级的选择、重组和变异函数,对应下方的都是
低级的选择、重组和变异函数;*plot 是以 plot 名字结尾的用于可视化输出函数。ndsort*
是与多目标优化的非支配排序函数。rwGA、awGA、refselect、crowdis 均是多目标进化
优化算法中需要用到的函数,功能分别为随机权重目标聚合、自适应权重目标聚合、基
于参考点关联的个体选择、目标空间拥挤距离的计算。indicator 是一个评价指标模块,
里面包含众多评价指标的计算,如 GD、IGD、HV、Spacing 等。
红色文字的方框表示 Problem 类中的自定义目标函数,假设 myProblem 为一个继承
了 Problem 类的自定义问题类的对象,则通过 myProblem.aimFunc() 即可调用自定义的
目标函数。
蓝色文字的方框表示 Algorithm 类的相关方法,run 是算法模板的执行方法,stat 是
Algorithm 类中的用于统计和分析处理的函数。
这些函数所用的数据都统一遵循 Geatpy 的数据结构规范 (详见下一章节“Geatpy 数
据结构”)。
3. Geatpy 核心模块一览
以下是 Geatpy 的核心模块,其详细用法可以通过 help() 命令查看或翻阅 Geatpy 的
API 文档。其中的算法模板等模块的源码均有详细的注释。例如:
import geatpy as ea
help(ea.crtfld)
3.1 与初始化种群染色体相关的函数
• crtfld (生成译码矩阵,俗称“区域描述器”)
• crtbp (创建二进制种群染色体矩阵)
• crtip (创建元素是整数的种群染色体矩阵)
• crtpp (创建排列编码种群染色体矩阵)
• crtrp (创建元素是实数的种群染色体矩阵)
• meshrng (网格化决策变量范围)
3.2 进化迭代
当完成了种群染色体的初始化后,就可以进行进化迭代了。这部分是在进化算法模
板里调用。迭代过程中包括:
• 调用 ranking 或 scaling 等计算种群适应度。
• 调用 selecting 进行选择操作 (也可以直接调用低级选择函数)。
• 调用 recombin 进行重组操作 (也可以直接调用低级重组函数)。
• 调用 mutate 进行重组操作 (也可以直接调用低级变异函数)。
3.3 适应度计算
• ranking (基于等级划分的适应度分配计算)
• scaling (线性尺度变换适应度计算)
• indexing (指数尺度变换适应度计算)
• powing (幂尺度变换适应度计算)
对于多目标进化优化,由于各种多目标优化算法所采用的适应度计算方法门类有很
多,因此此时的适应度计算交由继承了 Algorithm 的具体算法模板类中实现,详见相关
源码。
3.4 选择
selecting 是高级选择函数,它调用下面的低级选择函数:
• dup (Duplication,基于适应度排序的直接复制选择)
• ecs (Elite Copy Selection,精英复制选择)
• etour (精英保留锦标赛选择)
• otos (One-to-One Survivor Selection,一对一生存者选择)
• rcs (Random Compensation Selection,随机补偿选择)
• rps (Random Permutation Selection,随机排列选择)
• rws (Roulette Wheel Selection,轮盘赌选择)
• sus (Stochastic Universal Sampling,随机抽样选择)
• tour (Tournament,锦标赛选择)
• urs (Uncommitted Random Selection,无约束随机选择)
3.5 重组
重组包括了交叉。recombin 是高级的重组函数,它调用下面的低级重组函数:
• recdis (离散重组)
• recint (中间重组)
• reclin (线性重组)
• recndx (正态分布交叉)
• recsbx (模拟二进制交叉)
• xovbd (二项式分布交叉)
• xovdp (两点交叉)
• xovexp (指数交叉)
• xovmp (多点交叉)
• xovox (顺序交叉)
• xovpmx (部分匹配交叉)
• xovsec (洗牌指数交叉)
• xovsh (洗牌交叉)
• xovsp (单点交叉)
• xovud (均匀分布交叉)
注意:所有重组算子都不会检查重组结果是否满足所设边界范围。下面的变异算子
则是内置对边界范围的检查和修复。因此如果在进化算法中要单是使用重组算子,则需
要调用“ea.boundfix”函数进行边界修复。详见“help(ea.boundfix)”。
在重组过程中,种群的前一半个体会与后一半个体的染色体按照个体顺序进行一一
配对。这些重组算子可通过设置传入参数“Half”的值为 True,来使得重组后只保留一
半的个体,此时将保留上面所说的一一配对重组后的第一条染色体。
Geatpy2.2.2 版本之后在进化算法框架中新增了面向对象的 Recombination(重组算子
接口),上述的低级重组算子均有与之对应的重组算子类来进行参数的设置及调用,这
些新增的类命名为首字母大写的对应低级重组算子的名称:
• Recdis (离散重组算子类)
• Recint (中间重组算子类)
• Reclin (线性重组算子类)
• Recndx (正态分布交叉算子类)
• Recsbx (模拟二进制交叉算子类)
• Xovbd (二项式分布交叉算子类)
• Xovdp (两点交叉算子类)
• Xovexp (指数交叉算子类)
• Xovmp (多点交叉算子类)
• Xovox (顺序交叉算子类)
• Xovpmx (部分匹配交叉算子类)
• Xovsec (洗牌指数交叉算子类)
• Xovsh (洗牌交叉算子类)
• Xovsp (单点交叉算子类)
• Xovud (均匀分布交叉算子类)
在调用某个重组算子时,可以直接调用低级重组算子进行重组;也可以利用高级
重组算子 recombine 通过指定低级重组算子的名称来调用低级重组算子进行重组,如
recombine(’xovdp’, ...);也可以通过实例化重组算子类的对象,然后执行该对象的 do()
函数进行重组,例如:recOper = Xovdp(...),recOper.do(...)。具体结构详见这些类的源码。
3.6 突变
mutate 是高级的突变函数,它调用下面的低级突变函数:
• mutbga (Mutation for Breeder Genetic Algorithm,Breeder GA 算法突变算子)
• mutbin (Mutation for Binary Chromosomes,二进制变异算子)
• mutde (Mutation for Differential Evolution,差分变异算子)
• mutgau (Gaussian Mutation,高斯突变算子)
• mutinv (Invertion Mutation,染色体片段逆转变异算子)
• mutmove (Mutation by Moving,染色体片段移位变异算子)
• mutpolyn (Polynomial Mutation,多项式变异)
• mutpp (Mutation of Permutation Chromosomes,排列编码变异算子)
• mutswap (Two Point Swapping Mutation,染色体两点互换变异算子)
• mutuni (Uniform Mutation,均匀变异算子)
注意:对于 mutbga、mutde、mutgau、mutpolyn、mutuni,变异是先按实数值来变异,
然后对于标记了是离散型变量进行四舍五入。因此结果往往会是浮点“float”类型的,
此时如果要把这些离散值用作其他变量的下标,需要对其进行强制类型转换。
Geatpy2.2.2 版本之后在进化算法框架中新增了面向对象的 Mutation(变异算子算子
接口),上述的低级变异算子均有与之对应的变异算子类来进行参数的设置及调用,这
些新增的类命名为首字母大写的对应低级变异算子的名称:
• Mutbga (Mutation for Breeder Genetic Algorithm,Breeder GA 算法突变算子类)
• Mutbin (Mutation for Binary Chromosomes,二进制变异算子类)
• Mutde (Mutation for Differential Evolution,差分变异算子类)
• Mutgau (Gaussian Mutation,高斯突变算子类)
• Mutinv (Invertion Mutation,染色体片段逆转变异算子类)
• Mutmove (Mutation by Moving,染色体片段移位变异算子类)
• Mutpolyn (Polynomial Mutation,多项式变异类)
• Mutpp (Mutation of Permutation Chromosomes,排列编码变异算子类)
• Mutswap (Two Point Swapping Mutation,染色体两点互换变异算子类)
• Mutuni (Uniform Mutation,均匀变异算子类)
在调用某个变异算子时,可以直接调用低级变异算子进行重组;也可以利用高级
变异算子 mutate 通过指定低级变异算子的名称来调用低级变异算子进行重组,如 mu-
tate(’mutgau’, ...);也可以通过实例化变异算子类的对象,然后执行该对象的 do() 函数进
行变异,例如:mutOper = Mutgau(...),mutOper.do(...)。具体结构详见这些类的源码。
3.7 染色体解码
对于二进制/格雷编码的种群,我们要对其进行解码才能得到其表现型。
• bs2int (二进制/格雷码转整数)
• bs2real (二进制/格雷码转实数)
• bs2ri (二进制/格雷码转实整数)
3.8 可视化
• moeaplot (多目标优化目标空间绘图函数)
• soeaplot (单目标优化绘图函数)
• trcplot (进化记录器绘图函数)
• varplot (决策变量绘图函数)
如果是在 iPython 控制台中调用可视化绘图函数(例如使用 Spyder 开发工具),一
般图像会默认显示在控制台中。此时可以在控制台下执行%matplotlib 来设置把图像显
示在一个独立窗口中。
3.9 多目标相关
• awGA (适应性权重法多目标聚合函数)
• rwGA (随机权重法多目标聚合函数)
• ndsortDED (基于排除法的帕累托层级划分)
• ndsortESS (基于 ENS_SS 的快速非支配层级划分)
• ndsortTNS (基于 T_ENS 的快速非支配层级划分)
• crtgp (创建在单位超空间中均匀的网格点集)
• crtup (创建在单位超平面内均匀分布的点集)
• crowdis (拥挤距离计算)
• refgselect (利用参考点引导的个体选择)
• refselect (基于参考点的“入龛”个体选择)
3.10 内置的进化算法模板类
• soea_DE_best_1_bin_templet (差分进化 DE/best/1/bin 算法模板)
• soea_DE_best_1_L_templet (差分进化 DE/best/1/L 算法模板)
• soea_DE_rand_1_bin_templet (差分进化 DE/rand/1/bin 算法模板)
• soea_DE_rand_1_L_templet (差分进化 DE/rand/1/L 算法模板)
• soea_DE_targetToBest_1_bin_templet (差分进化 DE/targetToBest/1/bin 算法模板)
• soea_DE_targetToBest_1_L_templet (差分进化 DE/targetToBest/1/L 算法模板)
• soea_ES_1_plus_1_templet ((1+1) 进化策略模板)
• soea_EGA_templet (精英保留的遗传算法模板)
• soea_SEGA_templet (增强精英保留的遗传算法模板)
• soea_SGA_templet (最简单、最经典的遗传算法模板)
• soea_GGAP_SGA_templet (带代沟的简单遗传算法模板)
• soea_studGA_templet (种马遗传算法模板)
• soea_steady_GA_templet (稳态遗传算法模板)
• soea_psy_EGA_templet (精英保留的多染色体遗传算法模板)
• soea_psy_SEGA_templet (增强精英保留的多染色体遗传算法模板)
• soea_psy_SGA_templet (最简单、最经典的多染色体遗传算法模板)
• soea_psy_GGAP_SGA_templet (带代沟的多染色体简单遗传算法模板)
• soea_psy_studGA_templet (多染色体种马遗传算法模板)
• soea_psy_steady_GA_templet (多染色体稳态遗传算法模板)
• moea_awGA_templet (基于 awGA 算法的多目标进化算法模板)
• moea_NSGA2_DE_templet (基于 NSGA-II-DE 算法的多目标进化算法模板)
• moea_NSGA2_archive_templet (带全局存档的多目标进化 NSGA-II 算法模板)
• moea_NSGA2_templet (基于 NSGA-II 算法的多目标进化算法模板)
• moea_NSGA3_DE_templet (基于 NSGA-III-DE 算法的多目标进化算法模板)
• moea_NSGA3_templet (基于 NSGA-III 算法的多目标进化算法模板)
• moea_RVEA_templet (基于 RVEA 算法的多目标进化算法模板)
• moea_RVEA_RES_templet (基于带参考点再生策略的 RVEA 算法的多目标进化算法
模板)
• moea_psy_awGA_templet (基于 awGA 算法的多染色体多目标进化算法模板)
• moea_psy_NSGA2_archive_templet (带全局存档的多染色体多目标进化 NSGA-II 算
法模板)
• moea_psy_NSGA2_templet (基于 NSGA-II 算法的多染色体多目标进化算法模板)
• moea_psy_NSGA3_templet (基于 NSGA-III 算法的多染色体多目标进化算法模板)
• moea_psy_RVEA_templet (基于 RVEA 算法的多染色体多目标进化算法模板)
• moea_psy_RVEA_RES_templet (基于带参考点再生策略的多染色体 RVEA 算法的多
目标进化算法模板)
3.11 指标评价
评价指标计算函数基本上集中在 indicator 模块中,主要有以下几个评价指标:
• indicator.GD (多目标优化世代距离的计算)
• indicator.IGD (多目标优化反转世代距离的计算)
• indicator.HV (多目标优化超体积指标的计算)
• indicator.Spacing (多目标优化分布性指标 Spacing 的计算)
• indicator.moea_tracking (多目标优化进化过程指标追踪分析)
其中的 moea_tracking() 函数可用于多目标优化进化过程的指标变化追踪分析,如果
在进化过程中把历代种群对象保存到了 pop_trace 中,那么就可以利用该函数来计算各
代种群的指标,然后调用 trcplot() 函数绘制指标追踪分析图。
0BBBBBB@
0BBBBBBBBB@
1CCCCCCA
1CCCCCCCCCA
二、Geatpy 数据结构
Geatpy 的大部分数据都是存储在 numpy 的 array 数组里的,numpy 中另外还有 matrix
的矩阵类型,但我们不使用它,于是本文档默认 array 就是存储“矩阵”(也可以存储一
维向量,接下来会谈到)。其中有一些细节需要特别注意:numpy 的 array 在表示行向量
时会有 2 种不同的结构,一种是 1 行 n 列的矩阵,它是二维的;一种是纯粹的一维行向
量。因此,在 Geatpy 教程中会严格区分这两种概念,我们称前者为“行矩阵”,后者为
“行向量”。Geatpy 中不会使用超过二维的 array。
例如有一个行向量 x,其值为 [1 2 3 4 5 6],那么,用 print(x.shape) 输出其规格,可
以得到 (6,),若 x 是行矩阵而不是行向量,那么 x 的规格就变成是 (1,6) 而不再是 (6,)。
在 numpy 的 array 类型中,实际上没有“列向量”的概念。所谓“向量”是指一维
的,但用 numpy 的 array 表示列向量时,它实际上是二维的,只不过只有 1 列。我们不
纠结于这个细节,统一仍用“列向量”来称呼这种只有 1 列的矩阵。
在编程中,如果对 numpy 的 array 感到疑惑,你可以用”print(变量.shape)” 语句来输
出其维度信息,以确定其准确的维度。
一. 基本数据结构
1.1 种群染色体
Geatpy 中,种群染色体是一个 numpy 的 array 类型的二维矩阵,一般用 Chrom 命
名,每一行对应一个个体的一条染色体。若要采用多染色体,则可以创建多个相关联的
Chrom 即可。默认一个 Chrom 的一行对应的只有一条染色体。
我们一般把种群的规模 (即种群的个体数) 用 N ind 命名;把种群个体的染色体长度
用 Lind 命名,则 Chrom 的结构如下所示:
Chrom =
g1;1
g2;1
...
g1;2
g2;2
...
g1;3
g2;3
...
gN ind;1 gN ind;2 gN ind;3
g1;Lind
...
gN ind;Lind
g2;Lind
...
Geatpy2 中不再对一个种群染色体矩阵划分若干份形成多个子种群染色体矩阵。因
此若需要使用多种群,可以使用多个 Chrom 来存储各个种群的染色体。
1.2 种群表现型
种群表现型的数据结构跟种群染色体基本一致,也是 numpy 的 array 类型。我们一
般用 Phen 来命名。它是种群染色体矩阵 Chrom 经过解码后得到的基因表现型矩阵,每
一行对应一个个体,每一列对应一个决策变量。若用 N var 表示变量的个数,则种群表
现型矩阵 Phen 的结构如下图:
Phen =
x1;1
x2;1
x3;1
...
x1;2
x2;2
x3;2
...
x1;3
x2;3
x3;3
...
xN ind;1 xN ind;2 xN ind;3
x2;N var
x1;N var
...
xN ind;N var
x3;N var
...
Phen 的值与采用的解码方式有关。Geatpy 提供二进制/格雷码编码转十进制整数或
实数的解码方式。另外,在 Geatpy 也可以使用不需要解码的“实值编码”种群,这种种
群的染色体的每一位就对应着决策变量的实际值,即这种编码方式下 Phen 等价 Chrom。
这里需要注意的是:我们可以用不同的方式去解码一个种群染色体,得到的结果往
往是不同的。
1.3 目标函数值
Geatpy 采用 numpy 的 array 类型矩阵来存储种群的目标函数值。一般命名为 ObjV ,
每一行对应每一个个体,因此它拥有与 Chrom 相同的行数;每一列对应一个目标函数,
因此对于单目标函数,ObjV 会只有 1 列;而对于多目标函数,ObjV 会有多列。
例如 ObjV 是一个二元函数值矩阵:
0BBBBBBBBB@
ObjV =
f1 (x1;1; x1;2; x1;N var) ; f2 (x1;1; x1;2; x1;N var)
f1 (x2;1; x2;2; x2;N var) ; f2 (x2;1; x2;2; x2;N var)
f1 (x3;1; x3;2; x3;N var) ; f2 (x3;1; x3;2; x3;N var)
...
f1 (xN ind;1; xN ind;2; xN ind;N var) ; f2 (xN ind;1; xN ind;2; xN ind;N var)
1CCCCCCCCCA
1.4 个体适应度
Geatpy 采用列向量来存储种群个体适应度。一般命名为 F itnV ,它同样是 numpy
的 array 类型,每一行对应种群矩阵的每一个个体。因此它拥有与 Chrom 相同的行数。
0BBBBBBBBB@
f it1
f it2
f it3
...
1CCCCCCCCCA
FitnV =
Geatpy 中的适应度遵循“最小适应度为 0”的约定。
f itN ind
1.5 违反约束程度
Geatpy 采用 Numpy array 类型的矩阵 CV(Constraint Violation Value) 来存储种群个
体违反各个约束条件的程度。一般命名为 CV ,它的每一行对应种群的每一个个体,因
此它拥有与 Chrom 相同的行数;每一列对应一个约束条件,因此若有一个约束条件,那
么 CV 矩阵就会只有一列,如有多个约束条件,CV 矩阵就会有多列。如果设有 num 个
约束,则 CV 矩阵的结构如下图所示:
0BBBBBBBBB@
CV =
c1;1
c2;1
c3;1
...
c1;2
c2;2
c3;2
...
c1;3
c2;3
c3;3
...
cN ind;1
cN ind;2
cN ind;3
c2;num
c1;num
...
cN ind;num
c3;num
...
1CCCCCCCCCA
CV 矩阵的某个元素若小于或等于 0,则表示该元素对应的个体满足对应的约束条
件。若大于 0,则表示违反约束条件,在大于 0 的条件下值越大,该个体违反该约束的
程度就越高。Geatpy 提供两种处理约束条件的方法,一种是罚函数法,另一种是可行性
法则。在使用可行性法则处理约束条件时,需要用到 CV 矩阵。具体用法可详见后面两
章中关于使用可行性法则来处理约束条件的相关说明。
1.6 译码矩阵
Geatpy 使用译码矩阵 (俗称区域描述器) 来描述种群染色体的特征,如染色体中的
每一位元素所表达的决策变量的范围、是否包含范围的边界、采用二进制还是格雷码、
是否使用对数刻度、染色体解码后所代表的决策变量的是连续型变量还是离散型变量等
等。
在只使用工具箱的库函数而不使用 Geatpy 提供的面向对象的进化算法框架时,译
码矩阵可以单独使用。若采用 Geatpy 提供的面向对象的进化算法框架时,译码矩阵可
以与一个存储着种群染色体编码方式的字符串 Encoding 来配合使用。目前 Geatpy 中有
三种 Encoding,分别为:
• ’BG’ (二进制/格雷码)
• ’RI’ (实整数编码,即实数和整数的混合编码)
• ’P’ (排列编码,即染色体每一位的元素都是互异的)
这里有个小小的归类值得注意:’RI’ 和’P’ 编码的染色体都不需要解码,染色体上
的每一位本身就代表着决策变量的真实值,因此“实整数编码”和“排列编码”可统称
为“实值编码”。
′
1) 对于 Encoding =
BG
′ 的种群,使用 8 行 n 列的矩阵 FieldD 来作为译码矩阵,n
是染色体所表达的决策变量个数。FieldD 的结构如下:
0BBBBBBBBBBBBBBBBBB@
lens
lb
ub
codes
scales
lbin
ubin
1CCCCCCCCCCCCCCCCCCA
FieldD =
varT ypes
lens, lb, ub, codes, scales, lbin, ubin, varT ypes 均为长度等于决策变量个数的行向
量。
其中,lens 包含染色体的每个子染色体的长度。sum(lens) 等于染色体长度。
lb 和ub 分别代表每个决策变量的上界和下界。
codes 指明染色体子串用的是二进制编码还是格雷编码。codes[i] = 0 表示第i 个变
量使用的是标准二进制编码;codes[i] = 1 表示使用格雷编码。
scales 指明每个子串用的是算术刻度还是对数刻度。scales[i] = 0 为算术刻度,
scales[i] = 1 为对数刻度。对数刻度可以用于变量的范围较大而且不确定的情况,对
于大范围的参数边界,对数刻度让搜索可用较少的位数,从而减少了遗传算法的计算
量。(注意:当某个变量是对数刻度时,其取值范围中不能有 0,即要么上下界都大于 0,
要么上下界都小于 0。)
lbin 和ubin 指明了变量是否包含其范围的边界。0 表示不包含边界;1 表示包含边
界。
varT ypes 指明了决策变量的类型,元素为 0 表示对应位置的决策变量是连续型变
量;1 表示对应的是离散型变量。
例如:
FieldD =
0BBBBBBBBBBBBBBBBBB@
4
1
4
2
5
3
10 9 15
0
0
1
1
0
0
0
1
1
1
1
0
1
1
0
1CCCCCCCCCCCCCCCCCCA
它表示待解码的种群染色体矩阵 Chrom 解码后可以表示成 3 个决策变量,每个决
策变量的取值范围分别是 [1,10], [2,9], [3,15]。其中第一第二个变量采用的是二进制编
码,第三个变量采用的是格雷编码,且第一、第三个决策变量为连续型变量;第二个为
离散型遍历。若 Chrom 为:
0BBB@ 1 0 1 1 1 0 0 0 1 1 1 0 1
0 1 0 0 1 1 0 1 0 1 1 1 1
1CCCA
Chrom =
0 0 0 1 1 1 0 0 0 0 1 0 1
则可以执行以下语句进行解码:
import geatpy as ea
Phen = ea.bs2ri(Chrom, FieldD)
解码后得到的种群表现型矩阵为:
0BBB@7:6 5 11:51612903
1CCCA
3:4 8
6:87096774
5:32258065
1:6 7
Phen =
2) 对于实值编码 (即前面所说的不需要解码的编码方式) 的种群,使用 3 行 n 列的
矩阵 FieldDR 来作为译码矩阵,n 是染色体所表达的控制变量个数。FieldDR 的结构如
下:
FieldDR =
0BBB@ x1下界
x1上界
varT ypes1
xn下界
xn上界
varT ypesn
这种结构的译码矩阵适用于 Encoding 为’RI’(实整数编码) 和’P’(排列编码) 的种群
染色体的解码。其中’P’(排列编码) 的译码矩阵会稍微有一点特殊之处:它要求 FieldDR
的第一行所有元素都相等,第二行所有元素也都相等,且第三行元素均为 1(这是因为排
列编码本身变量是离散的)。此时若记 FieldDR 有 Lind 列 (即染色体长度为 Lind),则要
求上界 - 下界 + 1 Lind。例如:
0BBB@ 2
2
2
2
2
2
2
FieldDR =
10 10 10 10 10 10 10
1
1
1
1
1
1
1
1CCCA
1CCCA
它若是作为排列编码种群的译码矩阵,则表示种群染色体是由集合 2; 3; 4; 5; 6; 7; 8; 9; 10
中任意抽取 7 个数出来的全排列,比如 Chrom 为:
0@2 3 4 7 6 8 10
1A
8 7 6 4 9 5
3
Chrom =
上面的 FieldD 和 FieldDR 都是 numpy 的 array 类型,可统称为“Field”。可以直接
用代码创建,例如:
import numpy as np
FieldDR=np.array([[-3, -4, 0, 2],
[ 2, 3, 2, 2],
[ 0, 0, 0, 0]])
也可以用 Geatpy 内置的 crtfld 函数来方便地快速生成区域描述器,其详细用法可执
行 help(crtfld) 或查看 API 文档。
1.7 进化追踪器
在使用 Geatpy 进行进化算法编程时,常常建立一个进化追踪器 (如 pop_trace) 来记
录种群在进化的过程中各代的最优个体,尤其是采用无精英保留机制时,进化追踪器帮
助我们记录种群在进化的“历史长河”中产生过的最优个体。待进化完成后,再从进化
追踪器中挑选出“历史最优”的个体。这种进化记录器有多种,其中一种是 numpy 的
array 类型的,结构如下:
0BBBBBBBBB@
trace =
a1
a2
b1
b2
c1
c2
a3
...
b3
...
c3
...
...
!1
!2
!3
...
aM AXGEN bM AXGEN cM AXGEN !M AXGEN
1CCCCCCCCCA
其中 MAXGEN 是种群进化的代数。trace 的每一列代表不同的指标,比如第一列记
录各代种群的最佳目标函数值,第二列记录各代种群的平均目标函数值……trace 的每
一行对应每一代,如第一行代表第一代,第二行代表第二代……
另外一种进化记录器是一个列表,列表中的每一个元素都是一个拥有相同数据类型
的数据。比如在 Geatpy 的面向对象进化算法框架中的 pop_trace,它是一个列表,列表
中的每一个元素都是历代的种群对象。
二. 种群结构
2.1 Population 类
在 Geatpy 提供的面向对象进化算法框架中,种群类 (Population) 是一个存储着与种
群个体相关信息的类。它有以下基本属性:
sizes : int - 种群规模,即种群的个体数目。
Lind : int - 种群染色体长度。
Encoding : str - 染色体编码方式。
Field : array - 译码矩阵,可以是 FieldD 或 FieldDR。
Chrom : array - 种群染色体矩阵,每一行对应一个个体的一条染色体。
ObjV : array - 种群目标函数值矩阵。
FitnV : array - 种群个体适应度列向量。
CV : array - 种群个体违反约束条件程度的矩阵。
Phen : array - 种群表现型矩阵。
可以直接对种群对象进行提取个体、个体合并等操作,比如 pop1 和 pop2 是两个种
群对象,则通过语句“pop3 = pop1 + pop2”,即可把两个种群的个体合并,得到一个新
的种群。在合并的过程中,实际上是把种群的各个属性进行合并,然后用合并的数据来
生成一个新的种群 (详见 Population.py)。又比如执行语句“pop3 = pop1[[0]]”,可以把种
群的第 0 号个体抽取出来,得到一个新的只有一个个体的种群对象 pop3。值得注意的
是,种群的这种个体抽取操作要求下标必须为列表或是 Numpy array 类型的行向量,不
能是标量 (详见 Population.py)。
易错注意: 在 Geatpy 中,必要地对种群对象的这些成员属性进行合法性检查是必
要的,但过多的检查会在一定程度上降低框架的性能。其中最容易使得种群对象成员属
性出现异常的地方在于目标函数值矩阵 ObjV 以及 CV 矩阵的生成。在 Geatpy 中,ObjV
和 CV 是在 Problem 问题类的目标函数接口 aimFunc() 中计算生成的,无论中间过程它
们具体是如何计算的,计算得到的结果必须满足:ObjV 和 CV 都是 Numpy array 类型矩
阵,且行数等于种群的个体数目。ObjV 的每一行对应一个个体,每一列对应一个优化
目标。CV 矩阵的每一行也是对应一个个体,而每一列对应一个约束条件。根据 Geatpy
数据结构可知,种群对象中的 Chrom、ObjV、FitnV、CV 和 Phen 都是 Numpy array 类
型的行数等于种群规模 sizes 的矩阵,即这些成员属性的每一行都跟种群的每一个个体
是一一对应的。Geatpy 框架在运行过程中所抛出大多数异常都是由于这些变量不合法
所致。此时可以使用“shape”来输出变量的维度信息,比如:
print(pop.sizes)
print(pop.Chrom.shape)
print(pop.ObjV.shape)
print(pop.CV.shape)
其中 pop 为一个种群对象。
2.2 PsyPopulation 类
PsyPopulation 类是 Population 的子类,它提供 Population 类所不支持的多染色体混
合编码。它有以下基本属性:
sizes : int - 种群规模,即种群的个体数目。
ChromNum : int - 染色体的数目,即每个个体有多少条染色体。
Linds : list
- 存储种群各染色体长度的列表。
Encodings : list - 存储各染色体编码方式的列表。
Fields : list - 存储各染色体对应的译码矩阵的列表。
Chroms : list - 存储种群各染色体矩阵的列表。
ObjV : array - 种群目标函数值矩阵。
FitnV : array - 种群个体适应度列向量。
CV : array - 种群个体违反约束条件程度的矩阵。
Phen : array - 种群表现型矩阵。
可见 PsyPopulation 类基本与 Population 类一样,不同之处是采用 Linds、Encodings、
Fields 和 Chroms 分别存储多个 Lind、Encoding、Field 和 Chrom。
PsyPopulation 类的对象往往与带“psy”字样的进化算法模板配合使用,以实现多
染色体混合编码的进化优化。
三、快速入门
1. 安装
你可以通过联网安装或本地安装来安装 Geatpy。在安装前,你需要安装 Numpy、
Scipy 以及 matplotlib。
联网安装:
pip install geatpy
本地安装:在 github 下载 Geatpy 安装包,在本地控制台执行以下代码安装:
python setup.py install
查看版本号:
在 python 中执行:
import geatpy as ea
print(ea.__version__)
更新方法:
在本地控制台执行:
pip install --upgrade --user geatpy
2. 入门示例
在 Geatpy 上有 3 种基本的方法来求解问题:
• 直接使用工具箱提供的库函数,通过编写脚本来实现进化算法并且求解问题。
• 使用 Geatpy 提供的面向对象进化算法框架,利用内置的算法模板求解问题。
• 使用 Geatpy 提供的面向对象进化算法框架,通过自定义进化算法模板实现进化算法
并且求解问题。
本章将主要介绍如何使用前两种方法:
2.1 脚本编程法
脚本编程法是 Geatpy 最原生、最基础的使用方式。它通过直接调用工具箱的库函
数来实现进化算法,不涉及面向对象的编程方法,编程风格与 Matlab 比较相似,缺点是
代码量比较大。我们将用它来快速入门。
以求解标准测试函数——McCormick 函数的最小化搜索问题为例,其表达式为:
f (x, y) = sin(x + y) + (x − y)2 − 1.5x + 2.5y + 1
它是一个二元函数,它具有一个全局极小点:f = (−0.54719,−1.54719) = −1.9133,
函数图像如下:
拟采用带精英保留的遗传算法”Elitist Reservation GA” 来求解上述问题,于是编写
如下执行脚本:
# -*- coding: utf-8 -*-
"""demo.py"""
import numpy as np
import geatpy as ea # 导入geatpy库
import time
"""============================目标函数============================"""
def aim(Phen): # 传入种群染色体矩阵解码后的基因表现型矩阵
x1 = Phen[:, [0]] # 取出第一列,得到所有个体的第一个自变量
x2 = Phen[:, [1]] # 取出第二列,得到所有个体的第二个自变量
return np.sin(x1 + x2) + (x1 - x2) ** 2 - 1.5 * x1 + 2.5 * x2 +1
"""============================变量设置============================"""
x1 = [-1.5, 4] # 第一个决策变量范围
x2 = [-3, 4] # 第二个决策变量范围
b1 = [1, 1] # 第一个决策变量边界,1表示包含范围的边界,0表示不包含
b2 = [1, 1] # 第二个决策变量边界,1表示包含范围的边界,0表示不包含
# 生成自变量的范围矩阵,使得第一行为所有决策变量的下界,第二行为上界
ranges=np.vstack([x1, x2]).T
# 生成自变量的边界矩阵
borders=np.vstack([b1, b2]).T
varTypes = np.array([0, 0]) # 决策变量的类型,0表示连续,1表示离散
"""==========================染色体编码设置========================="""
Encoding = 'BG' # 'BG'表示采用二进制/格雷编码
codes = [1, 1] # 决策变量的编码方式,两个1表示变量均使用格雷编码
precisions =[6, 6] #
决策变量的编码精度,表示解码后能表示的决策变量的精度可达到小数点后6位
scales = [0, 0] # 0表示采用算术刻度,1表示采用对数刻度
# 调用函数创建译码矩阵
FieldD =
ea.crtfld(Encoding,varTypes,ranges,borders,precisions,codes,scales)
"""=========================遗传算法参数设置========================"""
NIND
MAXGEN
maxormins = [1] #
= 20 # 种群个体数目
= 100 # 最大遗传代数
表示目标函数是最小化,元素为-1则表示对应的目标函数是最大化
selectStyle = 'sus' # 采用随机抽样选择
recStyle = 'xovdp' # 采用两点交叉
mutStyle = 'mutbin' # 采用二进制染色体的变异算子
pc
pm
Lind = int(np.sum(FieldD[0, :])) # 计算染色体长度
obj_trace = np.zeros((MAXGEN, 2)) # 定义目标函数值记录器
var_trace = np.zeros((MAXGEN, Lind)) #
= 0.9 # 交叉概率
= 1 # 整条染色体的变异概率(每一位的变异概率=pm/染色体长度)
染色体记录器,记录历代最优个体的染色体
"""=========================开始遗传算法进化========================"""
start_time = time.time() # 开始计时
Chrom = ea.crtpc(Encoding,NIND, FieldD) # 生成种群染色体矩阵
variable = ea.bs2real(Chrom, FieldD) # 对初始种群进行解码
ObjV = aim(variable) # 计算初始种群个体的目标函数值
best_ind = np.argmin(ObjV) # 计算当代最优个体的序号
# 开始进化
for gen in range(MAXGEN):
FitnV = ea.ranking(maxormins * ObjV) # 根据目标函数大小分配适应度值
SelCh = Chrom[ea.selecting(selectStyle,FitnV,NIND-1),:] # 选择
SelCh = ea.recombin(recStyle, SelCh, pc) # 重组
SelCh = ea.mutate(mutStyle, Encoding, SelCh, pm) # 变异
# 把父代精英个体与子代的染色体进行合并,得到新一代种群
Chrom = np.vstack([Chrom[best_ind, :], SelCh])
Phen = ea.bs2real(Chrom, FieldD) # 对种群进行解码(二进制转十进制)
ObjV = aim(Phen) # 求种群个体的目标函数值
# 记录
best_ind = np.argmin(ObjV) # 计算当代最优个体的序号
obj_trace[gen,0]=np.sum(ObjV)/ObjV.shape[0]
#记录当代种群的目标函数均值
obj_trace[gen,1]=ObjV[best_ind] #记录当代种群最优个体目标函数值
var_trace[gen,:]=Chrom[best_ind,:] #记录当代种群最优个体的染色体
# 进化完成
end_time = time.time() # 结束计时
ea.trcplot(obj_trace, [['种群个体平均目标函数值',
'种群最优个体目标函数值']]) # 绘制图像
运行结果如下:
若要输出相关的数据结果,则可以在上面脚本最下方添加以下代码:
"""============================输出结果============================"""
best_gen = np.argmin(obj_trace[:, [1]])
print('最优解的目标函数值:', obj_trace[best_gen, 1])
variable = ea.bs2real(var_trace[[best_gen], :], FieldD) #
解码得到表现型(即对应的决策变量值)
print('最优解的决策变量值为:')
for i in range(variable.shape[1]):
print('x'+str(i)+'=',variable[0, i])
print('用时:', end_time - start_time, '秒')
输出结果为:
最优解的目标函数值:-1.9132229549785071
最优解的决策变量值为:
x0= -0.5471972283360038
x1= -1.5471987184523008
用时:0.05566329002380371 秒
寻优结果与理论值非常接近。
脚本详细解析:
编写 Geatpy 脚本风格与 Matlab 非常相近,对于新的问题,你可以复制上面的脚本,
结合实际问题修改函数定义和变量设置,以及绘图和输出便可以对新问题进行求解。实
际上你会发现脚本中大部分基本上可以固定不变的,因此在后面的章节中,我们会讲解
如何用更为方便的面向对象进化算法框架来解决问题,这样会大大节省开发时间。
下面详细讲解编写类似上述编程脚本的流程,你需要做以下步骤:
1. 导入相关库,如 numpy、Geatpy 等。
2. 定义了优化目标函数。(在后面的章节中,我们会详细介绍如何用问题类来取代
这种把函数定义写在脚本内部的方法)。
3. 优化问题的变量设置。其中 ranges、borders 是根据实际问题而编写的,但写法格
式是固定的。假如优化函数中包含 3 个变量,那么我们可以这么写:
x1 = [下界, 上界]
x2 = [下界, 上界]
x3 = [下界, 上界]
b1 = [0, 1] # (这里 0 表示变量 x1 不包含下界,1 表示包含上界)
b2 = [1, 0]
b2 = [1, 1]
ranges = np.vstack([x1, x2, x3]).T # 根据 x1,x2,x3 生成一个矩阵来表示所有变量的范
围
borders = np.vstack([b1, b2, b3]).T
...
varTypes 是一个存储决策变量类型的行向量。元素为 0 则对应的决策变量是连续型
变量;为 1 则表示离散型决策变量。比如 array([0,0,1]) 表示三个决策变量中的前 2 个是
连续型变量,第三个是离散型变量。
4. 染色体编码设置:这一部分将设置进化算法采用何种编码的染色体种群进行进
化。Encoding 指定了染色体的编码方式。Encoding 有三种类型:’BG’(二进制/格雷编码)、
’RI’(实整数编码) 以及’P’(排列编码)(详见前面“Geatpy 数据结构”章节)。当 Encoding
为’BG’ 时,需要用到 codes、precisions、scales 三个参数。这三个参数是给“crtfld”函数
来自动化生成译码矩阵用的。详细用法可在 Python 控制台中输入以下代码查看 API 文
档(或查看官网的 API 文档):
import geatpy as ea
help(ea.crtfld)
准备好这些参数后,就可以调用 crtfld() 函数自动化生成译码矩阵 (当然你也可以手
动创建)。crtfld 函数会根据变量的边界和精度自动地调整变量的范围,返回一个符合规
范的译码矩阵。译码矩阵有“FieldD”和“FieldDR”两种类型,详见前面“Geatpy 数据
结构”章节的相关描述。
5. 对进化算法的运行参数进行设置,比如确定种群规模 (即种群包含多少个个体)、
最大遗传代数、重组算子、变异算子、重组概率以及变异概率等。
6. 根据创建的译码矩阵计算染色体的长度。前面“Geatpy 数据结构”章节中讲解了
译码矩阵 FieldD 的第一行是代表控制各个变量的染色体基因数目。因此对它求和便可
算出染色体的长度。
7. 创建进化记录器,它的写法也是固定的,2 表示该进化记录器有 2 列,每一列存
放不同的数据。本例中,它的第一列是存放各代种群最优个体的目标函数值;第二列是
存放各代种群的平均目标函数值。
8. 创建最优个体记录器,记录各代种群的最优个体的染色体。
9. 做完这些基本步骤后,就开始初始化种群以及进化了,这些都是遗传算法的基本
流程了,上面的注释中有详细解释,这里不再重复讲解。
10. 绘图和输出寻优结果。Geatpy 中提供 trcplot 函数来根据进化记录器绘制进化过
程追踪图。关于其具体用法可在 Python 控制台中输入以下代码查看 API 文档(或查看
官网的 API 文档):
import geatpy as ea
help(ea.trcplot)
特别注意:
1) Geatpy 的适应度计算均遵循” 目标函数越大,适应度越小” 的约定。因此在涉及
需要利用目标函数来求适应度,抑或是根据目标函数来进行非支配排序,或者是根据
其来进行个体保留,等等,都要对目标函数乘上一个“目标最小最大化标记列表”——
maxormins。它是一个长度等于目标函数个数的 list 列表,元素为 1 表示对应的目标是最
小化目标,元素为-1 表示对应的是最大化目标。例如 maxormins = [1,1,-1],表示有三个
目标函数,其中前两个是最小化目标,第三个是最大化目标。
2) 使用 Geatpy 过程中最容易出错的地方在于目标函数值矩阵 ObjV 的计算。Geatpy
要求目标函数值矩阵 ObjV 的行数必须等于种群染色体矩阵 Chrom 的行数,以保持个体
属性的一一对应关系。详见上一章“Geatpy 数据结构”。
2.2 采用面向对象的进化算法框架
上面的脚本编程法虽然可以很深入到进化算法的每个步骤,但代码量确实是太大
了。虽然它也具备一定的通用性,比如当需要求解另一个优化问题时,可以通过修改
aim() 目标函数以及修改变量范围设置等来适应新的问题,但会比较容易出错。此外,上
述编程脚本所实现的仅仅是简单的带精英保留的遗传算法,当需要修改算法、采用新的
更好的进化算法进行问题的求解时,所需要改动的代码就非常大了。因此,更好、更简
便的方式是采用 Geatpy 提供的面向对象进化算法框架。
在前面的“Geatpy 总览”章节中提到 Geatpy 的进化算法框架由四个大类构成:算
法模板类 (Algorithm)、种群类 (Population)、多染色体混合编码种群类 (PsyPopulation) 以
及问题类 (Problem)。其中 Population 类和 PsyPopulation 类是可以直接被实例化成对象
去来使用的类;Algorithm 类和 Problem 类是父类,需要实例化其子类来使用。下面以两
个案例(一个单目标优化、一个多目标优化)来展示如何使用 Geatpy 所提供的面向对
象进化算法框架。
2.2.1 带约束的单目标优化问题
待优化的问题模型如下:
max f (x1, x2, x3) = 4x1 + 2x2 + x3
s.t.2x1 + x2 ≤ 1
x1 + 2x3 ≤ 2
x1 + x2 + x3 = 1
x1 ∈ [0, 1], x2 ∈ [0, 1], x3 ∈ (0, 2)
这是一个带不等式约束和等式约束的单目标最大化优化问题,存在多个局部最优解,
对进化算法具有一定的挑战性。全局最优解为:f (0.5, 0, 0.5) = 2.5。这里拟采用差分进
化算法“DE/best/1/L”来求解该问题 (算法模板源码详见“soea_DE_best_1_L_templet.py”
),此时只需要进行编写问题子类和编写执行脚本两个步骤即可完成问题的求解。
【第一步】:通过继承 Problem 问题类完成对问题模型的描述。
编写“MyProblem.py”文件:
# -*- coding: utf-8 -*-
"""MyProblem.py"""
import numpy as np
import geatpy as ea
class MyProblem(ea.Problem): # 继承Problem父类
def __init__(self):
name = 'MyProblem' # 初始化name(函数名称,可以随意设置)
M = 1 # 初始化M(目标维数)
maxormins = [-1] # 初始化目标最小最大化标记列表,1:min;-1:max
Dim = 3 # 初始化Dim(决策变量维数)
varTypes = [0] * Dim # 初始化决策变量类型,0:连续;1:离散
lb = [0,0,0] # 决策变量下界
ub = [1,1,2] # 决策变量上界
lbin = [1,1,0] # 决策变量下边界
ubin = [1,1,0] # 决策变量上边界
# 调用父类构造方法完成实例化
ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb,
ub, lbin, ubin)
def aimFunc(self, pop): # 目标函数,pop为传入的种群对象
Vars = pop.Phen # 得到决策变量矩阵
x1 = Vars[:, [0]] # 取出第一列得到所有个体的x1组成的列向量
x2 = Vars[:, [1]] # 取出第二列得到所有个体的x2组成的列向量
x3 = Vars[:, [2]] # 取出第三列得到所有个体的x3组成的列向量
# 计算目标函数值,赋值给pop种群对象的ObjV属性
pop.ObjV = 4*x1 + 2*x2 + x3
# 采用可行性法则处理约束,生成种群个体违反约束程度矩阵
pop.CV = np.hstack([2*x1 + x2 - 1, # 第一个约束
x1 + 2*x3 - 2, # 第二个约束
np.abs(x1 + x2 + x3 - 1)]) # 第三个约束
代码解析:语句 class MyProblem(ea.Problem) 意思是定义一个名为 MyProblem 的类,
括号中的“ea.Problem”指明了该类是继承了 Problem 类的。
(特别注意:Python 的面向对象和 Java 等语言的面向对象有个很大的不同之处在于:
子类实例化时并不会自动调用父类的构造方法。这意味着子类单纯编写 __init__() 构造
方法后,仍然无法继承父类所拥有的属性。而父类 Problem 中定义了跟问题有关的一些
重要属性(详见“Problem.py”),因此自定义的问题子类必须在构造方法中显式调用父
类的构造方法。)
问题类的“aimFunc()”函数即为待优化的目标函数。上面的目标函数传入了名为
“pop”的参数,它是一个 Population 类的对象,代表着一个种群。Population 类拥有 Chrom、
Phen、ObjV、CV、FitnV 等重要属性,分别指代种群的染色体矩阵、染色体解码后得到的
表现型矩阵、目标函数值矩阵、违反约束程度矩阵、适应度列向量(详见 Population.py
关于种群类的定义)。这些属性的每一行都对应着一个个体。这里的“表现型”矩阵实质
上是等价于决策变量矩阵的。因为染色体解码后对应的是决策变量。值得注意的是,“决
策变量矩阵”是整个种群所有个体的决策变量组成的矩阵,其每一行对应一个个体,每
一列对应一个决策变量。在计算目标函数后,赋值给种群对象 pop 的 ObjV 属性,至此
完成目标函数值的计算。
本案例的问题包含不等式约束和等式约束,在 Geatpy 中有两种处理约束条件的方
法:罚函数法和可行性法则。上面的代码展示的即为采用可行性法则处理约束的用法,
它需要计算每个个体违反约束的程度,并把结果保存在种群类的 CV 矩阵中。CV 矩阵
的每一行对应一个个体、每一列对应一个约束条件(可以是等式约束也可以是不等式约
束,关于 CV 的数据结构详见前面的“Geatpy 数据结构”章节),CV 矩阵中元素小于或
等于 0 表示对应个体满足对应的约束条件,否则是违反对应的约束条件,大于 0 的值越
大,表示违反约束的程度越高。
在上面的例子中,第一个约束是 2x1 + x2 ≤ 1,因此可以用 2x1 + x2 − 1 来衡量违
反该约束的程度。由于 x1、x2 和 x3 都是列向量,因此求得的 2x1 + x2 − 1 也是列向
量。第二、三个约束与之类似,最后把三个列向量(对应三个约束条件)用 Numpy 的
“hstack()”函数拼合在一起,得到 CV 矩阵。
采用可行性法则处理约束只需要关注如何生成 CV 矩阵,不需要关注算法具体是如
何根据 CV 矩阵处理约束条件的。因为不同的算法根据 CV 矩阵处理约束的方式往往会
有所不同,可详见 Geatpy 内置算法模板注释中所标注的算法参考文献。
若要使用罚函数法,则不需要生成 CV 矩阵,最简单的方法是利用 Numpy 的“where”
语句把违反约束条件的个体索引找到,并根据该索引对种群的对应位置上的目标函数值
加以惩罚即可。因此若要采用这种方法,目标函数“aimFunc()”可如下定义:
def aimFunc(self, pop): # 目标函数,pop为传入的种群对象
Vars = pop.Phen # 得到决策变量矩阵
x1 = Vars[:, [0]] # 取出第一列得到所有个体的x1组成的列向量
x2 = Vars[:, [1]] # 取出第二列得到所有个体的x2组成的列向量
x3 = Vars[:, [2]] # 取出第三列得到所有个体的x3组成的列向量
f = 4*x1 + 2*x2 + x3 # 计算目标函数值
# 采用罚函数法处理约束
exIdx1 = np.where(2*x1+x2>1)[0] # 找到违反约束条件1的个体索引
exIdx2 = np.where(x1+2*x3>2)[0] # 找到违反约束条件2的个体索引
exIdx3 = np.where(x1+x2+x3!=1)[0] # 找到违反约束条件3的个体索引
exIdx = np.unique(np.hstack([exIdx1,exIdx2,exIdx3])) # 合并索引
alpha = 2 # 惩罚缩放因子
beta = 1 # 惩罚最小偏移量
f[exIdx] += self.maxormins[0]*alpha *(np.max(f)-np.min(f)+beta)
pop.ObjV = f # 把目标函数值矩阵赋值给种群的ObjV属性
但本案例中包含一个等式约束,用这种简单的惩罚方法难以找到可行解(最坏结果
会是一个可行解都没有找到),读者可以自行尝试。
【第二步】:编写执行脚本“main.py”调用算法模板进行求解。
# -*- coding: utf-8 -*-
"""main.py"""
import numpy as np
import geatpy as ea # import geatpy
from MyProblem import MyProblem # 导入自定义问题接口
"""============================实例化问题对象========================"""
problem = MyProblem() # 实例化问题对象
"""==============================种群设置==========================="""
Encoding = 'RI'
NIND = 50
Field = ea.crtfld(Encoding, problem.varTypes, problem.ranges,
# 编码方式
# 种群规模
problem.borders) # 创建区域描述器
population = ea.Population(Encoding, Field, NIND) #
实例化种群对象(此时种群还没被真正初始化,仅仅是生成一个种群对象)
"""===========================算法参数设置=========================="""
myAlgorithm = ea.soea_DE_best_1_L_templet(problem, population) #
实例化一个算法模板对象
myAlgorithm.MAXGEN = 1000 # 最大遗传代数
myAlgorithm.mutOper.F = 0.5 # 设置差分进化的变异缩放因子
myAlgorithm.recOper.XOVR = 0.5 # 设置交叉概率
myAlgorithm.drawing = 1 # 设置绘图方式
"""=====================调用算法模板进行种群进化====================="""
[population, obj_trace, var_trace] = myAlgorithm.run() # 执行算法模板
# 输出结果
best_gen = np.argmax(obj_trace[:, 1]) # 记录最优种群是在哪一代
best_ObjV = obj_trace[best_gen, 1]
print('最优的目标函数值为:%s'%(best_ObjV))
print('最优的决策变量值为:')
for i in range(var_trace.shape[1]):
print(var_trace[best_gen, i])
print('有效进化代数:%s'%(obj_trace.shape[0]))
print('最优的一代是第 %s 代'%(best_gen + 1))
print('评价次数:%s'%(myAlgorithm.evalsNum))
print('时间已过 %s 秒'%(myAlgorithm.passTime))
运行结果如下:
最优的目标函数值为:2.5
最优的决策变量值为:
0.4999999999999999
3.539185541977619e-18
0.5000000000000002
有效进化代数:1000
最优的一代是第 835 代
评价次数:57900
时间已过 0.7870219230651855 秒
代码解析:在“main.py”执行脚本中,一开始需要实例化一个问题对象。然后是种
群对象的实例化。在实例化种群对象前,需要设定种群的编码方式 Encoding、种群规模
NIND,并且生成区域描述器 Field(或称译码矩阵),因为种群类的构造方法中需要至
少用到这三个参数(详见“Population.py”中种群类的构造方法)。值得注意的是,此时
种群并未真正初始化(它还没有染色体),它仅仅是被实例化而已。种群的初始化工作
在具体的算法模板中完成。
在完成了问题类对象和种群对象的实例化后,将其传入算法模板类的构造方法来实
例化一个算法模板对象。本案例中实例化的是“soea_DE_best_1_L_templet”算法模板。
它实现的是“DE/best/1/L”差分进化算法。里面的进化算法具体是如何操作的,可详见
“soea_DE_best_1_L_templet.py”。
完成算法模板的实例化后,可以在运行算法模板之前对算法模板的一些参数进行设
置,比如交叉概率、最大进化代数、绘图方式等等。也可以不进行设置,此时这些参数
的值将采用默认的设置(详见算法模板源码以及“Algorithm.py”)。
在算法模板类中有一个名为“drawing”的参数用于设置绘图的方式。它有三个可
以选择的值:
• drawing = 0 不绘图
• drawing = 1 绘制结果图
• drawing = 2 绘制进化过程动态图
默认情况下 drawing 的值为 1,此时在执行进化算法后,将会对结果进行绘图。
在完成对进化算法模板的参数设置后,便可以调用其“run()”方法来执行算法模板。
后面便是返回 population、obj_trace 和 var_trace。这个 population 为最后一代的种群(可
详见算法模板中的返回参数),而 obj_trace 和 var_trace 分别是目标函数值记录器和变量
值记录器,分别记录着历代种群最优个体的目标函数值和对应的决策变量值。
2.2.2 带约束的多目标优化问题
有了以上的经验后,对于如何使用 Geatpy 面向对象进化算法框架求解新的问题就
会得心应手。下面看一个带约束的多目标优化问题:
f1 (x, y) = 4x2 + 4y2
g1 (x, y) = (x − 5)2 + y2 ≤ 25
f2 (x, y) = 4(x − 5)2 + 4(y − 5)2
g2 (x, y) = (x − 8)2 + (y − 3)2 ≥ 7.7
0 ≤ x ≤ 5, 0 ≤ y ≤ 3
min =
s.t. =
编写问题类如下:
# -*- coding: utf-8 -*-
"""MyProblem.py"""
import numpy as np
import geatpy as ea
class MyProblem(ea.Problem): # 继承Problem父类
def __init__(self):
name = 'BNH' # 初始化name(函数名称,可以随意设置)
M = 2 # 初始化M(目标维数)
maxormins = [1] * M # 初始化maxormins
Dim = 2 # 初始化Dim(决策变量维数)
varTypes = [0] * Dim #
初始化varTypes(决策变量的类型,0:实数;1:整数)
lb = [0] * Dim # 决策变量下界
ub = [5, 3] # 决策变量上界
lbin = [1] * Dim # 决策变量下边界
ubin = [1] * Dim # 决策变量上边界
# 调用父类构造方法完成实例化
ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb,
ub, lbin, ubin)
def aimFunc(self, pop): # 目标函数
Vars = pop.Phen # 得到决策变量矩阵
x1 = Vars[:, [0]]
x2 = Vars[:, [1]]
f1 = 4*x1**2 + 4*x2**2
f2 = (x1 - 5)**2 + (x2 - 5)**2
# 采用可行性法则处理约束
pop.CV = np.hstack([(x1 - 5)**2 + x2**2 - 25,
-(x1 - 8)**2 - (x2 - 3)**2 + 7.7])
# 把求得的目标函数值赋值给种群pop的ObjV
pop.ObjV = np.hstack([f1, f2])
def calBest(self): # 计算全局最优解
N = 10000 # 欲得到10000个真实前沿点
x1 = np.linspace(0, 5, N)
x2 = x1.copy()
x2[x1 >= 3] = 3
return np.vstack((4 * x1**2 + 4 * x2**2,
(x1 - 5)**2 + (x2 - 5)**2)).T
Geatpy 的问题类有三大函数:构造函数(又称“构造方法”)__init()__()、目标函数
aimFunc() 以及计算全局最优解的函数 calBest()。如果实际问题并不知道模型的全局最
优解,那么 calBest() 函数的定义可以被省略。在多目标优化中,“真实帕累托前沿”可
在 calBest() 中进行计算或读取文件得到。
完成了问题类的定义后,对“2.2.1”中的执行脚本“main.py”略加修改便可以用于
当前问题的求解:
# -*- coding: utf-8 -*-
"""main.py"""
import geatpy as ea # import geatpy
from MyProblem import MyProblem # 导入自定义问题接口
"""=========================实例化问题对象==========================="""
problem = MyProblem() # 实例化问题对象
"""===========================种群设置=============================="""
Encoding = 'RI'
NIND = 100
Field = ea.crtfld(Encoding, problem.varTypes, problem.ranges,
# 编码方式
# 种群规模
problem.borders) # 创建区域描述器
population = ea.Population(Encoding, Field, NIND) #
实例化种群对象(此时种群还没被真正初始化,仅仅是生成一个种群对象)
"""=========================算法参数设置============================"""
myAlgorithm = ea.moea_NSGA2_templet(problem, population) #
实例化一个算法模板对象
myAlgorithm.MAXGEN = 200 # 最大遗传代数
myAlgorithm.drawing = 1 # 设置绘图方式
"""===================调用算法模板进行种群进化=======================
调用run执行算法模板,得到帕累托最优解集NDSet。
NDSet是一个种群类Population的对象。
NDSet.ObjV为最优解个体的目标函数值;NDSet.Phen为对应的决策变量值。
详见Population.py中关于种群类的定义。
"""
NDSet = myAlgorithm.run() # 执行算法模板,得到非支配种群
NDSet.save()
# 输出
print('用时:%f 秒'%(myAlgorithm.passTime))
print('评价次数:%d 次'%(myAlgorithm.evalsNum))
print('非支配个体数:%d 个'%(NDSet.sizes))
print('单位时间找到帕累托前沿点个数:%d 个'%(int(NDSet.sizes //
# 把结果保存到文件中
myAlgorithm.passTime)))
# 计算指标
PF = problem.getBest() # 获取真实前沿
if PF is not None and NDSet.sizes != 0:
GD = ea.indicator.GD(NDSet.ObjV, PF) # 计算GD指标
IGD = ea.indicator.IGD(NDSet.ObjV, PF) # 计算IGD指标
HV = ea.indicator.HV(NDSet.ObjV, PF) # 计算HV指标
Spacing = ea.indicator.Spacing(NDSet.ObjV) # 计算Spacing指标
print('GD: %f'%GD)
print('IGD: %f'%IGD)
print('HV: %f'%HV)
print('Spacing: %f'%Spacing)
"""=====================进化过程指标追踪分析========================"""
if PF is not None:
metricName = [['IGD'], ['HV']]
[NDSet_trace, Metrics] =
ea.indicator.moea_tracking(myAlgorithm.pop_trace, PF,
metricName, problem.maxormins)
# 绘制指标追踪分析图
ea.trcplot(Metrics, labels = metricName, titles = metricName)
上述执行脚本调用了多目标优化的“moea_NSGA2_templet”算法模板,它实现的
是多目标优化 NSGA-II 算法,详见“moea_NSGA2_templet.py”源码。
在调用完算法模板后,通过 problem 对象的“getBest()”方法获取真实前沿。这里也
许会产生疑问:前面“MyProblem.py”中不是定义了“calBest()”方法来计算问题的真实
前沿吗?并没有定义“getBest()”函数。实际上,“getBest()”函数是自定义的“MyProblem”
类的父类:“Problem”中定义的一个函数(详见“Problem.py”),它将先尝试读取本地
的特定格式的全局最优解记录文件,如果没有找到,它将调用自定义的“calBest()”函
数来计算全局最优解(这里即真实前沿),计算完成后,它把结果按照特定格式保存到
“Real_Best”文件夹下面的.csv 文件中。
NDSet 是执行进化算法模板后返回的非支配解集,它也是 Population 类型的,可
以通过 NDSet.ObjV 得到目标函数值,NDSet.Phen 得到决策变量值等等。后面的“ND-
Set.save()”是种群类中定义的一个函数,可用于把结果保存在“Result”文件夹下的
“*.csv”文件中。
上述执行脚本还展示了如何使用 Geatpy 提供的多目标优化进化过程指标追踪分析
功能。调用工具箱的“indicator.moea_tracking()”函数即可完成指标追踪分析,为此需
要一个保存着历代种群对象的列表“pop_trace”,该列表在进化算法模板的父类中有定
义(详见“Algorithm.py”中的“MoeaAlgorithm 类”——多目标进化优化算法模板父类,
Geatpy 中所有的多目标进化优化算法模板都继承了这个父类)。
运行上述脚本,结果如下:
种群信息导出完毕。
用时:0.158019 秒
评价次数:20000 次
非支配个体数:100 个
单位时间找到帕累托前沿点个数:632 个
GD: 0.010648
IGD: 0.527250
HV: 0.782376
Spacing: 0.780950
正在进行进化追踪指标分析,请稍后......
指标追踪分析结束,进化记录器中有效进化代数为: 200
3. 总结
至此,你已经可以模仿着上面的例子来用进化算法求解一些问题了。使用 Geatpy
提供的面向对象的进化算法框架可以让你轻松地把 Geatpy 的进化算法与实际问题或是
一些项目代码进行融合。另外 Geatpy 文件中还提供了大量的 demo(案例) 以及 testbed(实
验平台),当中涵盖了单目标优化、多目标优化、组合优化、约束优化的案例。
在后面的章节我们将会来剖析 Geatpy 进化算法模板的架构,让你可以快速深度掌
握 Geatpy 的进化算法模板,以便可以自己设计出能够更好求解实际问题的进化算法。
四、进化算法模板
1. 进化算法模板类一览
进化算法模板是 Geatpy 进化算法框架的核心。进化算法的核心流程是部体现在进
化算法模板类中。
Geatpy 目前内置以下进化算法模板,详细的算法流程可查看对应的源码。
• soea_DE_best_1_bin_templet (差分进化 DE/best/1/bin 算法模板)
• soea_DE_best_1_L_templet (差分进化 DE/best/1/L 算法模板)
• soea_DE_rand_1_bin_templet (差分进化 DE/rand/1/bin 算法模板)
• soea_DE_rand_1_L_templet (差分进化 DE/rand/1/L 算法模板)
• soea_DE_targetToBest_1_bin_templet (差分进化 DE/targetToBest/1/bin 算法模板)
• soea_DE_targetToBest_1_L_templet (差分进化 DE/targetToBest/1/L 算法模板)
• soea_ES_1_plus_1_templet ((1+1) 进化策略模板)
• soea_EGA_templet (精英保留的遗传算法模板)
• soea_SEGA_templet (增强精英保留的遗传算法模板)
• soea_SGA_templet (最简单、最经典的遗传算法模板)
• soea_GGAP_SGA_templet (带代沟的简单遗传算法模板)
• soea_studGA_templet (种马遗传算法模板)
• soea_steady_GA_templet (稳态遗传算法模板)
• soea_psy_EGA_templet (精英保留的多染色体遗传算法模板)
• soea_psy_SEGA_templet (增强精英保留的多染色体遗传算法模板)
• soea_psy_SGA_templet (最简单、最经典的多染色体遗传算法模板)
• soea_psy_GGAP_SGA_templet (带代沟的多染色体简单遗传算法模板)
• soea_psy_studGA_templet (多染色体种马遗传算法模板)
• soea_psy_steady_GA_templet (多染色体稳态遗传算法模板)
• moea_awGA_templet (基于 awGA 算法的多目标进化算法模板)
• moea_NSGA2_DE_templet (基于 NSGA-II-DE 算法的多目标进化算法模板)
• moea_NSGA2_archive_templet (带全局存档的多目标进化 NSGA-II 算法模板)
• moea_NSGA2_templet (基于 NSGA-II 算法的多目标进化算法模板)
• moea_NSGA3_DE_templet (基于 NSGA-III-DE 算法的多目标进化算法模板)
• moea_NSGA3_templet (基于 NSGA-III 算法的多目标进化算法模板)
• moea_RVEA_templet (基于 RVEA 算法的多目标进化算法模板)
• moea_RVEA_RES_templet (基于带参考点再生策略的 RVEA 算法的多目标进化算法
模板)
• moea_psy_awGA_templet (基于 awGA 算法的多染色体多目标进化算法模板)
• moea_psy_NSGA2_archive_templet (带全局存档的多染色体多目标进化 NSGA-II 算
法模板)
• moea_psy_NSGA2_templet (基于 NSGA-II 算法的多染色体多目标进化算法模板)
• moea_psy_NSGA3_templet (基于 NSGA-III 算法的多染色体多目标进化算法模板)
• moea_psy_RVEA_templet (基于 RVEA 算法的多染色体多目标进化算法模板)
• moea_psy_RVEA_RES_templet (基于带参考点再生策略的多染色体 RVEA 算法的多
目标进化算法模板)
其中,以“soea”开头的是单目标进化算法模板,以“moea”开头的是多目标进化
算法模板。前面的“Geatpy 总览”章节中提到“Algorithm”是进化算法模板的顶级父
类。但上述这些进化算法模板并不是直接继承该顶级父类,而是继承顶级父类之下的
“中间”算法模板类。
“中间”算法模板类有“单目标进化优化算法模板类”(SoeaAlgorithm) 和“多目标进
化优化算法模板类”(MoeaAlgorithm)。这两个算法模板类定义了一些在具体的进化算法
中公用的一些属性以及实现了一些公用的函数。比如用于记录进化过程动态图的“ax”
变量、进化记录器等属性,以及判断是否终止进化的“terminated()”函数、进化完成后
调用的用于一些后续处理的函数“finishing()”、在进化过程中用于分析记录的“stat()”
函数等等。这些属性和函数都是算法模板顶级父类没有定义的,且具体算法模板类公用
的一些属性和函数。
值得注意的是:在这些“中间”算法模板类中定义的属性和函数都是与具体进化算
法核心流程无关的。并且建议在自定义进化算法模板时,不要把一些与具体进化算法核
心流程有关的代码放在这些“中间类”中,如果有连续地自定义了若干个非常相近的算
法模板,那么可以另外创建一个位于“中间类”和具体算法模板类之间的一个类。比如
要自定义很多改进的 NSGA-II 算法,那么可以创建一个名为“BasicNSGA2”的类,来
实现那些在所有改进 NSGA2 中公用的代码。
2. 多目标优化 NSGA-II 算法模板详解
下面以 NSGA-II 的算法模板为例,深入分析该进化算法的整个执行过程。
# -*- coding: utf-8 -*-
import numpy as np
import geatpy as ea # 导入geatpy库
from sys import path as paths
from os import path
paths.append(path.split(path.split(path.realpath(__file__))[0])[0])
class moea_NSGA2_templet(ea.MoeaAlgorithm):
"""
moea_NSGA2_templet : class - 多目标进化NSGA-II算法模板
算法描述:
采用NSGA-II进行多目标优化,算法详见参考文献[1]。
模板使用注意:
本模板调用的目标函数形如:aimFunc(pop),
其中pop为Population类的对象,代表一个种群,
pop对象的Phen属性(即种群染色体的表现型)等价于种群所有个体的决策变量
组成的矩阵,
该函数根据该Phen计算得到种群所有个体的目标函数值组成的矩阵,并将其赋值
给pop对象的ObjV属性。
若有约束条件,则在计算违反约束程度矩阵CV后赋值给pop对象的CV属性(详见
Geatpy数据结构)。
该函数不返回任何的返回值,求得的目标函数值保存在种群对象的ObjV属性中,
违反约束程度矩阵保存在种群对象的CV属性中。
例如:population为一个种群对象,则调用aimFunc(population)即可完成目标
函数值的计算,
此时可通过population.ObjV得到求得的目标函数值,population.CV得到违反
约束程度矩阵。
若不符合上述规范,则请修改算法模板或自定义新算法模板。
参考文献:
[1] Deb K , Pratap A , Agarwal S , et al. A fast and elitist
multiobjective
genetic algorithm: NSGA-II[J]. IEEE Transactions on Evolutionary
Computation, 2002, 6(2):0-197.
"""
def __init__(self, problem, population):
ea.MoeaAlgorithm.__init__(self, problem, population) #
先调用父类构造方法
if str(type(population)) != "":
raise RuntimeError('传入的种群对象必须为Population类型')
self.name = 'NSGA2'
if self.problem.M < 10:
self.ndSort = ea.ndsortESS # 采用ENS_SS进行非支配排序
else:
self.ndSort = ea.ndsortTNS #
高维目标采用T_ENS进行非支配排序,速度一般会比ENS_SS要快
self.selFunc = 'tour' # 选择方式,采用锦标赛选择
if population.Encoding == 'P':
self.recOper = ea.Xovpmx(XOVR = 1) # 生成部分匹配交叉算子对象
self.mutOper = ea.Mutinv(Pm = 1) # 生成逆转变异算子对象
elif population.Encoding == 'BG':
self.recOper = ea.Xovud(XOVR = 1) # 生成均匀交叉算子对象
self.mutOper = ea.Mutbin(Pm = 1) # 生成二进制变异算子对象
elif population.Encoding == 'RI':
self.recOper = ea.Recsbx(XOVR = 1, n = 20) #
生成模拟二进制交叉算子对象
self.mutOper = ea.Mutpolyn(Pm = 1, DisI = 20) #
生成多项式变异算子对象
else:
raise RuntimeError('编码方式必须为''BG''、''RI''或''P''.')
def reinsertion(self, population, offspring, NUM):
"""
描述:
重插入个体产生新一代种群(采用父子合并选择的策略)。
NUM为所需要保留到下一代的个体数目。
注:这里对原版NSGA-II进行等价的修改:先按帕累托分级和拥挤距离来计算
出种群个体的适应度,
然后调用dup选择算子(详见help(ea.dup))来根据适应度从大到小的顺序选择
出个体保留到下一代。
这跟原版NSGA-II的选择方法所得的结果是完全一样的。
"""
# 父子两代合并
population = population + offspring
# 选择个体保留到下一代
[levels, criLevel] = self.ndSort(self.problem.maxormins *
population.ObjV, NUM, None, population.CV) #
对NUM个个体进行非支配分层
dis = ea.crowdis(population.ObjV, levels) # 计算拥挤距离
population.FitnV[:, 0] = np.argsort(np.lexsort(np.array([dis,
-levels])), kind = 'mergesort') # 计算适应度
chooseFlag = ea.selecting('dup', population.FitnV, NUM) #
调用低级选择算子dup进行基于适应度排序的选择,保留NUM个个体
return population[chooseFlag]
def run(self):
#==========================初始化配置===========================
population = self.population
NIND = population.sizes
self.initialization() # 初始化算法模板的一些动态参数
#===========================准备进化============================
if population.Chrom is None:
population.initChrom() #
初始化种群染色体矩阵(内含解码,详见Population类的源码)
else:
population.Phen = population.decoding() # 染色体解码
self.problem.aimFunc(population) # 计算种群的目标函数值
self.evalsNum = population.sizes # 记录评价次数
[levels, criLevel] = self.ndSort(self.problem.maxormins *
population.ObjV, NIND, None, population.CV) #
对NIND个个体进行非支配分层
population.FitnV[:, 0] = 1 / levels #
直接根据levels来计算初代个体的适应度
#===========================开始进化============================
while self.terminated(population) == False:
# 选择个体参与进化
offspring = population[ea.selecting(self.selFunc,
population.FitnV, NIND)]
# 对选出的个体进行进化操作
offspring.Chrom = self.recOper.do(offspring.Chrom) # 重组
offspring.Chrom = self.mutOper.do(offspring.Encoding,
offspring.Chrom, offspring.Field) # 变异
offspring.Phen = offspring.decoding() # 解码
self.problem.aimFunc(offspring) # 求进化后个体的目标函数值
self.evalsNum += offspring.sizes # 更新评价次数
# 重插入生成新一代种群
population = self.reinsertion(population, offspring, NIND)
return self.finishing(population) #
调用finishing完成后续工作并返回结果
该算法模板实现的是经典的 NSGA2 算法。但细心的读者会发现,在保留个体到下
一代的操作中,上述进化算法模板会跟原版 NSGA2 算法在代码逻辑上有所不同。原版
NSGA2 算法的个体保留方法如下:
step1: 设父代种群和子代种群的个体数目都为 N ind,则在父代和子代个体合并后,
对合并的种群进行非支配分层,同时找出位于临界层的个体,处于临界层及后面的个体
则不需要继续进行非支配分层。
step2: 处于临界层之前的个体(总是不大于 N ind)将会被直接保留到下一代,而
处于临界层的个体则计算其拥挤距离,根据拥挤距离从达到小的顺序选择若干个个体保
留到下一代,直至新一代种群的个体数目为 N ind。
如果用图来表示则为:
图 1 NSGA-II 新一代种群生成过程示意图
图 2 个体之间的拥挤距离
上图中的 Pt 为父代种群个体,Qt 为父代种群个体经过选择、重组、变异得到的子
代个体。F1; F2; F3 父子两代个体合并后进行非支配排序分层的结果。F1 为第一层,它
对应着不受其他个体支配的个体。F2 为第二层,它对应着除去第一层个体外,不受剩余
其他个体支配的个体。F3 为临界层。随后,对非支配层的个体进行拥挤距离计算,选出
前若干个拥有更大拥挤距离的个体保留到下一代,此外处于临界层之前各层的个体也被
保留到下一代。
而 Geatpy 内置的 NSGA2 算法模板使用了一个与之等价的方法来选择个体。它同
样先是对父子合并的种群进行非支配分层。然后调用 Geatpy 工具箱的“crowdis”函数
来不仅对临界层而且对临界层之前层级的个体逐层进行拥挤距离计算。随后,通过个体
的非支配分层情况以及拥挤距离计算出每个个体的适应度(计算方法详见上面的代码)。
此时该适应度可以使得处于低层的个体的适应度总比处于高层个体的大,且每一层中,
拥挤距离越大的个体适应度会越大。计算好个体的适应度后,通过调用 Geatpy 工具箱
的“dup”选择算子,将会根据适应度从大到小的顺序依次选择出 N ind 个个体保留到
下一代。
上述算法模板的主要执行流程如下:
(1) 外部的执行脚本调用构造函数 __init()__() 实例化该算法模板类的一个对象。
(2) 外部的执行脚本调用该模板对象的 run() 函数,开始执行进化算法。
(3) 调用继承自父类“MoeaAlgorithm”的 initialization() 函数,完成所需的一些动态
参数的初始化。
(4) 调用 population 对象的 initChrom() 函数,完成种群的初始化。在上一章“快速
入门”中提到执行脚本里面创建种群对象仅仅是完成了种群对象的实例化,在这里调用
了 initChrom() 后种群才拥有染色体,此时的种群才算是被初始化。
(5) 把 population 对象传入 problem 问题对象的目标函数 aimFunc() 中,计算种群个
体对应的目标函数值。执行完这一步之后,population.ObjV 便存储好了所有个体的所有
目标函数值(详见“Geatpy 数据结构”章节)。如果在目标函数 aimFunc() 中生成了 CV
矩阵 (种群个体违反约束程度矩阵),则此时 population.CV 便存储好了所有个体的违反
各个约束条件的程度。在计算完目标函数值后,要随之更新记录评价次数,即上面代码
中的 self.evalsNum = population.sizes。这是因为此时种群的所有个体进行了一次目标函
数值的计算,因此评价次数等于种群规模,即 population.sizes。
(6) 至此完成初代种群的相关工作,此时将进入 while() 循环。while 循环将在每次
循环之初调用继承自父类“MoeaAlgorithm”的“terminated()”函数,用于判断是否满
足进化终止条件,同时对这一代的种群进行统计分析(调用 stat() 函数)。具体代码详见
“Algorithm.py”源码,因为“MoeaAlgorithm”类编写在“Algorithm.py”中。如果需要
其他自定义的终止条件,可以在完成算法模板的实例化后,对其“terminated()”函数进
行重写。
(7) 在此之后便是选择交配个体、重组、变异、重插入生成新一代种群的相关操作,
具体可详见上面的代码。
(8) 完成进化后,调用继承自“MoeaAlgorithm”父类的“finishing()”函数完成一些
后续工作。这些后续工作往往是、也建议是跟算法模板所实现的 NSGA2 算法核心流程
是无关的。这些后续工作包括调用非支配排序筛选出种群中的非支配个体、根据种群的
CV 矩阵彻底排除结果里面的非可行解、更新用时记录、绘图等等。最后得到的种群命
名为 NDSet,意味着这是算法得到的非支配集,它跟 population 一样也是 Population 类
型。此时外部的执行脚本将获得这一返回值,并进行后续的一些工作,如结果的指标分
析、进化过程的指标追踪分析等等(详见上一章的执行脚本代码部分)。
3. 其他算法模板
上面详细讲解了 Geatpy 内置的多目标优化 NSGA-II 进化算法模板,实际上,Geatpy
内置的其他算法模板都与之类似,不同的是算法的核心流程,其他的辅助性工作 (如绘
图、分析记录等) 都是基本一样的。可分别打开算法模板的源码文件进行分析研究,每
个算法模板都有详尽的注释。
4. 约束条件的处理
在上一章“Geatpy 快速入门”中讲述了 Geatpy 处理约束条件的两种方法:罚函数
法和可行性法则。其中罚函数法是跟进化算法模板没有任何关系的,它在目标函数中就
已经对非可行解个体的目标函数值进行了惩罚。这里重点讲解可行性法则下如何生成合
适的 CV 矩阵(即种群个体违反约束程度矩阵)。
在 CV 矩阵中,每一行对应种群的一个个体,每一列对应一个约束条件,元素大于
0 表示违反约束条件,反之表示满足约束条件。当没有设置约束条件时,种群类对象在
实例化时会自动生成一个具有一列而且元素全为 0 的列向量,此时表示种群所有个体都
是可行解。
对于不同表现形式的约束条件,需要进行适当的转换才能生成正确的 CV 矩阵。主
要包含 4 种类型:
(1) 型:例如 x1 + x2 3。此时可将不等式左边移到右边即可得到 CV 矩阵:
CV1 = 3 x1 x2
这样就能够确保 CV1 中大于 0 的对应的是非可行解。
需要注意的是:这里的 CV1 需要确保是只有一列的列向量。
此外, 型可以转化为 型,用同样的方法得到 CV1。
(2) > 型:例如 x1 2x2 > 1。此时单是把不等式左边移到右边,并不能得到满足条
件的 CV 矩阵。如:
CV2 = 1 x1 + 2x2
这是因为它将会误把 1 x1 + 2x2 = 0 的误认为是可行解。此时需要额外添加代码
来处理:
import numpy as np
CV2[np.where(1-x1+2*x2==0)[0]] = 1
这里把算式等于 0 对应位置的 CV2 的值设为 1,实际上可以设任意大于 0 的值。
这样就能够确保 CV2 中大于 0 的对应的是非可行解。
此外,< 型可以转化为 > 型,用同样的方法得到 CV2。
(3) ̸= 型:例如 x3 ̸= 2。此时可以先生成一个全为 0 的列向量,然后用类似 (2) 中的
方法对等式的情况进行标记即可,代码如下:
import numpy as np
CV3 = np.zeros((N, 1)) # 设N为种群个体数目
CV3[np.where(x3 == 2)[0]] = 1 # 标记为任意大于0的值均可
(4) = 型:例如 x1 + x2 + x3 = 1。这种类型便是常见的等式约束。可把等式右边移
到左边,然后取绝对值来生成对应的 CV:
CV4 = jx1 + x2 + x3 1j
特别注意:上面均是对整个种群而言的,意味着 x1; x2; x3 都是整个种群所有个体的
x1; x2; x3,而不是单个个体的 x1; x2; x3。如果要分开对每个个体生成 CVi(i 为个体序号),
则此时必须所有个体都生成对应的 CVi,且此时的 CVi 是一个一行多列的向量,每个元
素对应一个约束条件。最后需要通过拼接的方式生成符合 Geatpy 数据结构的 CV 矩阵。
5. “遗忘策略”
Geatpy 的单目标优化和多目标优化都额外引入了“遗忘策略”(详见“Algorithm.py”
的源码),作用是通过种群的 CV 矩阵判断当代种群是否拥有满足约束条件的个体,如
果一个都没有,那么进化记录器将不对这一代种群进行记录,同时进化代数-1,这意味
着如果以“最大进化代数”作为进化停止的判断条件,那么此时由于当代种群没有一个
个体是满足约束条件的,因此后面需要多进化一代来作为弥补。如果一直进化都没有出
现满足约束条件的个体,达到所设定的上限时,进化同样会停止。
6. 使用算法模板求解问题
在上一章“快速入门”里讲述了如何使用内置的算法模板求解一个带约束的单目
标优化问题和一个带约束的双目标优化问题。在 Geatpy 目录下的“demo”和“testbed”
文件夹下均有大量案例,这些案例都是调用 Geatpy 内置的进化算法模板来求解问题的,
涵盖了单目标优化、多目标优化、组合优化、约束优化等。代码均有详尽注释,可边运
行边结合代码进行深入研究。
其中“testbed”是 Geatpy 的进化优化实验平台,里面实现了很多单目标和多目标的
测试函数。以多目标优化“DTLZ-1”测试函数为例,“testbed/moea_test/moea_test_DTLZ/”
文件夹中的“moea_test_DTLZ.py”是“DTLZ”系列优化测试的执行脚本。它提供目标维
数“M”的设置,这里设置 M=10,种群规模 NIND=275,设置调用“moea_RVEA_templet”
算法模板,最大进化代数为 500,执行“moea_test_DTLZ.py”脚本,结果如下:
种群信息导出完毕。
用时:3.130703 秒
评价次数:137500 次
非支配个体数:275 个
单位时间找到帕累托前沿点个数:87 个
GD 0.0012735503409334154
IGD 0.10778142268201366
HV 1.0
Spacing 0.02819853408975837
正在进行进化追踪指标分析,请稍后......
指标追踪分析结束,进化记录器中有效进化代数为: 500
你可以打开该文件(“moea_test_DTLZ.py”),修改相关设置,研究不同算法、不同参
数设置下的优化效果对比。每次的运行结果将会保存在“Result”文件夹下面的“.csv”
后缀的文件当中(注:多次运行这些记录文件会被刷新)。
7. 修改或自定义新的算法模板
在 Geatpy 中你可以很轻松地通过面向对象的方法修改或自定义新的算法模板。由
于自定义新的算法模板比较简单,只需在执行脚本所在的目录下新建一个文件,然后在
该文件中参照着内置算法模板的样式实现一个新的算法模板类即可。这里重点讲解如何
在内置算法模板的基础上进行一定的修改。
内置算法模板其实可修改的地方有很多,例如判断进化是否终止的 terminated() 函
数、进行环境选择生成新一代种群的 reinsertion() 函数,甚至说当如果需要实现自适应
的进化算法时,需要把内置算法模板的 run() 函数也给修改了。如果改动幅度较大,建
议是直接自定义一个新的算法模板类。而当改动幅度较小时,可以通过定义一个继承该
内置算法模板类的子类来覆盖父类的一些要修改的函数。
以修改 NSGA-III 算法模板的判断进化是否终止的 terminated() 函数以及修改其调
用的重组算子为例,要求通过时间限制来判断进化是否停止,并且设置重组算子为“正
态分布交叉”。这样的修改程度较小,可以直接在执行脚本中实现,代码如下:
# -*- coding: utf-8 -*-
""" main.py 执行脚本 """
import geatpy as ea # import geatpy
from MyProblem import MyProblem
"""==========================实例化问题对象======================="""
problem = MyProblem() # 生成问题对象
"""============================种群设置=========================="""
Encoding = 'RI'
NIND = 100
Field = ea.crtfld(Encoding, problem.varTypes, problem.ranges,
# 编码方式
# 种群规模
problem.borders) # 创建区域描述器
population = ea.Population(Encoding, Field, NIND) #
实例化种群对象(此时种群还没被初始化,仅仅是完成种群对象的实例化)
"""==========================算法参数设置========================"""
class My_moea_NSGA3_templet(ea.moea_NSGA3_templet):
def terminated(self, pop): # 判断是终止进化,pop为当代种群对象
self.stat(pop) # 进行统计分析,更新进化记录器
if self.passTime >= self.MAXTIME: # 增加时间限制
return True
if self.currentGen + 1 >= self.MAXGEN or self.forgetCount >=
self.maxForgetCount:
return True
else:
self.currentGen += 1 # 进化代数+1
return False
myAlgorithm = My_moea_NSGA3_templet(problem, population) #
实例化一个算法模板对象
myAlgorithm.MAXTIME = 0.2 # 限时0.2秒
myAlgorithm.MAXGEN = 500 # 最大进化代数
if population.Encoding == 'RI':
myAlgorithm.recOper = ea.Recndx(XOVR = 1) # 生成正态分布交叉算子对象
myAlgorithm.drawing = 1 #
设置绘图方式(0:不绘图;1:绘制结果图;2:绘制过程动画)
"""====================调用算法模板进行种群进化====================
调用run执行算法模板,得到帕累托最优解集NDSet。
NDSet是一个种群类Population的对象。
NDSet.ObjV为最优解个体的目标函数值;NDSet.Phen为对应的决策变量值。
详见Population.py中关于种群类的定义。
"""
NDSet = myAlgorithm.run() # 执行算法模板,得到非支配种群
NDSet.save()
# 把结果保存到文件中
在上面的代码里,通过实现一个继承“moea_NSGA3_templet”类来实现一个新的
算法模板类“My_moea_NSGA3_templet”,来修改 terminated() 函数,在当中加入了通
过判断 passTime 是否达到 MAXTIME 来限制进化的耗时。除此之外进化算法的其他代
码不用再进行编写,调用的时候调用的是其父类的代码。对于重组算子,上面的代码中
在实例化了算法模板对象后通过设置“recOper”为“ea.Recndx”来完成重组算子的修
改(NSGA-III 算法模板在种群染色体编码为’RI’ 时默认的重组算子是模拟二进制交叉
“Recsbx”,详见“moea_NSGA3_templet.py”)。
利用这种定义子类的方式来修改算法模板,可以在不改动原有的算法模板代码的基
础上很方便地实现新增的功能,从而避免不慎把内置的代码给改坏了,而且代码量也比
较小。当然地,如果改动幅度较大,实际上建议新建一个文件来自定义一个改进的算法
模板类。
五、多染色体混合编码
对于大多数复杂的实际问题,单靠一种编码是很难甚至是完全无法进行求解的。这
个时候需要混合编码。Geatpy 的染色体本身有三种最基础的编码方式:’BG’(二进制/格
雷编码)、’RI’(实数整数混合编码) 以及’P’(排列编码),这意味着一条染色体只能是这三
种编码方式的其中一种。因此当需要更加复杂的编码时,需要用多条染色体来进行协同
表达。
Geatpy 的四个大类中的 Population 种群类只支持单染色体,其 Chrom 属性 (种群染
色体矩阵) 中每一行对应的是种群的一条染色体,因此只支持’BG’、’RI’ 或’P’ 中的一种
编码方式。
这里引入 PsyPopulation 种群类,它是继承了 Population 类的一个新的类,它用 Linds
列表代替 Population 中的 Lind 来存储各染色体的长度;用 Encodings 代替 Population 中
的 Encoding 来存储各染色体的编码方式;用 Fields 代替 Population 中的 Field 来存储
各染色体的译码矩阵;用 Chroms 代替 Population 中的 Chrom 来存储各个染色体矩阵。
PsyPopulation 和 Population 的 UML 类图对比如下所示:
图 1 PsyPopulation 与 Population 对比图
上图中的 Field, Chrom, ObjV, FitnV, CV, Phen 等均满足 Geatpy 的数据结构(详见
“Geatpy 数据结构”章节)。
如果在进化过程中采用的是 PsyPopulation 类的种群,而不采用 Population,那么原
有的算法模板都要作出一定的修改。首先是染色体的初始化,需要遍历各种编码的染色
体进行初始化操作。然后对于重组和变异,也是需要遍历各种编码的染色体分别进行重
组和变异;因此 Geatpy 为各个算法模板提供其对应的多染色体版本,并且用“psy”字
符串加以标识。比如多目标优化 NSGA2 的算法模板“moea_NSGA2_templet”,其对应
的多染色体版本为“moea_psy_NSGA2_templet”。
下面以一个单目标带约束问题为例,阐述如何使用多染色体混合编码求解问题:
问题描述:
max f (X) = sin (2x1) − cos (x2) + 2x2
3
− 3x4 + (x5 − 3)2 + 7x6
s.t.
−1.5 ≤ x1, x2 ≤ 2.5
x3, x4, x5, x6 ∈ {1, 2, 3, 4, 5, 6, 7}
x3 ̸= x4 ̸= x5 ̸= x6
问题分析:
该问题可以像以往那样,直接使用实整数编码’RI’ 的染色体来表达所有的变量,然
后加一个不等式约束使得 x3 ̸= x4 ̸= x5 ̸= x6。但这里我们采用另一种编码方法:不难
发现 x3, x4, x5, x6 ∈ 1, 2, 3, 4, 5, 6, 7 和 x3 ̸= x4 ̸= x5 ̸= x6 正好符合排列编码的特征,即
x3, x4, x5, x6 是从 1,2,3,4,5,6,7 中任意挑选出 4 个数字的排列。因此前两个变量用’RI’ 编
码 (实整数编码) 的染色体(并把对应的 varTypes 设为 0 标记这 2 个变量为连续型变量);
后 4 个变量用’P’ 编码 (排列编码) 的染色体。实验代码如下:
第一步:编写问题类 MyProblem,写在 MyProblem.py 文件中。
# -*- coding: utf-8 -*-
"""MyProblem.py"""
import numpy as np
import geatpy as ea
class MyProblem(ea.Problem): # 继承Problem父类
def __init__(self):
name = 'MyProblem' # 初始化name(函数名称,可以随意设置)
M = 1 # 初始化M(目标维数)
# 初始化maxormins(目标最小最大化标记列表,1:最小化;-1:最大化)
maxormins = [-1]
Dim = 6 # 初始化Dim(决策变量维数)
# 初始化决策变量的类型,元素为0表示变量是连续的;1为离散的
varTypes = [0,0,1,1,1,1]
lb = [-1.5,-1.5,1,1,1,1] # 决策变量下界
ub = [2.5,2.5,7,7,7,7] # 决策变量上界
lbin = [1] * Dim # 决策变量下边界
ubin = [1] * Dim # 决策变量上边界
# 调用父类构造方法完成实例化
ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb,
ub, lbin, ubin)
def aimFunc(self, pop): # 目标函数
X = pop.Phen # 得到决策变量矩阵
x1 = X[:, [0]]
x2 = X[:, [1]]
x3 = X[:, [2]]
x4 = X[:, [3]]
x5 = X[:, [4]]
x6 = X[:, [5]]
pop.ObjV = np.sin(2*x1) - np.cos(x2) + 2*x3**2 -3*x4 + (x5-3)**2 +
7*x6 # 计算目标函数值,赋值给pop种群对象的ObjV属性
第二步:编写执行脚本,写在 main.py 文件中。
# -*- coding: utf-8 -*-
"""main.py"""
import numpy as np
import geatpy as ea # import geatpy
from MyProblem import MyProblem # 导入自定义问题接口
"""===========================实例化问题对象========================"""
problem = MyProblem() # 生成问题对象
"""=============================种群设置==========================="""
NIND = 40
# 创建区域描述器,这里需要创建两个,前2个变量用RI编码,其余用排列编码
Encodings = ['RI', 'P']
Field1 = ea.crtfld(Encodings[0], problem.varTypes[:2],
# 种群规模
problem.ranges[:,:2], problem.borders[:,:2])
Field2 = ea.crtfld(Encodings[1], problem.varTypes[2:],
problem.ranges[:,2:], problem.borders[:,2:])
Fields = [Field1, Field2]
population = ea.PsyPopulation(Encodings, Fields, NIND) #
实例化种群对象(此时种群还没被初始化,仅仅是完成种群对象的实例化)
"""===========================算法参数设置=========================="""
myAlgorithm = ea.soea_psy_SEGA_templet(problem, population) #
实例化一个算法模板对象
myAlgorithm.MAXGEN = 25 # 最大进化代数
"""======================调用算法模板进行种群进化===================="""
[population, obj_trace, var_trace] = myAlgorithm.run() # 执行算法模板
population.save() # 把最后一代种群的信息保存到文件中
# 输出结果
best_gen = np.argmax(obj_trace[:, 1]) # 记录最优种群是在哪一代
best_ObjV = obj_trace[best_gen, 1]
print('最优的目标函数值为:%s'%(best_ObjV))
print('最优的控制变量值为:')
for i in range(var_trace.shape[1]):
print(var_trace[best_gen, i])
print('有效进化代数:%s'%(obj_trace.shape[0]))
print('最优的一代是第 %s 代'%(best_gen + 1))
print('评价次数:%s'%(myAlgorithm.evalsNum))
print('时间已过 %s 秒'%(myAlgorithm.passTime))
代码解析
Geatpy 的问题类是不用管种群用何种编码方式的,只需把问题描述清楚。上面的代
码中,种群染色体采用何种编码方式是在执行脚本 main.py 中进行设置的;由于上述问
题中前两个变量是实数,后四个变量是互不相等的整数,于是设置两个 Encoding,把它
存储在 Encodings 列表中,因此有“Encodings = [’RI’, ’P’]”,由于有两种编码,因此需要
创建两个译码矩阵 Field1 和 Field2,最后把它们存储到 Fields 列表中。在随后的实例化种
群对象时,要注意用的种群类是 PsyPopulation 而不是 Population。因为 PsyPopulation 是
Population 衍生出来的支持多染色体混合编码的种群类。最后在实例化算法模板对象时,
要注意挑选的算法模板的名称要带有“psy”字符串,表示这是一个支持多染色体混合编
码的算法模板。由于本例是一个单目标优化问题,因此可以采用“soea_SEGA_templet”
的多染色体版本:“soea_psy_SEGA_templet”算法模板进行求解。
运行 main.py,得到如下结果:
种群信息导出完毕。
最优的目标函数值为:142.78352001006124
最优的控制变量值为:
0.862088871082229
2.490232467651367
7.0
1.0
5.0
6.0
有效进化代数:25
最优的一代是第 23 代
评价次数:1000
时间已过 0.12832368850708008 秒
下面详细探讨多染色体版本的算法模板跟原始算法模板有什么区别。
查看“soea_SEGA_templet.py”以及“soea_psy_SEGA_templet.py”,代码如下:
# -*- coding: utf-8 -*-
"""soea_SEGA_templet.py"""
import geatpy as ea # 导入geatpy库
from sys import path as paths
from os import path
paths.append(path.split(path.split(path.realpath(__file__))[0])[0])
class soea_SEGA_templet(ea.SoeaAlgorithm):
"""
soea_SEGA_templet : class - Strengthen Elitist GA
templet(增强精英保留的遗传算法模板)
算法描述:
本模板实现的是增强精英保留的遗传算法。算法流程如下:
1) 根据编码规则初始化N个个体的种群。
2) 若满足停止条件则停止,否则继续执行。
3) 对当前种群进行统计分析,比如记录其最优个体、平均适应度等等。
4) 独立地从当前种群中选取N个母体。
5) 独立地对这N个母体进行交叉操作。
6) 独立地对这N个交叉后的个体进行变异。
7) 将父代种群和交叉变异得到的种群进行合并,得到规模为2N的种群。
8) 从合并的种群中根据选择算法选择出N个个体,得到新一代种群。
9) 回到第2步。
该算法宜设置较大的交叉和变异概率,甚至可以将其设置为大于1,否则生成的
新一代种群中会有越来越多的重复个体。
模板使用注意:
本模板调用的目标函数形如:aimFunc(pop),
其中pop为Population类的对象,代表一个种群,
pop对象的Phen属性(即种群染色体的表现型)等价于种群所有个体的决策变量组成
的矩阵,
该函数根据该Phen计算得到种群所有个体的目标函数值组成的矩阵,并将其赋值给
pop对象的ObjV属性。
若有约束条件,则在计算违反约束程度矩阵CV后赋值给pop对象的CV属性(详见
Geatpy数据结构)。
该函数不返回任何的返回值,求得的目标函数值保存在种群对象的ObjV属性中,
违反约束程度矩阵保存在种群对象的CV属性中。
例如:population为一个种群对象,则调用aimFunc(population)即可完成目标函
数值的计算,
此时可通过population.ObjV得到求得的目标函数值,population.CV得到违反约束
程度矩阵。
若不符合上述规范,则请修改算法模板或自定义新算法模板。
"""
def __init__(self, problem, population):
ea.SoeaAlgorithm.__init__(self, problem, population) #
先调用父类构造方法
if str(type(population)) != "":
raise RuntimeError('传入的种群对象必须为Population类型')
self.name = 'SEGA'
self.selFunc = 'tour' # 锦标赛选择算子
if population.Encoding == 'P':
self.recOper = ea.Xovpmx(XOVR = 1) # 生成部分匹配交叉算子对象
self.mutOper = ea.Mutinv(Pm = 1) # 生成逆转变异算子对象
else:
self.recOper = ea.Xovdp(XOVR = 1) # 生成两点交叉算子对象
if population.Encoding == 'BG':
self.mutOper = ea.Mutbin(Pm = 1) # 生成二进制变异算子对象
elif population.Encoding == 'RI':
self.mutOper = ea.Mutbga(Pm = 1, MutShrink = 0.5, Gradient
= 20) # 生成breeder GA变异算子对象
else:
raise RuntimeError('编码方式必须为''BG''、''RI''或''P''.')
def run(self):
#==========================初始化配置===========================
population = self.population
NIND = population.sizes
self.initialization() # 初始化算法模板的一些动态参数
#===========================准备进化============================
if population.Chrom is None:
population.initChrom(NIND) #
初始化种群染色体矩阵(内含染色体解码,详见Population类的源码)
self.problem.aimFunc(population) # 计算种群的目标函数值
population.FitnV = ea.scaling(self.problem.maxormins *
population.ObjV, population.CV) # 计算适应度
self.evalsNum = population.sizes # 记录评价次数
#===========================开始进化============================
while self.terminated(population) == False:
# 选择
offspring = population[ea.selecting(self.selFunc,
population.FitnV, NIND)]
# 进行进化操作
offspring.Chrom = self.recOper.do(offspring.Chrom) # 重组
offspring.Chrom = self.mutOper.do(offspring.Encoding,
offspring.Chrom, offspring.Field) # 变异
# 求进化后个体的目标函数值
offspring.Phen = offspring.decoding() # 染色体解码
self.problem.aimFunc(offspring) # 计算目标函数值
self.evalsNum += offspring.sizes # 更新评价次数
population = population + offspring # 父子合并
population.FitnV = ea.scaling(self.problem.maxormins *
population.ObjV, population.CV) # 计算适应度
# 得到新一代种群
population = population[ea.selecting(self.selFunc,
population.FitnV, NIND)]
return self.finishing(population) #
调用finishing完成后续工作并返回结果
=================================================================
# -*- coding: utf-8 -*-
"""soea_psy_SEGA_templet.py"""
import geatpy as ea # 导入geatpy库
from sys import path as paths
from os import path
paths.append(path.split(path.split(path.realpath(__file__))[0])[0])
class soea_psy_SEGA_templet(ea.SoeaAlgorithm):
"""
soea_psy_SEGA_templet : class - Polysomy Strengthen Elitist GA
templet(增强精英保留的多染色体遗传算法模板)
模板说明:
该模板是内置算法模板soea_SEGA_templet的多染色体版本,
因此里面的种群对象为支持混合编码的多染色体种群类PsyPopulation类的对象。
算法描述:
本模板实现的是增强精英保留的遗传算法。算法流程如下:
1) 根据编码规则初始化N个个体的种群。
2) 若满足停止条件则停止,否则继续执行。
3) 对当前种群进行统计分析,比如记录其最优个体、平均适应度等等。
4) 独立地从当前种群中选取N个母体。
5) 独立地对这N个母体进行交叉操作。
6) 独立地对这N个交叉后的个体进行变异。
7) 将父代种群和交叉变异得到的种群进行合并,得到规模为2N的种群。
8) 从合并的种群中根据选择算法选择出N个个体,得到新一代种群。
9) 回到第2步。
该算法宜设置较大的交叉和变异概率,甚至可以将其设置为大于1,否则生成的
新一代种群中会有越来越多的重复个体。
模板使用注意:
本模板调用的目标函数形如:aimFunc(pop),
其中pop为种群类的对象,代表一个种群,
pop对象的Phen属性(即种群染色体的表现型)等价于种群所有个体的决策变量组成
的矩阵,
该函数根据该Phen计算得到种群所有个体的目标函数值组成的矩阵,并将其赋值给
pop对象的ObjV属性。
若有约束条件,则在计算违反约束程度矩阵CV后赋值给pop对象的CV属性(详见
Geatpy数据结构)。
该函数不返回任何的返回值,求得的目标函数值保存在种群对象的ObjV属性中,
违反约束程度矩阵保存在种群对象的CV属性中。
例如:population为一个种群对象,则调用aimFunc(population)即可完成目标函
数值的计算,
此时可通过population.ObjV得到求得的目标函数值,population.CV得到违反约束
程度矩阵。
若不符合上述规范,则请修改算法模板或自定义新算法模板。
"""
def __init__(self, problem, population):
ea.SoeaAlgorithm.__init__(self, problem, population) #
先调用父类构造方法
if str(type(population)) != "":
raise RuntimeError('传入的种群对象必须为PsyPopulation类型')
self.name = 'psy-SEGA'
self.selFunc = 'etour' # 锦标赛选择算子
# 由于有多个染色体,因此需要用多个重组和变异算子
self.recOpers = []
self.mutOpers = []
for i in range(population.ChromNum):
if population.Encodings[i] == 'P':
recOper = ea.Xovpmx(XOVR = 1) # 生成部分匹配交叉算子对象
mutOper = ea.Mutinv(Pm = 1) # 生成逆转变异算子对象
else:
recOper = ea.Xovdp(XOVR = 1) # 生成两点交叉算子对象
if population.Encodings[i] == 'BG':
mutOper = ea.Mutbin(Pm = 1) # 生成二进制变异算子对象
elif population.Encodings[i] == 'RI':
mutOper = ea.Mutbga(Pm = 1, MutShrink = 0.5, Gradient =
20) # 生成breeder GA变异算子对象
else:
raise RuntimeError('编码方式必须为''BG''、''RI''或''P''.')
self.recOpers.append(recOper)
self.mutOpers.append(mutOper)
def run(self):
#==========================初始化配置===========================
population = self.population
NIND = population.sizes
self.initialization() # 初始化算法模板的一些动态参数
#===========================准备进化============================
population.initChrom(NIND) #
初始化种群染色体矩阵(内含染色体解码,详见Population类的源码)
self.problem.aimFunc(population) # 计算种群的目标函数值
population.FitnV = ea.scaling(self.problem.maxormins *
population.ObjV, population.CV) # 计算适应度
self.evalsNum = population.sizes # 记录评价次数
#===========================开始进化============================
while self.terminated(population) == False:
# 选择
offspring = population[ea.selecting(self.selFunc,
population.FitnV, NIND)]
# 进行进化操作,分别对各种编码的染色体进行重组和变异
for i in range(population.ChromNum):
offspring.Chroms[i] =
self.recOpers[i].do(offspring.Chroms[i]) # 重组
offspring.Chroms[i] =
self.mutOpers[i].do(offspring.Encodings[i],
offspring.Chroms[i], offspring.Fields[i]) # 变异
# 求进化后个体的目标函数值
offspring.Phen = offspring.decoding() # 染色体解码
self.problem.aimFunc(offspring) # 计算目标函数值
self.evalsNum += offspring.sizes # 更新评价次数
population = population + offspring # 父子合并
population.FitnV = ea.scaling(self.problem.maxormins *
population.ObjV, population.CV) # 计算适应度
# 得到新一代种群
population = population[ea.selecting(self.selFunc,
population.FitnV, NIND)]
return self.finishing(population) #
调用finishing完成后续工作并返回结果
代码分析:
观察上面两个算法模板的代码,可见基本流程是完全一样的,主要的两个不同之处
在于多染色体版本的算法模板在构造函数上把所需要用到的重组和变异算子存储在一
个列表中,目的是在进化过程中可以让各个染色体矩阵独立地用列表中的重组和变异算
子进行重组和变异。因此,对于进化过程中的重组变异那一块的代码,多染色体版本采
用一个循环来对 Chroms 列表中的每一个 Chrom(种群染色体矩阵) 进行重组和变异,这
是因为多染色体版本的算法模板中种群类是 PsyPopulation 而不是 Population,它的染色
体矩阵是多个而不是单个(存储在 Chroms 列表中)。
以下是 Geatpy 目前内置的多染色体版本的进化算法模板,其具体代码均可在源码
中查看到:
• soea_psy_EGA_templet (精英保留的多染色体遗传算法模板)
• soea_psy_SEGA_templet (增强精英保留的多染色体遗传算法模板)
• soea_psy_SGA_templet (最简单、最经典的多染色体遗传算法模板)
• soea_psy_GGAP_SGA_templet (带代沟的多染色体简单遗传算法模板)
• soea_psy_studGA_templet (多染色体种马遗传算法模板)
• soea_psy_steady_GA_templet (多染色体稳态遗传算法模板)
• moea_psy_awGA_templet (基于 awGA 算法的多染色体多目标进化算法模板)
• moea_psy_NSGA2_archive_templet (带全局存档的多染色体多目标进化 NSGA-II 算
法模板)
• moea_psy_NSGA2_templet (基于 NSGA-II 算法的多染色体多目标进化算法模板)
• moea_psy_NSGA3_templet (基于 NSGA-III 算法的多染色体多目标进化算法模板)
• moea_psy_RVEA_templet (基于 RVEA 算法的多染色体多目标进化算法模板)
• moea_psy_RVEA_RES_templet (基于带参考点再生策略的多染色体 RVEA 算法的多
目标进化算法模板)