优化算法(二)遗传算法及python实现

1. 基本原理

1.1 生物启示

遗传算法是群智能优化算法计算中应用最为广泛最为成功最具代表性的智能优化方法。以达尔文的生物进化论和孟德尔的遗传变异理论为基础,模拟生物界的进化过程,以对种群进行优化搜索的方法。

  • 根据进化论的观点:具有较强适应环境变化能力额个体具有更高的生存能力,容易存活,即有更多机会繁殖后代;反之,适应力较小的个体易于被淘汰,繁殖后代的机会较少。即优胜劣汰,适者生存
  • 根据遗传变异理论:生物在繁殖过程中会进行交叉变异操作以改变增强染色体,改变生物表现型。

1.2 基本思想

首先生成一个种群,然后种群中每一个个体对于环境具有不同的生存能力,即适应度,而根据每个个体不同的适应度,每个个体能够存活并繁殖后代的概率有所不同,一般遵循正比选择策略,即较高适应度产生后代的概率较高,较低适应度产生后代概率较低。而在产生后代时决定每个个体适应度(即表现型)的基因会以一定概率发生交叉变异,得到的个体会得到较高或者较低的适应度,并重复繁殖过程若干代,结束选出最高适应度的一个或多个个体作为优化结果。

1.3 组成要素

在遗传算法中主要存在基因编码方式、评估适应环境能力的适应度函数、选择繁殖后代个体的选择方式、繁殖后代时可能会发生的交叉和变异方式、以及停止算法方式。

1.3.1 编码方式

遗传算法中编码方式往往决定了个体的基因型表达方式,以及交叉和变异操作,是影响算法执行效率的重要因素之一。主要包括二进制编码、实数编码、顺序编码等。

  • 二进制编码:将原问题的解空间映射到位串空间 B = { 0 , 1 } B=\{0,1\} B={0,1}上,然后作为基因在位串空间上进行遗传操作,得到后代的基因再经过解码还原成表现型(换算为原编码方式计算适应度函数值)进行评估和选择操作。
    • 一般可以表述为由0,1组成的字符串(二进制数?),如:10101111。每一位0,1称为位值
    • 优点是数据表示的范围较大,有利于位值的计算,缺点是编码太长难以计算
  • 实数编码:直接将求解问题的每个变量进行堆积形成基因,变量维数即基因位数。
    • 一般直接用多维数据表示,如问题解向量 X = ( x 1 , x 2 , . . . , x n ) X=(x_1,x_2,...,x_n) X=(x1,x2,...,xn)中,变量 x 1 x_1 x1 x 2 x_2 x2……即基因的位值
    • 优点是特别适用于实优化问题,直接在实数上进行基因操作,无需另外编码解码操作,缺点是难以反映出基因的特征
  • 顺序编码:顺序编码采用自然数进行编码,不允许重复或遗漏。
    • 可以用于解决旅行商问题(traveling salesman problem, TSP)、任务排序问题(task ordering problem, TOP)、多处理调度问题(multiprocessor scheduling problem, MSP)等优化问题

1.3.2 适应度函数

适应度函数一般应准确表示个体在环境下的适应能力,是一代一代筛选优秀个体的重要影响因素之一,一般由目标函数变换而成。常见的变换方式有线性变换、幂律变换、指数变化、对数变换。需要根据实际问题需要选择变换方式,如放大或缩小目标函数值的差别。

变换名称变换描述备注
线性变换 F ( x ) = a k f ( x ) + b k F(x)=a^kf(x)+b^k F(x)=akf(x)+bkk一般随着进化次数的增加而变化,k=1时为静态型变换, k ≠ 1 k\neq1 k=1时为动态型
幂律变换 F ( x ) = f ( x ) a F(x)=f(x)^a F(x)=f(x)a
指数变换 F ( x ) = a ⋅ e b f ( x ) + c F(x)=a\cdot e^bf(x)+c F(x)=aebf(x)+c一般用于扩大目标函数的差别
对数变换 F ( x ) = a ⋅ l n f ( x ) + b F(x)=a\cdot lnf(x)+b F(x)=alnf(x)+b一般用于缩小目标函数的差别

