遗传算法学习笔记DEAP库学习笔记

遗传算法学习—DEAP库学习笔记

简介

  • DEAP- 实现更灵活的进化
    由François-Michel De Rainville,Félix-Antoine Fortin,Marc-André Gardner,Marc Parizeau和Christian Gagné编写

DEAP是一个用Python编写的分布式进化算法(EA)框架,旨在帮助研究人员开发定制的进化算法。其设计理念提倡明确的算法和透明的数据结构,与大多数其他进化计算软件相比,后者倾向于使用黑匣子方法封装标准化算法。这种理念使其成为EA研究中快速原型设计框架,用于测试新想法。

介绍

DEAP框架设计遵循以下三个基本原则:

数据结构对于进化计算至关重要。它们必须促进算法的实现,并易于定制。 操作者选择和算法参数对于进化有很强的影响,但往往是问题依赖的。用户应该能够以最小的复杂度对算法的每个方面进行参数化。 EAs通常具有尴尬的并行性。因此,实现分布式范例的机制应该很容易使用。 借助其姊妹项目SCOOP和Python编程语言的强大,DEAP在简单而优雅的设计中实现了这三个原则。

数据结构

设计任何算法成功的一个非常重要的部分 - 如果不是最重要的部分 - 就是选择适当的数据结构。在设计解决现实世界问题的进化算法过程中,自由创建类型至关重要。DEAP的创作者模块允许用户:

使用一行代码(继承)创建类; 添加属性(组成); 将类分组在单个模块中(沙盒)。 在以下清单中,我们创建了一个最小化适应度。

from deap import base, creator
# 创建一个FitnessMin类,继承自base.Fitness,并设置权重为(-1.0,),表示最小化目标函数
creator.create("FitnessMin", base.Fitness, weights=(-1.0,)) 

创建函数期望至少有两个参数;要创建的类的名称和它继承自的基类。接下来的参数被用作类属性。因此,刚刚创建的类是一个 FitnessMin,继承自基类 Fitness,并具有一个权重属性设置为一个元组 (-1.0,),表示单目标最小化。多目标适应度可以使用多元素元组创建。

接下来,我们使用相同的机制定义一个继承自列表并组成一个适应度属性的 Individual 类。

创建一个Individual类,继承自list,并设置fitness属性为creator.FitnessMin,表示最小化目标函数

creator.create("Individual", list, fitness=creator.FitnessMin)

当一个个体被实例化时,它的适应度会被初始化为先前定义的FitnessMin类的一个实例。这在下面的例子中有所说明。

# 初始化一个个体,用于后续的进化计算
# 这里的个体是一个基因序列,由0和1组成
ind = creator.Individual([1,0,1,0,1])
# 设置个体的适应度值
# 适应度值是基于个体基因序列的某种计算结果
# 这里使用基因序列的总和作为适应度值
ind.fitness.values = (sum(ind),) # 求和
print(ind)
print(ind.fitness.values)

[1, 0, 1, 0, 1]
(3.0,)
在DEAP中,个体是由一系列二进制值创建的,其适应度值被设定为其元素的总和。适应度值始终是多目标的,单目标情况下是一个元素的元组。这个元组是由紧随求和操作的逗号实例化的。

操作符
运算符的选择是进化算法的另一个至关重要的部分。它必须简单直观。DEAP的工具箱使用户能够:

  • 为操作符创建别名;
  • 注册操作符的参数;
  • 高效地交换操作符;
  • 将所有操作符重新分组在一个结构中。
    下一个示例介绍了工具箱的构建以及如何注册操作符及其参数。
from deap import tools
# 这段代码创建了一个工具箱实例,用于存储和管理遗传算法中的各种算子和操作
toolbox = base.Toolbox()
# 将tools.cxOnePoint函数注册为toolbox中的"mate"操作符。
# toolbox是在遗传算法中用来存储操作符和参数的工具箱。
# 在这种情况下,将cxOnePoint函数注册为"mate"操作符意味着在遗传算法的繁殖过程中,
# 将会使用此函数来执行一点交叉操作。"mate"操作符通常用于交叉两个个体以产生新个体
toolbox.register("mate", tools.cxOnePoint)
# 将一个被称为"mutate"的突变操作注册到工具箱中,使用的是Gaussian突变工具,
# 其中mu代表均值为0.0,std代表标准差为1.0
toolbox.register("mutate", tools.mutGaussian, mu=0.0, std=1.0)

register函数需要至少两个参数;函数的别名和函数本身。当调用函数时,后续参数将传递给函数,类似于标准functools模块中的partial函数。因此,第一个运算符是一个注册在别名mate下的一点交叉。第二个运算符,高斯变异,以其参数注册在通用名称mutate下。这两个运算符与tools模块中的许多其他工具一起提供,以支持演化,这些工具将在本文末尾介绍。

