1.遗传算法简介

遗传算法(GA)是用于解决NP难问题如JSP问题,TSP问题常用的启发式算法。上世纪70年代由美国的John holland提出,是运用计算机仿真,通过交叉变异等方式,模拟自然进化过程搜索最优解的方法。主要特点是对非线性极值问题能以概率 1 跳出局部最优解,找到全局最优解。

2.初始种群的选择

在求解取值连续的问题时可使用完全随机的值,但在求解旅行商问题等非连续的问题时通常采用改良圈法,得到一个相对较优的解,然后再利用遗传算法得出最优解。

改良圈法基本原理

对于随机产生的某条路线
python 遗传算法 优化算法 退火算法_启发式算法
如果满足
python 遗传算法 优化算法 退火算法_遗传算法_02
其中d(x,y)为x,y两点的间距
原路线修改为
python 遗传算法 优化算法 退火算法_数学建模_03
即u,v之间所有点的顺序反转

改良圈法代码实现

这部分的代码以华为面试的蜜蜂采蜜问题为例
即输入A,B,C,D,E五个点的相对于原点的坐标,得出从原点出发经过这五个点后返回原点的最小路径
测试用例:
输入:200,0,200,10,200,50,200,30,200,25
输出:456

import numpy as np
import math
x=[0]
y=[0]
final_list=[]
#计算路径的长度
def cul_dist(org_rout):
    sum=0
    for i in range(len(org_rout)-1):
        sum+=d[org_rout[i]][org_rout[i+1]]
    return sum
#输入坐标
for i in range(5):
    temp_x=int(input("请输入x坐标"))
    temp_y=int(input("请输入y坐标"))
    x.append(temp_x)
    y.append(temp_y)
#初始化并得到距离矩阵
d=np.zeros((6,6))
for i in range(6):
    for j in range(6):
        d[i][j]=math.sqrt((x[i]-x[j])**2+(y[i]-y[j])**2)
#容易陷入局部最优,多次运行确保得到最小值
for i in range(20):
    #随机生成的路线
    org_rout=np.random.choice(range(1,6),size=5,replace=False)
    #这里是改良圈法的开始
    for m in range(6):
        #退出条件
        flag=0
        for j in range(3):
            for k in range(j+2,5):
                #进行判断
                if d[org_rout[j]][org_rout[k-1]]+d[org_rout[j+1]][org_rout[k]]<d[org_rout[j]][org_rout[j+1]]+d[org_rout[k-1]][org_rout[k]]:
                    org_rout[j+1:k]=org_rout[k-1:j:-1]
                    flag=1
        #退出
        if flag==0:
            break
    #补上起点终点,便于计算长度
    org_rout=np.insert(org_rout,0,0)
    org_rout=np.append(org_rout,0)
    final_list.append(cul_dist(org_rout))
#得到最小值
print(np.array(final_list).min())

得到结果为456.155,与题目答案一致
PS:这并不是最优的解法,只是恰好可以使用改良圈法
容易发现改良圈法通常只是得到局部最优解,也就引出了我们今天的主题“遗传算法”,将其作为遗传算法的初始种群,可以大大减少遗传代数

3.遗传算法

包括前面提到的种群选择,还有建立目标函数,交叉,变异,计算适应度以及选择等几大操作,接下来我以一道相对复杂的TSP问题为例,介绍遗传算法的使用

问题简介

A市某辆大货车要前往三十个点中确定的十四个点送货,已知其可从任意一点出发出发和各点的位置坐标,求一条最短的路线,使大货车送完之后仍然回到出发点

编码与解码

离散点的问题可采用一串随机数作为编码,解码的方式为随机数从小到大排列的下标,例如对于五个点的问题,有编码[0.22,0.98,0.34,0.42,0.17],对应路径5-1-3-4-2

对于连续取值的问题,可使用二进制编码
二进制的编码和解码可以参考其他的文章
遗传算法详解 附python代码实现

构造目标函数

通常由个体解码后对应值计算得到,本题中为路径的长度

进行单点交叉

由父本和母本两个个体繁殖产生新的个体的过程被称为交叉,子代获取了部分父本和部分母本的DNA,这个过程中子代继承了上一代的优良特性,在不断的交叉和变异中,最终得到问题的最优解
通常选取交叉率为0.8~1之间
具体的操作为,随机选取一个交叉点,一个子代得到交叉点之前父本的基因和交叉点之后母本的基因,另一个子代得到交叉点之前母本的基因和交叉点之后父本的基因
例如选取父本[0.22,0.98,0.34,0.42,0.17]
母本[0.55,0.32,0.76,0.23,0.01]
选取第二个点之后为交叉点
得到子代分别为[0.22,0.98,0.76,0.23,0.01]和[0.55,0.32,0.34,0.42,0.17]
最后将子代加入群体

随机进行变异

从序列中随机选取三个位置u<v<w,位于u,v之间的部分移出并插入w之后
这是实现种群多样性的手段,也是实现全局最优的保证