注: f ( x ) f(x) f(x) F ( x ) F(x) F(x)分别是目标函数和适应度函数

1.3.3 选择方式

选择过程是在父代中依概率选择若干个体进行遗传和交叉操作,选择过程应遵循优胜劣汰的法则,使适应度函数值较大的个体拥有更多繁殖的机会,以保证后代的优秀,而适应度函数值较小的个体也不应直接去除,仍需保持小概率可繁殖以保证种群的多样性,即解向量的全局分布情况。

常见的选择方式是轮盘赌(旋轮法),即将一个圆盘以各个体的适应度函数值为比重分为若干份,然后随机生成指针选择可繁殖的后代,一般保持种群数量不变。每个个体被选中的概率可以表示为 P i = F i ∑ i = 1 n F i , i = 1 , 2 , 3 , . . . , n P_i = \frac{F_i}{\sum_{i=1}^nFi},i=1,2,3,...,n Pi=i=1nFiFi,i=1,2,3,...,n (这里我也不知道为什么累加的参数部分变成了上下标)。

1.3.4 交叉和变异

交叉和变异两种运算是遗传算法搜索最优解的关键也是根本办法所在,也是遗传算法改进最多的地方。

  • 交叉算子:一般由两个父代个体的基因的部分交换,得到两个子代的基因,常用的交叉方式有单点交叉和双点交叉。

    • 单点交叉:选择某个基因位置作为交叉点,将交叉点的两侧看成两个字串,两个父代个体交换右侧的字串变为两个字串,但是子代与父代的区别较小,且交叉点位置的随机选择不利于较优个体的生成。变换操作如下所示:(为了直观表示,选择了二进制编码方式作为基因编码方式)
      父 代 基 因 串 1       1010   1111 ⟶ 1010   1001 父 代 基 因 串 2       0100   1001 ⟶ 0100   1111 父代基因串1 \ \ \ \ \ \boxed{1010}\ \boxed{1111} \longrightarrow \boxed{1010}\ \boxed{1001} \\ 父代基因串2 \ \ \ \ \ \boxed{0100}\ \boxed{1001} \longrightarrow \boxed{0100}\ \boxed{1111} 1     1010 11111010 10012     0100 10010100 1111
  • 两点交叉:同时选择两个父代基因上的两个交叉点,并交换两个交叉点之间的基因。而交叉点的增加可推广为多点交叉,两点交叉的操作如下:
    父 代 基 因 串 1       1010   1111   1111 ⟶ 1010   1001   1111 父 代 基 因 串 2       0100   1001   1001 ⟶ 0100   1111   1001 父代基因串1 \ \ \ \ \ \boxed{1010}\ \boxed{1111} \ \boxed{1111} \longrightarrow \boxed{1010}\ \boxed{1001} \ \boxed{1111} \\ 父代基因串2 \ \ \ \ \ \boxed{0100}\ \boxed{1001} \ \boxed{1001}\longrightarrow \boxed{0100}\ \boxed{1111} \ \boxed{1001} 1     1010 1111 11111010 1001 11112     0100 1001 10010100 1111 1001

  • 变异算子:在交叉后的子代个体中~~(不能直接变异吗)~~ ,某基因位有可能发生突变转换成同位基因,一般突变的方式一样(二进制编码取反或者加上一个服从均匀分布、指数分布、正态分布的数值)

1.3.5 算法终止方式

算法停止的准则一般采用设定最大迭代次数或者适应值函数评估次数,或者是规定的搜索精度。

1.4 名词汇总

遗传算法(genetic algorithm, GA)、种群(population)、个体(individual)、适应度(fitness)、交叉(crossover)、变异(mutation)、基因(gene)、父代(parent)、子代(offspring)、模式定理(schema theory)、积木块(building block)、多目标优化问题(multi-objective optimization problem,MOP)

