遗传算法详解与实践(单目标)

简介

遗传算法模拟了自然界物竞天择、适者生存的的进化过程,保留好的物种。

遗传算法是从代表问题可能潜在的解集的一个种群(population)开始的,而一个种群则由经过基因(gene)编码的一定数目的个体(individual)组成。每个个体实际上是染色体(chromosome)带有特征的实体。

是一种搜索最优解的优化模型,个人感觉很多这种搜索算法都是为了解决局部最优解的问题。遗传算法并不保证你能获得问题的最优解,但是使用遗传算法的最大优点在于你不必去了解和操心如何去“找”最优解,而只要简单的“否定”一些表现不好的个体就行了。

理解

遗传算法中每一条染色体/个体,对应着遗传算法的一个解决方案,一般我们用适应性函数(fitness function)来衡量这个解决方案的优劣。所以从一个基因组到其解的适应度形成一个映射。可以把遗传算法的过程看作是一个在多元函数里面求最优解的过程。

生动理解:

在遗传算法中,有很多袋鼠,它们降落到喜玛拉雅山脉的任意地方。这些袋鼠并不知道它们的任务是寻找珠穆朗玛峰。但每过几年,就在一些海拔高度较低的地方射杀一些袋鼠,并希望存活下来的袋鼠是多产的,在它们所处的地方生儿育女。就这样经过许多年,这些袋鼠们竟然都不自觉地聚拢到了一个个的山峰上,可是在所有的袋鼠中,只有聚拢到珠穆朗玛峰的袋鼠被带回了美丽的澳洲(最优解)。

概念

列举

基因型(genotype):性状染色体的内部表现;

表现型(phenotype):染色体决定的性状的外部表现,或者说,根据基因型形成的个体的外部表现;

进化(evolution):种群逐渐适应生存环境,品质不断得到改良。生物的进化是以种群的形式进行的。

适应度(fitness):度量某个物种对于生存环境的适应程度。

选择(selection):以一定的概率从种群中选择若干个个体。一般,选择过程是一种基于适应度的优胜劣汰的过程。

复制(reproduction):细胞分裂时,遗传物质DNA通过复制而转移到新产生的细胞中,新细胞就继承了旧细胞的基因。

交叉(crossover):两个染色体的某一相同位置处DNA被切断,前后两串分别交叉组合形成两个新的染色体。也称基因重组或杂交;

变异(mutation):复制时可能(很小的概率)产生某些复制差错,变异产生新的染色体,表现出新的性状。

编码(coding):DNA中遗传信息在一个长链上按一定的模式排列。遗传编码可看作从表现型到基因型的映射。

解码(decoding):基因型到表现型的映射。

个体(individual):指染色体带有特征的实体;

种群(population):个体的集合,该集合内个体数称为种群的大小。

编码方式

二进制编码和浮点数编码基因型

人的基因型是46条染色体所描述的却能解码成一个眼,耳,口,鼻等特征各不相同的活生生的人。

编码的前提是了解需要编码的个体的特征,尤其是与问题相关的特征。

物竞天择

物竞体现在适应度函数上。

天择体现在适应度高的个体以较大概率繁殖后代,在种群中产生更多的基因留存。轮盘赌(Roulette Wheel Selection)选择法即是计算适应度函数输入结果占比来确定这个概率。

基因交叉(重组)和变异

重组/交叉

二进制编码,其实就是简单对基因序列进行交换、移位、翻转等操作。

浮点数编码则可能产生介于中间的值,或者其他。

变异/突变

二进制编码体现在某个或多个位以一定概率取反。

浮点数编码体现在随机增加或减小一个步长,步长开始大,后面小,动态改变步长,尽可能加快收敛,同时保证能收敛到精确的点上。

题目实践

问题

\(f(x)=2*\sin{(x)}+\cos{(x)}\)的最大值

求解完整代码

代码来自参考中,比较详尽,可以直接运行,可能要去掉第3和7行。

轮盘赌部分没看懂,抽奖的方法,转盘转。

函数作用表

函数 作用
species_origin 返回个体数*基因数的矩阵population
translation 输入population,得到每个个体基因的十进制数的1*population_size的矩阵
function 输入population,得到每个个体的适应度\(f(x)\)一维数组
fitness 对function输入结果进行过滤,小于0的变为0(没看懂,可能是因为要计算适应度的和)
cumsum 输入fitness结果,输出各适应度所占的区间,依次增大,最后一个为1
selection 输入population和fitness,根据轮盘赌算法输出新的种群
crossover 输入population,基因的交叉操作,每相邻两个个体之间交换基因
mutation 变异操作,根据pm,在某一位置上取反
b2d 取最大值函数

