超大规模种群进化优化,体验“飞一般的进化之旅”【Geatpy】

728 阅读10分钟

Geatpy拥有极高的性能,尤其是超大规模种群的进化优化,可以让您体验“飞一般的进化之旅”。

简介

Geatpy是一个高性能实用型进化算法工具箱,提供许多已实现的进化算法中各项重要操作的库函数,并提供一个高度模块化、耦合度低的面向对象的进化算法框架,利用“定义问题类 + 调用算法模板”的模式来进行进化优化,可用于求解单目标优化、多目标优化、复杂约束优化、组合优化、混合编码进化优化等,并且能和SCOOP等框架紧密配合进行分布式计算。 GitHub:geatpy

Geatpy已全面支持:

  • Windows 32和64位的Python3.5, 3.6,3.7,3.8,3.9,3.10;
  • Linux 64位的Python3.5, 3.6,3.7,3.8,3.9,3.10;
  • MacOS系统x86架构的Python3.5, 3.6,3.7,3.8,3.9,3.10;
  • MacOS系统Arm架构的Python3.8, 3.9;
  • 以及Linux Arm aarch64的Python 3.7, 3.9。

安装方法

  1. 直接在系统控制台中运行命令:pip install geatpy 。此时将自动匹配合适版本的Geatpy安装包进行下载安装。
  2. 强制版本安装,在系统控制台中运行命令:pip install geatpy==2.7.0,此时将强制安装指定的版本。
  3. github release中下载.whl文件,然后执行pip install xxx.whl进行安装。
  4. github上下载源码文件后,解压,然后在系统控制台中执行:python setup.py install ,此时将自动选择合适版本的内核进行编译安装。【注意:2.7.0之后,github的源码包中只保留了Windows和Linux 64位的Python3.6版本,如果使用的是其他版本的Python,请用其他安装方法。】

快速入门

经过历代版本的更迭,Geatpy目前支持多种使用风格。分别为:“求解器模式”写法、“面向对象”写法、“面向过程”写法。 其中“求解器模式”是Geatpy2.7.0之后新增的写法。可以大大节省代码量、更加专注于问题的定义。

入门案例

先以“求解器模式”为例来快速入门。帮助您掌握如何快速构建问题、定义算法以及求解问题。

案例1

用增强精英保留策略的遗传算法求解以下问题:

min f(x1,x2,x3,x4,x5)=(x1r)2+(x2r)2+...+(x5r)2,r=1s.t.  (x10.5)20.25(x11)211x110x241x351x421x51min f(x1,x2,x3,x4,x5)=(x1−r)2+(x2−r)2+...+(x5−r)2,r=1 \\ s.t.  (x1−0.5)2≤0.25 \\ (x1−1)2≤1 \\ −1≤x1≤1 \\ 0≤x2≤4 \\ 1≤x3≤5 \\ 1≤x4≤2 \\ 1≤x5≤1 \\

其中x1,x2,x3,x4,x5x1,x2,x3,x4,x5x1,x2,x3,x4,x5x1,x2,x3,x4,x5为决策变量。

1x110x241x351x421x511x110x241x351x421x51−1≤x1≤1 \\ 0≤x2≤4 \\ 1≤x3≤5 \\ 1≤x4≤2 \\ 1≤x5≤1 \\ −1≤x1≤1 \\ 0≤x2≤4 \\ 1≤x3≤5 \\ 1≤x4≤2 \\ 1≤x5≤1 \\

为每个决策变量的范围。下面来看该问题如何用Geatpy求解:

import geatpy as ea
import numpy as np

# 构建问题
r = 1  # 目标函数需要用到的额外数据
@ea.Problem.single
def evalVars(Vars):  # 定义目标函数(含约束)
    f = np.sum((Vars - r) ** 2)  # 计算目标函数值
    x1 = Vars[0]
    x2 = Vars[1]
    CV = np.array([(x1 - 0.5)**2 - 0.25,
                    (x2 - 1)**2 - 1])  # 计算违反约束程度
    return f, CV

