基于DEAP库的python进化算法-2.进化算法各元素的DEAP实现

一.问题定义、个体编码与创建初始种群

1.优化问题定义

单目标优化

creator.create('FitnessMin', base.Fitness, weights=(-1.0, ))

在创建单目标优化问题时,weights用来指示最大化和最小化。此处-1.0即代表问题是一个最小化问题,对于最大化,应将weights改为正数,如1.0。
另外即使是单目标优化,weights也需要是一个tuple,以保证单目标和多目标优化时数据结构的统一。
对于单目标优化问题,weights 的绝对值没有意义,只要符号选择正确即可。

多目标优化

creator.create('FitnessMulti', base.Fitness, weights=(-1.0, 1.0))

对于多目标优化问题,weights用来指示多个优化目标之间的相对重要程度以及最大化最小化。如示例中给出的(-1.0, 1.0)代表对第一个目标函数取最小值,对第二个目标函数取最大值。

2.个体编码

实数编码(Value encoding):直接用实数对变量进行编码。优点是不用解码,基因表达非常简洁,而且能对应连续区间。但是实数编码后搜索区间连续,因此容易陷入局部最优。

实数编码的DEAP实现

'''
    creator是允许创建可以满足您的进化算法需求的类,实际上,可以从任何可以想象的类型(从列表到集合,dict,PrimitiveTree等)构建新的类,
    从而提供了实现遗传算法,遗传编程,进化策略,粒子群优化器等的可能性
    
    deap.creator.create(name, base[, attribute[, ...]])
        从creator模块中创建一个新的名为name的类,该类从base继承
        name:要创建类的名称
        base:继承的基类
        attribute:在此类的实例上添加的一个或多个属性
    deap.base.Fitness([values])
        如果将值作为元组提供,则使用这些值初始化适应度,否则为空  
        values:适应度的初始值作为元组 
    deap.base.Toolbox()
        包含演化运算符的演化工具箱
        clone()方法,该方法复制作为参数传递的任何元素,该方法默认为copy.deepcopy()函数
        map()方法,该方法将作为第一个参数给出的函数应用于作为下一个参数给出的可迭代对象的每个项目,该方法默认为map()函数
        register()方法用其他函数填充工具箱
            register(alias, method[, argument[, ...]])
            在alias name下注册一个函数,您可以提供默认参数,这些默认参数将在调用注册函数时自动传递。
            然后可以在函数调用时覆盖固定参数
            参数:
                alias:操作符在toolbox中接受的名称,如果别名已经存在,它将覆盖已经存在的操作符
                method:函数
                argument:一个或多个参数在调用时自动传递给已注册的函数
    tools.initRepeat(container, func, n)
        调用函数func n次,并在容器类型的容器中返回结果
        参数:
            container:从func放入数据的类型
            func:这个函数将被调用n次来填充容器
            n:重复func的次数
        返回:容器实例,其中填充了func的数据               
'''

from deap import creator,base,tools
import numpy as np

size = 5
creator.create('FitnessMin',base.Fitness,weights=(-1,)) # 单目标优化求最小值
creator.create('Individual',list,fitness = creator.FitnessMin)  # 创建Individual类,继承list

toolbox = base.Toolbox()
toolbox.register('Attr_float',np.random.rand)
# creator.Individual容器类型,toolbox.Attr_float函数
toolbox.register('Individual',tools.initRepeat,creator.Individual,toolbox.Attr_float,n=size)

ind = toolbox.Individual()
print(ind)

# [0.38990108948604063, 0.4021053788901321, 0.9262767512903585, 0.8699811269656622, 0.85975277694355]

二进制编码
在二进制编码中,用01两种数字模拟人类染色体中的碱基,用一定长度的01字符串来描述变量。
其优点在于种群多样性大,
但是需要解码,而且不连续,容易产生Hamming cliff(例如0111=7, 1000=8,改动了全部的4位数字之后,实际值只变动了1),在接近局部最优位置时,染色体稍有变动,就会使变量产生很大偏移(格雷编码(Gray coding)能够克服汉明距离的问题,但是实际问题复杂度较大时,格雷编码很难精确描述问题)。

变量的二进制编码:
由于通常情况下,搜索空间都是实数空间,因此在编码时,需要建立实数空间到二进制编码空间的映射。使用二进制不能对实数空间进行连续编码,但是可以在给定精度下对连续空间进行离散化。

例子来说明如何对变量进行二进制编码,假设需要对一个在区间[-2, 2]上的变量进行二进制编码:

选择编码长度:在需要6位精度的情况下,我们需要将该区间离散为(2+2) * 10^6个数。由于2 ^ 22 > 4*10 ^ 6,我们至少需要22位二进制数字来满足我们的精度要求。

设置解码器:将二进制数字x^bin转化为十进制x ^dec之后(在python中可以用int(‘Binary number’, 2)来实现),按照公式解码,
在这里插入图片描述
就可以得到一个在[-2,2]区间内的实数。
二进制编码的DEAP实现
以随机生成一个长度为10的二进制编码为例,本身DEAP库中没有内置的Binary encoding,我们借助Scipy模块中的伯努利分布来生成一个二进制序列。

from deap import base,creator,tools
from scipy.stats import bernoulli   # 伯努利分布

creator.create('FitnessMin',base.Fitness,weights=(-1.0,))   # 单目标优化
creator.create('Individual',list,fitness=creator.FitnessMin)

gen_size = 10

