一起来学算法(1):遗传算法

本文深入探讨遗传算法的应用,包括自然语言生成、机器人路径规划、自动排班等场景,详细解析算法框架,如种群生成、染色体解码、适应度函数定义等关键步骤,并通过旅行商问题(TSP)实例展示其在解决复杂优化问题上的潜力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

应用

1、用在NLG(自然语言生成)技术中,比如自动生成宋词。推荐一篇论文(游维前辈写的):基于遗传算法的宋词自动生成研究
2、机器人的路径规划
3、自动排班,自动排课,车间调度等事件规划
4、组合优化问题,比如旅行商问题
5、

框架

1、生成种群染色体矩阵
调用ea.crtbp(这里的ea指代的是geatpy,以下同)方法,参数是种群个数和染色体长度。

Chrom = ea.crtbp(NIND, Lind)  # 生成种群染色体矩阵

2、种群染色体矩阵解码为基因表现型矩阵
调用ea.bs2real方法,参数是种群染色体矩阵和译码矩阵

FieldD = ea.crtfld(Encoding, varTypes, ranges, borders, precisions, codes, scales)  # 调用函数生成译码矩阵
variable = ea.bs2real(Chrom, FieldD)  # 对初始种群进行解码,二进制/格雷编码矩阵到实数值矩阵的转换

3、定义适应度函数(求解函数)
4、把基因表现型矩阵代入适应度函数,并找出最优种群(最大值或者最小值)

ObjV = aim(variable)  # 计算初始种群个体的目标函数值
best_ind = np.argmax(ObjV)  # 计算当代最优个体的序号

5、根据适应度函数值排序进行适应度分配

FitnV = ea.ranking(-ObjV)  # 根据目标函数大小分配适应度值(由于遵循目标最小化约定,因此最大化问题要对目标函数值乘上-1)

6、然后进行选择、交叉、变异三个操作

SelCh = Chrom[ea.selecting('rws', FitnV, NIND - 1), :]  # 选择,采用'rws'轮盘赌选择
SelCh = ea.recombin('xovsp', SelCh, 0.7)  # 重组(采用两点交叉方式,交叉概率为0.7)
SelCh = ea.mutbin(Encoding, SelCh)  # 二进制种群变异

7、把经过第6步操作后的新种群和最优种群合并

Chrom = np.vstack([Chrom[best_ind, :], SelCh])

8、重复2-7,直到满足要求(遗传的代数达到一定次数,或者种群个数只剩下若干个)

for gen in range(MAXGEN):

python实现

"""demo.py"""
import numpy as np
import geatpy as ea  # 导入geatpy库
import matplotlib.pyplot as plt
import time

"""============================目标函数============================"""


def aim(x):  # 传入种群染色体矩阵解码后的基因表现型矩阵
    return x * np.sin(10 * np.pi * x) + 2.0

x = np.linspace(-1, 2, 200)
plt.plot(x, aim(x))  # 绘制目标函数图像
"""============================变量设置============================"""
x1 = [-1, 2]  # 自变量范围
b1 = [1, 1]  # 自变量边界
varTypes = np.array([0])  # 自变量的类型,0表示连续,1表示离散
Encoding = 'BG'  # 'BG'表示采用二进制/格雷编码
codes = [1]  # 变量的编码方式,2个变量均使用格雷编码
precisions = [4]  # 变量的编码精度
scales = [0]  # 采用算术刻度
ranges = np.vstack([x1]).T  # 生成自变量的范围矩阵
borders = np.vstack([b1]).T  # 生成自变量的边界矩阵
"""=========================遗传算法参数设置========================="""
NIND = 40;  # 种群个体数目
MAXGEN = 25;  # 最大遗传代数
FieldD = ea.crtfld(Encoding, varTypes, ranges, borders, precisions, codes, scales)  # 调用函数生成译码矩阵
Lind = int(np.sum(FieldD[0, :]))  # 计算编码后的染色体长度
obj_trace = np.zeros((MAXGEN, 2))  # 定义目标函数值记录器
var_trace = np.zeros((MAXGEN, Lind))  # 定义染色体记录器,记录每一代最优个体的染色体
"""=========================开始遗传算法进化========================"""
start_time = time.time()  # 开始计时
Chrom = ea.crtbp(NIND, Lind)  # 生成种群染色体矩阵
variable = ea.bs2real(Chrom, FieldD)  # 对初始种群进行解码,二进制/格雷编码矩阵到实数值矩阵的转换
ObjV = aim(variable)  # 计算初始种群个体的目标函数值
best_ind = np.argmax(ObjV)  # 计算当代最优个体的序号
# 开始进化
for gen in range(MAXGEN):
    FitnV = ea.ranking(-ObjV)  # 根据目标函数大小分配适应度值(由于遵循目标最小化约定,因此最大化问题要对目标函数值乘上-1)
    SelCh = Chrom[ea.selecting('rws', FitnV, NIND - 1), :]  # 选择,采用'rws'轮盘赌选择
    SelCh = ea.recombin('xovsp', SelCh, 0.7)  # 重组(采用两点交叉方式,交叉概率为0.7)
    SelCh = ea.mutbin(Encoding, SelCh)  # 二进制种群变异
    # 把父代精英个体与子代合并
    Chrom = np.vstack([Chrom[best_ind, :], SelCh])
    variable = ea.bs2real(Chrom, FieldD)  # 对育种种群进行解码(二进制转十进制)
    ObjV = aim(variable)  # 求育种个体的目标函数值
    # 记录
    best_ind = np.argmax(ObjV)  # 计算当代最优个体的序号
    obj_trace[gen, 0] = np.sum(ObjV) / NIND  # 记录当代种群的目标函数均值
    obj_trace[gen, 1] = ObjV[best_ind]  # 记录当代种群最优个体目标函数值
    var_trace[gen, :] = Chrom[best_ind, :]  # 记录当代种群最优个体的变量值
