美文网首页
遗传算法简析

遗传算法简析

作者: 旭Louis | 来源:发表于2020-02-28 11:41 被阅读0次

    遗传算法(genetic algorithm (GA))是用于解决最优化的搜索算法,是进化算法的一种。遗传算法基于自然选择和遗传学的思想。这是随机搜索的智能化优化,可将搜索引导到解决方案空间中性能更好的区间。其优点是原理和操作简单、通用性强、不受限制条件的约束,全局解搜索能力,而且天生具有并行性,代码很容易放到集群上进行分布式并行处理,它们通常用于优化问题和搜索问题,提供比较有效的解决方案。

    遗传算法就是模拟自然选择的过程,物竞天择,适者生存,只有适应环境的种群才能繁殖并进入下一代。简而言之,他们在连续几代人中模拟“适者生存”,以解决问题。每一代都由一群人组成,每个人代表搜索空间和可能解决方案中的一个点。在程序中我们可以用字符串,整数,浮点数或者位来表示每一个人,类似于染色体都是独一无二的解。下面我们一起认识遗传算法的解题思路和几个相关概念。

    遗传算法的基本思路是模拟种群染色体的遗传结构和行为,大家想一想动物是怎样择偶的。

    (1)个体在有限的资源里面争取资源和交配权力。

    (2)那些最强壮的个体(适应者)就有更多交配权,产生更多的后代。我们知道在猩猩,狮子,狼等群居动物,一般只有一只雄性拥有交配权,它也是通过打败其他雄性对手才能做到这个领地的王者。

    (3)优秀父母的基因会传播给他们下一代,下一代很有可能超越父母成为更好的后代。

    (4)通过这样的方式每一代都会更适合这个生活环境。

    染色体怎样表示呢?它和问题的解有什么关系?

    每个个体是搜索空间中的一个解,它们会被编码,编码的长度取决于问题的可变成分,因此一条染色体(个体)由几个基因组成,如图所示。

    image.png

    如何判断哪些是适应者?

    当然我们要构建一个适应能力评分函数,计算每一个个体的适用能力,从中挑选最佳适应者。遗传算法可维护n个个体(染色体或问题的一个解)的种群及其适应能力评分,适应能力评分较高的个体比其他个体具有更多的繁殖机会。这些个体通过结合父母的染色体而交配并产生更好的后代。个体总是不变的,因此必须为新生命开辟空间。因此,当上一代的所有交配机会都用尽时,一些个体就会死亡并被新生命代替,最终创造了新一代。算法总是希望在新生代中找到更好的解决方案,同时使最不适合的个体死亡。

    一般情况下,每个新一代都比前几代有更多好基因。因此,每个新生代都有比前几代更好的“部分解决方案”。一旦产生的后代与前几代的适应能力没有显着差异,该种群便已经完全适应了这个环境,也就是说该算法已收敛到该问题的一组解决方案了。

    怎样模拟种群的繁衍?两个个体基因怎样遗传给下一代?

    盘古开天,创建初生代后,然后算法将使用以下操作来模拟演化。

    (1)选择操作:挑选优秀适应者(适应能力评分高的个体),让他们将基因传递给后代。一般的遗传算法都有一个交配概率,范围一般是0.6~1,这个交配概率反映两个被选中的个体进行交配的概率。例如,交配概率为0.8,则80%的“夫妻”会生育后代。

    (2)交叉操作:模拟个体之间的交配,使用选择操作挑选两个个体,并随机选择交叉位置。然后交换这些交换位置的基因,从而创造出一个全新的个体(后代)。如下图6-26所示,分别挑选两个个体不同部分,组合为一个新个体。

    注意:图中红色交换位置是随机产生的,可以是染色体的任意位置,不一定是连续,可以间隔挑选。

    (3)突变操作:模拟自然界中的基因突变,在后代中插入随机基因以保持种群的多样性。一般遗传算法都有一个固定的突变常数(又称为变异概率),通常是0.1或者更小,这代表变异发生的概率。根据这个概率,新个体的染色体随机的突变,通常就是改变染色体的一个字节,如图6-27所示。

    image.png

    遗传算法看上去就像拥有生命的算法,多么神奇,那么它的实际效果如何呢?,什么问题适合使用呢?适应能力评估函数,交配概率,突变概率需要怎样设置?下面我们通过一个具体例子来深入了解。

    吊死鬼游戏

    有一个小游戏叫吊死鬼游戏(hangman),在我们学习英语的时候,可能都在课堂上玩过。老师给定一个英文单词,同学们就猜是什么单词,猜错一次老师就画一笔,如果把吊死鬼画出来就没有机会猜了,游戏结束。现在我们不限猜测的次数,让电脑也来玩一下,看它要多久才能猜中。

    (1)把背景剥离,找出问题核心,简化问题。

    对于计算机来说,最简单就是盲目搜索,穷举所有情况,直到猜中为止。这样的穷举算法有兴趣的读者可以尝试用代码实现。这里我们当然要用刚学习的遗传算法,根据前面的介绍的算法思路,结合实际情况,我们来设置几个关键要素。

    • 字符从a至z视为基因,由这些字符生成的字符串被认为是染色体(个体),也就是一个解。
    • 适应能力得分就是用个体字符串与目标字符串比较,相同位置有相同字符的个数。 因此,具有较高适应值的个体(猜中更多字母的解)将获得更多的繁殖权力。
    • 设置种群大小(一代个体的个数),初始种群的数量很重要,如果初始种群数量过多,算法会占用大量系统资源;如果初始种群数量过少,算法很可能忽略掉最优解。这次我们设置种群的数量为100,同一代会有一百个体。
    • 设置下一代组成,适应能力前10%直接进入下一代,这称为精英模式,适应能力前50%的个体拥有繁衍权力,则交配概率为50%,也就是必然产生下一代。我们一般取较大的交配概率,因为交配操作可以加快解区间收敛,使解达到最有希望的最佳解区域,但交配概率太高也可能导致过早收敛,则称为早熟,只找到局部最优解就停止进化。还有突变概率,这里设置为0.1,也就是说在组合新一代的每一个基因,有45%来自父亲基因,有45%来自母亲基因,有10%发生变异,随机产生一个基因。
    • 终止条件是适应能力值等于单词长度,也就是找到了目标单词。

    (2)估算数据规模,算法复杂度

    如果用穷举算法,我们可以估算时间复杂度,就是每一个位置尝试一遍26个字母,所以时间复杂度O(26^n),n为单词的长度。但对于遗传算法来说,这是它的一个缺点,由于太多不确定性,遗传算法的精度、可行度、计算复杂性等方面参数还没有有效的定量分析方法。

    (3)动手写代码

    import random
    GENES = 'abcdefghijklmnopqrstuvwxyz'  # 基因
    class Individual(object): 
        '''此类代表种群中的个体'''
        def __init__(self, target, chromosome=None):
            self.target = target
            if chromosome: # 个体染色体,也就是所猜的单词解
                self.chromosome = chromosome
            else: # 若没有,创建一个随机的染色体
                self.chromosome = self.create_gnome()
            self.fitness = self.cal_fitness()  # 此个体的适用能力值
        @classmethod # 修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数
        def mutated_genes(cls):
            # 基因突变,也就是从a-z中随机挑选一个字母
            global GENES 
            gene = random.choice(GENES) 
            return gene
        def create_gnome(self):
            # 初始化个体基因
            gnome_len = len(self.target) # 根据目标单词长度随机构建一个字符串
            return [self.mutated_genes() for _ in range(gnome_len)]
        def mate(self, parent, mutation=0.1):
            # 进行繁衍,产生新一代
            pass
        def cal_fitness(self): 
            # 计算适应能力值,记录正确的字符数量
            fitness = 0
            for gs, gt in zip(self.chromosome, self.target): 
                if gs == gt: fitness+= 1
            return fitness
    class GeneticAlgorithm(object):
        '''遗传算法'''
        def __init__(self, target, population_size=100,
                     proba_elitism=10, proba_crossover=50,
                     mutation=0.1, max_generation=100):
                     pass
        def init_population(self):
            # 初始化第一代个体
            for _ in range(self.population_size):
                self.population.append(Individual(self.target))
        def main(self):
            # 主函数
            self.init_population() # 产生第一代个体
            # 若没有找到答案,并且没有达到最大进化代数,继续下一代演化
            pass
    

    这里有两个类【Individual】是代表个体,【GeneticAlgorithm】是代表遗传算法。【Individual】个体在实例化的时候会先判断是否有染色体,如果没有就通过create_gnome()函数随机生成一个染色体,然后马上通过cal_fitness()函数计算它的适应能力值。mate()函数负责繁衍下一代新个体和mutated_genes()函数负责模拟基因突变。【GeneticAlgorithm】在实例化的时候就要设定遗传算法的各种配置,其中目标单词【target】是必要参数,其他参数是可选参数,都已经设定了默认值。然后通过调用main()函数来启动算法运行,在运算过程中我们把每一代适应能力值最大的个体打印出来,一起观察种群的进化过程。现在通过一些例子来验证算法是否可行。

    ga = GeneticAlgorithm('generation')
    ga.main()
    # ---------结果-----------
    ga = GeneticAlgorithm('generation')
    第1代 单词: eikehaviro  适应值: 3
    第2代 单词: eikehaviro  适应值: 3
    第3代 单词: toneibxinn  适应值: 4
    第4代 单词: binagavion  适应值: 5
    第5代 单词: binagavion  适应值: 5
    第6代 单词: emnenahion  适应值: 6
    第7代 单词: emnenahion  适应值: 6
    第8代 单词: emnenahion  适应值: 6
    第9代 单词: ltneratton  适应值: 7
    第10代    单词: eenaration  适应值: 8
    第11代    单词: eeneration  适应值: 9
    第12代    单词: eeneration  适应值: 9
    第13代    单词: generation  适应值: 10
    

    从打印在的屏幕的信息中我们看到刚开始的适应能力值非常低,通过不断进化,适应值也逐步上升,到第十三代就找到答案,粗略估算每次100个个体,那么一共尝试了13*100=13000次就找到结果,效果还是不错的。因为遗传算法是具有不定性,当然不可能每次都是在第十三代就能得到结果,大家可以多尝试几次就会知道。同时你们可以尝试调整其他默认参数,看看对结果有什么影响。比如下面,我们测试不同大小种群,看一下有什么变化。

    target = 'announcement'
    for p in range(100, 1000, 100): # 种群大小,每次增加100
        generate = 0
        for _ in range(10): # 每一种种群经过10次运算
            ga = GeneticAlgorithm(target, population_size=p)
            ga.main()
            generate += ga.generation # 统计总共经过多少次进化
        print('种群数量为%d, 总进化代数为%d, 平均每次通过%.1f进化得到结果' % (p, generate, generate/10))
    # ---------结果-----------
    种群数量为100, 总进化代数为199, 平均每次通过19.9进化得到结果
    种群数量为200, 总进化代数为144, 平均每次通过14.4进化得到结果
    种群数量为300, 总进化代数为132, 平均每次通过13.2进化得到结果
    种群数量为400, 总进化代数为128, 平均每次通过12.8进化得到结果
    种群数量为500, 总进化代数为131, 平均每次通过13.1进化得到结果
    种群数量为600, 总进化代数为121, 平均每次通过12.1进化得到结果
    种群数量为700, 总进化代数为115, 平均每次通过11.5进化得到结果
    种群数量为800, 总进化代数为118, 平均每次通过11.8进化得到结果
    种群数量为900, 总进化代数为118, 平均每次通过11.8进化得到结果
    

    从结果我们看到,适当增加种群的个体数量,可以更快地获得结果。对于其他参数,大家都可以通过同样的方法进行测试,以下是其他参数的测试结果汇总到表6-28,每一次只改变一个参数的值,其他参数按默认值。

    参数 数值 结果(平均进化代数)
    突变概率 0.1 21.6
    突变概率 0.15 20.4
    突变概率 0.2 31.2
    突变概率 0.25 61.7
    突变概率 0.3 91.3
    突变概率 0.35 100
    突变概率 0.4 100
    突变概率 0.45 100
    繁衍权力 10 23.3
    繁衍权力 20 14.6
    繁衍权力 30 15.5
    繁衍权力 40 13.9
    繁衍权力 50 17.7
    繁衍权力 60 23.1
    繁衍权力 70 30.2
    繁衍权力 80 45.4
    繁衍权力 90 59.5

    从结果中我们知道,突变概率确实不能太大,越大需要更多次进化,并且在大于0.35就不能求出结果,因为我们设置了最大迭代次数就100。查看繁衍权力这个参数,当它在40时候,也就是种群前40%的个体能够有机会繁衍下一代,得到进化代数结果是最小的。在解决实际问题的时候大家也可以通过这样的调试方式,找到合适的参数值。现在我们再用调试过的参数,种群数是400,突变概率是0.15,繁衍权力是40,重新测试一下,看是否真的能提高效率。

    ga = GeneticAlgorithm('generation', population_size=400,
                           proba_crossover=40, mutation=0.15)
    ga.main()
    # ---------结果-----------
    第1代 单词: uedpwxdkon  适应值: 3
    第2代 单词: uedpwxdkon  适应值: 3
    第3代 单词: gefzrbvbin  适应值: 4
    第4代 单词: gexewgticx  适应值: 5
    第5代 单词: oeteravign  适应值: 6
    第6代 单词: xeneratijn  适应值: 8
    第7代 单词: xeneratijn  适应值: 8
    第8代 单词: xeneratijn  适应值: 8
    第9代 单词: generation  适应值: 10
    

    看来调优过的参数果然起作用,比刚才少了4代,在第九代就找到答案了。也许这只是一个意外,那么我们来做一个更严谨的测试,同样运行10次,看一下是否真实提高效率。

    generate1 = 0
    generate2 = 0
    for _ in range(10): # 经过10次运算
        ga = GeneticAlgorithm(target)
        ga.main()
        generate1 += ga.generation
        ga = GeneticAlgorithm(target, population_size=400, proba_crossover=40, mutation=0.15)
        ga.main()
        generate2 += ga.generation
    print('默认参数:总进化代数为%d, 平均每次通过%.1f进化得到结果' % (generate1, generate1/10))
    print('调优参数:总进化代数为%d, 平均每次通过%.1f进化得到结果' % (generate2, generate2 / 10))
    # ---------结果-----------
    默认参数:总进化代数为148, 平均每次通过14.8进化得到结果
    调优参数:总进化代数为101, 平均每次通过10.1进化得到结果
    

    相关文章

      网友评论

          本文标题:遗传算法简析

          本文链接:https://www.haomeiwen.com/subject/wxbkhhtx.html