toolbox = base.Toolbox()
# bernoulli.rvs:生成随机数
toolbox.register('Binary',bernoulli.rvs,0.5)    # 概率为0.5
toolbox.register('Individual',tools.initRepeat,creator.Individual,toolbox.Binary,n=gen_size)

ind = toolbox.Individual()
print(ind)
# [1, 0, 1, 0, 0, 0, 1, 0, 0, 0]

序列编码
通常在求解顺序问题时用到,例如TSP问题,序列编码的每个染色体都是一个序列
序列编码的DEAP实现

'''
通常在求解顺序问题时用到,序列编码中每个染色体都是一个序列

    deap.tools.initIterate(container, generator)
    以iterable作为唯一参数调用函数容器,该Iterable必须由方法或对象生成器返回
    参数:
        container:从func放入数据的类型
        generator:可迭代的函数(列表,元组等),该可迭代的内容将填充容器
    返回:容器的实例,其中填充了来自生成器的数据
    此辅助功能可以与Toolbox结合使用,以将填充容器的生成器注册为individual
    
    
'''
from deap import base,creator,tools
import numpy as np
import random

creator.create('FitnessMin',base.Fitness,weights=(-1.0,))   # 单变量优化
creator.create('Individual',list,fitness=creator.FitnessMin)
# np.random.permutation
size =10
toolbox = base.Toolbox()
toolbox.register('Indices',random.sample,range(size),size)
toolbox.register('Individual',tools.initIterate,creator.Individual,toolbox.Indices)
ind = toolbox.Individual()

print(ind)
#[0, 1, 3, 6, 2, 8, 7, 4, 5, 9]

粒子
粒子是一种特殊个体,主要用于粒子群算法。相对于普通的个体,它额外具有速度、速度限制并且能记录最优位置。
粒子DEAP实现

'''
    粒子相对于普通个体,它额外具有速度、速度限制并且能记录最优位置
'''
from deap import base,creator,tools
import numpy as np
import random

# 创建实例
creator.create('FitnessMax',base.Fitness,weights=(1.0,1.0))     # 多目标优化求最大值
creator.create('Particle',list,fitness = creator.FitnessMax,speed=None,smin=None,smax=None,best=None)

# 自定义的粒子初始化函数
def initParticle(pcls,size,pmin,pmax,smin,smax):
    part = pcls(random.uniform(pmin,pmax) for _ in range(size))
    # part = pcls(np.random.uniform(low=pmin,high=pmax,size=size))
    part.speed = [random.uniform(smin,smax) for _ in range(size)]
    part.smin = smin
    part.smax = smax
    return part

toolbox = base.Toolbox()
# 为自己编写的initParticle函数注册一个alias "Particle",调用时生成一个2维粒子,放在容器creator.Particle中,
# 粒子的位置落在(-6,6)中,速度限制为(-3,3)
toolbox.register('Particle',initParticle,creator.Particle,size=2,pmin=-6,pmax=6,smin=-3,smax=3)

ind = toolbox.Particle()
print(ind)
print(ind.speed)
print(ind.smin,ind.smax)

# [-0.5091176056711042, -3.411699051528683]
# [1.9910066837235334, 0.238618206895568]
# -3 3

# [-4.512169085697154, -3.3189384493833614]
# [-2.18937298561473, 0.9288041818827244]
# -3 3

print(ind.fitness.valid)
# 结果为False,因为当前还没有计算适应度函数,所以粒子的最优适应度值还是invalid
3.初始族群的创建

一般族群
这是最常用的族群类型,族群中没有特别的顺序或者子族群
一般族群的DEAP实现
以二进制编码为例,以下代码生成由10个长度为5的随机二进制编码个体组成的一般族群

from scipy.stats import bernoulli
from deap import base,creator,tools
import numpy as np

# 定义问题
creator.create('FitnessMin',base.Fitness,weights=(-1.0,))   # 单目标优化最小值
creator.create('Individual',list,fitness = creator.FitnessMin)

# 生成个体
gene_size = 5
toolbox = base.Toolbox()
toolbox.register('Binary',bernoulli.rvs,0.5)
toolbox.register('Individual',tools.initRepeat,creator.Individual,toolbox.Binary,n = gene_size)

# 生成初始族群
N_pop = 10
toolbox.register('Population',tools.initRepeat,list,toolbox.Individual)
ind = toolbox.Population(n=N_pop)

print(ind)
# [[1, 1, 1, 1, 1], [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 1, 1, 0, 0], [1, 1, 1, 0, 1], [0, 1, 0, 0, 1], [1, 1, 1, 1, 0], [0, 0, 0, 0, 0], [1, 0, 1, 0, 0], [0, 0, 1, 0, 0]]
ind = np.array(ind).reshape(-1,5)
print(ind)
# [[1 1 1 1 1]
#  [1 1 0 0 0]
#  [1 1 0 0 0]
#  [0 1 1 0 0]
#  [1 1 1 0 1]
#  [0 1 0 0 1]
#  [1 1 1 1 0]
#  [0 0 0 0 0]
#  [1 0 1 0 0]
#  [0 0 1 0 0]]

同类群
同类群即一个族群中包含几个子族群。在有些算法中,会使用本地选择(Local selection)挑选育种个体,这种情况下个体仅与同一个邻域的个体相互作用
同类群的DEAP实现

from scipy.stats import bernoulli
from deap import base,creator,tools
import numpy as np

# 定义问题
creator.create('FitnessMin',base.Fitness,weights=(-1.0,))   # 单目标优化问题
creator.create('Individual',list,fitness = creator.FitnessMin)