计算个体在环境的适应度

为个体在环境中的适应程度,适应度越高代表个体越适合于这个环境,被选择的几率也就越大
本题中解码出路径越短适应性也就越强,我采用了种群解码的最大值减去个体解码的值作为该个体的适应度

选择个体

为了体现出达尔文“物竞天择,适者生存”的自然选择法则,我们应该尽可能选择适应度更高的个体,淘汰掉不适合环境的个体

选择新的种群时可以进行概率选择,也可以直接选择适应度最高的那一部分个体

总的代码如下:

import numpy as np
import pandas as pd
import math

pop_size=1000
cross_rate=0.95
heter_rate=0.1
generation=50

data=pd.read_csv("经纬坐标.csv")
distance_arr=np.zeros([30,30])
#城市中道路往往呈网格状,利用坐标差绝对值得到距离矩阵
for i in range(30):
    for j in range(30):
        distance_arr[i][j]=abs(data.values[i,1]-data.values[j,1])+abs(data.values[i,2]-data.values[j,2]) 
area=[6,7,8,9,10,21,22,23,24,25,26,27,28,30]
pop_total=[]
#解码,根据随机数列表返回对应路径
def intep(rand_rout):
    rand_arg=np.argsort(rand_rout)
    point_choiced=[]
    for k in rand_arg:
        point_choiced.append(area[k])
    return point_choiced
#根据路径计算对应长度
def culc(pop_total):
    dist_list=[]
    for pot in pop_total:
        rout=intep(pot)
        dist=distance_arr[rout[len(rout)-1]-1][rout[0]-1]
        for i in range(len(rout)-1):
            dist+=distance_arr[rout[i]-1][rout[i+1]-1]
        dist_list.append(dist)
    dist_list=np.array(dist_list)
    return (dist_list.max()-dist_list)+1e-4
#交叉操作
def cross(pop_total):
    new_pop_total=pop_total.copy()
    for father in pop_total: 
        if np.random.rand() < cross_rate:
            #子代c1,c2
            c1=[]
            c2=[]
            choc=math.floor(np.random.rand()*len(pop_total))
            mother=pop_total[choc]
            cross_p=np.random.randint(low=0, high=14)
            c1=father
            c1[cross_p:]=mother[cross_p:]
            new_pop_total=np.vstack((new_pop_total,c1))
            c2=mother
            c2[cross_p:]=father[cross_p:]
            #插入种群中
            new_pop_total=np.vstack((new_pop_total,c2))
    return new_pop_total
#变异操作
def heter(pop_total):
    for i in pop_total: 
        if np.random.rand() < cross_rate:
            i_c=[]
            for ele in i:
                i_c.append(ele)
            #随机产生的三个位置
            rand_p=np.floor(np.random.rand(3)*14).astype(int)
            rand_p=np.sort(rand_p)
            temp=i_c[rand_p[0]:rand_p[1]]
            del i_c[rand_p[0]:rand_p[1]]
            i_c.insert(rand_p[2],temp)
            i=i_c
    return pop_total
#从种群中挑选下一代的个体
def select(pop_total, fitness): 
    index = np.argsort(-fitness)
    return np.array(pop_total)[index[:pop_size]]

#生成初始种群
for i in range(pop_size):
    rand_rout=np.random.rand(14)
    point_choiced=intep(rand_rout)
    #使用改良圈法
    for j in range(len(point_choiced)):
        flag=0
        for m in range(len(point_choiced)-2):
            for n in range(m+2,len(point_choiced)):
                if distance_arr[point_choiced[m]-1][point_choiced[n-1]-1]+distance_arr[point_choiced[m+1]-1][point_choiced[n]-1]<distance_arr[point_choiced[m]-1][point_choiced[m+1]-1]+distance_arr[point_choiced[n]-1][point_choiced[n-1]-1]:
                    rand_rout[m+1:n]=rand_rout[n-1:m:-1]
                    flag=1
        if flag==0:
            break
    pop_total.append(rand_rout)
pop_total=np.array(pop_total)

for i in range(generation):    
    pop_total=cross(pop_total)
    pop_total=heter(pop_total)
    fitness=culc(pop_total)
    pop_total=select(pop_total, fitness)
dist_list=[]
for pot in pop_total:
    rout=intep(pot)
    dist=distance_arr[rout[len(rout)-1]-1][rout[0]-1]
    for i in range(len(rout)-1):
        dist+=distance_arr[rout[i]-1][rout[i+1]-1]
    dist_list.append(dist)
print(np.array(dist_list).min())

用时约15s,得出结果为12.9128

4.改良遗传算法

a.在个体配对时讲究“门当户对”
b.使用正态分布选择变异点
c.优化适应度函数,提高选择更优解的比例
d.防止优良解因为变异遭到破坏,对于最优解进行保护
e.引入遗传-灾变算法,有效避免了局部最优解的垄断,增强了全局搜索的能力