Python实现遗传算法解决TSP问题
- 遗传算法介绍
- 生物学概念和算法概念之间的对应关系
- 种群---编码集合
- 种群适应环境的能力---目标函数
- 环境阻力---适应度函数
- TSP问题简介
- 遗传算法中TSP问题的处理
- 城市坐标
- 编码
- 遗传算法中参数和函数设计
- 目标函数
- 适应度函数
- 算法流程图
- 交叉操作
- 变异操作
- 选择操作
- 种群的相关参数
- 编程实现
- 编程思路
- 代码
- 路径可视化
遗传算法介绍
遗传算法是一种全局仿生优化算法,通过模拟环境和生物种群之间的相互作用以改进传统搜索算法。
生物学概念和算法概念之间的对应关系
生物学概念 | 算法概念 |
种群 | 解的编码集合 |
环境阻力 | 适应度函数 |
种群适应环境的能力 | 目标函数 |
通过环境的筛选保留优势个体,完成种群的进化 | 通过一定的选择规则保留优质解,最终找到最优解 |
种群—编码集合
通过对 解空间中的解进行编码,一个编码代表一个解,编码构成构成 编码空间 代表解空间
算法开始时,需要有一个 初始种群 ,初始种群就是随机从编码空间中抽取 编码 所构成的编码空间子集
种群适应环境的能力—目标函数
目标函数是 根据所求解的问题得出的,比如在TSP问题中是所经过城市 路线的长度,目标函数值直接反映了解的质量
对应到生物学概念中就是种群中个体适应环境的能力
环境阻力—适应度函数
适应度函数是 对解质量的描述,即通过函数的方式 将解的质量 映射到 0-1之间,方便后续对解的选择
TSP问题简介
TSP问题全称 Travelling Salesman Problem (旅行商问题)
给出城市坐标,求解旅行商 如何每个城市仅经过一次就可以遍历所有城市,而且要求路径长度最短
遗传算法中TSP问题的处理
城市坐标
城市 | 坐标 | 城市 | 坐标 | 城市 | 坐标 |
0 | (18,54) | 10 | (71,44) | 20 | (7,64) |
1 | (87,76) | 11 | (64,60) | 21 | (22,60) |
2 | (74,78) | 12 | (68,58) | 22 | (25,62) |
3 | (71,71) | 13 | (83,69) | 23 | (62,32) |
4 | (25,38) | 14 | (58,69) | 24 | (87,7) |
5 | (58,35) | 15 | (54,62) | 25 | (91,38) |
6 | (4,50) | 16 | (51,67) | 26 | (83,46) |
7 | (13,40) | 17 | (37,84) | 27 | (41,26) |
8 | (18,40) | 18 | (41,94) | 28 | (45,21) |
9 | (24,42) | 19 | (2,99) | 29 | (44,35) |
编码
城市编号从0至29,而且需要记录30个城市的顺序,所以采用实数编码
具体形式如下:
编码空间中的一个编码,也就是解空间中的一个解,也是种群中的一个个体
用向量表示
path = [20, 1, 3, 7, 14, 25, 10, 2, 27, 24, 8, 0, 16, 26, 22, 5, 18, 28, 4, 19, 13, 12, 11, 21, 6, 23, 29, 9, 17, 15]
该向量含义:
从城市20开始,经历城市1,3,7, 14, 25, 10, 2, 27, 24, 8, 0, 16, 26, 22, 5, 18, 28, 4, 19, 13, 12, 11, 21, 6, 23, 29, 9, 17 ,到城市15再回到20结束
遗传算法中参数和函数设计
目标函数
将路径长度函数作为目标函数,两城市间 距离按照欧式距离(向量差的二范数)计算
定义城市 i 和城市 j 之间的距离,用Xi代表第i个城市的坐标,Xj代表第j个城市的坐标
定义推销员走过的路径总长度,path[i]代表路径中经过的第i个城市的编号,数据可以参考上一段中编码的内容
path作为一个可行解
适应度函数
简单的适应度函数 要根据 需要的问题设计
比如在TSP问题中,要求找到的解的目标函数值越小,对解的评价越好,这样就可以直接使用 目标函数值的倒数作为适应度函数值
下列公式中path作为一个可行解
也可以自己设计适应度函数,比如
借鉴了sigmoid函数的形式,并对数据做了最大最小标准化,A、B是人为给定的常系数
mean、max、min是种群所有个体的目标函数值的均值、最大值、最小值
图像如下A=5,B=0
目标函数值小于均值的个体有很大的概率被训责进入下一代种群中
目标函数值较大的个体则被选择进入下一代种群中的概率很小
算法流程图
适应度计算中包含了目标函数值的计算
交叉操作
交叉操作 模拟的是生物将染色体遗传到下一代中时,染色体的交叉现象
在遗传算法中,交叉操作 是为实现算法的区域随机搜索功能而存在的
执行交叉操作时,随机将种群中的个体两两配对,按照交叉概率(Pc)选择是否对一对个体进行染色体片段的交换
简单介绍两种交叉操作的方式:
假设某问题的一对待交叉个体的编码是(| |标记的是交叉位点):
X1 = 1 2 |4 6 3| 9 8
X2 = 4 2 |9 1 8| 3 6
顺序交叉:
将X1 的交换片段取出
X1‘ = _ _ |4 6 3| _ _
使用X2中不与X1’中相等的元素,按原顺序填充到X1‘中
X2中不与X1’中相等的元素
_ 2 |9 1 8| _ _
得到 X1‘ = 2 9 |4 6 3| 1 8
同理可得到 X2’ = 2 4 |9 1 8| 6 3
匹配交叉:
将X1与X2的交换片段直接交换,若有重复元素,则用交换片段的对位 替换非交换片段中的重复元素
X1‘ = 1 2 |9 1 8| 9 8
X2’ = 4 2 |4 6 3| 3 6
很明显X1中9 1 8 均重复,所以使用4 6 3 替换掉非交换片段中的9 1 8
X1‘’ = 6 2 |9 1 8| 4 3
同理可得
X2‘’ = 9 2 |4 6 3| 8 1
变异操作
变异操作 模拟生物遗传时的 基因突变现象
遗传算法中,变异操作保证了个体(解,也是编码)的多样性,保证算法能在足够多的方向上探索可能的解
执行变异操作时,对每一个个体(解,也是编码),根据变异概率(Pm)决定是否将编码的某一个元素用另一个元素代替掉
借用介绍交叉操作时用的例子:
X1 = 1 2 4 6 3 9 8
若满足变异条件,则随机选择一个元素a,再随机选择另一个元素b,用b替换a,为保证X1中无重复元素也用a替换b
假设a=2,b=3,则变异后的X1:
X1’ = 1 3 4 6 2 9 8
选择操作
选择操作 模拟环境对生物种群中个体的选择,适者生存
选择比例:根据一定的选择规则和策略对每个个体进行筛选,选择出的进入下一代种群的个体数量占原种群的比例
种群的相关参数
种群规模 Np = 200
交叉概率 Pc = 0.4
变异概率 Pm = 0.01
选择比例 C = 0.35
最大迭代次数 G = 1000 ——> 也就是终止规则
编程实现
编程思路
采用函数式编程
程序中 一个解(编码)以列表的形式储存,数据类型是 list,元素数据类型是int
处理路径长度时,将城市间的距离以二维列表的形式储存起来,计算解的目标函数值时仅相加就可以,数据类型是list,list的元素数据类型是list
选择操作使用轮盘赌的方式实现,对每一个解,选择时,使用random模块随机产生一个0-1之间的数p,若p小于这个解的适应度值,则选择这个解进入下一代解集中
代码
使用的是jupyter代码
In[1]:
locations = [
[18,54],[87,76],[74,78],[71,71],[25,38],
[58,35],[4,50],[13,40],[18,40],[24,42],
[71,44],[64,60],[68,58],[83,69],[58,69],
[54,62],[51,67],[37,84],[41,94],[2,99],
[7,64],[22,60],[25,62],[62,32],[87,7],
[91,38],[83,46],[41,26],[45,21],[44,35]
]
In[2]:
import numpy as np
def dist_cal(x,y):
return ((x[0]-y[0])**2+(x[1]-y[1])**2)**0.5
#距离矩阵
dist_mat = np.zeros((30,30))
for i in range(0,30):
for j in range(i+1,30):
tmp = dist_cal(locations[i],locations[j])
dist_mat[i][j] = tmp
dist_mat[j][i] = tmp
In[3]:
import math
#目标函数
def target_fuc(order):
global dist_mat
sum_dist = 0
for i in range(1,len(order)):
sum_dist += dist_mat[order[i]][order[i-1]]
return sum_dist
#适应度函数
def fitness(dist):
m,me = max(dist)-min(dist),sum(dist)/len(dist)
def sigmoid(x):
return 1/(1+math.exp(5*x))
temp = map(lambda x:(x-me)/m,dist)
fit_values = [sigmoid(i) for i in temp]
return fit_values
In[4]:
import random
#实现选择操作
def choice(target_values,Np,pd):
#轮盘赌的实现
re = copy.deepcopy(target_values)
i = 0
while len(re)/Np > pd:
k = random.random()
for i in re:
if i<k:
re.remove(i)
return [target_values.index(item) for item in re]
In[5]:
#实现交叉操作函数
import copy
def op(l1,l2):
len_v = len(l1)
oops = random.randint(0,len_v-3)
for t in range(oops,oops+len_v//10):
elem1 = l1[t]
elem2 = l2[t]
k = l1.index(elem2)
l1[t] = l2[t]
l2[t] = l2[k]
l2[k] = l1[k]
l1[k] = elem1
def change(pc,target_exam):
target_vecs = copy.deepcopy(target_exam)
nums = 0
len_t = len(target_vecs)
#交叉操作以及映射
#对选中的P群体进行交叉操作
for i in range(len_t-1):
for j in range(i+1,len_t):
k = random.random()
if k > pc:
continue
op(target_vecs[i],target_vecs[i+1])
return target_vecs
In[6]:
#实现变异操作
def mutate(pm,target_data):
target = copy.deepcopy(target_data)
len_v = len(target)
for i in range(len_v):
k = random.random()
if k > pm:
continue
id1 = random.randint(0,len_v-1)
id2 = random.randint(0,len_v-1)
temp = target[id1]
target[id1] = target[id2]
target[id2] = temp
return target
In[7]:
#随机初始化种群
def rand_creat_group(Np,len_vec):
res = []
for i in range(Np):
temp = []
while len(temp)<len_vec:
ite = random.randint(0,len_vec-1)
if ite not in temp:
temp.append(ite)
res.append(temp)
return res
In[8]:
#参数
Np = 200
pc = 0.4
pm = 0.01
pd = 0.35
#初始种群
group = rand_creat_group(Np,len(locations))
In[9]:
G = 1000
processing_array = dict()
min_path = list()
gmin = 1e5
len_vec = len(group[0])
group1 = group
while G>0:
G -= 1
#计算访问顺序总距离
target_values = []
for item in group1:
tmp = target_fuc(item)
target_values.append(tmp)
#记录每次迭代的最优路径
if gmin > tmp:
gmin = tmp
min_path.append(copy.deepcopy(item))
fitness_values = fitness(target_values)
#存放每一次迭代的结果
processing_array[1000-G] = min(target_values)
#根据适应值选取遗传个体
choice_ids = choice(fitness_values,Np,pd)
choice_group = list()
for i in choice_ids:
choice_group.append(group1[i])
temp_group = copy.deepcopy(choice_group)
#实现交叉遗传,扩张种群和维持种群规模不变
temp_group += change(pc,choice_group)
while len(temp_group)<Np:
temp_group += copy.deepcopy(temp_group)
temp_group = temp_group[:200]
#对种群实现变异
for i in range(len(temp_group)):
temp_group[i] = mutate(pm,temp_group[i])
group1 = copy.deepcopy(temp_group)
In[10]:
#结果可视化
import matplotlib.pyplot as plt
generties = list(processing_array.keys())
dist_square = list(processing_array.values())
In[11]:
plt.rcParams['figure.figsize'] = (20,10)
plt.title('Np={} pc={} pm={} pd={} G={} min_dist={}'.format(Np,pc,pm,pd,1000,min(dist_square)))
plt.plot(generties,dist_square)
plt.xlabel('Generations')
plt.ylabel('Distance of path')
#plt.savefig('./result/{}_{}_{}_{}_{}_{}_5_blog.png'.format(Np,pc,pm,pd,100,2))
Out[11]:
路径可视化