在随后的实验证明中,通过将前一个清单的第三行替换为以下内容,将一点交叉替换为两点交叉就像措辞一样简单。

# 在遗传算法工具箱中注册一个操作函数叫"mate",对应的操作是两点交叉(crossover)。
toolbox.register("mate", tools.cxTwoPoint)

无论在哪里使用通用功能mate,都会使用新的两点交叉。

并行化

DEAP已经做好了并行准备。其想法是使用映射操作,将一个函数应用于序列的每个项目,例如评估个体的适应性。默认情况下,每个工具箱都注册了Python的标准映射函数。要并行评估个体,只需用类似于SCOOP提供的并行映射来替代这个别名,SCOOP是一个能够将任务分配给计算机集群的库。

from scoop import futures
# 使用register方法将futures.map函数注册到toolbox中,注册名称为"map"
# 这样,在toolbox中就可以通过"map"来调用futures.map函数了
toolbox.register("map", futures.map)

DEAP还兼容多进程标准模块,如果用户只想在单个多核机器上运行。

# 导入multiprocessing模块,用于后续创建进程池
import multiprocessing

# 初始化一个进程池,用于并行处理任务
pool = multiprocessing.Pool()

# 注册一个映射函数,使用进程池的map方法代替原生的map函数
# 这允许我们并行地应用一个函数到一个序列上,提高处理效率
toolbox.register("map", pool.map)

有了这些强大的工具,DEAP允许科学家和研究人员在很少的编程知识的情况下轻松实现分布式和并行EA。

实例阐述(Preaching by Example )
用DEAP进行进化计算的最佳介绍是展示简单而引人入胜的示例。以下部分阐明了算法如何易于实现,同时保持对它们行为的牢固掌握。第一部分介绍了一个传统的遗传算法,并展示了不同的显式级别。第二部分展示了如何在DEAP中实现遗传编程以及GP模块的多功能性。最后一个示例演示了如何轻松实现一个通用的分布式岛屿模型与SCOOP。

一个简单的遗传算法

进化计算中通常使用的一个例子是OneMax问题,它的目标是最大化二进制解中1的数量。一个个体包含的1越多,它的适应值就越高。使用遗传算法来找到这样一个个体相对比较简单。在随机生成的二进制个体群体上应用杂交和变异,并在每一代选择适应性最强的个体通常很快就会导致一个完美的(全为1)解决方案。这么简单的问题应该用一个非常简单的程序解决。

import random # 导入了必要的模块
from deap import algorithms, base, creator, tools
# 创建了两种类型;最大化的适应度(注意正权重),以及由该最大化适应度实例组成的列表个体
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)
# 定义了评估函数。通过对二进制列表的元素求和来计算其中的1的数量
# (再次注意只返回一个元素的元组,对应于单一目标适应度)
def evalOneMax(individual):
    return (sum(individual),)
# 实例化了一个工具箱,在其中注册了必要的运算符
toolbox = base.Toolbox()
# 生成二进制值,在本例中是在[0, 1]范围内的整数,使用了标准随机模块
toolbox.register("attr_bool", random.randint, 0, 1)
# 被分配给辅助函数initRepeat,该函数将容器作为第一个参数,
# 生成内容的函数作为第二个参数,重复次数作为最后一个参数。
# 因此,调用individual函数通过重复调用注册的attr_bool函数来实例化n=100位的个体
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_bool, n=100)
# 使用了相同的重复初始化函数来生成作为个体列表的群体。缺失的重复次数n将在程序的后面给出
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
# 注册评估、交叉、变异和选择运算符以及它们的所有参数
toolbox.register("evaluate", evalOneMax) # 评估
toolbox.register("mate", tools.cxTwoPoint) # 交叉
toolbox.register("mutate", tools.mutFlipBit, indpb=0.05) # 变异
toolbox.register("select", tools.selTournament, tournsize=3) # 选择

if __name__ == "__main__":
    # 实例化了一个由n=300个体组成的群体
    pop = toolbox.population(n=300)
    # 算法利用该群体和工具箱运行了ngen=40代,每一代具有0.5的交配概率cxpb和0.2的每一代变异个体的概率mutpb
    algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=40, verbose=False)
    #选择了结果群体中的最佳个体并在屏幕上显示
    print(tools.selBest(pop, k=1))

