(三) 操作与算法
在开始复杂的算法之前,我们将会看到一些DEAP的基本操作。首先,我们将会通过创造简单的个体开始(上一节所讲内容),并且通过使用操作符使得它们之间产生联系。然后,我们将会学习如何使用算法和其他工具。
- A First Individual
首先导入需要的模块并且为创建的个体注册不同的函数,就是一些float类型的lists和两个最小优化目标。
from deap import tools, creator, base
IND_SIZE = 10 #A single individual has 10 arguments
#creating individuals
creator.create('MaxFitness', base.Fitness, weights = (1.0,1.0)) #double optimization object
creator.create('Individual', list, fitness = creator.MaxFitness) #creating a list to store individual
toolbox = base.Toolbox() #called Toolbox
toolbox.register('generating_method', random.random) #define random method
toolbox.register('individual', tools.initRepeat ,creator.Individual, toolbox.generating_method, n = IND_SIZE) #define individual, mode:initRepeat, each individual has 10 arguments
init = toolbox.individual()
print(init.fitness.valid)
- Evaluation
适应值计算是演化算法中最个性化的部分,这是库中唯一你必须自己写的部分。一个典型的适应度函数会采取一个个体作为元素,并且以元组(不可更改,是python的一种数据类型)的形式返回适应度函数。就像核心节所示的一样,适应度值是一个由float类型的数组组成的list,并且拥有验证优先级——即了解某个体是否被重新计算过适应值(即经历了一系列遗传操作)。适应度最终通过设置values与元组连接的集合。例如,下面的对之前创建个体的适应度计算并且将计算结果分配给相应的值。
def evaluate(individual):
#do some hard computing on the individual
a = sum(individual)
b = len(individual)
return a, 1. /b
init.fitness.value = evaluate(init) #setting value
print(init.fitness.valid)
print(init.fitness.value)
对于单目标优化问题没有什么不同,适应度函数必须返回一个元组,这是因为单目标优化是多目标优化的一个特例。
这一节讲的是适应度函数的设计,这个自主性很强,根据优化目标的数量返回相应数目的结果。
- Mutation
下一种的操作符是变异操作。在deap.tools中有许多种不同的变异操作,每一种变异都有它独特的特征,它们可能会适应于不同类型的个体。在阅读文档时要注意选择运算符是为了避免一些我们不想看到的情况(这里应该是指避免出现类似要求最大值而选择具有最小值的个体)。
变异操作的常规规则是它们只会变异,这就意味着一个独立的拷贝必须要在个体变异之前进行,这是为了对原个体和变异个体进行择优选择。
为了在init上实现变异操作(这里的例子是gaussian mutation),常用的函数如下
mutant = toolbox.clone(init) #define mutant as the clone of init
#new offspring init2 from mutant by Gaussian mutation
init2, = tools.mutGaussian(mutant, mu = 0.0, sigma = 0.2, indpb = 0.2)
del mutant.fitness.values
最后一行删除变异个体的适应度值是因为它们与个体不再相关了(因为变异之后个体的适应度值肯定与之前的值不一致)。如上所述,变异操作只会对一个个体进行变异,并且变异之后的个体与之前的适应度值没有任何关系,下面的代码将会展示init2与mutant实际上是一个个体。
print(init2 is mutant) #True
print(mutant is init) #False
- Crossover
第二种类型的操作就是交叉操作。同样在tools中有许多种不同的交叉操作方法(deap.tools)。每一种交叉操作也有它独特的特点,并且可以应用在不同类型的个体上。同样选择操作也会避免一些我们不想看到的问题的发生。
交叉操作的常规规则是这种操作只会发生在兄弟个体间(即在操作前要对进行交叉操作的个体完成配对)。这就意味着一个独立的拷贝必须在个体配对前完成,这是为了保证原始个体被保留或者该个体被其它个体引用。
让我们运用交叉操作去产生两个被克隆的子代个体
#clone two children
child1, child2 = [toolbox.clone(init) for init in (init, init2)]
#crossover operation
tools.cxBlend(child1, child2, 0.5)
del child1.fitness.values
del child2.fitness.values
- Selection
选择是在整个种群中通过可在deap.tools中提供的选择操作完成的。选择运算符通常将可迭代的个体容器和要选择的个体数作为第一个参数。它返回一个包含被选择个体的list,选择过程如下
#selecting the best one from child1 and child2
selected = tools.selBest([child1, child2], 2)
print(child2 in selected) #True
Warning:很重要的一点是选择操作并不会在选择过程中复制任何个体。如果一个个体被选择了两次并且其中的一个被修改过,那么其他的个体也同样会被修改。只有引用的个体会被复制。
通常情况下整个种群的复制将会在选择之后或者变化之前完成。
#clone will after the selection
offspring = [toolbox.clone(ind) for ind in selected]
- Using the Toolbox
toolbox(base.Toolbox())是包含所有进化操作的工具箱,从目标初始化到适应度计算。它允许在每个算法中实现简单的构造。toolbox基本上由两种方法组成,register()和unregister(),这两种函数是用来添加和移除工具的。
这部分的教程将会关注在遗传操作工具的registration上而不是初始化。通常的遗传操作工具的名字包括mate()、mutata()、evaluate()和selected(),然而,任何名字都可以被独特的register。这里会展示它们是如何在toolbox中注册的
def evaluateInd(individual):
result = individual **2
return result
#def mate method
toolbox.register('mate', tools.cxTwoPoint)
#def mutate method
toolbox.register('mutate', tools.mutGaussian, mu = 0, sigma = 1, indpb = 0.2)
#def select method
toolbox.register('select', tools.selTournament, toursize = 3)
#def evaluate method
toolbox.register('evalutae', evaluateInd)
使用toolbox作为注册工具可以帮助剩余的算法与操作集合之间保持独立(不必在剩余的代码中频繁调用toolbox以及tools)。使用这一结构可以使得代码可读性更强,同时可以轻松地改变所用的遗传操作方法。
- Using the Tools
当建立一个演化算法时,toolbox是用来确定你要使用的遗传操作方法的,它会调用一些通用的名字。例如,这里有一个十分简单的演化算法
fitnesses = list(map(toolbox.evaluate, pop))
for ind, fit in zip(pop, fitnesses):
ind.fitness.values = fit
toolbox.register('population', tools.initRepeat,list, toolbox.individual)
pop = toolbox.population(n = 30)
NGEN = 100 #ITERATION NUMBER
CXPB = 0.1 #CROSSOVER RATE
MUPB = 0.1 #MUTATION RATE
for i in range(NGEN):
#selection
offspring = toolbox.select(pop, len(pop)) #select len(pop) individuals from pop
#clone the selected individuals
offspring = list(map(toolbox.clone, offspring))
#crossover
for child1, child2 in zip(offspring[::2], offspring[1::2]):
if random.random() < CXPB:
toolbox.mate(child1, child2)
del child1.fitness.values
del child2.fitness.values
#mutation
for mutant in offspring:
if random.random() < MUPB:
toolbox.mutate(mutant)
del mutant.fitness.values
#evaluate the individuals with an invalid fitness
invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
fitness = toolbox.map(toolbox.evaluate, invalid_ind)
for ind, fit in zip(invalid_ind, fitness):
ind.fitness.values = fit
#the population is entirely replaced by offspring
pop[:] = offspring
这是一个完整的算法(虽然我也不知道它为啥用不了),它适用于任何的个体和操作,只要操作适合于个体的种类。就像最后一个例子这样,toolbox的使用允许去写一些很接近与伪代码的算法。现在你可以自己觉得你要去做什么了。
整个流程基本介绍完毕,首先是个体初始化和目标初始化creator.create()。随后使用toolbox.register()完成种群创建、适应度函数定义、遗传算子的选择(交叉+变异)、选择方法(轮盘赌or锦标赛...)。
- Tool Decoration
工具装饰是一个很强力的特征,它可以帮助你在演化过程中去精确地对算法和操作进行控制。一个装饰是指将函数进行包装,这叫做在真实的函数被调用前后做一些初始化和终止工作。例如,在一个有限的区域内,一种应用装饰符的方法是在变异和交叉的过程中保证个体不会越界。下面的定义就是检查是否有个体越界,装饰符是通过三个函数定义的,为了接收最小值和最大值。当变异或者交叉被调用时,边界将会检查它们的运算结果。
def checkBounds(min, max):
def decorator(func):
def wrapper(*args, **kargs):
offspring = func(*args, **kargs)
for child in offspring:
for i in xrange(len(child)):
if child[i] > max:
child[i] = max
elif child[i] < min:
child[i] = min
return offspring
return wrapper
return decorator
toolbox.register("mate", tools.cxBlend, alpha=0.2)
toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=2)
toolbox.decorate("mate", checkBounds(MIN, MAX))
toolbox.decorate("mutate", checkBounds(MIN, MAX))
TD就是一个越界判别的运算符,在每一步完成迭代之后可以对新生成的种群进行检查。
- Variations
变化允许建立简单的算法,使用预定义好的小模块。为了使用变化,toolbox一定会建立一个包含所需运算符的集合。例如,在最近展示的完整算法案例中,交叉和变异运算在varAnd()中被重新整合。这个函数要求toolbox包含mate()和mutate()函数,这种变化可以用来简化算法,就像下面这样
from deap import algorithms
for g in range(NGEN):
# Select and clone the next generation individuals
offspring = map(toolbox.clone, toolbox.select(pop, len(pop)))
# Apply crossover and mutation on the offspring
offspring = algorithms.varAnd(offspring, toolbox, CXPB, MUPB)
# Evaluate the individuals with an invalid fitness
invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
for ind, fit in zip(invalid_ind, fitnesses):
ind.fitness.values = fit
# The population is entirely replaced by the offspring
pop[:] = offspring
- Algorithms
有许多种可以在algorithm模块中使用的算法,它们十分简单并且可以反映演化算法的基本类型。这些算法使用Toolbox作为定义好的内容。一旦toolbox被设定完成,它们将会去调用这些算法。简单的演化算法需要五个参数,种群、toolbox、交叉率、变异率和种群迭代次数。
from deap import algorithms
algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=50)
理解简单演化算法的最好方法是阅读文档和理解源代码。
现在你可以在Python中构建自己的演化算法了!