# 生成个体
gen_size = 5
toolbox = base.Toolbox()
toolbox.register('Binary',bernoulli.rvs,0.5)
toolbox.register('Individual',tools.initRepeat,creator.Individual,toolbox.Binary,n=gen_size)

# 生成同类群
toolbox.register('deme',tools.initRepeat,list,toolbox.Individual)

deme_size = 10,50,100
population = [toolbox.deme(n=i) for i in deme_size]

print(population)
# [[[1, 1, 1, 1, 1], [0, 1, 1, 0, 1], [0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 1, 1, 0, 0], [0, 1, 1, 0, 1], [0, 1, 1, 1, 1], [1, 0, 1, 0, 0], [0, 1, 0, 0, 0], [1, 0, 0, 0, 1]], [[1, 0, 0, 0, 0], [1, 1, 0, 1, 0], [0, 0, 1, 1, 1], [0, 0, 0, 0, 1], [0, 0, 1, 1, 0], [1, 1, 1, 0, 1], [0, 1, 0, 1, 0], [0, 1, 0, 0, 0], [1, 1, 1, 1, 1], [1, 1, 1, 0, 0], [0, 1, 1, 1, 0], [0, 0, 1, 0, 0], [1, 1, 0, 1, 1], [1, 0, 1, 0, 0], [0, 0, 0, 0, 0], [1, 0, 1, 1, 1], [0, 0, 1, 0, 0], [0, 0, 1, 1, 1], [0, 0, 0, 1, 1], [1, 0, 0, 0, 0], [0, 0, 0, 0, 1], [0, 1, 1, 0, 1], [1, 0, 1, 0, 1], [1, 1, 1, 0, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 1], [0, 0, 0, 0, 0], [0, 0, 1, 1, 1], [1, 0, 1, 1, 1], [0, 1, 0, 0, 1], [1, 0, 1, 1, 1], [0, 1, 1, 0, 1], [1, 1, 0, 0, 0], [0, 1, 0, 0, 1], [1, 1, 1, 0, 1], [1, 0, 1, 0, 0], [0, 0, 0, 1, 1], [1, 1, 0, 1, 0], [1, 0, 0, 0, 1], [0, 1, 1, 1, 1], [1, 0, 0, 1, 0], [1, 0, 0, 1, 0], [0, 1, 0, 1, 1], [1, 0, 0, 1, 1], [0, 1, 0, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 0, 0], [1, 0, 1, 0, 0], [0, 1, 1, 1, 1], [0, 0, 0, 1, 0]], [[0, 1, 0, 0, 0], [0, 1, 1, 0, 1], [0, 0, 1, 0, 1], [0, 0, 1, 1, 1], [0, 1, 1, 0, 1], [0, 0, 0, 1, 0], [0, 0, 0, 1, 0], [0, 0, 0, 1, 1], [1, 0, 1, 0, 0], [1, 1, 0, 0, 1], [0, 1, 1, 0, 1], [0, 1, 0, 0, 0], [1, 1, 1, 1, 1], [1, 1, 0, 0, 0], [0, 0, 1, 0, 1], [1, 0, 0, 0, 1], [1, 1, 0, 1, 1], [0, 0, 1, 1, 0], [0, 1, 0, 1, 0], [1, 1, 0, 1, 1], [1, 1, 0, 1, 0], [0, 0, 1, 0, 1], [1, 0, 1, 0, 1], [0, 1, 0, 1, 1], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [1, 0, 0, 0, 1], [0, 0, 0, 1, 1], [0, 1, 0, 0, 0], [1, 0, 1, 1, 0], [0, 1, 1, 1, 1], [1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 1, 1], [1, 1, 0, 1, 1], [1, 0, 1, 0, 0], [1, 0, 1, 1, 1], [0, 0, 1, 0, 1], [1, 0, 0, 1, 1], [0, 1, 1, 1, 1], [1, 1, 1, 1, 0], [1, 1, 0, 1, 0], [0, 1, 0, 1, 0], [0, 1, 1, 0, 1], [1, 0, 1, 1, 1], [1, 1, 0, 1, 0], [0, 1, 1, 0, 1], [1, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 1, 1, 1, 0], [0, 1, 1, 0, 0], [1, 1, 0, 0, 0], [1, 1, 1, 1, 1], [0, 1, 0, 1, 1], [1, 1, 0, 1, 1], [0, 1, 0, 0, 0], [0, 1, 1, 1, 0], [0, 1, 0, 1, 0], [1, 1, 1, 1, 0], [1, 0, 1, 0, 0], [0, 0, 1, 1, 1], [0, 1, 0, 0, 1], [1, 1, 1, 0, 1], [0, 1, 0, 0, 0], [1, 0, 0, 0, 0], [0, 1, 1, 0, 0], [0, 0, 1, 1, 0], [0, 1, 1, 0, 1], [0, 1, 0, 1, 1], [1, 0, 0, 1, 0], [1, 1, 0, 1, 1], [0, 1, 1, 1, 0], [0, 1, 1, 1, 0], [1, 1, 0, 1, 0], [0, 1, 1, 1, 1], [0, 0, 1, 1, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 1], [1, 1, 1, 1, 0], [1, 0, 1, 1, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 1], [1, 1, 1, 1, 0], [1, 0, 1, 0, 0], [0, 1, 0, 1, 1], [0, 0, 0, 0, 1], [0, 1, 0, 0, 1], [1, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 1, 0, 1, 1], [1, 0, 0, 1, 1], [1, 0, 0, 0, 0], [1, 0, 1, 1, 0], [0, 1, 0, 0, 1], [1, 1, 0, 1, 1], [0, 1, 0, 1, 0], [1, 0, 0, 1, 0], [0, 1, 1, 1, 1], [0, 1, 0, 0, 0], [1, 1, 0, 0, 1]]]

