本文主要是介绍DEAP 1.4.1 documention,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
创建类型
本教程展示了如何使用 `creator` 创建类型,并使用 `toolbox` 进行初始化。
适应度
提供的 `Fitness` 类是一个抽象类,需要 `weights` 属性才能发挥作用。最小化适应度使用负权重,而最大化适应度使用正权重。例如,以下代码在 `creator` 中创建了一个单目标最小化适应度 `FitnessMin`:
creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
`create()` 函数至少需要两个参数,一个是新创建类的名称,另一个是基类。任何后续参数都将成为类的属性。`weights` 属性必须是一个元组,这样多目标和单目标的适应度可以被相同地处理。例如:
creator.create("FitnessMulti", base.Fitness, weights=(-1.0, 1.0))
这段代码生成的适应度会最小化第一个目标,并最大化第二个目标。权重还可以用于调整每个目标之间的重要性。
个体
通过思考不同类型的进化算法(如GA、GP、ES、PSO、DE等),我们可以看到可能存在非常多样化的个体,这进一步支持了开发者无法提供所有可能类型的假设。这里有一个指南,介绍如何使用 `creator` 创建这些个体,并使用 `Toolbox` 初始化它们。
浮点数列表
第一个创建的个体将是一个包含浮点数的简单列表。为了生成这种个体,我们需要使用 `creator` 创建一个类,该类继承自标准列表类型并具有 `fitness` 属性。
import randomfrom deap import base
from deap import creator
from deap import toolscreator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)IND_SIZE=10toolbox = base.Toolbox()
toolbox.register("attr_float", random.random)
toolbox.register("individual", tools.initRepeat, creator.Individual,toolbox.attr_float, n=IND_SIZE)
新引入的 `register()` 方法至少需要两个参数:一个别名和分配给该别名的函数。任何后续参数在调用时都会传递给函数。因此,上述代码在 `toolbox` 中创建了两个别名:`attr_float` 和 `individual`。第一个别名指向 `random.random()` 函数。第二个别名是 `initRepeat()` 函数的快捷方式,将其参数固定为 `creator.Individual` 类,将函数参数固定为 `toolbox.attr_float()`,将重复次数参数固定为 `IND_SIZE`。
现在,调用 `toolbox.individual()` 将调用 `initRepeat()`,并返回一个由浮点数构成的完整 `creator.Individual` 个体,该个体具有单目标最大化 `fitness` 属性。
# 生成一个浮点数列表个体
individual = toolbox.individual()print(individual)
# [0.6321, 0.1143, 0.9382, 0.7981, 0.4325]
通过继承 `array.array` 或 `numpy.ndarray`,可以创建这种类型的不同变体。
creator.create("Individual", array.array, typecode="d", fitness=creator.FitnessMax)
creator.create("Individual", numpy.ndarray, fitness=creator.FitnessMax)
继承自 `array` 的类型在初始化时需要一个 `typecode`,就像原始类一样。
`typecode` 是在 Python 中用于定义 `array.array` 数据类型的一个字符。`array.array` 是一种数组类型,专门用于存储同一类型的元素。`typecode` 决定了数组中元素的类型,例如:
- `'b'`:有符号字节 (int, 1字节)
- `'B'`:无符号字节 (unsigned int, 1字节)
- `'h'`:有符号短整数 (int, 2字节)
- `'H'`:无符号短整数 (unsigned int, 2字节)
- `'i'`:有符号整数 (int, 4字节)
- `'I'`:无符号整数 (unsigned int, 4字节)
- `'d'`:双精度浮点数 (float, 8字节)例如,`typecode="d"` 表示创建一个双精度浮点数数组。
排列
排列表示法的个体与一般列表个体非常相似。实际上,它们都继承自基本的列表类型。唯一的区别是,排列个体需要生成一个随机排列,并将该排列赋予个体,而不是用一系列浮点数填充列表。
import random
from deap import base, creator, toolscreator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Individual", list, fitness=creator.FitnessMin)IND_SIZE = 10
toolbox = base.Toolbox()
toolbox.register("indices", random.sample, range(IND_SIZE), IND_SIZE)
toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.indices)
第一个注册函数调用 `random.sample()`,并固定其参数以从给定范围内抽样。第二个注册函数是 `initIterate()` 的快捷方式,参数为 `creator.Individual` 类和别名 `toolbox.indices()`。
调用 `toolbox.individual()` 会生成一个排列形式的 `creator.Individual` 个体,其单目标 `fitness` 最小化。
# 生成一个排列个体
individual = toolbox.individual()print(individual)
# [2, 0, 4, 1, 3]
群体(Population)
在进化算法中,群体是由多个个体、策略或粒子组成的集合。群体的初始化类似于个体的初始化,但群体内部通常包含多个个体。
无序群体(Bag Population)
无序群体(Bag Population)是最常用的一种群体类型。它通常使用列表来实现,没有特定的顺序,因此也不需要定义特殊的类来表示这种群体。初始化时,直接使用 `toolbox` 和 `initRepeat` 函数即可。
为了创建群体,我们需要将 `initRepeat` 函数与 `list` 和个体生成器(`toolbox.individual`)结合。`initRepeat` 函数会多次调用个体生成器,并将生成的个体放入列表中,形成群体。
from deap import tools# 假设已经定义好 toolbox.individual(见前面的例子)
# 使用 initRepeat 函数注册群体生成方法
toolbox.register("population", tools.initRepeat, list, toolbox.individual)# 生成一个包含 100 个体的群体
population = toolbox.population(n=100)print(population)
运行上述代码后,你将得到一个包含 100 个体的列表,这就是你的群体:
[[1, 0, 7, 9, 2, 4, 3, 8, 6, 5], [9, 2, 3, 8, 1, 5, 7, 4, 6, 0], ... [0, 3, 6, 4, 8, 7, 9, 2, 1, 5]]
运算符和算法
创建第一个个体
首先,我们需要导入必要的模块,并注册不同的函数来创建具有双目标最小化适应度的浮点数列表个体。
import random
from deap import base
from deap import creator
from deap import tools# 个体的长度
IND_SIZE = 5# 创建适应度类(最小化两个目标)
creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0))# 创建个体类,继承自 list,并具有 FitnessMin 属性
creator.create("Individual", list, fitness=creator.FitnessMin)# 定义工具箱
toolbox = base.Toolbox()# 注册生成随机浮点数的函数
toolbox.register("attr_float", random.random)# 注册生成个体的函数
toolbox.register("individual", tools.initRepeat, creator.Individual,toolbox.attr_float, n=IND_SIZE)# 生成第一个个体
ind1 = toolbox.individual()# 打印个体及其适应度是否有效
print(ind1) # [0.86..., 0.27..., 0.70..., 0.03..., 0.87...]
print(ind1.fitness.valid) # False 表示该个体的适应度当前无效,因为适应度值尚未被计算或分配。
通过简单的步骤,你可以使用 DEAP 创建个体并为其定义适应度。
在生成个体后,必须对个体的适应度进行计算,以使其变为有效。
接下来,你可以使用各种运算符(如交叉、变异)和算法(如选择、进化)来操作和优化这些个体。
评估
在进化算法中,评估是最具个性化的部分,也是唯一需要你自己编写的部分。典型的评估函数接受一个个体作为参数,并返回其适应度作为一个元组。
适应度是由浮点值构成的列表,可以通过 `valid` 属性来判断该个体的适应度是否需要重新评估。当适应度需要更新时,可以将计算后的值赋给适应度。
def evaluate(individual):# 对个体进行一些计算a = sum(individual) # 计算个体元素的总和b = len(individual) # 计算个体的长度return a, 1. / b # 返回适应度值# 将评估后的适应度值赋给个体的适应度属性
ind1.fitness.values = evaluate(ind1)# 打印适应度是否有效
print(ind1.fitness.valid) # True# 打印个体的适应度值
print(ind1.fitness) # (2.73, 0.2)
单目标适应度
即使是单目标适应度,评估函数也必须返回一个元组,因为单目标被视为多目标的特殊情况。因此,哪怕只有一个目标,返回的适应度也应当是一个包含单个值的元组,例如 `(a,)`。
变异
变异操作符是进化算法中的一种关键操作,负责对个体进行随机改变,以引入多样性。在 DEAP 工具库 (`deap.tools`) 中,提供了多种变异操作符,每种变异都有其特定的特性,并适用于不同类型的个体。因此,在使用某个变异操作符之前,请仔细阅读其文档以避免出现不期望的行为。
变异操作的通用规则
仅变异: 变异操作符只负责变异。这意味着如果需要保留原始个体,或原始个体是另一个个体的引用,在变异之前必须制作一个独立的副本。
适应度无效化: 变异操作符只负责修改个体本身,不会自动处理适应度无效化等其他任务。因此,在变异后,需要手动删除个体的适应度值,以表明适应度与新的个体不再相关。
示例:高斯变异
以下代码演示了如何对个体 `ind1` 应用高斯变异(Gaussian mutation)。
import random
from deap import base
from deap import creator
from deap import tools# 假设 ind1 已经通过之前的步骤创建
mutant = toolbox.clone(ind1) # 创建 ind1 的副本
ind2, = tools.mutGaussian(mutant, mu=0.0, sigma=0.2, indpb=0.2) # 应用高斯变异
del mutant.fitness.values # 删除适应度值,因为适应度已无效
解释
克隆个体:
`mutant = toolbox.clone(ind1)`:在变异之前,首先克隆一个个体 `ind1`,以便保留原始个体。`mutant` 是 `ind1` 的副本。
高斯变异:
`ind2, = tools.mutGaussian(mutant, mu=0.0, sigma=0.2, indpb=0.2)`:对 `mutant` 应用高斯变异。`mu` 是均值,`sigma` 是标准差,`indpb` 是每个属性发生变异的概率。变异后的个体 `ind2` 仍然是 `mutant`,即它们是同一个对象。
删除适应度值:
`del mutant.fitness.values`:删除变异个体 `mutant` 的适应度值,因为变异后,适应度已不再有效,需要重新评估。
print(ind2 is mutant) # True,表明 ind2 和 mutant 是同一个对象
print(mutant is ind1) # False,表明 mutant 是 ind1 的副本,而非引用
总结
在变异操作之前,必须克隆个体以保留原始个体。
变异操作符仅负责变异,不会自动处理适应度无效化。
变异后必须删除适应度值,以确保适应度能够在下一次计算中得到正确更新。
交叉 (Crossover)
交叉操作符是进化算法中的另一种关键操作,负责将两个个体进行组合以生成新的后代。在 DEAP 工具库 (`deap.tools`) 中,同样提供了多种交叉操作符,每种交叉操作符都有其独特的特性,并适用于不同类型的个体。因此,在使用某个交叉操作符之前,请仔细阅读其文档以避免不期望的行为。
交叉操作的通用规则
仅交叉: 交叉操作符只负责将个体配对,生成后代。这意味着如果需要保留原始个体,或原始个体是另一个个体的引用,在交叉之前必须制作独立的副本。
适应度无效化: 交叉操作符不会自动处理适应度无效化。生成新个体后,必须手动删除适应度值,以表明这些适应度已不再有效。
示例:混合交叉 (Blend Crossover)
以下代码演示了如何对两个个体 `ind1` 和 `ind2` 应用混合交叉操作(Blend Crossover)来生成两个新的后代 `child1` 和 `child2`。
import random
from deap import base
from deap import creator
from deap import tools# 预先克隆两个个体,以便在交叉操作中使用
child1, child2 = [toolbox.clone(ind) for ind in (ind1, ind2)]# 对两个个体进行混合交叉操作
tools.cxBlend(child1, child2, alpha=0.5)# 删除后代的适应度值,因为它们的适应度已无效
del child1.fitness.values
del child2.fitness.values
解释
克隆个体:
`child1, child2 = [toolbox.clone(ind) for ind in (ind1, ind2)]`:在交叉之前,首先克隆 `ind1` 和 `ind2`,以便保留原始个体。`child1` 和 `child2` 是 `ind1` 和 `ind2` 的副本。
混合交叉:
`tools.cxBlend(child1, child2, alpha=0.5)`:对 `child1` 和 `child2` 应用混合交叉操作。`alpha` 参数控制混合的程度。交叉后,`child1` 和 `child2` 是组合后的新个体。
删除适应度值:
`del child1.fitness.values` 和 `del child2.fitness.values`:删除后代个体的适应度值,以确保适应度能够在下一次计算中得到正确更新。
注意事项
在 Python 中,不能使用 `toolbox.clone([ind1, ind2])` 这种形式,因为如果 `ind1` 和 `ind2` 引用的是内存中的同一位置(即同一个个体),则只会生成一个独立副本,第二个个体将是这个独立副本的引用。这是由于深度拷贝机制的限制。深度拷贝时,第一次看到个体时会将其放入“memo”字典中,下一次看到相同的个体时,深度拷贝会停止,并将之前创建的深度副本的引用放入容器中。因此,在深度拷贝容器时需要格外小心。
总结
在交叉操作之前,必须克隆个体以保留原始个体。
交叉操作符仅负责交叉,不会自动处理适应度无效化。
交叉后必须删除适应度值,以确保适应度能够在下一次计算中得到正确更新。
选择 (Selection)
在进化算法中,选择操作是从群体中挑选出个体用于生成下一代的关键步骤。DEAP 提供了多种选择操作符,它们位于 `deap.tools` 模块中。选择操作符通常以个体的可迭代容器作为第一个参数,并指定要选择的个体数量。选择的结果是一个包含所选个体引用的列表。
示例:最佳选择 (Best Selection)
以下代码演示了如何从两个子代个体中选择出表现最好的个体:
selected = tools.selBest([child1, child2], 2)
print(child1 in selected) # 输出:True
重要提示
引用拷贝: 在选择过程中,选择操作符不会复制任何个体。如果某个个体被多次选择,任何一个对象的修改都会影响其他引用到该对象的个体。这是因为选择操作符仅复制了个体的引用,而不是创建新的副本。
即当你对一个已经被选择的个体进行修改时,如果这个个体被多个地方引用,那么这些引用到该个体的所有对象都会受到修改的影响。
举个例子来解释:
假设我们有一个群体中的两个个体 `ind1` 和 `ind2`。在选择操作中,这两个个体都被选中了,并且它们都被加入到了 `selected` 列表中。但实际上,`selected` 列表中的 `ind1` 和 `ind2` 只是对原始个体的引用,并不是全新的个体。
selected = tools.selBest([ind1, ind2], 2)
如果此时你对 `selected` 列表中的某个个体 `selected[0]` 进行修改:
selected[0][0] = 100 # 修改个体中的第一个元素
这个修改不仅会影响 `selected[0]`,同时也会影响原始的 `ind1`,因为 `selected[0]` 实际上只是对 `ind1` 的引用。
所以,当你修改了 `selected[0]` 的内容时,`ind1` 的内容也会随之改变。
这种行为是因为 Python 中的列表赋值是引用传递的,不会创建新的对象,而是将已有对象的内存地址传递给新变量。因此,修改任何一个引用到该对象的变量,都会影响所有引用到同一对象的变量。
# 原始个体
ind1 = [1, 2, 3]
ind2 = [4, 5, 6]# 选择操作(此处只选出 ind1)
selected = tools.selBest([ind1, ind2], 1)# 修改选中的个体
selected[0][0] = 100print(ind1) # 输出:[100, 2, 3]
在这个例子中,当你修改了 `selected[0]` 中的内容时,`ind1` 也会被同步修改,因为 `selected[0]` 实际上只是 `ind1` 的引用。
完整复制的时机: 通常情况下,群体的完整复制会在选择之后或变异之前进行。这可以确保原始个体不被修改,从而保持算法的稳定性。
示例:选择与复制
以下代码演示了如何从群体中选择个体,并复制这些个体以生成后代:
# 从群体中选择 LAMBDA 个个体
selected = toolbox.select(population, LAMBDA)# 克隆所选的个体,生成后代
offspring = [toolbox.clone(ind) for ind in selected]
解释
选择操作:
`selected = toolbox.select(population, LAMBDA)`:使用工具箱中的选择操作符从整个群体中挑选 `LAMBDA` 个个体。`population` 是一个包含所有个体的列表。
克隆选中的个体:
`offspring = [toolbox.clone(ind) for ind in selected]`:对选中的个体进行克隆,生成后代。这确保每个后代都是一个独立的个体,而不是对原始个体的引用。
总结
- 选择操作符返回的是个体的引用,而不是新创建的副本。
- 修改已选择的个体时,所有引用该个体的对象都会受到影响。
- 通常在选择之后,变异之前,对选中的个体进行完整的复制,以避免影响原始个体。
这种选择与复制机制在进化算法中确保了个体在变异和交叉过程中能独立进化,同时保证原始数据的完整性。
使用Toolbox
Toolbox 是 DEAP 框架中的一个核心组件,用于存储和管理进化算法中所需的各种工具。这些工具包括个体初始化器、变异和交叉操作、选择策略以及适应度评估函数。通过使用 Toolbox,我们可以方便地配置和调整算法中的各个部分,使代码更加模块化和易于维护。
Toolbox 的主要方法
- `register(name, function, *args, **kargs)`: 将一个函数或操作注册到工具箱中,并为其分配一个别名。`name` 是函数的别名,`function` 是要注册的函数,后续的参数 `*args` 和 `**kargs` 是在调用该函数时传递的默认参数。
- `unregister(name)`: 从工具箱中移除一个已注册的工具。
注册进化操作工具
通常在进化算法中,我们会使用以下几种基本操作:
mate(): 用于交叉(或称为杂交),通常涉及两个个体的基因交换。
mutate(): 用于变异,改变个体的一部分以引入新的特性。
evaluate(): 用于评估个体的适应度。
select(): 用于选择下一代个体。
下面是一个简单的代码示例,展示了如何使用 Toolbox 注册和配置这些工具:
from deap import base
from deap import tools# 创建一个Toolbox对象
toolbox = base.Toolbox()# 定义适应度评估函数
def evaluateInd(individual):# 在这里执行一些计算来评估个体result = sum(individual) # 举个例子,适应度是个体基因的总和return result, # 返回一个元组,DEAP要求返回值必须是一个元组# 注册各种进化操作工具到Toolbox
toolbox.register("mate", tools.cxTwoPoint) # 注册双点交叉操作
toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=1, indpb=0.2) # 注册高斯变异操作
toolbox.register("select", tools.selTournament, tournsize=3) # 注册锦标赛选择操作
toolbox.register("evaluate", evaluateInd) # 注册适应度评估函数
使用 Toolbox 的好处
模块化: 通过将各种操作工具注册到 Toolbox 中,可以让进化算法的其他部分独立于具体的操作工具,这使得算法的设计更加模块化。
易于维护和扩展: 如果需要更改某个操作工具,只需要修改 Toolbox 中的注册即可,而不需要深入到算法的各个部分进行修改。
便于实验: 可以轻松地在不同的实验中切换使用不同的交叉、变异或选择策略,只需要注册不同的工具即可。
一旦工具被注册到 Toolbox 中,你就可以在进化算法的不同阶段调用这些工具。例如:
# 创建两个个体
ind1 = [1, 2, 3, 4, 5]
ind2 = [6, 7, 8, 9, 10]# 执行交叉操作
toolbox.mate(ind1, ind2)# 执行变异操作
toolbox.mutate(ind1)# 执行适应度评估
fitness = toolbox.evaluate(ind1)
print(fitness)# 执行选择操作
selected = toolbox.select([ind1, ind2], 1)
通过以上方式,Toolbox 将所有的操作集中管理,使得进化算法的各个环节更加清晰和易于控制。
使用工具构建进化算法
在构建进化算法时,工具箱用于包含各种操作符,这些操作符通过它们的通用名称进行调用。以下是一个非常简单的代际进化算法示例,展示了如何利用工具箱中的工具来执行进化过程。
for g in range(NGEN):# 选择下一代个体offspring = toolbox.select(pop, len(pop))# 克隆选择的个体offspring = list(map(toolbox.clone, offspring))# 对后代进行交叉操作for child1, child2 in zip(offspring[::2], offspring[1::2]):'''
offspring[::2]:这是对列表 offspring 的切片操作。
::2 表示从列表的第一个元素开始,每隔两个元素取一个,所以它会选取列表中的所有偶数索引位置的元素(索引为 0, 2, 4, ...)。
offspring[1::2]:这是另一个对列表 offspring 的切片操作。
1::2 表示从列表的第二个元素开始,每隔两个元素取一个,所以它会选取列表中的所有奇数索引位置的元素(索引为 1, 3, 5, ...)。
zip(offspring[::2], offspring[1::2]):zip 函数将两个列表逐个元素配对,生成一个由元组组成的迭代器。每个元组包含来自两个列表的对应位置的元素。
这里,offspring[::2] 和 offspring[1::2] 是两个切片后的列表。zip 将它们配对,将偶数位置的元素和下一个奇数位置的元素配对成一个元组。'''if random.random() < CXPB:toolbox.mate(child1, child2)del child1.fitness.valuesdel child2.fitness.values# 对后代进行变异操作for mutant in offspring:if random.random() < MUTPB:toolbox.mutate(mutant)del mutant.fitness.values# 评估具有无效适应度的个体invalid_ind = [ind for ind in offspring if not ind.fitness.valid]fitnesses = map(toolbox.evaluate, invalid_ind)for ind, fit in zip(invalid_ind, fitnesses):ind.fitness.values = fit# 用后代完全替换当前种群pop[:] = offspring
代码逐步解析
1.选择下一代个体:
使用 `toolbox.select(pop, len(pop))` 从当前种群 `pop` 中选择个体生成下一代。选择的个体数量等于当前种群的大小。
2. 克隆选择的个体:
使用 `toolbox.clone` 对选择的个体进行克隆,确保后续操作(如交叉和变异)不会影响原始个体。
3. 交叉操作:
对克隆的后代进行交叉操作。这里使用了两个孩子一组的方式,每对孩子有 `CXPB` 的概率进行交叉。交叉后的个体的适应度值被删除,因为交叉会改变个体的基因,需要重新评估适应度。
4. 变异操作:
通过 `toolbox.mutate` 对每个后代进行变异操作。每个个体有 `MUTPB` 的概率进行变异,变异后的个体的适应度值同样被删除以待重新评估。
5. 评估无效适应度的个体:
找出那些因交叉或变异而适应度无效的个体,并使用 `toolbox.evaluate` 重新评估其适应度。
6. 更新种群:
最后,用生成的后代完全替换当前种群 `pop[:] = offspring`。
这篇关于DEAP 1.4.1 documention的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!