zoukankan      html  css  js  c++  java
  • 遗传算法(白话文版)

    遗传算法是一种全局最优化算法,其很好地借鉴了自然界中生物进化的规律,通过模拟这种优胜劣汰的规律以寻找极值点。本文将从其原理,实现过程以及可行性的角度通俗地解剖这种算法!


    一、算法基本思想

           遗传算法是进化算法中的一种,初见其字面其实就可以大致推知出其的思想了。遗传算法就是建立在达尔文的生物进化论基础上的,通过模拟自然界不断筛选适应度比较好的个体,并通过使它们发生交叉(生物学中的专用名词,简单地理解就是生儿育女),变异,产生的新的个体会组成新的种群,继续进行新的一轮进化,最终留下的个体都有很高的适应度,这些个体中往往包含我们需要寻找的极值点。
           太抽象不理解?没关系,笔者还为你准备了精美的演示图片:
     

    1. 很久之前,有一群快乐小猪生活在山区里,它们生活的位置都是不同的。
    1. 然而,有一天突然从山脚下升起了一股毒烟,并慢慢向山上蔓延,此时,一些生活在低海拔的小猪就被毒死了,而生活在较高海拔的小猪侥幸活了下来,但它们知道毒烟不断向上蔓延,所以它们会向高处迁居。
    1. 迁居的过程是漫长的,在这个过程中,有些小猪会相爱?,并生下后代,然后,它们自身就会死去,它们的子孙从它们父母已达到的高度继续向上迁居。这样,一代又一代,有一天,这些小猪发现自己达到了最高点附近,此时,毒烟渐渐散去,小猪们在最高点继续快乐地生活下去。

    以上就是遗传算法的基本思想了。


    二、实现过程

           接下来,我们根据上面的图片讲解遗传算法怎么实现。
           首先,我们注意,小猪的初始生活点是随机的,也就是,有的小猪生活的海拔高,有的就比较低,对应到算法中就是,算法开始,我们会随机生成一组点(当然这些点需要在可行域内)。
           然后,毒气的出现,使得一些海拔低的小猪被毒死了,而海拔高的小猪活了下来,对应到算法就是选择(Selection)的过程(有?轮盘赌算法?和锦标赛算法),在这个过程中,我们会把适应度高的点保留下来,而遗弃适应度低的点(所谓适应度可以通俗地理解为这个点对应的函数值的大小)。
           接着,活下来的小猪会生育后代,其后代会在父母已达到的高度继续向上迁居,对应算法的过程是交叉(Crossover)过程,这个过程中,我们随机抽取几对点,每对点会进行类似“中和”的操作,适应度高的点和适应度低的点“中和”生成平均适应度更高的点。这个地方我们通过一个具体的例子来进行理解:我们假设抽取的一个点的坐标为(1,1),其适应度为8,而另一个点的坐标为(2,2),适应度为2,此时,这对点的平均适应度为5,接着,我们随机生成一个随机数a(0~1之间),然后,a(1,1)+(1-a)(2,2)和(1-a)(1,1)+a(2,2)这两个点(也就是(1,1)和(2,2)连线上的两点)就是之前两个点“中和”的结果,我们会发现这两个新生成的点平均适应度往往更好,最后我们用新生成的点来取代原先的两个点。当然,交叉的操作的实现方式有很多种,上面的只是其中一种实现方式。
           然而,我们知道,生物在进化过程中可能会发生变异(上面的漫画中未体现),变异后的个体适应度可能更好,当然也可能更差,所以我们会回到选择的过程,将适应度好的保留下来,将差的剔除掉,接着继续交叉,变异,这样就开始了新的一轮循环。当迭代到一定次数时,这些点渐渐都来到了最高点附近。在上面的迭代过程中,我们会记录下适应度最好的那个体,如果,新的迭代中产生更好的个体,则将该个体记录为最好的,算法结束后,我们返回这个适应度最好的个体即可。


    三、可行性评估

           遗传算法既然流传了几十年,解决了很多问题,从实践角度看,其肯定是可行的,在接下来的程序运行中我们也会看到。我们还需要从理论角度评估其可行性。
           首先,选择过程中,我们默认采用了?轮盘赌算法?,这个算法可以使得适应度高的个体有更大的概率保留下来,而适应度低的个体保留下来的概率则小,当然,也有可能,适应度低的个体保留下了而抛弃了适应度高的,但从概率角度看,随着测试次数的增加,往往适应度高的个体能被保留下来。
           我们再来看交叉的过程,这个过程中,当有一对个体发生交叉时,则产生的后代的适应度是居于其父母的适应度的之间的,这样看起来把适应度高的个体“拖累”了,但这样的结果是产生的后代的平均适应度往往增大了,也就是种群的平均水平提高了。
           上面讲到交叉操作使得种群的平均适应度提高了而“拖累”了适应度高的点,这样不就实现不了找最高的目标了呀。所以,我们还需要变异操作,使其中一些点发生“变异”,变异的点可能适应度更高,在下一轮的选择过程中,这些适应度高的点更有可能保留下来,组成新的种群,整个过程下来,整个种群的平均适应度基本上一直在提高(除非都到了最高点附近),而变异这种碰运气的操作使得我们可能瞎猫拖到一个最高点。
           可以说,整个遗传算法的各个阶段都是碰运气的过程,但碰到适应度高的点的可能性大,所以,在多次迭代和多次测试中,这些点往往能收敛于最高点。


    四、具体代码

    激动人心的时候来了,有了上述基础后,我们就可以阅读具体代码了(这段代码笔者在anaconda3中多次测试通过,但并不保证代码完全正确,如果你发现问题的话,可以在评论中留言,谢谢)。

    # -*- coding: utf-8 -*-
    """
    If you find bugs,please contact us :sj2050@vip.163.com or
    									zzzain46@gmail.com or
    									ruanyipu@gmail.com
    Thank you! 
    
    @author: SJ2050
    """
    
    import numpy as np
    import matplotlib.pyplot as plt
    
    # Solve Chinese garbled problem
    plt.rcParams['font.sans-serif']=['SimHei']
    plt.rcParams['axes.unicode_minus']=False 
    
    def ga_real(population_size,fit_func,feasible_range,**options):
        """
        input:
            popolation_size = population_size (int)
            fit_func = name of fitness value function (function) 
            feasible_range = feasible restrain region (two dimensional array)
            
            options : is_print(plot,default = True) ,
                    max_iter(iteration number,default = 3*population_size),
                    p_c(crossover possibility, default = 0.75),
                    p_m(mutation_possibility, default = p_c/20)
    
        return:
            best_value_so_far,
            best_chromosome_so_far,
            search_points,
            upper,
            lower,
            average
        """
        # initialize input
        is_print = options.get('is_print')
        if (is_print==None or is_print!=False):
            is_print = True
            
    #    selection = options.get('selection')
    #    if (selection==None or type(selection)!=int):
    #        selection = 1
        
        max_iter = options.get('max_iter')
        if(max_iter==None or type(max_iter)!= int or max_iter <= 0):
            max_iter = 3*population_size
        
        p_c = options.get('p_c')
        if(p_c==None or type(p_c) not in [int,float] or p_c <= 0 or p_c > 1 ):
            p_c = 0.75
        
        p_m = options.get('p_m')
        if (p_m==None or type(p_m) not in [int, float] or p_m<=0 or p_m > 1):
            p_m = p_c/20
        
        lower_boundary = np.array([feasible_range[i][0] for i in range(len(feasible_range))])
        upper_boundary = np.array([feasible_range[i][1] for i in range(len(feasible_range))])
        
        upper = np.array(np.zeros(max_iter+1))
        average = np.array(np.zeros(max_iter+1))
        lower = np.array(np.zeros(max_iter+1))
        search_points = np.array([[]])
        
        P = np.array(np.zeros((population_size,len(feasible_range))))
        fitness = np.array(np.zeros(population_size))
        
        # produce groups of chromosomes randomly
        for i in range(population_size):
            P[i,:] = lower_boundary + np.array([np.random.rand()*(upper_boundary[0] - lower_boundary[0]),
                                                np.random.rand()*(upper_boundary[1] - lower_boundary[1])])
            
        best_value_so_far = 0
        
        for i in range(population_size):
            fitness[i] = fit_func(P[i,:])
        
        max_value_index = np.argmax(fitness)
        max_value = fitness[max_value_index]   
    
        best_value_so_far = max_value
        best_chromosome_so_far = P[max_value_index,:]  
        
        upper[0] = max_value
        average[0] = np.average(fitness)
        lower[0] = np.min(fitness)
        search_points = np.append(search_points,[best_chromosome_so_far],axis = 1)
        
        # start iteration
        for i in range(max_iter):
            # when all chromosomes become same
            if(not check_same(fitness)):      
                 # Selection
                 mating_pool = roulette_wheel(P[:],fitness[:]) # deliver a copy of fitness
            
            # Crossover
            new_mating_pool = mating_pool.copy()
            for j in range(population_size//2):
                ind1 = int(np.floor(np.random.rand()*population_size))
                ind2 = int(np.floor(np.random.rand()*population_size))
                while(ind1 == ind2):
                    ind2 = int(np.floor(np.random.rand()*population_size))
               
                parent1 = mating_pool[ind1,:]
                parent2 = mating_pool[ind2,:]
    
                if (np.random.rand()<p_c):
                    alpha = np.random.rand()
                    offspring1 = alpha*parent1 + (1-alpha)*parent2
                    offspring2 = alpha*parent2 + (1-alpha)*parent1
                    new_mating_pool[ind1,:] = offspring1
                    new_mating_pool[ind2,:] = offspring2
            
            # Mutation
            for j in range(population_size):
                if (np.random.rand() < p_m):
                    alpha = np.random.rand()
                    new_mating_pool[j,:] = alpha*new_mating_pool[j,:] + 
                                            (1-alpha)*(lower_boundary + np.array([np.random.rand()*(upper_boundary[0] - lower_boundary[0]),
                                                                                  np.random.rand()*(upper_boundary[1] - lower_boundary[1])]))            
            P = new_mating_pool
            
            # Evaluation
            for j in range(population_size):
                fitness[j] = fit_func(P[j,:])
            
            max_value_index = np.argmax(fitness)
            max_value = fitness[max_value_index]   
            
            if (max_value > best_value_so_far): 
                best_value_so_far = max_value
                best_chromosome_so_far = P[max_value_index,:]
              
            upper[i+1] = max_value
            average[i+1] = np.average(fitness)
            lower[i+1] = np.min(fitness)
            search_points = np.concatenate((search_points,[P[max_value_index,:]]))
        
        # Plotting
        if is_print:
            plt.plot(range(0,max_iter+1),upper,'o:',range(0,max_iter+1),average,'x-',range(0,max_iter+1),lower,'*--')
            plt.title('遗传算法(real_num)迭代变化图')
            plt.legend(('最好','平均','最差'))
            plt.xlabel('迭代次数')
            plt.ylabel('适应度')
            plt.show()
        
        return best_value_so_far,best_chromosome_so_far,search_points,upper,lower,average
        
    # one way to achieve crossover process    
    def roulette_wheel(chromosomes,fitness):
        fitness = fitness - min(fitness) # to keep fitness positive
        each_fitness_proportion = fitness/np.sum(fitness)
        cumulation_proportion = 0
        mating_pool = np.array(np.zeros(chromosomes.shape))
        
        for i in range(len(fitness)):
            random_num =  np.random.rand()
            
            for j in range(len(fitness)):
                if (cumulation_proportion <= random_num and random_num < cumulation_proportion + each_fitness_proportion[j]):
                    mating_pool[i,:] = chromosomes[j,:]
                    break
                
                cumulation_proportion += each_fitness_proportion[j]
    
            cumulation_proportion = 0
            
        return mating_pool
    
    # check whether all the chromosomes become same
    def check_same(fitness):
        fitness = fitness - min(fitness) # to keep fitness positive
        # to check whether all the chromosomes are same 
        for i in range(len(fitness)):
            if (fitness[i]!=0):
                return False
    
        return True
    
    if __name__ == '__main__':
        def fit_func(chromosome):
            x,y = chromosome[0],chromosome[1]
            
            return 3*(1-x)**2*np.e**(-x**2-(y+1)**2) - 10*(x/5-x**3-y**5)*np.e**(-x**2-y**2) - (np.e**(-(x+1)**2-y**2))/3
        
        best_value,best_index,search_points,upper,lower,average = ga_real(20,fit_func,[[-3,3],[-3,3]],is_print = True)
        print(best_value)
        print(best_index)
    

    这个代码是实数版的遗传代码实现(我们这里也附上二进制版的遗传代码),选择过程选择了?轮盘赌算法?,交叉过程之前已提到过。我在这个代码的必要部分已经做好了注释,如果读者按照之前讲的那些思想来阅读代码的相应片段的,我相信,这些并不能难倒聪明的你。


    五、运行结果

           在笔者多次调试和修改后,这个代码的稳定性很好,收敛速度也很快,基本可以找到可行域中的最高点,偏差2%左右,如果多运行几次,所得结果基本可和真值媲美。

    在这里插入图片描述

    通过这个算法,我们也可以体会到大自然的美,顺自然者兴,逆自然者亡。

     
     

    最后,感谢阅读!如果你觉得可以的话,也可以为我点赞?哦!

     
     
     
     
     
     
     
     
     
     
     
     
     
     


    • 参考资料
      1、《An introduction to optimization 4th edition》
      2、某高校最优化课件
      3、轮盘赌算法
  • 相关阅读:
    configure: error: invalid variable name: `'
    [bzoj2002][Hnoi2010]Bounce弹飞绵羊——分块
    [bzoj2049][Sdoi2008]Cave 洞穴勘测——lct
    [bzoj4765]普通计算姬——分块
    [bzoj4766]文艺计算姬——完全二分图生成树个数
    [bzoj2243][SDOI2011]染色——树链剖分+线段树
    [bzoj3306]树——树上倍增+dfs序+线段树
    [bzoj1977][BeiJing2010组队]次小生成树 Tree——树上倍增+lca
    [bzoj3697]采药人的路径——点分治
    小蒟蒻的天坑
  • 原文地址:https://www.cnblogs.com/sj2050/p/13413689.html
Copyright © 2011-2022 走看看