粒子群
粒子群中所有粒子共享全局最优。在实现时需要额外传入全局最优位置与全局最优适应度给族群。
粒子群的DEAP实现

'''
    算法迭代时,需要更新该轮迭代中最优位置和最优适应度
'''
from scipy.stats import bernoulli
from deap import creator,base,tools

from deap import base,creator,tools
import numpy as np
import random

# 创建实例
creator.create('FitnessMax',base.Fitness,weights=(1.0,1.0))     # 多目标优化求最大值
creator.create('Particle',list,fitness = creator.FitnessMax,speed=None,smin=None,smax=None,best=None)

# 自定义的粒子初始化函数
def initParticle(pcls,size,pmin,pmax,smin,smax):
    # part = pcls(random.uniform(pmin,pmax) for _ in range(size))
    part = pcls(np.random.uniform(low=pmin,high=pmax,size=size))
    part.speed = [random.uniform(smin,smax) for _ in range(size)]
    part.smin = smin
    part.smax = smax
    return part

toolbox = base.Toolbox()
# 为自己编写的initParticle函数注册一个alias "Particle",调用时生成一个2维粒子,放在容器creator.Particle中,
# 粒子的位置落在(-6,6)中,速度限制为(-3,3)
toolbox.register('Particle',initParticle,creator.Particle,size=2,pmin=-6,pmax=6,smin=-3,smax=3)


creator.create('Swarm',list,gbest=None,gbestfit=creator.FitnessMax)
toolbox.register('swarm', tools.initRepeat, creator.Swarm, toolbox.Particle)

ind = toolbox.swarm(n=10)
print(ind)

# 生成10个二维粒子
# [[5.200304148961134, 3.4213399206695705], [3.8944090824509114, -3.88745134526645], [-2.9514487706857992, -4.304590950219268], [3.450157870566315, 1.642318575999842], [1.2612015164914165, 2.795146317474739], [3.3333351993489586, -1.2831641529678635], [3.728597597972149, 3.164079972288679], [2.3048321859467826, -1.3671834158126241], [-4.5201070374662775, 5.394044091350391], [5.162017907192103, -2.0265946827646792]]

二.评价

评价部分是根据任务的特性高度定制的,DEAP库中没有预置的评价函数模板。
使用DEAP时,需要注意的是:无论是单目标还是多目标优化,评价函数的返回值必须是一个tuple类型。
简单的例子

import numpy as np
from scipy.stats import bernoulli
from deap import base,creator,tools

# 定义问题
creator.create('FitnessMin',base.Fitness,weights=(-1.0,))    # 单目标优化,求最小值
creator.create('Individual',list,fitness=creator.FitnessMin)

# 生成个体
gen_size = 5
toolbox = base.Toolbox()
toolbox.register('Attr_float',np.random.rand)
toolbox.register('Individual',tools.initRepeat,creator.Individual,toolbox.Attr_float,n = gen_size)

# 生成初始族群
pop_size =10
toolbox.register('Population',tools.initRepeat,list,toolbox.Individual)
pop = toolbox.Population(n=pop_size)

# 定义评价函数
def evaluate(individual):
    return sum(individual),     # 注意这个逗号,即使是单变量优化问题,也需要返回tuple

# 评价初始族群
toolbox.register('Evaluate',evaluate)
fitnesses = map(toolbox.Evaluate,pop)
for ind,fit in zip(pop,fitnesses):
    # 适应度
    ind.fitness.values = fit
    print(ind.fitness.values)
    
# (2.799073230362443,)
# (3.182562718729237,)
# (2.666247235597166,)
# (1.3358638076609075,)
# (3.1632687857686936,)
# (2.3002100285403086,)
# (2.7960543695400584,)
# (1.9780333514536967,)
# (3.48145577698652,)
# (1.984007510985768,)

三.选择操作

1.DEAP内置的选择操作

DEAP的tools模块中内置了13种选择操作,对全部选择算子的描述可参考官方文档
在这里插入图片描述

2.常用选择介绍

下面介绍一些常用的配种选择操作
锦标赛选择

deap.tools.selTournament(individuals, k, tournsize, fit_attr = 'fitness')

锦标赛选择顾名思义,就是模拟锦标赛的方式,首先在族群中随机抽取tournsize个个体,然后从中选取具有最佳适应度的个体,将此过程重复k次,获得育种族群。tournsize越大,选择强度(selection intensity)越高,在选择之后留下的育种族群的平均适应度也就越高。比较常用的tournsize是2。

下图给出了由5个个体构成的族群中进行一次tournsize为3的锦标赛选择的过程。

在这里插入图片描述
锦标赛选择相比于轮盘赌选择,通常能够有更快的收敛速度,在实际场景中应用较多。
轮盘赌选择

deap.tools.selRoulette(individuals, k, fit_attr = 'fitness')

轮盘赌选择是最常见的选择策略,它可以看作是有放回的随机抽样。
在轮盘赌选择中,每个个体ai被选中的概率P(ai)与其适应度函数f(ai)成正比
在这里插入图片描述
在这里插入图片描述
注意在适应度可能为负数时,不适合用轮盘赌选择。