输出:
[[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
这段代码是一个简单的遗传算法示例,用于解决一个最简单的优化问题。以下是代码的解释:

首先,从Python的random库以及deap库中导入了一些必要的模块和函数。

创建了两个新的类,FitnessMax和Individual,分别表示个体的适应度和个体本身。FitnessMax类继承自base.Fitness类,weights=(1.0,)表示适应度的权重为1.0。

定义了评估函数evalOneMax,该函数将输入个体的所有元素相加并返回一个元组。

初始化了一个工具箱toolbox,并注册了一些函数,包括生成随机布尔值的attr_bool函数,初始化个体和种群的函数,评估函数,交叉函数,变异函数以及选择函数。

在主函数中,生成了一个初始种群pop,包含300个个体。然后使用eaSimple函数运行遗传算法,其中包括交叉概率cxpb=0.5,变异概率mutpb=0.2,迭代次数ngen=40,verbose=False表示不输出详细信息。

最后,打印出遗传算法找到的最优个体,该个体由tools.selBest函数返回。

控制算法行为

在开发、研究或使用EAs时,预先实施的固定算法很少能够满足所有需求。通常,开发人员/研究人员/用户必须深入框架来调整、添加或替换原始算法的某一部分。 DEAP在这一点上打破了传统的黑匣子方法; 它鼓励用户快速构建自己的算法。通过DEAP提供的不同工具,可以设计出一个能够解决大多数问题的灵活算法。

import random
from deap import algorithms, base, creator, tools
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)
def evalOneMax(individual):
    return (sum(individual),)

toolbox = base.Toolbox()
toolbox.register("attr_bool", random.randint, 0, 1)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_bool, n=100)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("evaluate", evalOneMax)
toolbox.register("mate", tools.cxTwoPoint)
toolbox.register("mutate", tools.mutFlipBit, indpb=0.05)
toolbox.register("select", tools.selTournament, tournsize=3)

if __name__ == "__main__":
    pop = toolbox.population(n=300)
    ngen, cxpb, mutpb = 40, 0.5, 0.2
    fitnesses = toolbox.map(toolbox.evaluate, pop)
    for ind, fit in zip(pop, fitnesses):
        ind.fitness.values = fit
    for g in range(ngen):
        pop = toolbox.select(pop, k=len(pop))
        pop = algorithms.varAnd(pop, toolbox, cxpb, mutpb)        
        invalids = [ind for ind in pop if not ind.fitness.valid]
        fitnesses = toolbox.map(toolbox.evaluate, invalids)
        for ind, fit in zip(invalids, fitnesses):
            ind.fitness.values = fit
	print(tools.selBest(pop, k=1))

这段代码使用了Python编程语言中的DEAP库来实现一个简单的遗传算法。主要步骤包括定义适应度函数、个体和群体,注册基本操作如交叉和变异,以及实现遗传算法的迭代过程。

在该代码中,首先利用creator模块创建了两个对象,分别代表适应度函数和个体,并定义了一个评估适应度的函数evalOneMax。然后使用base.Toolbox注册了各种操作,包括定义个体基因、初始化种群、评估、交叉、变异和选择等。

在主程序部分,首先生成了一个包含300个个体的种群,设置了迭代次数ngen、交叉概率cxpb和变异概率mutpb。随后对种群进行评估,并根据适应度值对个体进行排序。然后进行指定次数的迭代,每一代都进行选择、交叉和变异操作,直到达到设定的迭代次数。最后输出具有最佳适应度值的个体。

从以前的OneMax解决方案开始,算法的第一个分解是将预定义的eaSimple函数替换为一种世代循环。同样,这个例子虽然详尽但仍然非常简单。在前3行中,通过每个工具箱中包含的map函数将评估应用于种群中的每个个体。接下来,通过种群和评估的适应度值进行一个循环,设置每个个体的适应度值。之后,世代循环开始。它始于从种群中选择k个个体。然后,通过varAnd函数对选择的个体进行交叉和变异。也可以使用第二种变异方案varOr,其中个体通过交叉或变异产生。修改后,个体将针对下一个迭代进行评估。只有新产生的个体需要被评估;它们根据其适应性有效性进行筛选。这个程序的这个版本提供了改变停止准则和向进化添加组件的可能性。

import random, math
from deap import algorithms, base, creator, tools

creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)

def evalOneMax(individual):
    return (sum(individual),)

toolbox = base.Toolbox()
toolbox.register("attr_bool", random.randint, 0, 1)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_bool, n=100)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("evaluate", evalOneMax)
toolbox.register("mate", tools.cxTwoPoint)
toolbox.register("mutate", tools.mutFlipBit, indpb=0.05)
toolbox.register("select", tools.selTournament, tournsize=3)

