1.遗传算法简介
遗传算法(GA)是用于解决NP难问题如JSP问题,TSP问题常用的启发式算法。上世纪70年代由美国的John holland提出,是运用计算机仿真,通过交叉变异等方式,模拟自然进化过程搜索最优解的方法。主要特点是对非线性极值问题能以概率 1 跳出局部最优解,找到全局最优解。
2.初始种群的选择
在求解取值连续的问题时可使用完全随机的值,但在求解旅行商问题等非连续的问题时通常采用改良圈法,得到一个相对较优的解,然后再利用遗传算法得出最优解。
改良圈法基本原理
对于随机产生的某条路线
C
i
j
=
w
1
w
2
w
3
.
.
.
w
u
−
1
w
u
w
u
+
1
.
.
.
w
v
−
1
w
v
w
v
+
1
.
C_{ij} = w_{1}w_{2}w_{3}...w_{u-1}w_{u}w_{u+1}...w_{v-1}w_{v}w_{v+1}.
Cij=w1w2w3...wu−1wuwu+1...wv−1wvwv+1.
如果满足
d
(
w
u
,
w
v
−
1
)
+
d
(
w
u
+
1
,
w
v
)
<
d
(
w
u
,
w
u
+
1
)
+
d
(
w
v
,
w
v
−
1
)
d(w_{u},w_{v-1})+d(w_{u+1},w_{v})<d(w_{u},w_{u+1})+d(w_{v},w_{v-1})
d(wu,wv−1)+d(wu+1,wv)<d(wu,wu+1)+d(wv,wv−1)
其中d(x,y)为x,y两点的间距
原路线修改为
C
i
j
=
w
1
w
2
w
3
.
.
.
w
u
−
1
w
u
w
v
−
1
w
v
−
2
.
.
.
w
u
+
1
w
v
w
v
+
1
.
C_{ij} = w_{1}w_{2}w_{3}...w_{u-1}w_{u}w_{v-1}w_{v-2}...w_{u+1}w_{v}w_{v+1}.
Cij=w1w2w3...wu−1wuwv−1wv−2...wu+1wvwv+1.
即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.引入遗传-灾变算法,有效避免了局部最优解的垄断,增强了全局搜索的能力