【遗传编程/基因规划】python DEAP框架学习笔记

python DEAP框架

DEAP: 一个Python进化算法框架
DEAP 图标

Core核心模块:

  1. base: 基本结构。包含Toolbox(存储自定的EA运行所需的对象和操作), Fitness(个体的适应度的基类)等。
  2. creator: 允许通过动态添加属性或函数来创建符合问题需求的类,常用来创建个体。
  3. tools: 包含多种选择(selection)操作函数,遗传算子操作函数(多种crossover, mutation)等。

还会用到的模块:

  • algorithms (包含常用进化算法),
  • gp (提供了DEAP框架上实行genetic programming的类与方法,如创建GP树的类)
DEAP框架结构示意图 (semanticscholar.org)
DEAP结构示意图

Genetic Programming基本过程回顾

  1. Initialization of Population
    初始化族群:设定 primitive set——设定包含所用函数、操作的函数集(function set),设定包含变量、常量和无参函数的终止集(terminal set)。给定参数,随机生成初始族群。
    * 族群生成方法:grow, full, ramped half and half
  2. Fitness Evaluation
    适应度评估:根据具体问题设计适应度函数(fitness function),评估各个个体的适应度。
  3. Selection
    选择父系:(绝大部分情况)根据适应度,随机选出相对优秀的个体。
    * 选择方法:锦标赛选择法、轮盘赌选择法等
  4. Genetic Operation
    遗传操作:父体通过交叉、变异、复制生成下一代个体。
    * 遗传算子:subtree crossover, one-point crossover, subtree mutation, reproduction
  5. Termination or Iteration
    判断终止或继续迭代

DEAP 举例讲解

DEAP官方文档给出的一个简单GP例子 (符号回归问题): Symbolic Regression Problem: Introduction to GP

在这里令我们的期望曲线为
( x 4 + x 3 + x 2 + x ) (x^4+x^3+x^2+x) (x4+x3+x2+x)
并在[-1, 1]区间设置20个等分点来评估适应度(fitness),来寻找一个GP生成的最拟合的表达式。

以下是对它的讲解,代码部分的comment当中也加入了对代码过程和操作的解读。

0. 导入模块

import math
import random
import operator  # python内部操作符,包含对象比较、逻辑比较、算术运算和序列操作
import numpy
# deap的常用五个模块齐了
from deap import base    # core
from deap import creator # core
from deap import tools	 # core
from deap import gp 
from deap import algorithms

1. Primitive Set的创建

* 注:Primitive set 为function set和terminal set的并集。primitives不是指functions + terminals,primitives仅仅指functions

def protectedDiv(left, right):  # 保护性除法,防崩
    try:
        return left / right
    except ZeroDivisionError:
        return 1

pset = gp.PrimitiveSet("MAIN", 1)  # "MAIN"是名称, 1为程序的inputs数量

# primitive set有了,可以向里面添加function(primitive)和terminal了
pset.addPrimitive(operator.add, 2) 
pset.addPrimitive(operator.sub, 2)
pset.addPrimitive(operator.mul, 2)
pset.addPrimitive(protectedDiv, 2)
pset.addPrimitive(operator.neg, 1)
pset.addPrimitive(math.cos, 1)
pset.addPrimitive(math.sin, 1)

# Ephemeral Constant: 没有固定值的"常量"
# 当这个节点被选择加到树里面时,所包含的函数被执行,函数输出被作为一个constant加入树中。
# 本质上它还是一个终止符,但这里不是用 addTerminal 方法生成的
pset.addEphemeralConstant("rand101", lambda: random.randint(-1, 1))  # (随机-1, 0, 1)

pset.renameArguments(ARG0='x')  # 程序inputs名称默认为ARG0,ARG1,ARG2,...,可自行修改
								# 上面设置了程序inputs数量为1, 所以这里可以改一个ARG0

