了却一个心愿
文章目录
目录
文章目录
前言
二、主要内容
三、使用步骤
1.将压缩包下载解压
2.读入数据
3.最终结果
前言
遗传算法(Genetic Algorithm,GA)最早是由美国的 John holland于20世纪70年代提出,该算法是根据大自然中生物体进化规律而设计提出的。是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法。相信对于路径规划来说,这种方法其实也是一种目前较好的寻找最优解的方法。
一、遗传算法原理
原理都是一样的,有很多博客都写得很好,其理论的都是万变不离其宗。
二、主要内容
虽然理论摆在那里,有人能够看懂,有人看不懂,我也是只能看懂一点点,这就使得理论运用到代码当中就变得相当困难了,所以对于路径规划这样的题目,我想自己编出一个合适的遗传算法几乎就不可能了,还好在github里面发现了一个关于遗传算法TSP问题的代码,代码简单易懂(注释多),我自己试着运行了一下,发现效果也还不错,运行时间短,结果也不会总变来变去(几乎就是正确答案)。
GItHub链接
大哥的视频讲解
三、使用步骤
1.将压缩包下载解压
会发现有如下三个代码文件:
config.py:各参数配置
ga.py:遗传算法实现
main.py:程序入口,数据预处理,效果展示
ga.py:就用了Python的一两个基础库,强我只能说。
from config import ConfigParser
import random
city_dist_mat = None
Cf = ConfigParser(city_nums=101, gen_nums=10000)
config = Cf.get_config()
# 各项参数
gene_len = config.city_num
individual_num = config.individual_num
gen_num = config.gen_num
mutate_prob = config.mutate_prob
def copy_list(old_arr: [int]):
new_arr = []
for element in old_arr:
new_arr.append(element)
return new_arr
# 个体类
class Individual:
def __init__(self, genes=None):
# 随机生成序列
if genes is None:
genes = [i for i in range(gene_len)]
random.shuffle(genes)
self.genes = genes
self.fitness = self.evaluate_fitness()
def evaluate_fitness(self):
# 计算个体适应度
fitness = 0.0
for i in range(gene_len - 1):
# 起始城市和目标城市
from_idx = self.genes[i]
to_idx = self.genes[i + 1]
fitness += city_dist_mat[from_idx, to_idx]
# 连接首尾
fitness += city_dist_mat[self.genes[-1], self.genes[0]]
return fitness
class Ga:
def __init__(self, input_):
global city_dist_mat
city_dist_mat = input_
self.best = None # 每一代的最佳个体
self.individual_list = [] # 每一代的个体列表
self.result_list = [] # 每一代对应的解
self.fitness_list = [] # 每一代对应的适应度
def cross(self):
new_gen = []
random.shuffle(self.individual_list)
for i in range(0, individual_num - 1, 2):
# 父代基因
genes1 = copy_list(self.individual_list[i].genes)
genes2 = copy_list(self.individual_list[i + 1].genes)
index1 = random.randint(0, gene_len - 2)
index2 = random.randint(index1, gene_len - 1)
pos1_recorder = {value: idx for idx, value in enumerate(genes1)}
pos2_recorder = {value: idx for idx, value in enumerate(genes2)}
# 交叉
for j in range(index1, index2):
value1, value2 = genes1[j], genes2[j]
pos1, pos2 = pos1_recorder[value2], pos2_recorder[value1]
genes1[j], genes1[pos1] = genes1[pos1], genes1[j]
genes2[j], genes2[pos2] = genes2[pos2], genes2[j]
pos1_recorder[value1], pos1_recorder[value2] = pos1, j
pos2_recorder[value1], pos2_recorder[value2] = j, pos2
new_gen.append(Individual(genes1))
new_gen.append(Individual(genes2))
return new_gen
def mutate(self, new_gen):
for individual in new_gen:
if random.random() < mutate_prob:
# 翻转切片
old_genes = copy_list(individual.genes)
index1 = random.randint(0, gene_len - 2)
index2 = random.randint(index1, gene_len - 1)
genes_mutate = old_genes[index1:index2]
genes_mutate.reverse()
individual.genes = old_genes[:index1] + genes_mutate + old_genes[index2:]
# 两代合并
self.individual_list += new_gen
def select(self):
# 锦标赛
group_num = 10 # 小组数
group_size = 10 # 每小组人数
group_winner = individual_num // group_num # 每小组获胜人数
winners = [] # 锦标赛结果
for i in range(group_num):
group = []
for j in range(group_size):
# 随机组成小组
player = random.choice(self.individual_list)
player = Individual(player.genes)
group.append(player)
group = Ga.rank(group)
# 取出获胜者
winners += group[:group_winner]
self.individual_list = winners
@staticmethod
def rank(group):
# 冒泡排序
for i in range(1, len(group)):
for j in range(0, len(group) - i):
if group[j].fitness > group[j + 1].fitness:
group[j], group[j + 1] = group[j + 1], group[j]
return group
def next_gen(self):
# 交叉
new_gen = self.cross()
# 变异
self.mutate(new_gen)
# 选择
self.select()
# 获得这一代的结果
for individual in self.individual_list:
if individual.fitness < self.best.fitness:
self.best = individual
def train(self, ifplot=False):
# 初代种群
self.individual_list = [Individual() for _ in range(individual_num)]
self.best = self.individual_list[0]
# 迭代
for i in range(gen_num):
self.next_gen()
# 连接首尾
result = copy_list(self.best.genes)
result.append(result[0])
self.result_list.append(result)
self.fitness_list.append(self.best.fitness)
return self.result_list, self.fitness_list
config.py:我为了运行时调参,将其改为类帮助我不用每次都要到隔壁去修改参数
# -*- coding: utf-8 -*-
import argparse
class ConfigParser:
def __init__(self, city_nums=101, pos_dimensions=2, individual_nums=60, gen_nums=1000, mutate_probs=0.25):
'''
参数调整,默认值可修改
:param city_num: 城市数量
:param pos_dimension: 坐标维度
:param individual_num: 个体数
:param gen_num: 迭代轮数
:param mutate_prob: 变异概率
:return: 参数说明
'''
self.parser = argparse.ArgumentParser(description='Configuration file')
self.arg_lists = []
# Data
data_arg = self.add_argument_group('Data')
data_arg.add_argument('--city_num', type=int, default=city_nums, help='city num') # 城市数量
data_arg.add_argument('--pos_dimension', type=int, default=pos_dimensions, help='city num') # 坐标维度
data_arg.add_argument('--individual_num', type=int, default=individual_nums, help='individual num') # 个体数
data_arg.add_argument('--gen_num', type=int, default=gen_nums, help='generation num') # 迭代轮数
data_arg.add_argument('--mutate_prob', type=float, default=mutate_probs, help='probability of mutate') # 变异概率
def add_argument_group(self, name):
arg = self.parser.add_argument_group(name)
self.arg_lists.append(arg)
return arg
def get_config(self):
self.config, self.unparsed = self.parser.parse_known_args()
return self.config
def print_config(self):
print('\n')
print('Data Config:')
print('* city num:', self.config.city_num)
print('* individual num:', self.config.individual_num)
print('* generation num:', self.config.gen_num)
print('* probability of mutate:', self.config.mutate_prob)
if __name__ == '__main__':
Cf = ConfigParser(city_nums=101, gen_nums=10000)
config = Cf.get_config()
print("路径个数:", config.city_num)
print("迭代次数:", config.gen_num)
main.py:我将ga.py里的代码拷到这里来了,所以没有调用ga,py,方便调参
import numpy as np
import sys
sys.path.append(r'D:\86176\PycharmProjects\pythonProject\Interesting argrism\ga-tsp-main')
from config import ConfigParser
import matplotlib.pyplot as plt
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['KaiTi'] # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
from haversine import haversine
import time
import random
Cf = ConfigParser(city_nums=101, gen_nums=10000)
config = Cf.get_config()
print("路径个数:", config.city_num)
print("迭代次数:", config.gen_num)
# 各项参数
gene_len = config.city_num
individual_num = config.individual_num
gen_num = config.gen_num
mutate_prob = config.mutate_prob
def build_dist_mat(input_list):
n = config.city_num
dist_mat = np.zeros([n, n])
for i in range(n):
for j in range(i + 1, n):
d = input_list[i, :] - input_list[j, :]
# 计算点积
dist_mat[i, j] = np.dot(d, d)
dist_mat[j, i] = dist_mat[i, j]
return dist_mat
def dist_ance(sj):
n = config.city_num
distance = np.zeros([n, n])
for i in range(n):
for j in range(i + 1, n):
distance[i, j] = haversine(sj[i, :], sj[j, :])
distance[j, i] = distance[i, j]
return distance
#######################################
# 遗传算法主要部分GA
def copy_list(old_arr: [int]):
new_arr = []
for element in old_arr:
new_arr.append(element)
return new_arr
# 个体类
class Individual:
def __init__(self, genes=None):
# 随机生成序列
if genes is None:
genes = [i for i in range(gene_len)]
random.shuffle(genes)
self.genes = genes
self.fitness = self.evaluate_fitness()
def evaluate_fitness(self):
# 计算个体适应度
fitness = 0.0
for i in range(gene_len - 1):
# 起始城市和目标城市
from_idx = self.genes[i]
to_idx = self.genes[i + 1]
fitness += city_dist_mat[from_idx, to_idx]
# 连接首尾
fitness += city_dist_mat[self.genes[-1], self.genes[0]]
return fitness
class Ga:
def __init__(self, input_):
global city_dist_mat
city_dist_mat = input_
self.best = None # 每一代的最佳个体
self.individual_list = [] # 每一代的个体列表
self.result_list = [] # 每一代对应的解
self.fitness_list = [] # 每一代对应的适应度
def cross(self):
new_gen = []
random.shuffle(self.individual_list)
for i in range(0, individual_num - 1, 2):
# 父代基因
genes1 = copy_list(self.individual_list[i].genes)
genes2 = copy_list(self.individual_list[i + 1].genes)
index1 = random.randint(0, gene_len - 2)
index2 = random.randint(index1, gene_len - 1)
pos1_recorder = {value: idx for idx, value in enumerate(genes1)}
pos2_recorder = {value: idx for idx, value in enumerate(genes2)}
# 交叉
for j in range(index1, index2):
value1, value2 = genes1[j], genes2[j]
pos1, pos2 = pos1_recorder[value2], pos2_recorder[value1]
genes1[j], genes1[pos1] = genes1[pos1], genes1[j]
genes2[j], genes2[pos2] = genes2[pos2], genes2[j]
pos1_recorder[value1], pos1_recorder[value2] = pos1, j
pos2_recorder[value1], pos2_recorder[value2] = j, pos2
new_gen.append(Individual(genes1))
new_gen.append(Individual(genes2))
return new_gen
def mutate(self, new_gen):
for individual in new_gen:
if random.random() < mutate_prob:
# 翻转切片
old_genes = copy_list(individual.genes)
index1 = random.randint(0, gene_len - 2)
index2 = random.randint(index1, gene_len - 1)
genes_mutate = old_genes[index1:index2]
genes_mutate.reverse()
individual.genes = old_genes[:index1] + genes_mutate + old_genes[index2:]
# 两代合并
self.individual_list += new_gen
def select(self):
# 锦标赛
group_num = 10 # 小组数
group_size = 10 # 每小组人数
group_winner = individual_num // group_num # 每小组获胜人数
winners = [] # 锦标赛结果
for i in range(group_num):
group = []
for j in range(group_size):
# 随机组成小组
player = random.choice(self.individual_list)
player = Individual(player.genes)
group.append(player)
group = Ga.rank(group)
# 取出获胜者
winners += group[:group_winner]
self.individual_list = winners
@staticmethod
def rank(group):
# 冒泡排序
for i in range(1, len(group)):
for j in range(0, len(group) - i):
if group[j].fitness > group[j + 1].fitness:
group[j], group[j + 1] = group[j + 1], group[j]
return group
def next_gen(self):
# 交叉
new_gen = self.cross()
# 变异
self.mutate(new_gen)
# 选择
self.select()
# 获得这一代的结果
for individual in self.individual_list:
if individual.fitness < self.best.fitness:
self.best = individual
def train(self):
# 初代种群
self.individual_list = [Individual() for _ in range(individual_num)]
self.best = self.individual_list[0]
# 迭代
# plt.figure()
# plt.ion()
for i in range(gen_num):
self.next_gen()
# 连接首尾
result = copy_list(self.best.genes)
result.append(result[0])
self.result_list.append(result)
self.fitness_list.append(self.best.fitness)
# plt.plot(self.fitness_list)
# plt.pause(0.01)
# plt.title(u"适应度曲线")
# plt.legend()
return self.result_list, self.fitness_list
if __name__ == '__main__':
Start = time.time()
# 城市坐标
# 读取数据
city_pos_list = np.loadtxt(r'D:\86176\PycharmProjects\pythonProject\sj.txt', dtype=np.float32)
city_pos_list = np.vstack([
city_pos_list[:, [2 * i, 2 * i + 1]]
for i in range(4)
])
d0 = np.array([70, 40])
city_pos_list = np.vstack([d0, city_pos_list])
# 城市距离矩阵
city_dist_mat = dist_ance(city_pos_list)
# print(city_pos_list)
# print(city_dist_mat)
# 遗传算法运行
ga = Ga(city_dist_mat)
result_list, fitness_list = ga.train()
result = result_list[-1]
idx = result.index(0)
result = result[idx:-1]
result.extend(result_list[-1][:idx])
result.append(0)
result_pos_list = city_pos_list[result, :]
# fig = plt.figure()
# plt.plot(result_pos_list[:, 0], result_pos_list[:, 1], 'o-r')
# plt.title(u"路线")
# plt.legend()
# fig.show()
# 画图
# plt.figure()
# plt.ion()
# # plt.plot(fitness_list)
# xx = [fitness_list[0]]
# for i in range(1, len(fitness_list)):
# xx.append(fitness_list[i])
# plt.plot(xx)
# plt.pause(0.01)
# plt.title(u"适应度曲线")
# plt.legend()
# plt.show()
plt.figure()
plt.ion()
xs = [result_pos_list[0, 0]]
ys = [result_pos_list[0, 1]]
for i in range(1, len(result_pos_list)):
xs.append(result_pos_list[i, 0])
ys.append(result_pos_list[i, 1])
plt.plot(xs, ys, '-o')
plt.pause(0.1)
End = time.time()
print("用时", End - Start, '秒')
print('运行路线为:')
for i in result:
print(i, end=' ')
print("\n最短总距离为:", fitness_list[-1])
2.读入数据
经纬度坐标
经度 | 纬度 | 经度 | 纬度 | 经度 | 纬度 | 经度 | 纬度 |
53.7121 | 15.3046 | 51.1758 | 0.0322 | 46.3253 | 28.2753 | 30.3313 | 6.9348 |
56.5432 | 21.4188 | 10.8198 | 16.2529 | 22.7891 | 23.1045 | 10.1584 | 12.4819 |
20.105 | 15.4562 | 1.9451 | 0.2057 | 26.4951 | 22.1221 | 31.4847 | 8.964 |
26.2418 | 18.176 | 44.0356 | 13.5401 | 28.9836 | 25.9879 | 38.4722 | 20.1731 |
28.2694 | 29.0011 | 32.191 | 5.8699 | 36.4863 | 29.7284 | 0.9718 | 28.1477 |
8.9586 | 24.6635 | 16.5618 | 23.6143 | 10.5597 | 15.1178 | 50.2111 | 10.2944 |
8.1519 | 9.5325 | 22.1075 | 18.5569 | 0.1215 | 18.8726 | 48.2077 | 16.8889 |
31.9499 | 17.6309 | 0.7732 | 0.4656 | 47.4134 | 23.7783 | 41.8671 | 3.5667 |
43.5474 | 3.9061 | 53.3524 | 26.7256 | 30.8165 | 13.4595 | 27.7133 | 5.0706 |
23.9222 | 7.6306 | 51.9612 | 22.8511 | 12.7938 | 15.7307 | 4.9568 | 8.3669 |
21.5051 | 24.0909 | 15.2548 | 27.2111 | 6.207 | 5.1442 | 49.243 | 16.7044 |
17.1168 | 20.0354 | 34.1688 | 22.7571 | 9.4402 | 3.92 | 11.5812 | 14.5677 |
52.1181 | 0.4088 | 9.5559 | 11.4219 | 24.4509 | 6.5634 | 26.7213 | 28.5667 |
37.5848 | 16.8474 | 35.6619 | 9.9333 | 24.4654 | 3.1644 | 0.7775 | 6.9576 |
14.4703 | 13.6368 | 19.866 | 15.1224 | 3.1616 | 4.2428 | 18.5245 | 14.3598 |
58.6849 | 27.1485 | 39.5168 | 16.9371 | 56.5089 | 13.709 | 52.5211 | 15.7957 |
38.43 | 8.4648 | 51.8181 | 23.0159 | 8.9983 | 23.644 | 50.1156 | 23.7816 |
13.7909 | 1.951 | 34.0574 | 23.396 | 23.0624 | 8.4319 | 19.9857 | 5.7902 |
40.8801 | 14.2978 | 58.8289 | 14.5229 | 18.6635 | 6.7436 | 52.8423 | 27.288 |
39.9494 | 29.5114 | 47.5099 | 24.0664 | 10.1121 | 27.2662 | 28.7812 | 27.6659 |
8.0831 | 27.6705 | 9.1556 | 14.1304 | 53.7989 | 0.2199 | 33.649 | 0.398 |
1.3496 | 16.8359 | 49.9816 | 6.0828 | 19.3635 | 17.6622 | 36.9545 | 23.0265 |
15.732 | 19.5697 | 11.5118 | 17.3884 | 44.0398 | 16.2635 | 39.7139 | 28.4203 |
6.9909 | 23.1804 | 38.3392 | 19.995 | 24.6543 | 19.6057 | 36.998 | 24.3992 |
4.1591 | 3.1853 | 40.14 | 20.303 | 23.9876 | 9.403 | 41.1084 | 27.7149 |
求最短路径。
3.最终结果
只需修改运行main.py中的文件就行了
将结果坐标放入直角坐标中展示
迭代结果:
实时迭代结果的展示可能会使得算法在计算时速度会减慢,如果电脑好那就另说了,不建议实时结果展示。
运行路线为: 0 44 66 1 91 81 47 71 13 26 9 83 17 39 78 76 30 96 84 64 63 10 93 69 18 62 61 25 28 33 65 89 85 7 38 77 46 56 27 87 60 48 67 6 24 22 57 80 21 70 36 31 23 12 72 11 52 88 5 95 54 55 20 98 99 43 37 53 4 74 32 3 40 90 15 68 75 59 8 14 49 79 97 50 41 19 29 73 82 86 58 100 51 45 92 42 35 94 34 2 16 0 最短总距离为: 40546.764132575256
总结
这是我在之前国赛训练训练时的一个题目,虽然很简单,但是当时是真的觉得很难,记录一下,希望对有需要的同学有帮助,其实我现在是对这个算法理解的也还算深了,因为和它对抗了很久,总是想找一个合适的Python算法没找到,自己写的也不尽人意,这也算是我对自己这一模块结束的标志了吧。