手把手教你写遗传算法(GA,求解函数最值)

最近在学习组合优化系列的问题,接触到一系列的启发式算法,今天分享在学习过程中对于遗传算法的一些理解,手把手写了完整的遗传算法用于求解目标函数的最值问题。
遗传算法的主要步骤总共分为4个步骤:
1、初始化种群
2、选择
3、交叉
4、变异
然后就是循环步骤2、3、4,迭代寻找最优解。
这里以求解函数:y = x1 + x2^2 + sin(x3) + cos(x4)的最大值为例进行逐步分析。
1、初始化种群
函数中存在四个自变量x,因此每一代染色体需要四个基因,然后自己设置染色体的数量,用于一轮的交叉变异操作,迭代也是围绕着这个数量进行。代码如下:

    def __init__(self,cross_rate,mutate_rate,n_population,n_iter,gen_size):
        self.cross_rate = cross_rate
        self.mutate_rate = mutate_rate
        self.n_population = n_population
        self.n_iter = n_iter
        self.gen_size = gen_size
        pop_total = np.random.randint(low=1,high=30,size=(self.n_population,self.gen_size))
        pop = []
        for i in range(len(pop_total)):
            fit = self.f(pop_total[i])
            pop.append({"gene":pop_total[i].tolist(),"fitness":fit})
        self.pop = pop
        self.bestind = self.selectBest(self.pop)

初始化的过程设置交叉的概率以及变异的概率增大随机性,初始化的父代以字典的形式放入到pop数组中,并计算适应度,这里就是指函数的值,在初代中保留适应读最大的用于后续交叉和比较。适应度函数如下:

    def f(self,geneX):
        x1 = geneX[0]
        x2 = geneX[1]
        x3 = geneX[2]
        x4 = geneX[3]
        y = x1 + x2**2 + np.sin(math.radians(x3)) + np.cos(math.radians(x4))
        
        return y

这里将三角函数传入的角度值转换为弧度制计算。
选择适应最大:

    def selectBest(self,pop):
        start_gen = sorted(pop,key=itemgetter("fitness"),reverse=True)
        return start_gen[0]

2、选择
按照适应度从大到小排序,适应度大的考虑大的概率作为交叉产生下一代,因此进入选择缓解,别的教程上都是计算概率和累计概率,思路大差不差,核心就是通过概率选择适应度大的,这里产生随机数,如果累计适应读区间则保留下来。代码如下:

    def selection(self,individuals,k):
        g_inds = sorted(individuals,key=itemgetter("fitness"),reverse=True)
        sum_fits = sum(ind["fitness"] for ind in individuals)
        chosen = []
        for i in range(k):
            r = random.random() * sum_fits
            sum_per = 0 
            for ind in g_inds:
                sum_per += ind["fitness"]
                if sum_per >= r:
                    chosen.append(ind)
                    break
        chosen = sorted(chosen,key=itemgetter("fitness"),reverse=False)
        return chosen

3、交叉
选择完父代之后则是交叉产生子代,分为单点交叉和双点交叉,就是交换的数据多或少而已,这里选择双点交叉随机性更大一点。

    def crossDNA(self,geneP):
        gen1 = geneP[0]["gene"]
        gen2 = geneP[1]["gene"]
        dim = len(geneP[0]["gene"])
        if dim ==1:
            pos1 = 1
            pos2 = 1
        else:
            pos1 = random.randrange(1,dim)
            pos2 = random.randrange(1,dim)

        newG1 = []
        newG2 = []
        for i in range(dim):
            if(min(pos1,pos2)<=i<=max(pos1,pos2)):
                newG1.append(gen1[i])
                newG2.append(gen2[i])
            else:
                newG1.append(gen2[i])
                newG2.append(gen1[i])
        return newG1,newG2

4、变异
交叉的过程中有一定的概率会发生变异,目的就是创造跟大的随机性,这里采用单点变异,也就是随机变异基因中的一个值。

    def mutation(self,crossoff):
        dim = len(crossoff)

        if dim==1:
            pos = 0
        else:
            pos = random.randrange(0,dim)
        crossoff[pos] = random.randint(1,30)
        return crossoff

5、迭代
主函数主要负责上述过程的循环,主函数如下:

    def GA_main(self):
        print("++++++Evolution beginning ++++++")
        for iter in range(self.n_iter):
            print("=========genertation{}===========".format(iter))

            GenSelect = self.selection(self.pop,self.n_population)
            nextG = []
            while len(nextG) != self.n_population:
                genP = [GenSelect.pop() for _ in range(2)]
                if random.random() < self.cross_rate:
                    newG1 , newG2 = self.crossDNA(genP)
                    if random.random() < self.mutate_rate:
                        mu1 = self.mutation(newG1)
                        mu2 = self.mutation(newG2)
                        fit_mu1 = self.f(mu1)
                        fit_mu2 = self.f(mu2)
                        nextG.append({"gene": mu1, "fitness": fit_mu1})
                        nextG.append({"gene": mu2, "fitness": fit_mu2})
                    else:
                        fit_cro1 = self.f(newG1)
                        fit_cro2 = self.f(newG2)
                        nextG.append({"gene": newG1, "fitness": fit_cro1})
                        nextG.append({"gene": newG2, "fitness": fit_cro2})
                else:
                    nextG.extend(genP)

            self.pop = nextG
            fits = [ind["fitness"] for ind in self.pop]
            best_ind = self.selectBest(self.pop)
            if best_ind["fitness"] > self.bestind["fitness"]:
                self.bestind = best_ind
            print("best individual found is: {},{}".format(self.bestind["gene"],self.bestind["fitness"]))
            print("max fitness of current pop : {}".format(max(fits)))
        print("+++++++finish++++++++")