在实际应用中,很多文章都指出轮盘赌选择的性能较差,在通常情况下都不如随机抽样选择和锦标赛选择。
随机普遍抽样选择

deap.tools.selStochasticUniversalSampling(individuals, k, 
fit_attr = 'fitness')

随机普遍抽样选择是一种有多个指针的轮盘赌选择,其优点是能够保存族群多样性,而不会像轮盘赌一样,有较大几率对重复选择最优个体。
在这里插入图片描述
NSGA-II选择

deap.tools.selNSGA2(individuals, k, nd = 'standard')

NSGA-II全称为 Nondominated sorting genetic algorithm II,是Kalyanmoy Deb于2002年提出的。该方法解决了前代NSGA的三个痛点:计算复杂度高;缺少精英选择;需要给定额外参数值。

在使用该函数时,需要注意族群中个体数量必须要比k值大,因为在该算法中,每个个体在返回的选择列表中至多出现一次。

基于排序的选择算法
Linear ranking selection, Exponential ranking selection等,在DEAP中都没有给出直接的函数,需要自己实现。

3.选择操作代码示例

锦标赛选择

import numpy as np
from scipy.stats import bernoulli
from deap import base,creator,tools

# 定义问题
creator.create('FitnessMin',base.Fitness,weights=(-1.0,))    # 单目标优化,求最小值
creator.create('Individual',list,fitness=creator.FitnessMin)

# 生成个体
gen_size = 5
toolbox = base.Toolbox()
toolbox.register('Attr_float',np.random.rand)
toolbox.register('Individual',tools.initRepeat,creator.Individual,toolbox.Attr_float,n = gen_size)

# 生成初始族群
pop_size =10
toolbox.register('Population',tools.initRepeat,list,toolbox.Individual)
pop = toolbox.Population(n=pop_size)

# 定义评价函数
def evaluate(individual):
    return sum(individual),     # 注意这个逗号,即使是单变量优化问题,也需要返回tuple

# 评价初始族群
toolbox.register('Evaluate',evaluate)
fitnesses = map(toolbox.Evaluate,pop)
for ind,fit in zip(pop,fitnesses):
    # 适应度
    ind.fitness.values = fit

# 选择方式1:锦标赛选择
toolbox.register('TourSel',tools.selTournament,tournsize=2)     # tournsize=2的锦标赛选择
selectedTour = toolbox.TourSel(pop,5)       # 重复选择5次
print('锦标赛选择结果:')
for ind in selectedTour:
    print(ind)
    print(ind.fitness.values)

# 锦标赛选择结果:
# [0.5729872692894181, 0.008673765598957717, 0.6006883278136533, 0.36975231024932076, 0.19855155932776247]
# (1.7506532322791124,)
# [0.21388615180584047, 0.31945979630877375, 0.4338560359091559, 0.7876917252475398, 0.9495119641480025]
# (2.7044056734193127,)
# [0.7669843252601127, 0.13083426935753661, 0.0942547981612144, 0.23742334817419075, 0.47322101773875513]
# (1.7027177586918096,)
# [0.7839533597901636, 0.899141593580464, 0.7864007285990595, 0.5290679145037971, 0.17188869028685316]
# (3.1704522867603373,)

轮盘赌选择

import numpy as np
from scipy.stats import bernoulli
from deap import base,creator,tools

# 定义问题
creator.create('FitnessMin',base.Fitness,weights=(-1.0,))    # 单目标优化,求最小值
creator.create('Individual',list,fitness=creator.FitnessMin)

# 生成个体
gen_size = 5
toolbox = base.Toolbox()
toolbox.register('Attr_float',np.random.rand)
toolbox.register('Individual',tools.initRepeat,creator.Individual,toolbox.Attr_float,n = gen_size)

# 生成初始族群
pop_size =10
toolbox.register('Population',tools.initRepeat,list,toolbox.Individual)
pop = toolbox.Population(n=pop_size)

# 定义评价函数
def evaluate(individual):
    return sum(individual),     # 注意这个逗号,即使是单变量优化问题,也需要返回tuple

# 评价初始族群
toolbox.register('Evaluate',evaluate)
fitnesses = map(toolbox.Evaluate,pop)
for ind,fit in zip(pop,fitnesses):
    # 适应度
    ind.fitness.values = fit

# 选择方式2:轮盘赌选择
toolbox.register('RoulSel',tools.selRoulette)
selectedRoul = toolbox.RoulSel(pop,5)
print('轮盘赌选择结果:')
for ind in selectedRoul:
    print(ind)
    print(ind.fitness.values)   # 评价

# 轮盘赌选择结果:
# [0.9550769988029374, 0.6578118916399777, 0.7848918026380387, 0.4753637629038301, 0.8646738654753418]
# (3.7378183214601255,)
# [0.029709984320678395, 0.6451635420509633, 0.97539125983733, 0.9888777564043769, 0.7875687869284083]
# (3.4267113295417566,)
# [0.32535329853320827, 0.6346170850171972, 0.1472596326968919, 0.6370886415454745, 0.4328503853543919]
# (2.1771690431471633,)
# [0.9550769988029374, 0.6578118916399777, 0.7848918026380387, 0.4753637629038301, 0.8646738654753418]
# (3.7378183214601255,)
# [0.09799494527494135, 0.6574255771977907, 0.3028956551261657, 0.6399829324763072, 0.8012790620839682]
# (2.499578172159173,)

