1. 遗传算法背景介绍
遗传算法是一种基于生物进化论中的自然选择和遗传机制的优化算法,模拟了生物进化过程以搜索最优解。
通过仿真染色体的交叉、变异等操作,遗传算法将求解过程转换为类似生物进化的迭代运算。
该算法在解决复杂的组合优化问题时,通常比常规优化算法更高效,且具有广泛应用,包括组合优化、机器学习、信号处理、自适应控制和人工生命等领域
2. 遗传算法基本解题思路
遗传算法的设计思路主要受到大自然中生物体进化规律的启发,旨在有效地模拟自然进化过程搜索 最优解,如最短路径、最低耗能等最优解或最差解问题,以下为遗传算法的流程图:
图- 1 遗传算法流程图
3. 任务介绍
本任务旨在实现一个基于遗传算法的城市旅行问题(TSP)求解器。城市旅行问题(TSP)是一个经典的组合优化问题,其目标是找到一条最短路径,使得旅行者能够访问给定的一组城市,并最终返回起始城市。该问题在物流、交通规划和网络设计等多个领域都有广泛应用
3.1. 内容介绍
(1)城市坐标生成:
使用 NumPy 库的 np.random.rand 函数来生成一个随机的二维数组,用于表示城市的坐标,并确保城市之间保持一定的最小距离(间隔因子),以避免坐标过于接近。这一过程有助于确保解的多样性,促进算法在搜索空间中的探索。
(2)距离矩阵构建:
根据生成的城市坐标计算城市之间的距离矩阵。通过计算城市之间的平方距离,优化了计算效率。这一矩阵为后续的适应度评估和路径优化提供了基础数据。
(3)遗传算法实现:
使用遗传算法进行路径优化,主要包括以下几个步骤:
- 选择操作:实现了 锦标赛选择(Tournament Selection)方法。在锦标赛选择中,个体被随机分成若干小组(在这里是10个小组,每组10个个体),然后在每个小组内通过冒泡排序比较适应度来选择最佳个体。
- 交叉操作:在 cross 方法中,首先将个体列表随机打乱,然后每次选择两个相邻个体(即父代和母代)进行交叉。具体步骤如下:
- 通过这种基因片段的交换,生成新的个体,这些个体既继承了父代的部分基因特征,又具有一定的变异,从而在解空间中探索更多的可能性。
- 在 index1 到 index2 的区间内,对两个个体的基因进行交换。交换时,通过 pos1_recorder 和 pos2_recorder 字典记录每个基因的位置,确保基因位置交换后依然保持唯一性。
- 复制两个个体的基因序列,并随机生成交叉的起始位置 index1 和结束位置 index2。
- 变异操作:代码实现:在 mutate 方法中,根据变异概率 mutate_prob,对 new_gen 中的个体逐一进行变异判断。如果随机数小于变异概率,则对该个体进行变异,具体操作如下:
- 首先复制当前个体的基因序列 old_genes。
- 随机选择两个位置 index1 和 index2,提取 old_genes 中 index1 到 index2 的基因片段,进行翻转操作,以模拟基因的突变。
- 最后,将翻转后的基因片段重新插入到原序列中指定的位置,更新个体的基因序列。
- 变异操作通过随机改变基因片段的顺序来增加基因多样性,避免算法陷入局部最优解,提高找到全局最优解的可能性。
- 适应度评估:
通过计算每个个体的适应度值来评估其质量。在TSP(旅行商问题)中,适应度值与路径的总距离成正比关系。总距离越小,适应度值越小,路径越短表示适应度越好。适应度值的计算为个体的选择和进化提供了依据,从而帮助算法逐步接近最优解
3.2. 运行结果截图
(1)结果展示:
输出最优路径的城市索引顺序,并绘制城市连接的路径图和适应度曲线图,以可视化算法的效果和收敛过程。路径图展示了旅行者的最优行程,而适应度曲线图则展示了适应度值随着代数的变化情况,反映出算法的收敛趋势。
图- 2 程序运行数据展示
图- 3 程序生成适应度曲线
图- 4 程序生成路线曲线图
(2)参数调优与分析:
在作业的最后阶段,分析不同参数(如种群规模、交叉率和变异率)对算法性能的影响,进行实验,探索最佳参数组合,以提升算法的求解效率和准确性。
这里以一组固定数据作为数据集,通过改变变异率,观察其适应度收敛情况,数据集和效果如下:
图- 5 固定数据集
图- 6 不同变异率适应度收敛曲线
从不同变异率曲线分析可知,在变异率过低的时候,容易出现早期的停滞现象,且曲线的迭代次数相对较多,而变异率中等的曲线,收敛速度快且结果稳定。
4. 核心代码
(1)距离矩阵计算代码如下:
# 构建距离矩阵函数
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] # 对称赋值y
return dist_mat
(2)锦标赛选择代码如下:
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 = GeneticAlgorithm.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
# 返回排序后的小组。
(3)交叉代码如下:
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)}
# 记录基因序列1的位置。
pos2_recorder = {value: idx for idx, value in enumerate(genes2)}
# 记录基因序列2的位置。
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
# 返回交叉后的新一代。
(4)变异代码如下:
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
# 将变异后的新一代加入当前个体列表。
(5)个体类代码如下:
# 个体类
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