使用Python实现的遗传算法 附完整代码

简介: 使用Python实现的遗传算法 附完整代码

遗传算法是模仿自然界生物进化机制发展起来的随机全局搜索和优化方法,它借鉴了达尔文的进化论和孟德尔的遗传学说。其本质是一种高效、并行、全局搜索的方法,它能在搜索过程中自动获取和积累有关搜索空间的知识,并自适应的控制搜索过程以求得最优解。遗传算法操作使用适者生存的原则,在潜在的解决方案种群中逐次产生一个近似最优解的方案,在遗传算法的每一代中,根据个体在问题域中的适应度值和从自然遗传学中借鉴来的再造方法进行个体选择,产生一个新的近似解。这个过程导致种群中个体的进化,得到的新个体比原来个体更能适应环境,就像自然界中的改造一样。


遗传算法具体步骤:


(1)初始化:设置进化代数计数器 t=0、设置最大进化代数 T、交叉概率、变异概率、随机生成 M 个个体作为初始种群 P

(2)个体评价:计算种群 P 中各个个体的适应度

(3)选择运算:将选择算子作用于群体。以个体适应度为基础,选择最优个体直接遗传到下一代或通过配对交叉产生新的个体再遗传到下一代

(4)交叉运算:在交叉概率的控制下,对群体中的个体两两进行交叉

(5)变异运算:在变异概率的控制下,对群体中的个体进行变异,即对某一个体的基因进行随机调整

(6) 经过选择、交叉、变异运算之后得到下一代群体 P1。


重复以上(1)-(6),直到遗传代数为 T,以进化过程中所得到的具有最优适应度个体作为最优解输出,终止计算。


旅行推销员问题(Travelling Salesman Problem, TSP):有 n 个城市,一个推销员要从其中某一个城市出发,唯一走遍所有的城市,再回到他出发的城市,求最短的路线。

应用遗传算法求解 TSP 问题时需要进行一些约定,基因是一组城市序列,适应度是按照这个基因的城市顺序的距离和分之一。


1.2 实验代码

import random
import math
import matplotlib.pyplot as plt
# 读取数据
f=open("test.txt")
data=f.readlines()
# 将cities初始化为字典,防止下面被当成列表
cities={}
for line in data:
    #原始数据以\n换行,将其替换掉
    line=line.replace("\n","")
    #最后一行以EOF为标志,如果读到就证明读完了,退出循环
    if(line=="EOF"):
        break
    #空格分割城市编号和城市的坐标
    city=line.split(" ")
    map(int,city)
    #将城市数据添加到cities中
    cities[eval(city[0])]=[eval(city[1]),eval(city[2])]
# 计算适应度,也就是距离分之一,这里用伪欧氏距离
def calcfit(gene):
    sum=0
    #最后要回到初始城市所以从-1,也就是最后一个城市绕一圈到最后一个城市
    for i in range(-1,len(gene)-1):
        nowcity=gene[i]
        nextcity=gene[i+1]
        nowloc=cities[nowcity]
        nextloc=cities[nextcity]
        sum+=math.sqrt(((nowloc[0]-nextloc[0])**2+(nowloc[1]-nextloc[1])**2)/10)
    return 1/sum
# 每个个体的类,方便根据基因计算适应度
class Person:
    def __init__(self,gene):
        self.gene=gene
        self.fit=calcfit(gene)
class Group:
    def __init__(self):
        self.GroupSize=100  #种群规模
        self.GeneSize=48    #基因数量,也就是城市数量
        self.initGroup()
        self.upDate()
    #初始化种群,随机生成若干个体
    def initGroup(self):
        self.group=[]
        i=0
        while(i<self.GroupSize):
            i+=1
            #gene如果在for以外生成只会shuffle一次
            gene=[i+1 for i in range(self.GeneSize)]
            random.shuffle(gene)
            tmpPerson=Person(gene)
            self.group.append(tmpPerson)
    #获取种群中适应度最高的个体
    def getBest(self):
        bestFit=self.group[0].fit
        best=self.group[0]
        for person in self.group:
            if(person.fit>bestFit):
                bestFit=person.fit
                best=person
        return best
    #计算种群中所有个体的平均距离
    def getAvg(self):
        sum=0
        for p in self.group:
            sum+=1/p.fit
        return sum/len(self.group)
    #根据适应度,使用轮盘赌返回一个个体,用于遗传交叉
    def getOne(self):
        #section的简称,区间
        sec=[0]
        sumsec=0
        for person in self.group:
            sumsec+=person.fit
            sec.append(sumsec)
        p=random.random()*sumsec
        for i in range(len(sec)):
            if(p>sec[i] and p<sec[i+1]):
                #这里注意区间是比个体多一个0的
                return self.group[i]
    #更新种群相关信息
    def upDate(self):
        self.best=self.getBest()