随机普遍抽样

import numpy as np
from scipy.stats import bernoulli
from deap import base,creator,tools

# 定义问题
creator.create('FitnessMin',base.Fitness,weights=(-1.0,))    # 单目标优化,求最小值
creator.create('Individual',list,fitness=creator.FitnessMin)

# 生成个体
gen_size = 5
toolbox = base.Toolbox()
toolbox.register('Attr_float',np.random.rand)
toolbox.register('Individual',tools.initRepeat,creator.Individual,toolbox.Attr_float,n = gen_size)

# 生成初始族群
pop_size =10
toolbox.register('Population',tools.initRepeat,list,toolbox.Individual)
pop = toolbox.Population(n=pop_size)

# 定义评价函数
def evaluate(individual):
    return sum(individual),     # 注意这个逗号,即使是单变量优化问题,也需要返回tuple

# 评价初始族群
toolbox.register('Evaluate',evaluate)
fitnesses = map(toolbox.Evaluate,pop)
for ind,fit in zip(pop,fitnesses):
    # 适应度
    ind.fitness.values = fit

# 选择方式3:随机普遍抽样
toolbox.register('StoSel',tools.selStochasticUniversalSampling)
selectedSto = toolbox.StoSel(pop,5)
print('随机普遍抽样选择结果:')
for ind in selectedSto:
    print(ind)
    print(ind.fitness.values)   # 评价

# 随机普遍抽样选择结果:
# [0.11743447300592158, 0.007155258960584665, 0.5496123423851749, 0.8194743589767185, 0.2663407793385737]
# (1.7600172126669733,)
# [0.4022002953002052, 0.5700670841477128, 0.6810491418747713, 0.30968413662931094, 0.6146393712698289]
# (2.577640029221829,)
# [0.23895973414426974, 0.9668862753877311, 0.6281296472666623, 0.9976928770103559, 0.03208842104803378]
# (2.8637569548570525,)
# [0.42934155709721167, 0.6651561511010192, 0.9919663951989708, 0.743237320593636, 0.9741755416189538]
# (3.803876965609791,)
# [0.8767025943724408, 0.7220956393470699, 0.9783334331656188, 0.7777399869822683, 0.6733775135894544]
# (4.028249167456853,)

四.交叉

1.DEAP内置的交叉操作(crossover)操作

在这里插入图片描述

2.常用交叉操作介绍

单点交叉

deap.tools.cxOnePoint(ind1, ind2)

最简单的交叉方式,选择一个切口,将两条基因切开之后,交换尾部基因段。尽管该方法非常简单,但是多篇文章指出,该算法在各种实验中性能都被其他交叉算法吊打,因此算是一种不建议使用的loser algorithm

两点交叉

deap.tools.cxTwoPoint(ind1, ind2)

用两个点切开基因后,交换切出来的基因段
在这里插入图片描述

均匀交叉

deap.tools.cxUniform(ind1, ind2, indpb)

指定一个变异几率,两父代中的每一个基因都以该几率交叉
在这里插入图片描述
部分匹配交叉

deap.tools.cxPartialyMatched(ind1, ind2)

部分匹配交叉主要用于序列编码的个体,进行部分匹配交叉包括三个步骤:
首先,选择父辈1的一段基因,复制到子代中;
其次,查找父辈2中同位置的基因段,选择没有被复制的基因,建立一个映射关系;
最后,进行冲突检查,如果有基因冲突,则通过建立的映射变换为无冲突的基因,保证形成的一对子代基因无冲突。
在这里插入图片描述
当解决路径规划问题时,如果最优sub-subrouine越长,PMX交叉后就越难在子代中保留。
有序交叉

deap.tools.cxOrdered(ind1, ind2)

在这里插入图片描述
混合交叉

deap.tools.cxBlend(ind1, ind2, alpha)

在这里插入图片描述
模拟二值交叉

deap.tools.cxSimulatedBinary(ind1, ind2, eta)

SBX是在1995年由Deb和Agrawal提出来的。二进制编码有只能进行离散搜索,Hamming cliff等问题,而实数编码尽管能在连续域上操作,但是搜索能力较弱(此处搜索能力定义为给定一对父辈,产生任意子代的几率,可以用扩散系数表征)。
模拟二值交叉试图综合二者的优势,在实数编码上模拟二进制编码的搜索特点。

参数ηc越大,产生的子代与父代越接近;该参数越小,产生的子代越可能与父代差距较大。

作者认为SBX在较难的测试中,表现比BLX-0.5要更优,尤其在多局部最优问题中表现出色。更具体的测试可以参见原文
在这里插入图片描述
混乱单点交叉

deap.tools.cxMessyOnePoint(ind1, ind2)

遗传算法中的基因都是如此有序:长短一致,编码方式整整齐齐,反而在自然界中这样的规律并不多见。基于这种情况,研究人员将交叉操作拆分为cut与splice,混乱单点交叉与一般的单点交叉最大的不同在于序列长度不会保持
在这里插入图片描述

3.交叉操作代码示例

官方提示最好不要直接用父代进行交叉,因此,有些交叉算法是in-place运算的,因此,最好先复制,再进行交叉。
单点交叉

import random
from deap import creator,base,tools

# 定义问题
creator.create('FitnessMin',base.Fitness,weights = (-1.0,))     # 单变量优化
creator.create('Individual',list,fitness=creator.FitnessMin)