problem = ea.Problem(name='soea quick start demo',
                        M=1,  # 目标维数
                        maxormins=[1],  # 目标最小最大化标记列表,1:最小化该目标;-1:最大化该目标
                        Dim=5,  # 决策变量维数
                        varTypes=[0, 0, 1, 1, 1],  # 决策变量的类型列表,0:实数;1:整数
                        lb=[-1, 1, 2, 1, 0],  # 决策变量下界
                        ub=[1, 4, 5, 2, 1],  # 决策变量上界
                        evalVars=evalVars)
# 构建算法
algorithm = ea.soea_SEGA_templet(problem,
                                    ea.Population(Encoding='RI', NIND=20),
                                    MAXGEN=50,  # 最大进化代数。
                                    logTras=1,  # 表示每隔多少代记录一次日志信息,0表示不记录。
                                    trappedValue=1e-6,  # 单目标优化陷入停滞的判断阈值。
                                    maxTrappedCount=10)  # 进化停滞计数器最大上限值。
# 求解
res = ea.optimize(algorithm, seed=1, verbose=True, drawing=1, outputMsg=T

分析

  1. 从上述代码可见,我们可以先定义一个目标函数,然后创建问题,再是构建算法,最后调用optimize()函数求解。 在上面的例子中,我们在定义目标函数时,加了一个@ea.Problem.single的标记。它可以让目标函数evalVars()的传入参数是一个Numpy ndarray类型的一维数组,它的实际含义是种群的某个个体对应的决策变量。在evalVars()中,我们通过f=np.sum((Vars - r) ** 2)计算出这组决策变量对应的目标函数值。
  1. 然后通过CV = np.array([(x1 - 0.5)**2 - 0.25, (x2 - 1)**2 - 1])来计算违反约束程度值。这是因为该问题除了变量范围外,有2个不等式约束。如果(x1−0.5)2−0.25(x1−0.5)2−0.25的值大于0,就说明违反了(x1−0.5)2≤0.25(x1−0.5)2≤0.25的约束,并且(x1−0.5)2−0.25(x1−0.5)2−0.25的值越大,违反约束的程度就越大。我们把计算得到的两个约束的违反约束程度值拼合在一起组成一个一维数组,就得到一个数组表示这组决策变量所有的违反约束程度值。
  1. 代码的第15行,这里调用了Geatpy的Problem问题类的构造函数,得到一个问题对象。 其中maxormins是一个记录着各个目标函数是最小化抑或是最大化的Numpy array行向量,其中元素为1表示对应的目标是最小化目标;为-1表示对应的是最大化目标。例如M=3,maxormins=array([1,-1,1]),此时表示有三个优化目标,其中第一、第三个是最小化目标,第二个是最大化目标。varTypes是一个记录着决策变量类型的行向量,其中的元素为0表示对应的决策变量是连续型变量;为1表示对应的是离散型变量。
  1. 接着,第24行,我们实例化了Geatpy内置实现的一个进化算法类的对象。其中第25行定义了该进化算法用什么样的种群去进化。Encoding='RI'表示改种群的染色体是'RI'编码,即“实数整数混合编码”,简称“实整数编码”。类似的还有'P'编码(排列编码,可以让对应的变量互不相同)、'BG'编码(二进制/格雷码编码)。然后NIND=20设定了种群有20个个体。
  1. 最后,在第31行,调用geatpy的optimize()函数进行求解。其中seed=1表示设置随机数种子。如果不想设置随机数种子,则把它设为None,或者不把它传入到optimize()函数中。dirName='result'表示把求解结果以文件的形式保存在当前工作目录下的result文件夹中。如果不传入dirName或者设dirName为None,则默认会以求解的起始时间的信息作为文件夹,把结果保存在里面。optimize()函数的其他传入参数以及31行获得的返回值res(类型为python字典)的内容可以用help(ea.optimize)或跳转进入源码查看。例如res['ObjV']表示算法搜索到的最佳目标函数值(它是个Numpy ndarray二维数组,每一行表示一组目标函数值。);res['Vars']表示对应的决策变量值矩阵(Numpy ndarray二维数组,每一行表示一组决策变量值)。

Geatpy架构说明

Geatpy2整体上看由工具箱内核函数(内核层)和面向对象进化算法框架(框架层)两部分组成。其中面向对象进化算法框架主要有四个大类:Problem问题类、Algorithm算法模板类、Population种群类和PsyPopulation多染色体种群类。UML图如下所示:

Problem类定义了与问题相关的一些信息,如问题名称name、优化目标的维数M、决策变量的个数Dim、决策变量的范围ranges、决策变量的边界borders等。maxormins是一个记录着各个目标函数是最小化抑或是最大化的Numpy array行向量,其中元素为1表示对应的目标是最小化目标;为-1表示对应的是最大化目标。例如M=3,maxormins=array([1,-1,1]),此时表示有三个优化目标,其中第一、第三个是最小化目标,第二个是最大化目标。varTypes是一个记录着决策变量类型的行向量,其中的元素为0表示对应的决策变量是连续型变量;为1表示对应的是离散型变量。待求解的目标函数定义在aimFunc()的函数中。calReferObjV()函数则用于计算或读取目标函数参考值(一般用理论上的目标函数的最优值作为参考值),该参考值可以用于后续的指标分析。在实际使用时,不是直接在Problem类的文件中修改相关代码来使用的,而是通过定义一个继承Problem的子类来完成对问题的定义的。这些在后面的章节中会详细讲述。getReferObjV()是Problem父类中已经实现了的一个函数,它先尝试读取特定文件夹中的目标函数值参考数据,如果读取不到,则调用calReferObjV()进行计算。对于Problem类中各属性的详细含义可查看Problem.py源码。

Population 类是一个表示种群的类。一个种群包含很多个个体,而每个个体都有一条染色体(若要用多染色体,则使用多个种群、并把每个种群对应个体关联起来即可)。除了染色体外,每个个体都有一个译码矩阵Field(或俗称区域描述器) 来标识染色体应该如何解码得到表现型,同时也有其对应的目标函数值以及适应度。种群类就是一个把所有个体的这些数据统一存储起来的一个类。比如里面的Chrom 是一个存储种群所有个体染色体的矩阵,它的每一行对应一个个体的染色体;ObjV 是一个目标函数值矩阵,每一行对应一个个体的所有目标函数值,每一列对应一个目标。对于Population 类中各属性的详细含义可查看Population.py 源码以及下一章“Geatpy 数据结构”。

PsyPopulation类是继承了Population的支持多染色体混合编码的种群类。一个种群包含很多个个体,而每个个体都有多条染色体。用Chroms列表存储所有的染色体矩阵(Chrom);Encodings列表存储各染色体对应的编码方式(Encoding);Fields列表存储各染色体对应的译码矩阵(Field)。EncoIdxs是一个list,其元素表示每条染色体对应编码哪一个变量。比如EncoIdxs = [[0], [1,2,3,4]],表示一共有5个变量,其中第一个变量编码成第一条子染色体;后4个变量编码成第二条子染色体。

Algorithm 类是进化算法的核心类。它既存储着跟进化算法相关的一些参数,同时也在其继承类中实现具体的进化算法。比如Geatpy 中的moea_NSGA3_templet.py 是实现了多目标优化NSGA-III 算法的进化算法模板类,它是继承了Algorithm 类的具体算法的模板类。关于Algorithm 类中各属性的含义可以查看Algorithm.py 源码。这些算法模板通过调用Geatpy 工具箱提供的进化算法库函数实现对种群的进化操作,同时记录进化过程中的相关信息,其基本层次结构如下图:

其中“算子类”是Geatpy2.2.2版本之后增加的特性,通过实例化算子类来调用低级操作函数,可以利用面向对象编程的优势使得对传入低级操作函数的一些参数的修改变得更加方便。目前内置的“算子类”有“重组算子类”和“变异算子类”。用户可以绕开对底层的低级操作函数的修改而直接通过新增“算子类”来实现新的算子例如自适应的重组、变异算子等。

例如:mutpolyn是多项式变异的低级变异算子。其对应的高级变异算子为:Mutpolyn。

附录

  1. 进化算法介绍:geatpy.com/index.php/e…
  2. Geatpy数据结构:geatpy.com/index.php/2…
  3. Geatpy总览:geatpy.com/index.php/%…
  4. Geatpy案例:geatpy.com/index.php/d…