参考:
话不多说,直接上代码
代码部分
#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@author: liujie
@software: PyCharm
@file: TSP.py
@time: 2020/11/17 15:47
"""
'''
问题是:旅行商要去20个城市(本题中随机化每个城市的位置)贩卖货物,请问他走哪条路径最短?
解题步骤:
1.随机初始化20个城市的横纵坐标
2.随机生成原始种群pop,假设种群中含有500条染色体,每个染色体上含有20个基因代表城市
3.从pop中依次选取每条染色体,得到所有染色体上所有城市的横纵坐标。将横坐标放入lx中,将纵坐标放入ly中(500*20)
4.计算每条染色体规定的路线的旅行路程,并以此进行适应度计算,适应度大的在下一步被选到的概率大
5.依概率从500条染色体中有放回地抽取500条染色体(有重复染色体),令这新的500条染色体组成新的种群pop_new
6.将pop_new复制为pop_copy来表示另一条父染色体用来与pop_new中的染色体进行交叉配对
7.从pop_new中选出第一条染色体(father)进行交叉检验
8.假设交叉的概率为0.1,则pop_new中的每条染色体都有10%的概率需要交叉,假设father是要进行交叉的(若未被选中则继续第二
条染色体的交叉检验,直至遍历完pop_new中所有染色体)
9.随机在pop_copy中选择一条染色体与pop_new中的第一条染色体进行交叉,假设选中pop_copy中第三条染色体(mother)
10.进行交叉操作。选择father上需要改变的基因有哪些呢,假设为第1,6,8,10,15个基因,假设他们代表的城市是0-4
11.将father上其他未选中的基因(5-19)保存下来,称为city_keep,在mother中找到代表城市0-4的基因,称为city_swap,
按顺序将city_keep和city_swap进行组合,得到子染色体child
12.对child上的每个基因进行变异检验,假设变异概率是0.02
13.假设child上的第一个基因需要变异,则从child上在随机选择一个基因(仍有可能是第一个基因),这两个基因互换位置。遍历完
child上的所有基因后,用child代替father
14.遍历完pop_new中的所偶遇染色体后,第一次迭代完成
15.达到迭代次数后,pop_new中适应度最高的染色体为最优染色体,即最优路线
'''
import numpy as np
N_CITIES = 20 # DNA size
POP_SIZE = 500
CROSS_RATE = 0.1 # 交叉概率0.1
MUTATE_RATE = 0.02 # 变异概率0.02
N_GENERATIONS = 100 # 迭代次数
# 得到所有城市的坐标
def translateDNA(DNA,city_position):
# np.empty_like-返回一个和输入矩阵shape相同的array
line_x = np.empty_like(DNA,dtype=np.float64)
line_y = np.empty_like(DNA,dtype=np.float64)
for i,d in enumerate(DNA):
# i 是从0到499这500个数;d是第i条染色体的所有基因
# 将第i条染色体上的所有基因(城市)对应的坐标取出,将横坐标放入city——coord第一列
city_coord = city_position[d]
# 将第i条染色体上的所有城市的横坐标取出并放到line_x的第i行上
line_x[i,:] = city_coord[:,0]
line_y[i,:] = city_coord[:,1]
return line_x,line_y
# 得到适应度,fitness = np.exp(N_CITIES * 2 / total_distance)
def get_fitness(line_x,line_y):
total_distance = np.empty((line_x.shape[0],),dtype=np.float64)
for i,(xs,ys) in enumerate(zip(line_x,line_y)):
# 计算第i条染色体上的总距离
total_distance[i] = np.sum(np.sqrt(np.square(np.diff(xs)) + np.square(np.diff(xs))))
# 扩大距离差距
fitness = np.exp(N_CITIES * 2 / total_distance)
return fitness,total_distance
# 依概率从500条染色体中有放回地抽取500条染色体(有重复染色体)
def select(pop,fitness):
idx = np.random.choice(np.arange(POP_SIZE),size=POP_SIZE,replace=True,p = fitness/fitness.sum())
return pop[idx]
# 交叉验证
def crossover(father,pop):
# np.random.rand()生成一个随机数(0-1内的浮点数)
# np.random.randint() 生成一个整型随机数
if np.random.rand() < CROSS_RATE:
i_ = np.random.randint(0,N_CITIES,size=1)
cross_points = np.random.randint(0,2,size=N_CITIES).astype(np.bool)
# 将染色体中cross_points为False的基因取出
keep_city = father[~cross_points] # 未选中的基因
# 种群中第i_条染色体上的所有基因
# print(pop[i_].ravel())
# np.isin()invert=False表示返回一样的bool索引,为True时表示返回不一样的bool索引
# print(np.isin(pop[i_].ravel(),keep_city,invert=True))
# 将i_染色体上keep_city中没有的基因取出
swap_city = pop[i_,np.isin(pop[i_].ravel(),keep_city,invert=True)]
father[:] = np.concatenate((keep_city,swap_city)) # 默认第一个轴
return father
# 基因变异检验
def mutant(child):
for point in range(N_CITIES):
if np.random.rand() < MUTATE_RATE:
swap_point = np.random.randint(0,N_CITIES,size=1)
swapA,swapB = child[point],child[swap_point]
child[point],child[swap_point] = swapB,swapA # 基因位置互换
return child
if __name__ == '__main__':
# 随机化城市坐标
city_position = np.random.rand(N_CITIES,2) # (20,2)
# 随机生成种群pop
# np.vstack()按垂直方向堆叠数组
pop = np.vstack([np.random.permutation((N_CITIES)) for _ in range(POP_SIZE)]) # (500,20)
for generation in range(N_GENERATIONS):
# 得到所有城市坐标x,y
lx,ly = translateDNA(pop,city_position)
# 得到适应度
fitness,total_distance = get_fitness(lx,ly)
# 找出适应度最大的索引,表明下一步选到概率大
best_idx = np.argmax(fitness)
# 打印现在情况
print('Gen:',generation,'| best fitness: %.2f'% fitness[best_idx],'| shortest distance:%.2f'%total_distance[best_idx],
'| path:',pop[best_idx])
# 依概率从500条染色体中有放回地抽取500条染色体(有重复染色体)
pop = select(pop,fitness)
# 将新的种群复制为pop_copy
pop_copy = pop.copy()
# 遍历完pop中的所有染色体后,第一次迭代完成
for father in pop:
# 交叉操作得到子染色体
child = crossover(father,pop_copy)
# 变异检验
child = mutant(child)
father = child
结果部分
Gen: 0 | best fitness: 2297.22 | shortest distance:5.17 | path: [14 17 4 5 9 8 10 11 19 18 0 7 3 1 13 12 16 2 6 15]
Gen: 1 | best fitness: 2297.22 | shortest distance:5.17 | path: [14 17 4 5 9 8 10 11 19 18 0 7 3 1 13 12 16 2 6 15]
Gen: 2 | best fitness: 9761.97 | shortest distance:4.35 | path: [14 17 4 5 0 8 10 11 19 18 7 9 3 1 13 12 16 2 6 15]
Gen: 3 | best fitness: 9761.97 | shortest distance:4.35 | path: [14 17 4 5 0 8 10 11 19 18 7 9 3 1 13 12 16 2 6 15]
Gen: 4 | best fitness: 18836.41 | shortest distance:4.06 | path: [14 17 4 5 0 8 10 7 19 18 11 9 3 1 13 12 16 2 6 15]
Gen: 5 | best fitness: 1336357.58 | shortest distance:2.84 | path: [14 17 4 5 0 8 10 11 9 18 7 19 3 1 13 12 16 2 6 15]
Gen: 6 | best fitness: 2934291.05 | shortest distance:2.69 | path: [14 17 4 5 0 8 10 11 9 18 7 19 3 1 13 12 16 15 6 2]
Gen: 7