本次博客是对于博客提出疑问的思考与实践。
在这里主要讨论第四个问题,中国邮政问题
其图论描述为:
给定图 G = ( V , A ) \mathrm{G }= (\mathrm{V},\mathrm{ A}) G=(V,A),其中 V \mathrm{V} V为顶点集, A \mathrm{A} A为各顶点相互连接的边集,设 d ( i j ) d(_{ij}) d(ij)是由顶点i和顶点j之间的距离所产生的距离矩阵,要求确定一条hamilton回路,即遍历所有顶点当且仅当一次的最短距离。
旅行商问题可分为如下两类:
旅行商问题数学模型:
min t i ∈ T ∑ i = 1 n d ( t i , t i + 1 ) ) \min_{t_i \in \mathrm{T}}{\sum_{i=1}^n}{d(t_{i},t_{i+1}))} ti∈Tmini=1∑nd(ti,ti+1))
T = { t 1 , t 2 , … , t n } \mathrm{T} = \{t_1, t_2, \dots, t_n\} T={t1,t2,…,tn}代表对于城市 V = { v 1 , v 2 , … , v n } \mathrm{V}=\{v_1, v_2, \dots, v_n\} V={v1,v2,…,vn}的一个访问顺序,其中 t i ∈ V , i ∈ { 1 , 2 , … , n } 且 t n + 1 = t 1 t_i \in \mathrm{V}, i\in\{1, 2, \dots, n\}且t_{n+1} = t_1 ti∈V,i∈{1,2,…,n}且tn+1=t1。
该问题是一个典型的优化组合难题,已被证实属于NP完全问题,即没有确定的算法能在多项式时间内得到问题的最优解。1
现有数据集
数据集读取: tsplib95
巡回旅行商问题是典型的NP难题。如果使用暴力搜索求解TSP问题,其时间复杂度为 O ( ( n − 1 ) ! ) O((n-1)!) O((n−1)!),减一是因为起始点确定。基于TSP的问题特性,构造性算法成为最先开发的求解算法,如最近领点,最近合并,最近插入,最远插入,最近添加,贪婪插入等。但是由于构造性算法优化质量较差,迄今为止已开发了许多性能较好的改进型搜索算法。主要有:
模拟退火算法思想:
模拟退火算法区别于爬山思想(只向比当前高的地方爬,容易陷入局部极小值),就是加入了一个概率方程组指导决策:
P k ( i → j ) = { 1 , f ( i ) ≤ f ( j ) exp ( − f ( i ) − f ( j ) t ) , otherwise P_k{(i\to j)} = \begin{cases} 1,f(i)\leq f(j) \\ \exp{(-\frac{f(i)-f(j)}{t})},\textrm{otherwise} \end{cases} Pk(i→j)={1,f(i)≤f(j)exp(−tf(i)−f(j)),otherwise
其中 f ( i ) f(i) f(i)表示适应度函数,t表示温度。
概率方程翻译过来表示,当温度t较大的时候,接受比当前差的状态转移;当温度较小的时候,只接受比当前好的状态转移。
伪代码:
/*
* J(y):在状态y时的评价函数值
* Y(i):表示当前状态
* Y(i+1):表示新的状态
* r: 用于控制降温的快慢
* T: 系统的温度,系统初始应该要处于一个高温的状态
* T_min :温度的下限,若温度T达到T_min,则停止搜索
*/
while( T > T_min )
{
dE = J( Y(i+1) ) - J( Y(i) ) ;
if ( dE >=0 ) //表达移动后得到更优解,则总是接受移动
Y(i+1) = Y(i) ; //接受从Y(i)到Y(i+1)的移动
else
{
// 函数exp( dE/T )的取值范围是(0,1) ,dE/T越大,则exp( dE/T )也越大
if ( exp( dE/T ) > random( 0 , 1 ) )
Y(i+1) = Y(i) ; //接受从Y(i)到Y(i+1)的移动
}
T = r * T ; //降温退火 ,0<r<1 。r越大,降温越慢;r越小,降温越快
/*
* 若r过大,则搜索到全局最优解的可能会较高,但搜索的过程也就较长。若r过小,则搜索的过程会很快,但最终可能会达到一个局部最优值
*/
i ++ ;
}
用python实现代码:(dantzig42.tsp)
# 模拟退火算法
import tsplib95
import numpy as np
import random
import math
import json
# 初始温度值
t_high = 10000
temperature = 10000
# 温度下界
t_low = 0.1
# 冷却速度
cool_rate = 0.001
# tsplib文件路径
path = "D:\\dataset\\tsp\\dantzig42.tsp\\dantzig42.tsp"
def read_tsp_question(tsp_path):
return tsplib95.load(tsp_path)
def calculate_seq_distance(city_seq):
distance = 0
for i in np.arange(0, len(city_seq)-1):
distance += problem.get_weight(city_seq[i], city_seq[i+1])
# print("city_seq[{}], city_seq[{}] distance = {}".format(i,i+1,problem.get_weight(city_seq[i], city_seq[i+1])))
distance += problem.get_weight(city_seq[len(city_seq)-1], city_seq[0])
return distance
def get_new_city_seq(city_seq):
new_city_seq = city_seq.copy()
# 随机选择两个位点, 交换两个城市的位置
first_index = random.randint(0, len(city_seq)-1)
second_index = random.randint(0, len(city_seq)-1)
while first_index == second_index:
second_index = random.randint(0, len(city_seq) - 1)
temp = new_city_seq[first_index]
new_city_seq[first_index] = new_city_seq[second_index]
new_city_seq[second_index] = temp
return new_city_seq
def initial_sequence(city_num):
initial_seq = np.arange(1, city_num + 1)
initial_seq[10] = 32
initial_seq[31] = 11
seq_str = "[1,2,25,4,5,6,7,8,9,10,32,12,13,14,15,21,17,18,19,20,16,22,23,24,3,26,27,28,29,30,31,36,33,34,35,11,37,38,39,40,41,42]"
initial_seq = json.loads(seq_str)
return initial_seq
def acceptance_probability(next_distance, current_distance, temperature):
if next_distance < current_distance:
return 2
else:
return math.exp((current_distance-next_distance)/temperature)
if __name__ == "__main__":
problem = read_tsp_question(path)
# 城市数量
city_num = problem.dimension
# 循环中最好的城市序列
best_city_sequence = None
best_distance = -1
# 当前的城市序列
current_city_seq = None
current_distance = -1
# 下一步的城市序列
next_city_seq = None
while temperature >= t_low:
if temperature == t_high and current_city_seq is None:
current_city_seq = initial_sequence(city_num)
best_city_sequence = current_city_seq
current_distance = calculate_seq_distance(current_city_seq)
best_distance = current_distance
print("initial_distance = {}".format(current_distance))
next_city_seq = get_new_city_seq(current_city_seq)
next_distance = calculate_seq_distance(next_city_seq)
if acceptance_probability(next_distance, current_distance, temperature) > random.random():
current_city_seq = next_city_seq
current_distance = next_distance
if current_distance < best_distance:
best_city_sequence = current_city_seq
best_distance = current_distance
# print("current_distance = {}\n".format(current_distance))
temperature *= (1-cool_rate)
print("best_city_seq: {}".format(best_city_sequence))
print("best_distance: {}".format(best_distance))
由于 ∪ i = 1 42 { x i ∣ x i = i } \cup_{i=1}^{42}\{x_i|x_i =i\} ∪i=142{xi∣xi=i}就是最优解(dantzig42 : 699),就初始化一个距离为1287的序列当作初始解,体现模拟退火算法的优化效果。
遗传算法是一种基于自然选择与基因遗传学原理的随机并行搜索算法,是一种寻找全局最优且不需要任何初始化信息的高效优化方法。它将问题的解集看做是一个种群,通过不断的选择、交叉、变异等遗传操作,使解的质量越来越好。该算法具有全局寻优能力,适用性、解决非线性优化问题具有较强的鲁棒性。对问题没有特定限制、计算过程简单、对搜索空间没有特殊要求、易于与其他算法结合等特点,,在函数优化、图像处理、系统辨识、自动控制、经济预测和工程优化等领域得到了广泛的应用, 在求解NP完全问题方面是一种较为有效的全局方法。1
简单遗传算法求解 TSP 问题的主要计算过程如下:
Step 1: 确定编码机制, 生成初始种群。解决 TSP问题通常采用城市序号对路径进行编码, 按照访问城市的顺序排列组成编码。
Step 2: 计算种群中每个个体的适应度值.。TSP 求解是要寻找使目标函数最小的个体, 因此选择适应度函数 fitness() = /().。设置常数, 防止路径值过大而导致适应度函数倒数接近于 0。 可以看出, 巡游路径越小, 适应度值越大。
Step 3: 选择算子。通常采用精英个体保存策略和赌轮选择算子, 即适应度最高的个体一定被选择. 计算每个个体在整个种群适应度中的被选择概率和累计概率分别为 p i = fitness ( i ) ∑ i = 1 popsize fitness ( i ) p_i=\frac{\textrm{fitness}(i)}{\sum_{i=1}^{\textrm{popsize}}\textrm{fitness}(i)} pi=∑i=1popsizefitness(i)fitness(i), Q i = ∑ j = 1 i p ( j ) Q_i = \sum_{j=1}^ip(j) Qi=∑j=1ip(j)。通过随机数 所在的区间范围选择遗传个体。
Step 4: 交叉算子.。由交叉概率 选择若干父体并进行配对, 按照交叉算法的规则生成新个体, 常用的规范方法有单点交叉、部分映射交叉、循环交叉等。
Step 5: 变异算子。为了保持种群个体的多样性,防止陷入局部最优, 需要按照某一变异概率 随机确定变异个体, 并实行相应变异操作, 通常采用逆序变异算子。
Step 6: 迭代终止条件。 若满足预定的终止条件(达到最大迭代次数), 则停止迭代, 所得的路径认为是满意的路径; 否则, 转至 Step 2, 计算新一代种群中每个个体的适应度值。
简单遗传算法往往存在收敛速度慢、易陷入局部最优和优化精度低等明显不足。 如何在提高算法收敛速度的同时确保种群多样性, 使寻优结果接近最优解是遗传算法不断改进的目标。
上述对于遗传算法的描述是通用的,只是阐述了遗传算法的思想与基本的步骤。阅读了几篇用遗传算法求解TSP问题的论文,优化方法包括了上述的六步。比如说,第一步中的初始化种群,就有提出使用贪婪算法生成初始化种群的。
编码方式:
最常规的编码方式为二进制编码,但是不使用于此问题。此问题的一个组合形如 ( 4 , 2 , 1 , 3 , 4 ) (4,2,1,3,4) (4,2,1,3,4),每个数字代表一个城市,一个序列代表着游览城市的序列,是一个回路。如果使用二进制编码,不仅要考虑数据溢出的问题,而且还要考虑交叉之后的序列存在重复城市编号。
TSP问题中的交叉算子:
路径表示(path representation)是表示旅程对应的基因编码的最自然、最简单的表示方法。例如,旅程 5-1-7-8-9-4-6-2-3 可以直接表示为(5 1 7 8 9 4 6 2 3),基于路径表示的编码方法,要求一个个体(即一条旅程)的染色体编码中不允许有重复的基因码,也就是说要满足任一个城市必须而且只能访问一次的约束。这样,基本遗传算法的交叉操作生成的个体一般不能满足这一约束条件。为此,人们提出了一个称为重排操作的新操作来处理这类表示问题,它包括三种操作:部分匹配交叉(Partially Matched Crossover,PMX)、郭涛交叉(Guo Tao Crossover,GTX)、循环交叉(Cycle Crossover,CX)。主要是在传统交叉操作上加入了“不允许有重复基因码”的限制。
下述是遗传算法选择,交叉,变异的常见算法:2
选择算子:
序号 | 名称 | 特点 | 研究者 |
---|---|---|---|
1 | 轮盘赌选择(回放式随机采样) | 选择误差较大 | DeJong,Brindle |
2 | 无回放式随机采样 | 降低选择误差,复制数(f/(f+1)),操作不便 | DeJong,Brindle |
3 | 确定采样 | 选择误差更小,操作简易 | Brindle |
4 | 柔性分段式选择 | 有效防止基因缺失但需要选择有关参数 | Yun |
5 | 自适应柔性分段式动态群体采样 | 群体自适应变化,提高搜索效率 | Yun |
6 | 无放回式余数随机采样 | 群体自适应变化,提高搜索效率 | DeJong,Brindle |
7 | 均匀排序 | 与适应度的大小差异程序正负无关 | Back |
8 | 稳态选择 | 保留父代中的一些高适应度的串 | Syswerda |
9 | 随机比赛 | Brindle | |
10 | 选择评价 | Whitley | |
11 | 最优串选择 | 全局收敛,提高搜索效率 | DeJong.Back |
12 | 最优串保留 | 保证全局收敛 | Yun |
13 | 适应度线性尺度变换 | 简单,可消除遗传早期的超级串现象 | Bagley |
14 | 适应度指数尺度变换 | Gillies | |
15 | 适应度自适应线性尺度变换 | 符合遗传机理 | Yun |
交叉算子:
序号 | 名称 | 特点 | 研究者 | 适用编码 |
---|---|---|---|---|
1 | 单点交叉 | 基于 GA 的成员 | Holland,DeJong,Goldberg | 符号 |
2 | 双点交叉 | 使用较多 | Cavicchio,Booker | 符号 |
3 | 均匀交叉 | 每一位以相同的概率交叉 | Syswerda,whitely,Yun | 符号 |
4 | 多点交叉 | 交换点大于 2 | DeJong,Spears | 符号 |
5 | 部分匹配(PMX) | Goldberg | 序号 | |
6 | 序号交叉(OX) | Davis | 序号 | |
7 | 圆交叉(CX) | Smith | 序号 | |
8 | 基于位置交叉 | Syswerda | 序号 | |
9 | 算术交叉 | Michalewicz | 实数 | |
10 | 启发式交叉 | 应用领域知识 | Grffenstette | 序号 |
变异算子:
序号 | 名称 | 特点 | 研究者 | 适用编码 |
---|---|---|---|---|
1 | 常规位突变 | 基本 GA 的成员 | DeJong | 符号 |
2 | 有效基因突变 | 避免有效基因缺失 | Yun | 符号 |
3 | 自适应有效基因突变 | 最低有效基因个数自适应变换 | Yun | 符号 |
4 | 概率自调整突变 | 由两个串的相似性确定突变概率 | Whitley | 符号 |
5 | 均匀突变 | 每一个实数元素以相同的概率在域内变动 | Michalewicz | 实数 |
6 | 非均匀突变 | 使整个矢量在解空间轻微变动 | Michalewicz | 实数 |
7 | 三次高斯近似突变 | Bosworth,Foo,zeigler | 实数 |
于莹莹,陈燕,李桃迎.改进的遗传算法求解旅行商问题[J].控制与决策,2014,29(08):1483-1488. ↩︎ ↩︎
王银年. 遗传算法的研究与应用[D].江南大学,2009. ↩︎