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个城市的坐标

python遗传算法deap Python遗传算法解决tsp_python遗传算法deap

定义推销员走过的路径总长度,path[i]代表路径中经过的第i个城市的编号,数据可以参考上一段中编码的内容
path作为一个可行解

python遗传算法deap Python遗传算法解决tsp_python遗传算法deap_02

适应度函数

简单的适应度函数 要根据 需要的问题设计
比如在TSP问题中,要求找到的解的目标函数值越小,对解的评价越好,这样就可以直接使用 目标函数值的倒数作为适应度函数值
下列公式中path作为一个可行解

python遗传算法deap Python遗传算法解决tsp_机器学习_03

也可以自己设计适应度函数,比如

python遗传算法deap Python遗传算法解决tsp_遗传算法_04

借鉴了sigmoid函数的形式,并对数据做了最大最小标准化,A、B是人为给定的常系数
mean、max、min是种群所有个体的目标函数值的均值、最大值、最小值
图像如下A=5,B=0
目标函数值小于均值的个体有很大的概率被训责进入下一代种群中
目标函数值较大的个体则被选择进入下一代种群中的概率很小

python遗传算法deap Python遗传算法解决tsp_重复元素_05

算法流程图

适应度计算中包含了目标函数值的计算

python遗传算法deap Python遗传算法解决tsp_python遗传算法deap_06

交叉操作

交叉操作 模拟的是生物将染色体遗传到下一代中时,染色体的交叉现象
在遗传算法中,交叉操作 是为实现算法的区域随机搜索功能而存在的
执行交叉操作时,随机将种群中的个体两两配对,按照交叉概率(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]:

python遗传算法deap Python遗传算法解决tsp_python遗传算法deap_07

路径可视化

python遗传算法deap Python遗传算法解决tsp_机器学习_08