完整代码

点击查看代码
# -*-coding:utf-8 -*-
#目标求解2*sin(x)+cos(x)最大值
%matplotlib notebook
import random
import math
import matplotlib.pyplot as plt
%matplotlib inline
class GA(object):
#初始化种群 生成chromosome_length大小的population_size个个体的种群

    def __init__(self,population_size,chromosome_length,max_value,pc,pm):

        self.population_size=population_size
        self.choromosome_length=chromosome_length
        # self.population=[[]]
        self.max_value=max_value
        self.pc=pc
        self.pm=pm
        # self.fitness_value=[]



    def species_origin(self):
        population=[[]]
        for i in range(self.population_size):

            temporary=[]
        #染色体暂存器
            for j in range(self.choromosome_length):

                temporary.append(random.randint(0,1))
            #随机产生一个染色体,由二进制数组成

            population.append(temporary)
            #将染色体添加到种群中
        return population[1:]
            # 将种群返回,种群是个二维数组,个体和染色体两维

    #从二进制到十进制
    #编码  input:种群,染色体长度 编码过程就是将多元函数转化成一元函数的过程
    def translation(self,population):

        temporary=[]
        for i in range(len(population)):
            total=0
            for j in range(self.choromosome_length):
                total+=population[i][j]*(math.pow(2,j))
            #从第一个基因开始,每位对2求幂,再求和
            # 如:0101 转成十进制为:1 * 20 + 0 * 21 + 1 * 22 + 0 * 23 = 1 + 0 + 4 + 0 = 5
            temporary.append(total)
        #一个染色体编码完成,由一个二进制数编码为一个十进制数
        return temporary
   # 返回种群中所有个体编码完成后的十进制数



#from protein to function,according to its functoin value

#a protein realize its function according its structure
# 目标函数相当于环境 对染色体进行筛选,这里是2*sin(x)+math.cos(x)
    def function(self,population):
        temporary=[]
        function1=[]
        temporary=self.translation(population)
        for i in range(len(temporary)):
            x=temporary[i]*self.max_value/(math.pow(2,self.choromosome_length)-10)
            function1.append(2*math.sin(x)+math.cos(x))

         #这里将sin(x)作为目标函数
        return function1

#定义适应度
    def fitness(self,function1):

        fitness_value=[]

        num=len(function1)

        for i in range(num):

            if(function1[i]>0):
                temporary=function1[i]
            else:
                temporary=0.0
        # 如果适应度小于0,则定为0

            fitness_value.append(temporary)
        #将适应度添加到列表中

        return fitness_value

#计算适应度和

    def sum(self,fitness_value):
        total=0

        for i in range(len(fitness_value)):
            total+=fitness_value[i]
        return total

#计算适应度斐伯纳且列表
    def cumsum(self,fitness1):
        for i in range(len(fitness1)-2,-1,-1):
        # range(start,stop,[step])
        # 倒计数
            total=0
            j=0

            while(j<=i):
                 total+=fitness1[j]
                 j+=1

            fitness1[i]=total
            fitness1[len(fitness1)-1]=1


#3.选择种群中个体适应度最大的个体
    def selection(self,population,fitness_value):
        new_fitness=[]
    #单个公式暂存器
        total_fitness=self.sum(fitness_value)
    #将所有的适应度求和
        for i in range(len(fitness_value)):
            new_fitness.append(fitness_value[i]/total_fitness)
    #将所有个体的适应度正则化
        self.cumsum(new_fitness)
    #
        ms=[]
    #存活的种群
        population_length=pop_len=len(population)
    #求出种群长度
    #根据随机数确定哪几个能存活

        for i in range(pop_len):
            ms.append(random.random())
    # 产生种群个数的随机值
    # ms.sort()
    # 存活的种群排序
        fitin=0
        newin=0
        new_population=new_pop=population

    #轮盘赌方式
        while newin<pop_len:
              if(ms[newin]<new_fitness[fitin]):
                 new_pop[newin]=population[fitin]
                 newin+=1
              else:
                 fitin+=1
        population=new_pop

#4.交叉操作
    def crossover(self,population):
