基于DEAP库的Python进化算法从入门到入土--(一)进化算法的基本操作与实现

目录

参考链接:基于DEAP库的Python进化算法从入门到入土--(一)进化算法的基本操作与实现 - 简书

前沿

关于DEAP

DEAP的特性:

进化算法简介

什么是进化算法

进化算法的优缺点

进化算法的基本元素利用这些元素,我们就可以依照流程图组成一个进化算法:

实现:问题定义、个体编码与创建初始族群 

1.优化问题的定义

creator.create('FitnessMin', base.Fitness, weights=(-1.0, ))函数讲解:

2.个体编码

creator.create('Individual', list, fitness = creator.FitnessMin)讲解

toolbox.register('Attr_float', random.random)讲解

toolbox.register('Individual', tools.initRepeat, creator.Individual, toolbox.Attr_float, n=IND_SIZE)讲解

二进制编码(Binary encoding)

 序列编码(Permutation encoding)

粒子(Particles)编码

3.初始族群的创建:

一般族群

同类群(Demes)

粒子群


参考链接:基于DEAP库的Python进化算法从入门到入土--(一)进化算法的基本操作与实现 - 简书

这次的博客,虽然参考了上述的链接,但是里面包含了函数讲解 ,可以快速入门,上手。

前沿

笔者最近开始学习如何用DEAP落实进化算法,本文既是教程,也是学习笔记,希望在帮助自己记忆理解的同时对同样正在学习的同学能有所帮助。碍于笔者水平有限,又非运筹优化科班出身,错误难免,请大家多多指正。 


关于DEAP

DEAP)是一个进化计算框架,能够帮助我们快速实现和测试进化算法。由于它的灵活与强大,目前在Github上已经有2848个star。


DEAP的特性:

各类遗传算法
遗传规划
进化策略
多目标优化
多种群之间的协作与竞争
并行计算
计算过程中设定检查点
设置基准模块,检验算法能力
支持粒子群算法、差分进化算法等
可以简单的使用 pip install deap 来安装,本文基于当前的最新版(deap - 1.3.1)


进化算法简介


什么是进化算法

进化算法(Evolutionary Algorithms)是一类元启发式算法的统称。这类算法借鉴大自然中生物的进化、选择与淘汰机制,通常先产生一个族群,然后不断进化与淘汰,最终产生能够在严酷的自然环境中生存的优异个体(也就是有较大适应度函数的可行解)。它具有自组织、自适应的特性,常被用来处理传统优化算法难以解决的问题。
 

进化算法的优缺点

优点:

  • 泛用性强,对连续变量和离散变量都能适用;
  • 不需要导数信息,因此不要求适应度函数的连续和可微性质(或者说不需要问题内在机理的相关信息);
  • 可以在解空间内大范围并行搜索;
  • 不容易陷入局部最优;
  • 高度并行化,并且容易与其他优化方法整合。

缺点:

  • 对于凸优化问题,相对基于梯度的优化方法(例如梯度下降法,牛顿/拟牛顿法)收敛速度更慢;
  • 进化算法需要在搜索空间投放大量个体来搜索最优解。对于高维问题,由于搜索空间随维度指数级膨胀,需要投放的个体数也大幅增长,会导致收敛速度变慢;
  • 设计编码方式、适应度函数以及变异规则需要大量经验。

进化算法的基本元素利用这些元素,我们就可以依照流程图组成一个进化算法:

  1. 个体编码(Individual representation): 将问题的解空间编码映射到搜索空间的过程。常用的编码方式有二值编码(Binary),格雷编码(Gray),浮点编码(Floating-point)等。
  2. 评价(Evaluation): 设定一定的准则评价族群内每个个体的优秀程度。这种优秀程度通常称为适应度(Fitness)。
  3. 配种选择(Mating selection): 建立准则从父代中选择个体参与育种。尽可能选择精英个体的同时也应当维护种群的多样性,避免算法过早陷入局部最优。
  4. 变异(Variation): 变异过程包括一系列受到生物启发的操作,例如重组(Recombination),突变(mutation)等。通过变异操作,父代的个体编码以一定方式继承和重新组合后,形成后代族群。
  5. 环境选择(Environmental selection): 将父代与子代重组成新的族群。这个过程中育种得到的后代被重新插入到父代种群中,替换父代种群的部分或全体,形成具有与前代相近规模的新族群。
  6. 停止准则(Stopping crieterion): 确定算法何时停止,通常有两种情况:算法已经找到最优解或者算法已经选入局部最优,不能继续在解空间内搜索。

 用文字表述实现一个简单进化算法的过程如下:

