问题:旅行商要去20个城市(本题中随机化每个城市的位置)贩卖货物,请问他走哪条路径路程最短?
解题步骤:
1、随机初始化20个城市的横纵坐标。
2、随机生成原始种群
,假设种群中含有500条染色体,每个染色体上含有20个基因代表城市。
3、从
中依次取出每条染色体,得到所有染色体上所有城市的横纵坐标。将横坐标放入
中,将纵坐标放入
中。(
和
都是
的矩阵)
4、计算每条染色体规定的路线的旅行路程,并以此进行适应度计算。适应度大的在下一步被选到的概率大。
5、依概率从500条染色体中有放回地抽取500条染色体(有重复染色体),令这新的500条染色体组成新的种群
。
6、将
复制为
来表示另一条父染色体用来与
中的染色体进行交叉配对。
7、从
中选出第一条染色体(称其为
)进行交叉检验。
8、假设交叉的概率为
,则
中的每条染色体都有
的概率需要交叉,假设
是要进行交叉的。(若未被选中则继续第二条染色体的交叉检验,直到遍历完
中所有染色体)
9、随机在
中选择一条染色体与
中第一条染色体进行交叉,假设选中
中第三条染色体(称其为
)。
9、进行交叉操作。选择
上需要改变的基因是哪些,假设为第1,6,8,10,15个基因,假设它们代表的城市是
。
10、将
上其他未被选中的基因(代表
)保存下来,称为
,在
中找到代表城市
的基因,称为
,按顺序将
和
进行组合,得到子染色体
11、对
上的每个基因进行变异检验,假设变异概率为0.02。
12、假设
上第一个基因需要变异,则从
上再随机选择一个基因(仍有可能为第一个基因),让这两个基因互换位置。遍历完
上的所有基因之后,用
代替
。
13、遍历完
中的所有染色体之后,第一次迭代完成。
14、达到迭代次数要求后,
中的适应度最高的染色体为最优染色体,即最优路线。
以下为
代码:
import numpy as np
N_CITIES = 20 # DNA size
CROSS_RATE = 0.1
MUTATE_RATE = 0.02
POP_SIZE = 500
N_GENERATIONS = 100
def translateDNA(DNA, city_position): # get cities' coord in order
line_x = np.empty_like(DNA, dtype=np.float64) # 返回一个和输入矩阵shape相同的array
line_y = np.empty_like(DNA, dtype=np.float64)
for i, d in enumerate(DNA):
# i 是从0到499这500个数;d 是第 i 条染色体的所有基因,如
# 0 [ 6 19 5 3 10 17 1 8 15 7 18 2 13 12 4 14 9 16 11 0]
city_coord = city_position[d] # 将第 i 条染色体上的所有基因(城市)对应的坐标取出,将横坐标放 city_coord 第一列,纵坐标放第二列
line_x[i, :] = city_coord[:, 0] # 将第 i 条染色体上的所有城市的横坐标取出并放到 line_x 的第 i 行上
line_y[i, :] = city_coord[:, 1] # 将第 i 条染色体上的所有城市的纵坐标取出并放到 line_y 的第 i 行上
return line_x, line_y
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)):
total_distance[i] = np.sum(np.sqrt(np.square(np.diff(xs)) + np.square(np.diff(ys)))) # 计算第i条染色体上的总距离
fitness = np.exp(N_CITIES * 2 / total_distance) # 扩大距离差距
return fitness, total_distance
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(parent, pop):
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)
keep_city = parent[~cross_points] # 将染色体中 cross_points 为 False 的基因取出
# pop[i_].ravel() --> pop 种群中第 i_ 条染色体上的所有基因
# np.isin(pop[i_].ravel(), keep_city, invert=True) --> invert=True 表示若 keep_city 中的基因与pop 种群中第 i_ 条染色体上的某基因相同
# 则将此位置置 False
swap_city = pop[i_, np.isin(pop[i_].ravel(), keep_city, invert=True)] # 将在 i_ 染色体上 keep_city 中没有的基因取出
parent[:] = np.concatenate((keep_city, swap_city))
return parent
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
city_position = np.random.rand(N_CITIES, 2)
pop = np.vstack([np.random.permutation(N_CITIES) for _ in range(POP_SIZE)])
for generation in range(N_GENERATIONS):
lx, ly = translateDNA(pop, city_position)
fitness, total_distance = get_fitness(lx, ly)
best_idx = np.argmax(fitness)
print('Gen:', generation, '| best fit: %.2f' % fitness[best_idx], '| shortest distance: % .2f' % total_distance[best_idx],
'| path:', pop[best_idx])
pop = select(pop, fitness)
pop_copy = pop.copy()
for parent in pop:
child = crossover(parent, pop_copy)
child = mutant(child)
parent = child