# 遗传算法的类,定义了遗传、交叉、变异等操作
class GA:
    def __init__(self):
        self.group=Group()
        self.pCross=0.35    #交叉率
        self.pChange=0.1    #变异率
        self.Gen=1  #代数
    #变异操作
    def change(self,gene):
        #把列表随机的一段取出然后再随机插入某个位置
        #length是取出基因的长度,postake是取出的位置,posins是插入的位置
        geneLenght=len(gene)
        index1 = random.randint(0, geneLenght - 1)
        index2 = random.randint(0, geneLenght - 1)
        newGene = gene[:]       # 产生一个新的基因序列,以免变异的时候影响父种群
        newGene[index1], newGene[index2] = newGene[index2], newGene[index1]
        return newGene
    #交叉操作
    def cross(self,p1,p2):
        geneLenght=len(p1.gene)
        index1 = random.randint(0, geneLenght - 1)
        index2 = random.randint(index1, geneLenght - 1)
        tempGene = p2.gene[index1:index2]   # 交叉的基因片段
        newGene = []
        p1len = 0
        for g in p1.gene:
              if p1len == index1:
                    newGene.extend(tempGene)     # 插入基因片段
                    p1len += 1
              if g not in tempGene:
                    newGene.append(g)
                    p1len += 1
        return newGene
    #获取下一代
    def nextGen(self):
        self.Gen+=1
        #nextGen代表下一代的所有基因
        nextGen=[]
        #将最优秀的基因直接传递给下一代
        nextGen.append(self.group.getBest().gene[:])
        while(len(nextGen)<self.group.GroupSize):
            pChange=random.random()
            pCross=random.random()
            p1=self.group.getOne()
            if(pCross<self.pCross):
                p2=self.group.getOne()
                newGene=self.cross(p1,p2)
            else:
                newGene=p1.gene[:]
            if(pChange<self.pChange):
                newGene=self.change(newGene)
            nextGen.append(newGene)
        self.group.group=[]
        for gene in nextGen:
            self.group.group.append(Person(gene))
            self.group.upDate()
    #打印当前种群的最优个体信息
    def showBest(self):
        print("第{}代\t当前最优{}\t当前平均{}\t".format(self.Gen,1/self.group.getBest().fit,self.group.getAvg()))
    #n代表代数,遗传算法的入口
    def run(self,n):
        Gen=[]  #代数
        dist=[] #每一代的最优距离
        avgDist=[]  #每一代的平均距离
        #上面三个列表是为了画图
        i=1
        while(i<n):
            self.nextGen()
            self.showBest()
            i+=1
            Gen.append(i)
            dist.append(1/self.group.getBest().fit)
            avgDist.append(self.group.getAvg())
        #绘制进化曲线
        plt.plot(Gen,dist,'-r')
        plt.plot(Gen,avgDist,'-b')
        plt.show()
ga=GA()
ga.run(3000)
print("进行3000代后最优解:",1/ga.group.getBest().fit)


1.3 实验结果


下图是进行一次实验的结果截图,求出的最优解是 11271


47eb0fc775cad86b8185eff80b208b53.png


为避免实验的偶然性,进行 10 次重复实验,并求平均值,结果如下。


ab1c44c84cdbad147145bd86b395c9be.png

a2878a89806bc8a2ee97916df4b4dd9e.png



上图横坐标是代数,纵坐标是距离,红色曲线是每一代的最优个体的距离,蓝色曲线是每一代的平均距离。可以看出两条线都呈下降趋势,也就是说都在进化。平均距离下降说明由于优良基因的出现(也就是某一段城市序列),使得这种优良的性状很快传播到整个群体。就像自然界中的优胜劣汰一样,具有适应环境的基因才能生存下来,相应的,生存下来的都是具有优良基因的。算法中引入交叉率和变异率的意义就在于既要保证当前优良基因,又要试图产生更优良的基因。如果所有个体都交叉,那么有些优良的基因片段可能会丢失;如果都不交叉,那么两个优秀的基因片段无法组合为更优秀的基因;如果没有变异,那就无法产生更适应环境的个体。不得不感叹自然的智慧是如此强大。