Generate the initial population P(0) at random, and set t = 0.
repeat
    Evaluate the fitness of each individual in P(t).
    Select parents from P(t) based on their fitness.
    Mate or Mutate to Selected P(t)
    Obtain population P(t+1) by making variations to parents.
    Set t = t + 1
until Stopping crieterion satisfied

实现:问题定义、个体编码与创建初始族群 

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)代表对第一个目标函数取最小值,对第二个目标函数取最大值。

creator.create('FitnessMin', base.Fitness, weights=(-1.0, ))函数讲解:

先看下函数的基本定义:该函数主要是为了创造出一个类

第一个参数:表示类名字

第二个参数:需要继承的一个类,因为这里是计算fitness,所以我们继承了base.fitness

第三个参数:会把接下来的所有参数,整理为该类的属性(attributes),下面的例子说明

函数举例:create("Foo", list, bar=dict, spam=1) 相当于:
        class Foo(list):
            spam = 1

            def __init__(self):
                self.bar = dict()

 Foo作为类名,并继承了list,如果参数是类(dict),则在__init__ 函数中初始化该实例,且该实例作为Foo类的一个属性(attributes)。如func.bar。如果参数不是类,则作为该类的一个静态属性(attributes)。如,func.spam

def create(name, base, **kargs):
    """Creates a new class named *name* inheriting from *base* in the
    :mod:`~deap.creator` module. The new class can have attributes defined by
    the subsequent keyword arguments passed to the function create. If the
    argument is a class (without the parenthesis), the __init__ function is
    called in the initialization of an instance of the new object and the
    returned instance is added as an attribute of the class' instance.
    Otherwise, if the argument is not a class, (for example an :class:`int`),
    it is added as a "static" attribute of the class.

    :param name: The name of the class to create.
    :param base: A base class from which to inherit.
    :param attribute: One or more attributes to add on instantiation of this
                      class, optional.

    The following is used to create a class :class:`Foo` inheriting from the
    standard :class:`list` and having an attribute :attr:`bar` being an empty
    dictionary and a static attribute :attr:`spam` initialized to 1. ::

        create("Foo", list, bar=dict, spam=1)

    This above line is exactly the same as defining in the :mod:`creator`
    module something like the following. ::

        class Foo(list):
            spam = 1

            def __init__(self):
                self.bar = dict()

    The :ref:`creating-types` tutorial gives more examples of the creator
    usage.

2.个体编码

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

实数编码DEAP实现:

from deap import base, creator, tools
import random
IND_SIZE = 5
creator.create('FitnessMin', base.Fitness, weights=(-1.0,)) #优化目标:单变量,求最小值
creator.create('Individual', list, fitness = creator.FitnessMin) #创建Individual类,继承list

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.8579615693371493, 0.05774821674048369, 0.8812411734389638, 0.5854279538236896, 0.12908399219828248]

creator.create('Individual', list, fitness = creator.FitnessMin)讲解

第一个参数:表示名字 Individual

第二个参数:需要继承的一个类,这里creator.Individual() 的对象本身有了和list一样的功能,可以append 和extend。这里我们数据的保存方式是list,所以list,也可以是tuple

第三个参数:将fitness 这个参数添加到Individual,作为Individual的一个属性(attributes)

经过刚刚操作,我们creator.Individual(),里面具有了两种类型的实例,一种是list,一种是fitness

第一个例子代表:继承 list 进行初始化赋值,比如a = list([1,2,3,4,5,6])

第二个例子代表:继承 list 进行append 

通过这个实例化过程,我们可以发现,creator.Individual(),给我们提供了两个位置,第一个位置表示继承了list这个类,第二个位置fitness 在初始化的时候,已经有了具体的参数。从下面的结果中看到creator.Individual()只能接收一个参数。

第三个例子代表:

根据a = list([1,2,3,4,5])这个特性,list(object),可以接收一个可迭代实例,包括数值型可迭代对象即序列、字典、集合对应的可迭代对象,同时也不知于此,所以:

creator.Individual()可以接收一个自身类的对象,相当于deepcopy,内容一致,但是内存地址不一致

toolbox.register('Attr_float', random.random)讲解

1、观察toolbox.register函数

    def register(self, alias, function, *args, **kargs):
        """Register a *function* in the toolbox under the name *alias*. You
        may provide default arguments that will be passed automatically when
        calling the registered function. Fixed arguments can then be overriden
        at function call time.

        :param alias: The name the operator will take in the toolbox. If the
                      alias already exist it will overwrite the the operator
                      already present.
        :param function: The function to which refer the alias.
        :param argument: One or more argument (and keyword argument) to pass
                         automatically to the registered function when called,
                         optional.

        The following code block is an example of how the toolbox is used. ::

            >>> def func(a, b, c=3):
            ...     print(a, b, c)
            ... 
            >>> tools = Toolbox()
            >>> tools.register("myFunc", func, 2, c=4)
            >>> tools.myFunc(3)
            2 3 4