#pc是概率阈值,选择单点交叉还是多点交叉,生成新的交叉个体,这里没用
        pop_len=len(population)

        for i in range(pop_len-1):

            if(random.random()<self.pc):

               cpoint=random.randint(0,len(population[0]))
           #在种群个数内随机生成单点交叉点
               temporary1=[]
               temporary2=[]

               temporary1.extend(population[i][0:cpoint])
               temporary1.extend(population[i+1][cpoint:len(population[i])])
           #将tmporary1作为暂存器,暂时存放第i个染色体中的前0到cpoint个基因,
           #然后再把第i+1个染色体中的后cpoint到第i个染色体中的基因个数,补充到temporary2后面

               temporary2.extend(population[i+1][0:cpoint])
               temporary2.extend(population[i][cpoint:len(population[i])])
        # 将tmporary2作为暂存器,暂时存放第i+1个染色体中的前0到cpoint个基因,
        # 然后再把第i个染色体中的后cpoint到第i个染色体中的基因个数,补充到temporary2后面
               population[i]=temporary1
               population[i+1]=temporary2
        # 第i个染色体和第i+1个染色体基因重组/交叉完成
    def mutation(self,population):
     # pm是概率阈值
         px=len(population)
    # 求出种群中所有种群/个体的个数
         py=len(population[0])
    # 染色体/个体基因的个数
         for i in range(px):
             if(random.random()<self.pm):
                mpoint=random.randint(0,py-1)
            #
                if(population[i][mpoint]==1):
               #将mpoint个基因进行单点随机变异,变为0或者1
                   population[i][mpoint]=0
                else:
                   population[i][mpoint]=1

#transform the binary to decimalism
# 将每一个染色体都转化成十进制 max_value,再筛去过大的值
    def b2d(self,best_individual):
        total=0
        b=len(best_individual)
        for i in range(b):
            total=total+best_individual[i]*math.pow(2,i)

        total=total*self.max_value/(math.pow(2,self.choromosome_length)-1)
        return total

#寻找最好的适应度和个体

    def best(self,population,fitness_value):

        px=len(population)
        bestindividual=[]
        bestfitness=fitness_value[0]
        # print(fitness_value)

        for i in range(1,px):
   # 循环找出最大的适应度,适应度最大的也就是最好的个体
            if(fitness_value[i]>bestfitness):

               bestfitness=fitness_value[i]
               bestindividual=population[i]

        return [bestindividual,bestfitness]


    def plot(self, results):
        X = []
        Y = []

        for i in range(500):
            X.append(i)
            Y.append(results[i][0])

        plt.plot(X, Y)
        plt.show()

    def main(self):

        results = [[]]
        fitness_value = []
        fitmean = []

        population = pop = self.species_origin()

        for i in range(500):
            function_value = self.function(population)
            # print('fit funtion_value:',function_value)
            fitness_value = self.fitness(function_value)
            # print('fitness_value:',fitness_value)

            best_individual, best_fitness = self.best(population,fitness_value)
            results.append([best_fitness, self.b2d(best_individual)])
        # 将最好的个体和最好的适应度保存,并将最好的个体转成十进制,适应度函数
            self.selection(population,fitness_value)
            self.crossover(population)
            self.mutation(population)
        results = results[1:]
        results.sort()
        self.plot(results)

if __name__ == '__main__':
   population_size=400#种群大小
   max_value=10#x的最大值,x是表现型,由基因构造的比率乘以最大值得到,其他情况下是乘以区间长度加上最小值
   chromosome_length=20#基因的位数,采用二进制编码
   pc=0.6#交叉中的概率阈值,但是这个代码貌似并没有用
   pm=0.01#突变的概率,突变时使用random生成一个概率和它比较
   ga=GA(population_size,chromosome_length,max_value,pc,pm)
   ga.main()

运行结果

500次每次的最大\(f(x)\),可以清除看到每次都在上升,但是貌似并没有标注是哪个\(x\)生成的。

轮盘赌算法

因为前面的轮盘赌算法没看懂,又去找博客看了看。

个人理解:假设有一个转盘和指针,指针转动一圈停留在哪个区间就是哪个。指针转动可以使用随机函数random.random模拟,随机落到0-1的区间,落在哪个区间对应的方案就是哪个。构造区间我们使用

​ $$q_i=\sum_{j=0}^{i}P(x_j)$$

\(P(x_j)\)\(x_j\)被选中的概率,依次生成\(0,q_1,q_2,q_3,\cdots,q_n,1\),随机一个概率\(r\),落在哪个区间就是哪个。例如\(q_2<r<q_3\),则选中了\(x_3\)

参考

用Python实现遗传算法 - 知乎 (zhihu.com)理解详尽,推荐

(10条消息) 轮盘赌算法_pymqq的专栏-CSDN博客

posted @ 2021-09-19 12:54  ddddd1234654732  阅读(467)  评论(0)    收藏  举报
Live2D