上面说到的基因片段就是 TSP 中的一小段城市序列,当某一段序列的距离和相对较小时,就说明这段序列是这几个城市的相对较好的遍历顺序。遗传算法通过将这些优秀的片段组合起来实现了 TSP 解的不断优化。而组合的方法正是借鉴自然的智慧,遗传、变异、适者生存。


1.4 实验总结


1、如何在算法中实现“优胜劣汰”?


所谓优胜劣汰也就是优良的基因保留,不适应环境的基因淘汰。在上述 GA 算法中,我使用的是轮盘赌,也就是在遗传的步骤中(无论是否交叉),根据每个个体的适应度来挑选。这样就能达到适应度高得个体有更多的后代,也就达到了优胜劣汰的目的。


在具体的实现过程中,我犯了个错误,起初在遗传步骤筛选个体时,我每选出一个个体就将这个个体从群体中删除。现在想想,这种做法十分愚蠢,尽管当时我已经实现了轮盘赌,但如果选出个体就删除,那么就会导致每个个体都会平等地生育后代,所谓的轮盘赌也不过是能让适应度高的先进行遗传。这种做法完全背离了“优胜劣汰”的初衷。正确的做法是选完个体进行遗传后再重新放回群体,这样才能保证适应度高的个体会进行多次遗传,产生更多后代,将优良的基因更广泛的播撒,同时不适应的个体会产生少量后代或者直接被淘汰。


2 、如何保证进化一直是在正向进行?


所谓正向进行也就是下一代的最优个体一定比上一代更适应或者同等适应环境。我采用的方法是最优个体直接进入下一代,不参与交叉变异等操作。这样能够防止因这些操作而“污染”了当前最优秀的基因而导致反向进化的出现。


我在实现过程中还出现了另一点问题,是传引用还是传值所导致的。对个体的基因进行交叉和变异时用的是一个列表,Python 中传列表时传的实际上是一个引用,这样就导致个体进行交叉和变异后会改变个体本身的基因。导致的结果就是进化非常缓慢,并且伴随反向进化。


3、交叉如何实现?


选定一个个体的片段放入另一个体,并将不重复的基因的依次放入其他位置。


在实现这一步时,因为学生物时对真实染色体行为的固有认识,“同源染色体交叉互换同源区段”,导致我错误实现该功能。我只将两个个体的相同位置的片段互换来完成交叉,显然这样的做法是错误的,这会导致城市的重复出现。


4、在刚开始写这个算法时,我是半 OOP,半面向过程地写。后续测试过程中发现要改参数,更新个体信息时很麻烦,于是全部改为 OOP,然后方便多了。对于这种模拟真实世界的问题,OOP 有很大的灵活性和简便性。


5、如何防止出现局部最优解?


在测试过程中发现偶尔会出现局部最优解,在很长时间内不会继续进化,而此时的解又离最优解较远。哪怕是后续调整后,尽管离最优解近了,但依然是“局部最优”,因为还没有达到最优。


算法在起初会收敛得很快,而越往后就会越来越慢,甚至根本不动。因为到后期,所有个体都有着相对来说差不多的优秀基因,这时的交叉对于进化的作用就很弱了,进化的主要动力就成了变异,而变异就是一种暴力算法了。运气好的话能很快变异出更好的个体,运气不好就得一直等。


防止局部最优解的解决方法是增大种群规模,这样就会有更多的个体变异,就会有更大可能性产生进化的个体。而增大种群规模的弊端是每一代的计算时间会变长,也就是说这两者是相互抑制的。巨大的种群规模虽然最终能避免局部最优解,但是每一代的时间很长,需要很长时间才能求出最优解;而较小的种群规模虽然每一代计算时间快,但在若干代后就会陷入局部最优。


猜想一种可能的优化方法,在进化初期用较小的种群规模,以此来加快进化速度,当适应度达到某一阈值后,增加种群规模和变异率来避免局部最优解的出现。用这种动态调整的方法来权衡每一代计算效率和整体计算效率之间的平衡。