第一个参数:表示别名 (后续函数调用用这个别名)

第二个参数:表示一个函数

第三个参数:实参组成元组传进来

第三个参数:实参组成字典传进来

2、观察random.random函数,作为第二个参数,function,由于random.random函数里面不需要传参,所以就没有了后续的内容

toolbox.register('Individual', tools.initRepeat, creator.Individual, toolbox.Attr_float, n=IND_SIZE)讲解

1、观察toolbox.register函数

上面已经说过了,这里不再赘述

2、观察tools.initRepeat函数

def initRepeat(container, func, n):
    """Call the function *container* with a generator function corresponding
    to the calling *n* times the function *func*.

    :param container: The type to put in the data from func.
    :param func: The function that will be called n times to fill the
                 container.
    :param n: The number of times to repeat func.
    :returns: An instance of the container filled with data from func.

    This helper function can be used in conjunction with a Toolbox
    to register a generator of filled containers, as individuals or
    population.

从函数名可以看出来,他包含了初始化+重复

第一个参数:container 是容器,表示可以把东西放进去的容器,但是我们可以放什么呢?

第二个参数:func 函数,这个func,是把他的结果填充到 容器中。

第三个参数:表示我们需要实现多少次func这个函数。

这个函数整体表示:我们执行n次func 的结果,填充到container中。

3、观察toolbox.register与tools.initRepeat整合在一起

这个函数里面一共包含了5个实参,那么如何划分呢?里面是对象,套,对象

首先toolbox.register里面需要三个参数

第一个参数:Individual

第二个参数:tools.initRepeat

第三个参数:(creator.Individual, toolbox.Attr_float)组成元组

第四个参数:{n:IND_SIZE} 组成字典

第三个和第四个参数具体如何分配,请查看链接:4. More Control Flow Tools — Python 2.7.18 documentation

第三个和第四个参数主要的意义是表明了register函数可以接收更多的参数,你来多少,我都可以接收。但是最重要的就是,你后续来的参数,其实都是为register里面的function服务的,具体来讲,后续来的参数creator.Individual, toolbox.Attr_float, n=IND_SIZE,都是为了传递给tools.initRepeat里面的,因为tools.initRepeat(container, func, n)包含了三个参数

其次tools.initRepeat

第一个参数:container 是容器,creator.Individual

第二个参数:func 函数,toolbox.Attr_float,也就是里面的 random.random

第三个参数:n=IND_SIZE,表示实现多少次func函数,也就是上面的 toolbox.Attr_float()

这其实相当于一个函数套娃。

 从这里可以观察到,把toolbox.Attr_float()结果组成了一个列表,然后传递到creator.Individual中的预留的位置list中(在2、个体编码中提到)

二进制编码(Binary encoding)

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

变量的二进制编码:

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

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

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

 设置解码器:

以随机生成一个长度为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) #创建Individual类,继承list

GENE_LENGTH = 10

toolbox = base.Toolbox()
toolbox.register('Binary', bernoulli.rvs, 0.5) #注册一个Binary的alias,指向scipy.stats中的bernoulli.rvs,概率为0.5
toolbox.register('Individual', tools.initRepeat, creator.Individual, toolbox.Binary, n = GENE_LENGTH) #用tools.initRepeat生成长度为GENE_LENGTH的Individual

ind1 = toolbox.Individual()
print(ind1)

# 结果:[1, 0, 0, 0, 0, 1, 0, 1, 1, 0]

这里咱们重点说明toolbox.register('Binary', bernoulli.rvs, 0.5)toolbox.register('Individual', tools.initRepeat, creator.Individual, toolbox.Binary, n = GENE_LENGTH)

toolbox.register('Binary', bernoulli.rvs, 0.5)

伯努利分布的取值,只要0和1,但是取0或者1的概率由参数决定:代码中(bernoulli.rvs, 0.5)表示进行一次伯努利实验,取0或者1的概率为0.5.根据以下实验证明:

 toolbox.register('Individual', tools.initRepeat, creator.Individual, toolbox.Binary, n = GENE_LENGTH)

上面已经说明了toolbox.register函数的具体意思。这里具体说明上述整理的意思:首先tools.initRepeat 里是一个重复性的过程,具体重复的函数是toolbox.Binary,同时承载这个函数的是容器creator.Individual,重复了n次toolbox.Binary,放入容器creator.Individual中。

最后通过 ind1 = toolbox.Individual() 完成了实例化操作

 序列编码(Permutation encoding)

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

同样的,这里的random.sampole也可以用np.random.permutation代替。

from deap import base, creator, tools
import random
creator.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)
ind1 = toolbox.Individual()
print(ind1)

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

 观察函数:random.sample(range(IND_SIZE), IND_SIZE)

  tools.initIterate 里面包含了容器和生成器,具体的来讲,容器指的是(creator.Individual),生成器指的是(toolbox.Indices   -> 具体指的是random.sample(range(IND_SIZE),IND_SIZE))

def initIterate(container, generator):
    """Call the function *container* with an iterable as
    its only argument. The iterable must be returned by
    the method or the object *generator*.

    :param container: The type to put in the data from func.
    :param generator: A function returning an iterable (list, tuple, ...),
                      the content of this iterable will fill the container.
    :returns: An instance of the container filled with data from the
              generator.

    This helper function can be used in conjunction with a Toolbox
    to register a generator of filled containers, as individuals or
    population.

        >>> import random
        >>> from functools import partial
        >>> random.seed(42)
        >>> gen_idx = partial(random.sample, list(range(10)), 10)
        >>> initIterate(list, gen_idx)      # doctest: +SKIP
        [1, 0, 4, 9, 6, 5, 8, 2, 3, 7]

    See the :ref:`permutation` and :ref:`arithmetic-expr` tutorials for
    more examples.
    """
    return container(generator())

粒子(Particles)编码

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

import random
from deap import base, creator, tools

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.speed = [random.uniform(smin, smax) for _ in range(size)]
    part.smin = smin
    part.smax = smax
    return part

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

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

# 结果:[-2.176528549934324, -3.0796558214905]
#[-2.9943676285620104, -0.3222138308543414]
#-3 3

print(ind1.fitness.valid)

# 结果:False
# 因为当前还没有计算适应度函数,所以粒子的最优适应度值还是invalid

3.初始族群的创建:

一般族群

这是最常用的族群类型,族群中没有特别的顺序或者子族群。

一般族群的DEAP实现:toolbox.register('population', tools.initRepeat, list, toolbox.individual)

以二进制编码为例,以下代码可以生成由10个长度为5的随机二进制编码个体组成的一般族群:

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)

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

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

同类群(Demes)

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

同类群的DEAP实现:

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)

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

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

toolbox.register("deme", tools.initRepeat, list, toolbox.Individual)
# 初始化同类群
DEME_SIZES = (10, 50, 100)
population = [toolbox.deme(n=i) for i in DEME_SIZES]
print(population)

粒子群

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

deap/examples/pso at 09b2562aad70c6f171ebca2bdac0c30387f5e8f5 · DEAP/deap · GitHub

import operator
import random

import numpy

from deap import base
from deap import benchmarks
from deap import creator
from deap import tools

creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Particle", list, fitness=creator.FitnessMax, speed=list, 
    smin=None, smax=None, best=None)

def generate(size, pmin, pmax, smin, smax):
    part = creator.Particle(random.uniform(pmin, pmax) for _ in range(size)) 
    part.speed = [random.uniform(smin, smax) for _ in range(size)]
    part.smin = smin
    part.smax = smax
    return part

toolbox = base.Toolbox()
toolbox.register("particle", generate, size=2, pmin=-6, pmax=6, smin=-3, smax=3)
toolbox.register("population", tools.initRepeat, list, toolbox.particle)

pop = toolbox.population(n=5)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值