2. 数学机理

2.1 模式介绍

  • 模式定义为若干位取确定值,某些位取不确定值的一类个体的总称,用H表示。
    • 比如用二进制编码时,在确定的位串空间 B = { 0 , 1 } B=\{0,1\} B={0,1}中加入统配字符,即 B = { 0 , 1 , ∗ } B=\{0,1,*\} B={0,1,},*表示该基因位上的基因取0或1。某模板 H 1 : ( 10 ∗ 1 ) = { 1001 , 1011 } H_1:(10*1)=\{1001,1011\} H1(101)={1001,1011}
  • 将具有低阶、短定义距(数学描述中有)及高适应度的模式称为积木块。有一个不严密的积木块假设:积木块在遗传算子的作用下,相互结合,能生成高阶、长定义距、更高平均适应度的模式,并可最终生成全局最优解。

2.2 数学描述

模板属性定义 ∗ 0 ∗ 10 ∗ 1 ∗ *0*10*1* 0101为例
原始长度 L ( H ) L(H) L(H)模板中总的基因位数 L ( H ) = 8 L(H)=8 L(H)=8
定义距 δ ( H ) \delta(H) δ(H)模板中从左到右第一确定字符到最后一个确定字符的距离 δ ( H ) = 6 − 1 = 5 \delta(H)=6-1=5 δ(H)=61=5
阶数 o ( H ) o(H) o(H)模板中包含的确定字符的个数 o ( H ) = 4 o(H)=4 o(H)=4
容量 D ( H ) D(H) D(H)模板包含字符串个数 D ( H ) = 2 4 = 16 D(H)=2^4=16 D(H)=24=16

2.3 推导过程

  • 分别计算选择算子、交叉算子、变异算子对模式H的生存数量的影响,然后计算下一代含有模式H的个体数期望值(我还没看懂推导过程,看懂再补,可以参考[5])

2.4 推论与结论

  • 在经典遗传算法中,积木块在子代种群中数目的期望值以指数级增加

3. 优化方法

3.1 种群生成

  • 基于反向学习优化的种群产生法*[2]*:首先随机生成种群P(N),然后根据反向生成的方式:
    m a x ( P ( N ) i ) + m i n ( P ( N ) i ) − P ( N ) i P ( N ) i : 种 群 中 基 因 的 每 一 个 分 量 max(P(N)_i)+min(P(N)_i)-P(N)_i \\ P(N)_i:种群中基因的每一个分量 max(P(N)i)+min(P(N)i)P(N)iP(N)i
    生成OP(N)种群,然后将P(N)和OP(N)两个种群混在一起以适应度函数值为排序依据,选取适应度函数值大的前N个个体组成初始种群

3.2 选择方式

  • 精英选择:经典遗传算法中,每一个个体都有被淘汰的概率,而保留每一代的最高适应度个体就是精英保留策略

  • 锦标赛算法:从种群中随机选择k个个体(k<n),然后在k个个体中选择适应度最高的一个个体,重复此过程n次,得到子代

  • 多种群遗传算法:保持多个种群同时进化,种群之间通过移民方式交换基因

3.3 交叉操作

  • 单形杂交*[3]*:从种群中选出n+1个个体作为父体,其基因作为向量在n维欧式空间形成单形,将单形沿各方向扩张得到子代。

  • 球形杂交*[4]*:给定 X = ( x 1 , x 2 , . . . , x n ) X=(x_1,x_2,...,x_n) X=(x1,x2,...,xn) Y = ( y 1 , y 2 , . . . , y n ) Y=(y_1,y_2,...,y_n) Y=(y1,y2,...,yn)两个父体的基因,以
    z j = a x j 2 + ( 1 − a ) y j 2 ,       j ∈ [ 1 , n ] z_j = \sqrt{ax^2_j+(1-a)y^2_j},\ \ \ \ \ j\in[1,n] zj=axj2+(1a)yj2 ,     j[1,n]
    的方式产生一个后代 Z = ( z 1 , z 2 , . . . , z n ) Z=(z_1,z_2,...,z_n) Z=z1,z2,...,zn)