相关文章
|
20天前
|
机器学习/深度学习 人工智能 算法
猫狗宠物识别系统Python+TensorFlow+人工智能+深度学习+卷积网络算法
宠物识别系统使用Python和TensorFlow搭建卷积神经网络,基于37种常见猫狗数据集训练高精度模型,并保存为h5格式。通过Django框架搭建Web平台,用户上传宠物图片即可识别其名称,提供便捷的宠物识别服务。
217 55
|
1月前
|
开发框架 数据建模 中间件
Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器是那些静悄悄的幕后英雄。它们不张扬,却能默默地为函数或类增添强大的功能。本文将带你了解装饰器的魅力所在,从基础概念到实际应用,我们一步步揭开装饰器的神秘面纱。准备好了吗?让我们开始这段简洁而富有启发性的旅程吧!
36 6
|
8天前
|
存储 缓存 监控
局域网屏幕监控系统中的Python数据结构与算法实现
局域网屏幕监控系统用于实时捕获和监控局域网内多台设备的屏幕内容。本文介绍了一种基于Python双端队列(Deque)实现的滑动窗口数据缓存机制,以处理连续的屏幕帧数据流。通过固定长度的窗口,高效增删数据,确保低延迟显示和存储。该算法适用于数据压缩、异常检测等场景,保证系统在高负载下稳定运行。 本文转载自:https://www.vipshare.com
102 66
|
4天前
|
Python
课程设计项目之基于Python实现围棋游戏代码
游戏进去默认为九路玩法,当然也可以选择十三路或是十九路玩法 使用pycharam打开项目,pip安装模块并引用,然后运行即可, 代码每行都有详细的注释,可以做课程设计或者毕业设计项目参考
49 33
|
30天前
|
机器学习/深度学习 人工智能 算法
【宠物识别系统】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+图像识别
宠物识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了37种常见的猫狗宠物种类数据集【'阿比西尼亚猫(Abyssinian)', '孟加拉猫(Bengal)', '暹罗猫(Birman)', '孟买猫(Bombay)', '英国短毛猫(British Shorthair)', '埃及猫(Egyptian Mau)', '缅因猫(Maine Coon)', '波斯猫(Persian)', '布偶猫(Ragdoll)', '俄罗斯蓝猫(Russian Blue)', '暹罗猫(Siamese)', '斯芬克斯猫(Sphynx)', '美国斗牛犬
155 29
【宠物识别系统】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+图像识别
|
12天前
|
存储 运维 监控
探索局域网电脑监控软件:Python算法与数据结构的巧妙结合
在数字化时代,局域网电脑监控软件成为企业管理和IT运维的重要工具,确保数据安全和网络稳定。本文探讨其背后的关键技术——Python中的算法与数据结构,如字典用于高效存储设备信息,以及数据收集、异常检测和聚合算法提升监控效率。通过Python代码示例,展示了如何实现基本监控功能,帮助读者理解其工作原理并激发技术兴趣。
50 20
|
5天前
|
JavaScript API C#
【Azure Developer】Python代码调用Graph API将外部用户添加到组,结果无效,也无错误信息
根据Graph API文档,在单个请求中将多个成员添加到组时,Python代码示例中的`members@odata.bind`被错误写为`members@odata_bind`,导致用户未成功添加。
31 10
|
5天前
|
算法 网络协议 Python
探秘Win11共享文件夹之Python网络通信算法实现
本文探讨了Win11共享文件夹背后的网络通信算法,重点介绍基于TCP的文件传输机制,并提供Python代码示例。Win11共享文件夹利用SMB协议实现局域网内的文件共享,通过TCP协议确保文件传输的完整性和可靠性。服务器端监听客户端连接请求,接收文件请求并分块发送文件内容;客户端则连接服务器、接收数据并保存为本地文件。文中通过Python代码详细展示了这一过程,帮助读者理解并优化文件共享系统。
|
10天前
|
存储 算法 Python
文件管理系统中基于 Python 语言的二叉树查找算法探秘
在数字化时代,文件管理系统至关重要。本文探讨了二叉树查找算法在文件管理中的应用,并通过Python代码展示了其实现过程。二叉树是一种非线性数据结构,每个节点最多有两个子节点。通过文件名的字典序构建和查找二叉树,能高效地管理和检索文件。相较于顺序查找,二叉树查找每次比较可排除一半子树,极大提升了查找效率,尤其适用于海量文件管理。Python代码示例包括定义节点类、插入和查找函数,展示了如何快速定位目标文件。二叉树查找算法为文件管理系统的优化提供了有效途径。
41 5
|
24天前
|
数据可视化 Python
以下是一些常用的图表类型及其Python代码示例,使用Matplotlib和Seaborn库。
通过这些思维导图和分析说明表,您可以更直观地理解和选择适合的数据可视化图表类型,帮助更有效地展示和分析数据。
64 8