# 生成序列个体
gene_size = 10
toolbox = base.Toolbox()
toolbox.register('Indices',random.sample,range(gene_size),gene_size)
toolbox.register('Individual',tools.initIterate,creator.Individual,toolbox.Indices)

# 创建两个序列编码个体
ind1,ind2 = [toolbox.Individual() for _ in range(2)]
print(ind1,'\n',ind2)
# [7, 9, 4, 1, 8, 5, 0, 6, 3, 2] 
#  [3, 0, 8, 7, 4, 9, 6, 1, 5, 2]

# 单点交叉
child1,child2 = [toolbox.clone(ind) for ind in (ind1,ind2)]
tools.cxOnePoint(child1,child2)
print(child1,'\n',child2)
# [7, 9, 8, 7, 4, 9, 6, 1, 5, 2] 
#  [3, 0, 4, 1, 8, 5, 0, 6, 3, 2]
# 可以看到从第三位开始被切开并交换

两点交叉

import random
from deap import creator,base,tools

# 定义问题
creator.create('FitnessMin',base.Fitness,weights = (-1.0,))     # 单变量优化
creator.create('Individual',list,fitness=creator.FitnessMin)

# 生成序列个体
gene_size = 10
toolbox = base.Toolbox()
toolbox.register('Indices',random.sample,range(gene_size),gene_size)
toolbox.register('Individual',tools.initIterate,creator.Individual,toolbox.Indices)

# 创建两个序列编码个体
ind1,ind2 = [toolbox.Individual() for _ in range(2)]
print(ind1,'\n',ind2)

# [2, 7, 5, 8, 4, 0, 1, 9, 3, 6]
#  [6, 5, 7, 2, 0, 9, 1, 4, 8, 3]

# 两点交叉
child1,child2 = [toolbox.clone(ind) for ind in (ind1,ind2)]
tools.cxTwoPoint(child1,child2)
print(child1,'\n',child2)
# [2, 7, 7, 8, 4, 0, 1, 9, 3, 6]
#  [6, 5, 5, 2, 0, 9, 1, 4, 8, 3]
# 可以看到基因段[7]与基因段[5]互换了

均匀交叉

mport random
from deap import creator,base,tools

# 定义问题
creator.create('FitnessMin',base.Fitness,weights = (-1.0,))     # 单变量优化
creator.create('Individual',list,fitness=creator.FitnessMin)

# 生成序列个体
gene_size = 10
toolbox = base.Toolbox()
toolbox.register('Indices',random.sample,range(gene_size),gene_size)
toolbox.register('Individual',tools.initIterate,creator.Individual,toolbox.Indices)

# 创建两个序列编码个体
ind1,ind2 = [toolbox.Individual() for _ in range(2)]
print(ind1,'\n',ind2)

# [4, 3, 1, 0, 9, 2, 7, 5, 8, 6]
#  [9, 4, 2, 8, 3, 5, 7, 6, 0, 1]

# 均匀交叉
child1,child2 = [toolbox.clone(ind) for ind in (ind1,ind2)]
tools.cxUniform(child1,child2,0.5)
print(child1,'\n',child2)

# [4, 3, 2, 8, 3, 2, 7, 5, 8, 1]
#  [9, 4, 1, 0, 9, 5, 7, 6, 0, 6]

部分匹配交叉

import random
from deap import creator,base,tools

# 定义问题
creator.create('FitnessMin',base.Fitness,weights = (-1.0,))     # 单变量优化
creator.create('Individual',list,fitness=creator.FitnessMin)

# 生成序列个体
gene_size = 10
toolbox = base.Toolbox()
toolbox.register('Indices',random.sample,range(gene_size),gene_size)
toolbox.register('Individual',tools.initIterate,creator.Individual,toolbox.Indices)

# 创建两个序列编码个体
ind1,ind2 = [toolbox.Individual() for _ in range(2)]
print(ind1,'\n',ind2)

# [6, 9, 3, 7, 2, 5, 1, 8, 4, 0]
#  [4, 9, 8, 6, 2, 7, 3, 1, 0, 5]

# 部分匹配交叉
child1,child2 = [toolbox.clone(ind) for ind in (ind1,ind2)]
tools.cxPartialyMatched(child1,child2)
print(child1,'\n',child2)
# [7, 9, 3, 6, 2, 5, 1, 8, 4, 0]
#  [4, 9, 8, 7, 2, 6, 3, 1, 0, 5]
# 与之前的交叉算子明显不同,这里的每个序列都没有冲突

有序交叉

import random
from deap import creator,base,tools

# 定义问题
creator.create('FitnessMin',base.Fitness,weights = (-1.0,))     # 单变量优化
creator.create('Individual',list,fitness=creator.FitnessMin)

# 生成序列个体
gene_size = 10
toolbox = base.Toolbox()
toolbox.register('Indices',random.sample,range(gene_size),gene_size)
toolbox.register('Individual',tools.initIterate,creator.Individual,toolbox.Indices)

# 创建两个序列编码个体
ind1,ind2 = [toolbox.Individual() for _ in range(2)]
print(ind1,'\n',ind2)

# [7, 6, 8, 4, 1, 5, 9, 2, 0, 3]
#  [5, 9, 3, 1, 0, 2, 7, 4, 8, 6]

# 有序交叉
child1,child2 = [toolbox.clone(ind) for ind in (ind1,ind2)]
tools.cxOrdered(child1,child2)
print(child1,'\n',child2)
# [6, 8, 1, 5, 9, 2, 7, 4, 0, 3] 
#  [5, 3, 1, 0, 7, 4, 9, 2, 8, 6]