3.4 其他操作

  • 局部搜索过程:在标准GA算法中的不可行个体的近邻区域形成一个局部搜索区域(不可行区域指超出约束部分位置),每个个体成为“待定的候选解”,以增加种群的多样性
  • 混合遗传算法:通过引入梯度法、爬山法和贪心法与经典GA算法结合形成混合遗传算法
  • 自适应遗传算法:当种群个体趋于一致时,交叉概率和变异概率增加,而种群个体较为分散时,交叉概率和变异概率减少(通过将两概率设置为与种群适应度相关实现,或者与个体适应度挂钩可以实现单个个体自适应)

4. 程序实现

  • 这里是参考了*[7]*的代码,本来根据自己的实际问题有个应用性变种的代码,但是不知道能不能公开(我自己写的代码就是屑,但是是课题组项目中的代码,所以先保留吧,之后看看情况再发
import random
from operator import itemgetter
import matplotlib.pyplot as plt
import numpy as np

class Gene:
    def __init__(self, **data):
        self.__dict__.update(data)          #在__dict__属性中更新data
        self.size = len(data['data'])


class GA:
    def __init__(self, parameter):
        #包括自变量可取的最大值,最小值,种群大小,交叉率,变异率,繁殖代数
        #parameter = [CXPB, MUTPB, NGEN, popsize, low, up]
        self.parameter = parameter
        self.NGEN = self.parameter[2]
        self.CXPB = self.parameter[0]
        self.MUTPB = self.parameter[1]
        low = self.parameter[4]
        up = self.parameter[5]
        self.bound = []
        self.bound.append(low)
        self.bound.append(up)

        pop = []
        for i in range(self.parameter[3]): #生成popsize的种群个数
            geneinfo = []
            for pos in range(len(low)):    #生成len(low)维的随机数据作为个体初始值
                geneinfo.append(random.randint(self.bound[0][pos],self.bound[1][pos]))

            fitness = self.evaluate(geneinfo)  #计算生成的随机个体的适应度函数值
            pop.append({'Gene':Gene(data=geneinfo), 'fitness':fitness})
        self.pop = pop
        self.bestindividual = self.selectBest(self.pop)   #保存最好的个体数据{'Gene':Gene(), 'fitness':fitness}


    def evaluate(self, geneinfo):
        #作为适应度函数评估该个体的函数值
        x1 = geneinfo[0]
        x2 = geneinfo[1]
        x3 = geneinfo[2]
        x4 = geneinfo[3]
        y = np.exp(x1 + x2 ** 2) + 100*np.sin(x3 ** 2) + x4**2
        return y

    def selectBest(self, pop):
        #选出当前代种群中的最好个体作为历史记录
        s_inds = sorted(pop, key=itemgetter("fitness"), reverse=True)    #从大到小排列
        return s_inds[0]            #返回依然是一个字典

    def selection(self, individuals, k):
        #用轮盘赌的方式,按照概率从上一代选择个体直至形成新的一代
        #k表示需要选出的后代个数
        s_inds = sorted(individuals, key=itemgetter("fitness"), reverse=True)
        sum_fits = sum(ind['fitness'] for ind in individuals)   #计算所有个体适应度函数的和
        chosen = []
        for i in range(k):
            u = random.random() * sum_fits   #随机产生一个[0,sun_fits]范围的数,即轮盘赌这局的指针
            sum_ = 0
            for ind in s_inds:
                sum_ += ind['fitness']       #逐次累加从大到小排列的个体的适应度函数的值,直至超过指针,即选择它
                if sum_ >= u:
                    chosen.append(ind)
                    break
        chosen = sorted(chosen, key=itemgetter('fitness'), reverse=False)  #从小到大排列选择的个体,方便进行交叉操作
        return chosen

    def crossperate(self, offspring):
        #实现交叉操作,交换倆数据随机两维之间的数据
        dim = len(offspring[0]['Gene'].data)  #获得数据维数,即基因位数

        geneinfo1 = offspring[0]['Gene'].data   #交叉的第一个数据
        geneinfo2 = offspring[1]['Gene'].data   #交叉的第二个数据

        if dim == 1:
            pos1 = 1
            pos2 = 1
        else:
            pos1 = random.randrange(1,dim)
            pos2 = random.randrange(1,dim)

        newoff1 = Gene(data=[])    #后代1
        newoff2 = Gene(data=[])    #后代2
        temp1 = []
        temp2 = []
        for i in range(dim):
            if min(pos1, pos2) <= i < max(pos1,pos2):   #交换的部分维度
                temp1.append(geneinfo2[i])
                temp2.append(geneinfo1[i])
            else:
                temp1.append(geneinfo1[i])
                temp2.append(geneinfo2[i])
        newoff1.data = temp1
        newoff2.data = temp2
        return newoff1, newoff2


    def mutation(self, crossoff, bound):
        #实现单点编译,不实现逆转变异
        dim = len(crossoff.data)

        if dim == 1:
            pos = 0
        else:
            pos = random.randrange(0, dim) #选择单点变异的点

        crossoff.data[pos] = random.randint(bound[0][pos], bound[1][pos])
        return crossoff

    def GA_main(self):
        popsize = self.parameter[3]
        print('Start of evolution')
        ever_best = []
        for g in range(self.NGEN):
            print("############ Generation {} ##############".format(g))
            #首先进行选择
            selectpop = self.selection(self.pop, popsize)
            nextoff = []
            while len(nextoff) != popsize:
                offspring = [selectpop.pop() for _ in range(2)] #后代间两两选择
                if random.random() < self.CXPB:       #进行交叉的后代
                    crossoff1, crossoff2 = self.crossperate(offspring)
                    if random.random() < self.MUTPB:
                        muteoff1 = self.mutation(crossoff1, self.bound)
                        muteoff2 = self.mutation(crossoff2, self.bound)
                        fit_muteoff1 = self.evaluate(muteoff1.data)
                        fit_muteoff2 = self.evaluate(muteoff2.data)
                        nextoff.append({'Gene':muteoff1, 'fitness':fit_muteoff1})
                        nextoff.append({'Gene': muteoff2, 'fitness': fit_muteoff2})
                    else:
                        fit_muteoff1 = self.evaluate(crossoff1.data)
                        fit_muteoff2 = self.evaluate(crossoff2.data)
                        nextoff.append({'Gene': crossoff1, 'fitness': fit_muteoff1})
                        nextoff.append({'Gene': crossoff2, 'fitness': fit_muteoff2})
                else:
                    nextoff.extend(offspring)   #直接追加两个后代

            self.pop = nextoff   #种群直接变为下一代
            fits = [ind['fitness'] for ind in self.pop]

            best_ind = self.selectBest(self.pop)
            if best_ind['fitness'] > self.bestindividual['fitness']:
                self.bestindividual = best_ind
            ever_best.append(self.bestindividual['fitness'])

            print("Best individual found is {}, {}".format(self.bestindividual['Gene'].data,
                                                           self.bestindividual['fitness']))
            print("  Max fitness of current pop: {}".format(max(fits)))
        plt.plot(ever_best)
        plt.show()
        print('------------ End ----------------')

if __name__ == '__main__':
    CXPB, MUTPB, NGEN, popsize = 0.8, 0.5, 100, 100

    up = [90, 10, 5, 6]
    low = [1, 2, 3, 1]
    parameter = [CXPB, MUTPB, NGEN, popsize, low, up]
    run = GA(parameter)
    run.GA_main()

程序运行结果如下:
在这里插入图片描述

  • 原本打算实现一下自适应版本的遗传算法,但是限于时间 ,没有改了,就直接这样放上来了

5. 总结

目标函数的表达式并不限于显示表达,只要能够通过一个函数(def)返回适应度函数值就可以,其次在设计适应度函数时并不拘于最大值的形式,只要在后续更新历史最佳,以及选择个体时代码注意配合即可。

且此处实现的遗传算法是经典版的,没有做任何优化,甚至连精英策略也没有加,推荐大家学习的时候可以先根据范例代码学习一次,然后再找一个实际问题自己写一次,最好是类型不相同的问题(总不能改个适应度函数就说自己解决了一个新问题吧),或者可以将自己觉得有提点(这里用提点这个词好像很奇怪,哈哈哈哈)的优化方法加入其中,这些都是能加深对GA算法理解的。

6. 参考资料

[1] 群智能优化算法及应用/汤可宗,杨静宇著. ——北京:科学出版社,2015.7 ISBN 978-7-03-044740-1

[2] Rahnamayan S, Tizhoosh H R. A novel population initialization method for accelerating evolutionary algorithms[J]. Computers and Mathematics with Applications, 2007, 53(10): 1605-1614.

[3] 周育人,李元香等. Pareto强度值演化算法求解约束优化问题[J]. 软件学报,2003,14(7):1243-1249

[4] Schoemauer M, Michalewic z. Sphere operators and their applicability for constrained parameter optimization problems[C]. Proceedings of the 7th International Conference on Evolutionary Programming, 1998: 241-250.

[5] 遗传算法(五)——模式定理_SamYu-CSDN博客
https://blog.csdn.net/weixin_30239361/article/details/101695793

[6] 遗传算法系列之四:遗传算法的变种 - 云+社区 - 腾讯云
https://cloud.tencent.com/developer/article/1015614

[7] Python手把手构建遗传算法(GA)实现最优化搜索 - FINTHON
https://finthon.com/python-genetic-algorithm/

原书自带的参考文献直接给我搬过来了,用于以后详细了解时有个出处

这里就不遵循标准应用格式了

  • 5
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
遗传优化算法是一种优化算法,它模拟自然界中的进化过程来求解问题。下面是一个使用Python实现遗传优化算法的示例代码: ```python import random # 适应度函数 def fitness(solution): # 计算解的适应度值 # 这里可以根据具体问题进行定义和实现 pass # 初始化种群 def initialize_population(population_size, chromosome_length): population = [] for _ in range(population_size): chromosome = [random.randint(0, 1) for _ in range(chromosome_length)] population.append(chromosome) return population # 选择操作 def selection(population, fitness_values): # 根据适应度值选择个体 # 这里可以使用轮盘赌选择、锦标赛选择等方法 pass # 交叉操作 def crossover(parent1, parent2): # 根据父代个体生成子代个体 # 这里可以使用单点交叉、多点交叉等方法 pass # 变异操作 def mutation(child): # 对个体进行变异操作 # 这里可以使用位翻转、位移变异等方法 pass # 遗传优化算法主函数 def genetic_algorithm(population_size, chromosome_length, generations): population = initialize_population(population_size, chromosome_length) for _ in range(generations): fitness_values = [fitness(solution) for solution in population] new_population = [] for _ in range(population_size): parent1 = selection(population, fitness_values) parent2 = selection(population, fitness_values) child = crossover(parent1, parent2) child = mutation(child) new_population.append(child) population = new_population # 返回最优解 best_solution = population[0] best_fitness = fitness_values[0] for i in range(1, population_size): if fitness_values[i] > best_fitness: best_solution = population[i] best_fitness = fitness_values[i] return best_solution # 示例用法 population_size = 100 chromosome_length = 10 generations = 100 best_solution = genetic_algorithm(population_size, chromosome_length, generations) print("Best Solution:", best_solution) ``` 需要根据具体问题对适应度函数、选择操作、交叉操作和变异操作进行定义和实现。以上代码只是一个简单的示例,你可以根据自己的需求进行修改和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值