if __name__ == "__main__":
    pop = toolbox.population(n=300)
    
    ngen, cxpb, mutpb = 40, 0.5, 0.2
    fitnesses = toolbox.map(toolbox.evaluate, pop)
    for ind, fit in zip(pop, fitnesses):
        ind.fitness.values = fit

    for g in range(ngen):
        pop = toolbox.select(pop, k=len(pop))
        pop = [toolbox.clone(ind) for ind in pop]

        for child1, child2 in zip(pop[::2], pop[1::2]):
            if random.random() < cxpb:
                toolbox.mate(child1, child2)
                del child1.fitness.values, child2.fitness.values

        for mutant in pop:
            if random.random() < mutpb:
                toolbox.mutate(mutant)
                del mutant.fitness.values
        
        invalids = [ind for ind in pop if not ind.fitness.valid]
        fitnesses = toolbox.map(toolbox.evaluate, invalids)
        for ind, fit in zip(invalids, fitnesses):
            ind.fitness.values = fit
    
    print(tools.selBest(pop, k=1))

输出:
[[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
更详细的内容可以通过用最后一个代码示例中提供的完整内容替换varAnd函数来获得。这个列表从在每个工具箱中可用的克隆工具对种群进行复制开始。然后,将交叉应用于一部分连续的个体。每个修改过的个体通过删除其值来使其适应度无效。最后,一定比例的种群会发生突变,他们的适应度值也会被删除。这种算法的变体可以控制应用顺序和运算符的数量,以及许多其他方面。

DEAP编写算法的明确性澄清了实验。这消除了关于算法不同方面的任何歧义,这些歧义可能会影响结果的可重复性和解释。

遗传编程

DEAP还包含设计遗传编程算法所需的每个组件,与遗传算法一样容易。例如,可以创建最常用的树个体如下。

import operator, random
from deap import algorithms, base, creator, tools, gp

creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMin)

原始树在gp模块中提供,因为它是Python标准库中少数几个未提供的数据类型之一。将填充树的基本操作符和终端的原语进行了重新分组。下面的清单显示了使用标准库operator模块提供的基本操作符实例化的原语集合。原语的arity是它的操作数数量。

pset = gp.PrimitiveSet(name="MAIN", arity=1)
pset.addPrimitive(operator.add, arity=2)
pset.addPrimitive(operator.sub, arity=2)
pset.addPrimitive(operator.mul, arity=2)
pset.addPrimitive(operator.neg, arity=1)
pset.renameArguments(ARG0="x")

DEAP 实现了由 Koza 提出的三种初始化方法来生成树:full(满树)、grow(生长树)和 half-and-half(一半一半)。在工具箱中注册初始化个体和种群的函数,就像在前面的遗传算法示例中一样。

toolbox = base.Toolbox()
toolbox.register("expr", gp.genFull, pset=pset, min_=1, max_=3)
toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.expr)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)

我们现在可以介绍一个符号回归评估函数的示例。首先,gp.compile函数将原始树转换为可执行形式,即一个Python函数,使用作为评估函数第三个参数给定的原始集合pset。然后,其余部分就是简单的数学运算:我们计算个体程序与目标 x 4 + x 3 + x 2 + x x^4+x^3+x^2+x x4+x3+x2+x
在一组点上的均方根误差,即评估函数的第二个参数。

def evaluateRegression(individual, points, pset):
    func = gp.compile(expr=individual, pset=pset)
    sqerrors = ((func(x) - x**4 - x**3 - x**2 - x)**2 for x in points)
    return math.sqrt(sum(sqerrors) / len(points)),

接下来,评估函数和变异操作符的注册方式与onemax示例类似,而其他操作符保持不变。

toolbox.register("evaluate", evaluateRegression, points=[x/10. for x in range(-10, 10)],
                 pset=pset)
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)
toolbox.register("select", tools.selTournament, tournsize=3)

if __name__ == "__main__":
    pop = toolbox.population(n=300)
    algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=40, verbose=False)
    bests = tools.selBest(pop, k=1)
    print(bests[0])
    print(bests[0].fitness)

输出:
sub(x, sub(mul(add(x, x), neg(x)), x))
(0.3186861041840387,)
此外,可以使用诸如NetworkX和PyGraphviz等外部库,将最佳的基本树形结构可视化如下。

import matplotlib.pyplot as plt
import networkx

nodes, edges, labels = gp.graph(bests[0])
graph = networkx.Graph()
graph.add_nodes_from(nodes)
graph.add_edges_from(edges)
pos = networkx.graphviz_layout(graph, prog="dot")

plt.figure(figsize=(7,7))
networkx.draw_networkx_nodes(graph, pos, node_size=900, node_color="w")
networkx.draw_networkx_edges(graph, pos)
networkx.draw_networkx_labels(graph, pos, labels)
plt.axis("off")
plt.show()

输出:
在这里插入图片描述

  • 32
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值