这部分主要使用gp模块创建primitive set并向里面添加函数(function/primitive)和终止符(terminal)。
gp模块两种primitive set的创建:

  1. Loosely Typed GP: 不指定节点的输入和输出类型(type)。常用于较为单一处理类型的问题,比如我们现在看的这个只用到浮点数float的符号回归。

    创建 primitive set使用 gp.PrimitiveSet(名称, inputs数量, prefix=‘ARG’)
    添加函数节点: gp.addPrimitive(函数, 参数数量, name=None)
    添加终止符: gp.(终止符, name=None)

  2. Strongly Typed GP: 指定每一个节点输入和输出的类型 (它真的很严格 ! )。在Srong Typed GP的随机过程中,如果一个下层节点的输出类型和它连接的一个上层节点输入类型不一致,那么存在这种连接的树会被丢弃。

    创建 primitive set使用 gp.PrimitiveSetTyped(名称, 输入types, 返回/输出type, prefix=‘ARG’)
    添加函数节点: addPrimitive(函数, 输入types, 返回/输出type, name=None)
    添加终止符: addTerminal(终止符, 返回/输出type, name=None)

gp模块方法 官方文档: https://deap.readthedocs.io/en/master/api/gp.html

2. creator创建个体类

# 使用 creator.create 创建基因型(genotype)的个体和适应度(fitness)
# weights*必须*用以元组表达,这里我们要一个minimizing fitness,故传入-1.0
creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMin)

creator允许我们动态添加函数、属性来创建自己的自定类型。

  • evolutionary program 中,至少需要用creator.create创建两个类型:个体类和它对应的适应度的类。个体类表现为一个gp.PrimitiveTree,适应度表现为base模块中的Fitness基类。
  • 我们一会用到的fitness function使用均方误差来评估个体适应度,越小的值表明个体越优良,所以我们想要一个minimizing fitness。

creator模块方法 官方文档: https://deap.readthedocs.io/en/master/api/creator.html
base模块 官方文档: https://deap.readthedocs.io/en/master/api/base.html

3. Toolbox

“注册一些工具~”:使用register将自定函数填充到工具箱(base.Toolbox())当中,在之后的算法部分可以通过 toolbox.name 调用。算法部分,暨遗传迭代的实行部分,可以自定、也可使用algorithms模块中提供的算法方法。使用algorithms模块中的方法时,工具箱注册的函数名称固定,如适应度评估必须注册为“evaluate”, 遗传操作交叉注册为“mate”,变异注册为“mute”等。

# 过程中所需参数动态绑定
toolbox = base.Toolbox()
toolbox.register("expr", gp.genHalfAndHalf, pset=pset, min_=1, max_=2)
toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.expr)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("compile", gp.compile, pset=pset)

def evalSymbReg(individual, points):
    # compile: 将表达式转换为一个可调用的函数
    # 这里将individual的树形表现形式转换为executable形式, 得以输入参数得到输出来和期望输出进行比较
    func = toolbox.compile(expr=individual)
    
    # 均方误差评估个体适应度
    sqerrors = ((func(x) - x**4 - x**3 - x**2 - x)**2 for x in points)
    return math.fsum(sqerrors) / len(points),  # 注意这里 return一个tuple 
    										   # (*DEAP存储 fitness为可迭代类型)

toolbox.register("evaluate", evalSymbReg, points=[x/10. for x in range(-10,10)])
toolbox.register("select", tools.selTournament, tournsize=3)
toolbox.register("mate", gp.cxOnePoint)
toolbox.register("expr_mut", gp.genFull, min_=0, max_=2)
toolbox.register("mutate", gp.mutUniform, expr=toolbox.expr_mut, pset=pset)

# 装饰器: 一个包装函数,在被包装函数被调用前和调用后执行一些操作 (initializaiton and termination)
# 这里是分别对之前注册的mate函数和mutate函数进行包装,限制树形最大深度为17
toolbox.decorate("mate", gp.staticLimit(key=operator.attrgetter("height"), max_value=17))
toolbox.decorate("mutate", gp.staticLimit(key=operator.attrgetter("height"), max_value=17))

对于 register 的过程理解,我们来体会一下在代码段中的两个注册例子:
Example 1 :

  toolbox.register("mate", gp.cxOnePoint)