完整代码如下:

from operator import itemgetter
import random
import numpy as np
import math


class GeneAlgorithm(object):
    def __init__(self,cross_rate,mutate_rate,n_population,n_iter,gen_size):
        self.cross_rate = cross_rate
        self.mutate_rate = mutate_rate
        self.n_population = n_population
        self.n_iter = n_iter
        self.gen_size = gen_size
        pop_total = np.random.randint(low=1,high=30,size=(self.n_population,self.gen_size))
        pop = []
        for i in range(len(pop_total)):
            fit = self.f(pop_total[i])
            pop.append({"gene":pop_total[i].tolist(),"fitness":fit})
        self.pop = pop
        self.bestind = self.selectBest(self.pop)

    
    def f(self,geneX):
        x1 = geneX[0]
        x2 = geneX[1]
        x3 = geneX[2]
        x4 = geneX[3]
        y = x1 + x2**2 + np.sin(math.radians(x3)) + np.cos(math.radians(x4))
        
        return y

    # 初始种群 后续放在了init部分便于迭代
    # def init_population(self):
    #     pop_total = np.random.randint(low=1,high=30,size=(self.n_population,self.gen_size))
    #     pop = []
    #     for i in range(len(pop_total)):
    #         fit = self.f(pop_total[i])
    #         pop.append({"gene":pop_total[i].tolist(),"fitness":fit})
    #     return pop
    
    def selectBest(self,pop):
        start_gen = sorted(pop,key=itemgetter("fitness"),reverse=True)
        return start_gen[0]

    # 选择
    def selection(self,individuals,k):
        g_inds = sorted(individuals,key=itemgetter("fitness"),reverse=True)
        sum_fits = sum(ind["fitness"] for ind in individuals)
        chosen = []
        for i in range(k):
            r = random.random() * sum_fits
            sum_per = 0 
            for ind in g_inds:
                sum_per += ind["fitness"]
                if sum_per >= r:
                    chosen.append(ind)
                    break
        chosen = sorted(chosen,key=itemgetter("fitness"),reverse=False)
        return chosen

    # 双点交叉 genP为随机选择的父代
    def crossDNA(self,geneP):
        gen1 = geneP[0]["gene"]
        gen2 = geneP[1]["gene"]
        dim = len(geneP[0]["gene"])
        if dim ==1:
            pos1 = 1
            pos2 = 1
        else:
            pos1 = random.randrange(1,dim)
            pos2 = random.randrange(1,dim)

        newG1 = []
        newG2 = []
        for i in range(dim):
            if(min(pos1,pos2)<=i<=max(pos1,pos2)):
                newG1.append(gen1[i])
                newG2.append(gen2[i])
            else:
                newG1.append(gen2[i])
                newG2.append(gen1[i])
        return newG1,newG2

    # 单点变异
    def mutation(self,crossoff):
        dim = len(crossoff)

        if dim==1:
            pos = 0
        else:
            pos = random.randrange(0,dim)
        crossoff[pos] = random.randint(1,30)
        return crossoff
    
    def GA_main(self):
        print("++++++Evolution beginning ++++++")
        for iter in range(self.n_iter):
            print("=========genertation{}===========".format(iter))

            GenSelect = self.selection(self.pop,self.n_population)
            nextG = []
            while len(nextG) != self.n_population:
                genP = [GenSelect.pop() for _ in range(2)]
                if random.random() < self.cross_rate:
                    newG1 , newG2 = self.crossDNA(genP)
                    if random.random() < self.mutate_rate:
                        mu1 = self.mutation(newG1)
                        mu2 = self.mutation(newG2)
                        fit_mu1 = self.f(mu1)
                        fit_mu2 = self.f(mu2)
                        nextG.append({"gene": mu1, "fitness": fit_mu1})
                        nextG.append({"gene": mu2, "fitness": fit_mu2})
                    else:
                        fit_cro1 = self.f(newG1)
                        fit_cro2 = self.f(newG2)
                        nextG.append({"gene": newG1, "fitness": fit_cro1})
                        nextG.append({"gene": newG2, "fitness": fit_cro2})
                else:
                    nextG.extend(genP)

            self.pop = nextG
            fits = [ind["fitness"] for ind in self.pop]
            best_ind = self.selectBest(self.pop)
            if best_ind["fitness"] > self.bestind["fitness"]:
                self.bestind = best_ind
            print("best individual found is: {},{}".format(self.bestind["gene"],self.bestind["fitness"]))
            print("max fitness of current pop : {}".format(max(fits)))
        print("+++++++finish++++++++")


if __name__ == "__main__":
    run  = GeneAlgorithm(cross_rate=0.9,mutate_rate=0.2,n_population=100,n_iter=1000,gen_size=4)
    run.GA_main()

这里设置初代一百条染色体,迭代1000次,运行过程如下:
运行结果
能找出最大值,并且准确。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值