# 进化完成
end_time = time.time()  # 结束计时
"""============================输出结果及绘图================================"""
best_gen = np.argmax(obj_trace[:, [1]])
print('目标函数最大值:', obj_trace[best_gen, 1])  # 输出目标函数最大值
variable = ea.bs2real(var_trace[[best_gen], :], FieldD)  # 解码得到表现型
print('对应的决策变量值为:')
print(variable[0][0])  # 因为此处variable是一个矩阵,因此用[0][0]来取出里面的元素
print('用时:', end_time - start_time)
plt.plot(variable, aim(variable), 'bo')
ea.trcplot(obj_trace, [['种群个体平均目标函数值', '种群最优个体目标函数值']])

优缺点

1、优点:擅长解决目标未知或难以描述、但却知道怎么评估的一类问题。具有很好的收敛性,在计算精度要求时,计算时间少,鲁棒性高等。
2、缺点:容易产生早熟收敛的问题,稳定性差,对非线性约束问题难以处理。

案例实现(TSP问题)

问题描述

一个外国的旅行商要走遍中国34个省会,现在知道全国省会的经纬度坐标,要用遗传算法算出怎么走的路程最短。

问题参数

1、种群:打乱了的城市序列
2、种群数量:打乱了的城市序列的数量
3、染色体长度:城市的数量(34)
4、适配函数:一个打乱了的城市序列的按序列号计算的总距离
5、交叉概率:
6、突变概率:
7、最优子代:
8、遗传代数:

需掌握的知识点

1、random.random():随机生成 01 之间的浮点数[0.0, 1.0)。用于判断是否进行交叉或者变异操作。
2、random.shuffle(items):把列表 items 中的元素随机打乱。用于打乱城市序列。
3、random.randint(a , b):随机生成 a 与 b 之间的整数[a, b]。用于进行交叉或者变异操作时确定哪个部分发生改变。
4list.extend(sublist):在list列表里一次性往末尾添加一个列表sublist。用于进行交叉操作时生成新种群。
5list.append(a):在list列表的末尾添加一个字符a。用于进行交叉或者变异操作时确定哪个部分发生改变。
6、string.replace(old,new):将字符串string里面的old字符串替换为new字符串,用于读取文本行数据时去掉回车。
7

代码实现

import random
import math
import matplotlib.pyplot as plt

shortestdistance = float('inf')
bestorder = []