过程解读: 将gp.cxOnePoint函数命名为"mate",放入工具箱中。可以看成一个alias重命名。
执行时,调用工具箱的 “mate”,便会直接调用gp.cxOnePoint ,进行交叉。
注意:现在这句里只有两个参数,名称和被注册函数,但并不代表我们不需要给gp.cxOnePoint这个被注册函数某些参数。register中包含一个偏函数partial,可以让我们在之后再以字典方式传入参数。下面的例句同样验证了这一点。

Example 2 :

toolbox.register("population", tools.initRepeat, list, toolbox.individual)

过程解读:这是种群列表的注册,注册名称"population",tools.initRepeat 为被注册函数,后面紧跟的* list*, toolbox.individualtools.initRepeat 的参数。initRepeat 还应该接收一个数量n为参数才能正常执行,我们可以看到这行语句没有任何参数n,但在 4. Launching部分(下面)的语句 pop = toolbox.population(n=300) 中,参数n以字典形式传入了注册函数 population中。
执行 toolbox.population(n=300) 时,tools.initRepeat(list, toolbox.individual, n=300) 被调用,它会进行300次调用 toolbox.individual 并将返回的结果填入list结构的操作 (toolbox.population()返回一个list)。同理,toolbox.individual 调用 tools.initIterate(creator.Individual, toolbox.expr) ,将 toolbox.expr 返回值填充入 creator.Individual 类型。

后面使用的evolutionary algorithms会抽调这些注册的操作。

tools模块方法 官方文档: https://deap.readthedocs.io/en/master/api/tools.html

3.5. 统计数据

    stats_fit = tools.Statistics(lambda ind: ind.fitness.values)
    stats_size = tools.Statistics(len)
    # 创建一个有着两个统计目标的统计对象
    mstats = tools.MultiStatistics(fitness=stats_fit, size=stats_size)
    # 注册统计目标的统计项
    mstats.register("avg", numpy.mean)
    mstats.register("std", numpy.std)
    mstats.register("min", numpy.min)
    mstats.register("max", numpy.max)

设置统计项,注册到统计容器mstats中,在后面能够通过log观察运行过程中每一代的数据。

4. Launching

    pop = toolbox.population(n=300)  # 设置种群数量
    hof = tools.HallOfFame(1)		 # 名人堂最多存储一个个体
    pop, log = algorithms.eaSimple(pop, toolbox, 0.5, 0.1, 40, stats=mstats, halloffame=hof, verbose=True)

初始化、运行。

HallOfFame 存储最适合的个体,传入参数为1则其包含一个最适合的个体。名人(程序)堂 (笑~)

这里使用algoritms模块的eaSimple,一个最基础的算法。传入参数分别为种群对象,toolbox工具箱,crossover概率,mutate概率,演化多少代,统计容器,名人堂,verbose=true设置显示statistics。

eaSimple算法会自动抽调传入的toolbox里注册过的工具,toolbox中必须按特定名称注册对应操作的工具,如"mate", “mutate”, “select”, “evaluate” 这些一定要有,不能随意起名字…

我们可以在运行结束后通过查看名人堂hof来找到GP生成的表达式。如下直接打印出来:

print(str(hof.items[0]))
print(hof.keys[0])
print(hof.items[0].fitness)

# 输出:
# add(mul(x, sub(x, neg(mul(x, sub(x, neg(mul(x, x))))))), x)
# (5.1229736520700476e-33,)
# (5.1229736520700476e-33,)

我们之前设置名人堂数量为1,所以直接找hof.items[0]就可以。将打印出的语法树转为表达式为 x + x 2 + x 3 + x 4 x+x^2+x^3+x^4 x+x2+x3+x4,GP找到的最优解即为我们的期望曲线。

algorithms模块方法 官方文档: https://deap.readthedocs.io/en/master/api/algo.html

参考资料 (Reference)

  • Poli, Riccardo, et al. A Field Guide to Genetic Programming. Lulu Press, 2008.
  • DEAP Official Homepage, https://deap.readthedocs.io/en/master/index.html
  • symbolic regression source code, https://github.com/DEAP/deap/blob/1f6308ed596be7924e6f8cc85237d35349c77d33/examples/gp/symbreg.py


感谢阅读~

  • 27
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值