混乱单点交叉

import random
from deap import creator,base,tools

# 定义问题
creator.create('FitnessMin',base.Fitness,weights = (-1.0,))     # 单变量优化
creator.create('Individual',list,fitness=creator.FitnessMin)

# 生成序列个体
gene_size = 10
toolbox = base.Toolbox()
toolbox.register('Indices',random.sample,range(gene_size),gene_size)
toolbox.register('Individual',tools.initIterate,creator.Individual,toolbox.Indices)

# 创建两个序列编码个体
ind1,ind2 = [toolbox.Individual() for _ in range(2)]
print(ind1,'\n',ind2)

# [9, 1, 3, 7, 6, 2, 0, 5, 8, 4] 
#  [7, 5, 9, 0, 3, 8, 4, 1, 6, 2]

# 混乱单点交叉
child1,child2 = [toolbox.clone(ind) for ind in (ind1,ind2)]
tools.cxMessyOnePoint(child1,child2)
print(child1,'\n',child2)
# [9, 1, 3, 7, 6, 2, 0, 9, 0, 3, 8, 4, 1, 6, 2] 
#  [7, 5, 5, 8, 4]
# 注意个体序列长度的改变

五.变异

1.DEAP内置的突变(Mutation)操作

在这里插入图片描述

2.常用突变介绍

高斯突变

tools.mutGaussian(individual, mu, sigma, indpb)

对个体序列中的每一个基因按概率变异,变异后的值为按均值为u,方差为σ的高斯分布选取的一个随机数。如果不希望均值发生变化,则应该将u设为0
乱序突变

tools.mutShuffleIndexes(individual, indpb)

将个体序列打乱顺序,每个基因位置变动的几率由indup给出
位翻转突变

tools.mutFlipBit(individual, indpb)

对个体中的每一个基因按给定对变异概率取非
有界多项式突变

tools.mutPolynomialBounded(individual, eta, low, up, indpb)

在这里插入图片描述
均匀整数突变

tools.mutUniformInt(individual, low, up, indpb)

对序列中的每一位按概率变异,变异后的值为[low,up]中按均匀分布随机选取的一个整数。

3.突变操作代码示例

如果想要保留父代作为参照,那么最好先复制,然后再进行变异

import random
from deap import creator,base,tools

random.seed(42)     # 保证结果可复现
# 定义问题
creator.create('FitnessMin',base.Fitness,weights=(-1.0,))
creator.create('Individual',list,fitness = creator.FitnessMin)

# 创建一个实数编码个体
gene_size = 5
toolbox = base.Toolbox()
toolbox.register('Attr_float',random.random)    # random.ramdom = np.random.rand
toolbox.register('Individual',tools.initRepeat,creator.Individual,toolbox.Attr_float,n = gene_size)

ind = toolbox.Individual()
print(ind)
# [0.6394267984578837, 0.025010755222666936, 0.27502931836911926, 0.22321073814882275, 0.7364712141640124]

# 高斯突变
mutant = toolbox.clone(ind)
tools.mutGaussian(mutant,3,0.1,1)
print(mutant)
# [3.672658632864655, 2.99827700737295, 3.2982590920597916, 3.339566606808737, 3.6626390539295306]
# 当均值给到3之后,变异形成的个体均值从0.5也增加大了3附近

# 乱序突变
mutant = toolbox.clone(ind)
tools.mutShuffleIndexes(mutant,0.5)
print(mutant)
# [0.22321073814882275, 0.7364712141640124, 0.025010755222666936, 0.6394267984578837, 0.27502931836911926]

# 有界多项式突变
mutant = toolbox.clone(ind)
tools.mutPolynomialBounded(mutant,20,0,1,0.5)
print(mutant)
# [0.674443861742489, 0.020055418656044655, 0.2573977358171454, 0.11555018832942898, 0.6725269223692601]

# 均匀整数突变
mutant = toolbox.clone(ind)
tools.mutUniformInt(mutant,1,5,0.5)
print(mutant)
# [0.6394267984578837, 3, 0.27502931836911926, 0.22321073814882275, 0.7364712141640124]
# 可以看出在第二个位置生成了整数3

六.环境选择

环境选择也就是重插入,在选择、交叉和突变之后,得到的育种后代族群规模与父代相比可能增加或减少。为保持种群规模,需要将育种后代插入到父代中,替换父代种群的一部分个体,或者丢弃一部分育种个体。

重插入分为全局重插入(Global reinsertion) 和本地重插入(Local reinsertion)两种,后者只有在使用含有本地邻域的算法时使用。
常用的全局重插入操作有以下四种:
1.完全重插入(Pure reinsertion):产生与父代个体数量相当的配种个体,直接用配种个体生成新一代族群。
2.均匀重插入(Uniform reinsertion):产生比父代个体少的配种个体,用配种个体随机均匀地替换父代个体。
3.精英重插入(Elitist reinsertion):产生比父代个体少的配种个体,选取配种后代中适应度最好的一些个体,插入父代中,取代适应度较低的父代个体。
4.精英保留重插入(Fitness-based reinsertion):产生比父代个体多的配种个体,选取其中适应度最大的配种个体形成新一代族群

通常来说,后两种方式由于精英保留的缘故,收敛速度更快,因此,比较推荐
DEAP中没有设定专门的reinsertion操作,可以用选择操作中的selBest,selWorst,selRandom来对育种族群和父代族群进行操作。

  • 11
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值