class GA(object):
    def __init__(self,aCrossRate, aMutationRate, aLifeCount, aGeneLength, aMatchFun = lambda life : 1):
        self.crossrate = aCrossRate #交叉概率
        self.mutationrate = aMutationRate #变异概率
        self.lifecount = aLifeCount #种群数量
        self.lives = [] #存放单位,种群及其分数为一个单位
        self.genelength = aGeneLength #染色体长度
        self.matchfun = aMatchFun #适应度函数
        self.bestone = None #最优种群
        self.generationcount = 1
        self.initpopulation()

    def initpopulation(self):#种群初始化
        for lc in range(self.lifecount):#产生多少种群
            gene = list(range(self.genelength))#城市序列,0-33
            random.shuffle(gene) #将城市序列打乱
            life = Population(gene) #将种群及其分数看成一个整体
            self.lives.append(life)#将这个整体放进单位里面

    def judge(self):
        global shortestdistance
        global bestorder
        self.bestone = self.lives[0]
        for life in self.lives:
            life.score = self.matchfun(life)
            if(life.score < self.bestone.score):
                self.bestone = life
        if(self.bestone.score < shortestdistance):
                shortestdistance = self.bestone.score
                bestorder = self.bestone.gene

    def cross(self,parent1,parent2):
        index1 = random.randint(0,self.genelength-1)
        index2 = random.randint(index1,self.genelength-1)
        tempgene = parent2.gene[index1:index2]
        new_gene = []
        for gl in range(self.genelength):
            if gl == index1:
                new_gene.extend(tempgene)
            if parent1.gene[gl] not in tempgene:
                new_gene.append(parent1.gene[gl])
        return new_gene

    def mutation(self,gene):
        index1 = random.randint(0,self.genelength-1)
        index2 = random.randint(0,self.genelength-1)
        gene[index1],gene[index2] = gene[index2],gene[index1]
        return  gene

    def getone(self):
        index = random.randint(0,self.genelength-1)
        life = self.lives[index]
        return life

    def new_one(self):
        parent1 = self.getone()

        aCrossrate = random.random()
        if self.crossrate > aCrossrate:
            parent2 = self.getone()
            gene = self.cross(parent1,parent2)
        else:
            gene = parent1.gene

        aMutationrate = random.random()
        if self.mutationrate > aMutationrate:
            gene = self.mutation(gene)

        return Population(gene)

    def next_generation(self):
        self.judge()
        new_lives = []
        new_lives.append(self.bestone)
        while len(new_lives) < self.lifecount:
            new_lives.append(self.new_one())
        self.lives = new_lives
        self.generationcount += 1

class Population(object):
    gene_score = -1
    def __init__(self,aGene=None):
        self.gene = aGene
        self.score = self.gene_score

class TSP_GA(object):
    def __init__(self):
        self.initcitys()
        self.ga = GA(aCrossRate=0.7,
                     aMutationRate=0.02,
                     aLifeCount=200,
                     aGeneLength=34,
                     aMatchFun=self.distancefunc())

    def initcitys(self):
        self.citys = []
        f = open(r'distanceMatrix.txt')
        while True:
            jwcity = str(f.readline())
            if jwcity:
                pass
            else:
                break
            jwcity = jwcity.replace("\n","")
            jwcity = jwcity.split("\t")
            self.citys.append((float(jwcity[1]),float(jwcity[2]),jwcity[0]))

    def distance(self, order):
        distance = 0.0
        # i从-1到32,-1是倒数第一个
        for i in range(-1, len(self.citys) - 1):
            index1, index2 = order[i], order[i + 1]
            city1, city2 = self.citys[index1], self.citys[index2]
            distance += math.sqrt((city1[0] - city2[0]) ** 2 + (city1[1] - city2[1]) ** 2)

        return distance

    # 适应度函数,因为我们要从种群中挑选距离最短的,作为最优解,所以(1/距离)最长的就是我们要求的
    def distancefunc(self):
        return lambda life: self.distance(life.gene)

    def run(self):
        while self.ga.generationcount < 1000:
            self.ga.next_generation()
            distance = self.distance(self.ga.bestone.gene)
            print(("%d : %f") % (self.ga.generationcount, distance))

        print("经过%d次迭代,最后最优解距离为:%f" % (self.ga.generationcount, distance))
        print(self.ga.bestone.gene)
        print("遍历城市顺序为:")
        for i in self.ga.bestone.gene:
            print(self.citys[i][2])
        self.drawline(self.ga.bestone.gene)
        print("经过%d次迭代,全局最优解距离为:%f" % (self.ga.generationcount, shortestdistance))
        print(bestorder)
        print("遍历城市顺序为:")
        for i in bestorder:
            print(self.citys[i][2])
        self.drawline(bestorder)


    def drawline(self,order):
        plot_x_set = []
        plot_y_set = []
        for i in order:
            # plt.plot(coordinates[i])
            plot_x_set.append(self.citys[i][0])
            plot_y_set.append(self.citys[i][1])
        plt.plot(plot_x_set, plot_y_set, 'r')
        plt.scatter(plot_x_set, plot_y_set, )
        # for i in order:
        #     plt.annotate(i, xy=(self.citys[i][0], self.citys[i][1]))
        # 首尾2个点的连线
        x = [plot_x_set[0], plot_x_set[-1]]
        y = [plot_y_set[0], plot_y_set[-1]]
        plt.plot(x, y, 'k')  # 用黑色标识
        plt.show()


if __name__ == '__main__':
    tsp = TSP_GA()
    tsp.run()

所遇问题

效果不好,有待改进,为什么不好呢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

取不到名字的Z先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值