如何解决为什么这种应用于 TSP 的模拟退火算法不收敛?
对于一个练习,我必须找到这个旅行推销员问题的最佳解决方案,唯一的区别是推销员可能不会两次访问第一个坐标。最佳值应该在给出的 1200 左右。我不明白为什么这不会收敛。另外一个重要的注意事项是使用曼哈顿距离度量而不是欧几里得距离。
def shuffle_coords(coords):
x0 = coords[0]
coords = shuffle(coords[1:])
return np.insert(coords,x0,axis = 0)
def distance(x,y):
return abs(x[1] - y[1]) + abs(x[0] - y[0])
这里的坐标是随机打乱的。
def shuffle(array):
df = pd.DataFrame(array)
return df.sample(len(array)).to_numpy()
def path_distance(path):
dist = []
for i in range(1,len(path)):
dist.append(distance(path[i],path[i-1]))
return np.sum(dist)
此处初始化模拟退火算法。
def SA_distance(path,T_0,T_min,alpha):
T = T_0
dist = path_distance(path)
while T > T_min:
new_path = gen_subtour(path)
diffF = path_distance(new_path) - dist
if diffF < 0:
path = new_path
dist = path_distance(path)
elif np.exp(-(diffF/T)) > random.uniform(0,1):
path = new_path
dist = path_distance(path)
T = T * alpha
print(dist,T)
return dist,path
def gen_subtour(path):
subset = shuffle(np.delete(path,axis =0))
subset = shuffle(path)
if random.uniform(0,1) < 0.5:
subset = np.flipud(subset)
else:
j = random.randint(1,(len(subset)-1))
p = subset[j-1]
q = subset[j]
subset = np.delete(subset,[j-1,j],axis = 0)
subset = np.insert(subset,p,len(subset),q,axis = 0)
return np.insert(subset,path[0],axis = 0)
def main():
T_0 = 12
T_min = 10**-9
alpha = 0.999
coords = np.array([[375,375],[161,190],[186,169],[185,124],[122,104],[109,258],[55,153],[120,49],[39,85],[59,250],[17,310],[179,265],[184,198]])
path,distance = SA_distance(coords,alpha)
解决方法
我已经测试过了,我的第一直觉是 gen_subtour()
运行不正常,可能是因为步数太多改变了路线。
我会尝试不同的版本并检查效果如何。 SA 方案似乎运行良好,这是我认为错误的建议。
无论如何,这里有一些代码,希望能帮助你更好地测试。
我使用 pdist
预先计算曼哈顿距离,即;
import numpy as np
from scipy.spatial.distance import pdist,cdist,squareform
coords = np.array([[375,375],[161,190],[186,169],[185,124],[122,104],[109,258],[55,153],[120,49],[39,85],[59,250],[17,310],[179,265],[184,198]])
Y = pdist(coords,'cityblock')
distance_matrix = squareform(Y)
nodes_count = coords.shape[0]
并定义开始于
def random_start():
"""
Random start,returns a state
"""
a = np.arange(0,nodes_count)
np.random.shuffle(a)
return a
目标函数,我们有不回原点的版本;
def objective_function( route ):
# uncomment when testing new/modify neighbors
# assert check_all_nodes_visited(route)
return np.sum( distance_matrix[route[1:],route[:-1]] )
我这里有 3 种基于路线的建议;
def random_swap( route ):
"""
Random Swap - a Naive neighbour function
Will only work for small instances of the problem
"""
route_copy = route.copy()
random_indici = np.random.choice( route,2,replace = False)
route_copy[ random_indici[0] ] = route[ random_indici[1] ]
route_copy[ random_indici[1] ] = route[ random_indici[0] ]
return route_copy
def vertex_insert( route,nodes=1 ):
"""
Vertex Insert Neighbour,inspired by
http://www.sciencedirect.com/science/article/pii/S1568494611000573
"""
route_copy = route.copy()
random_indici = np.random.choice( route,replace = False)
index_of_point_to_reroute = random_indici[0]
value_of_point_to_reroute = route[ random_indici[0] ]
index_of_new_place = random_indici[1]
route_copy = np.delete(route_copy,index_of_point_to_reroute)
route_copy = np.insert(route_copy,index_of_new_place,values=value_of_point_to_reroute)
return route_copy
def block_reverse( route,nodes=1 ):
"""
Block Reverse Neighbour,inspired by
http://www.sciencedirect.com/science/article/pii/S1568494611000573
Note that this is a random 2-opt operation.
"""
route_copy = route.copy()
random_indici = np.random.choice( route,replace = False)
index_of_cut_left = np.min(random_indici)
index_of_cut_right = np.max(random_indici)
route_copy[ index_of_cut_left:index_of_cut_right ] = np.flip(route_copy[ index_of_cut_left:index_of_cut_right ])
return route_copy
或者,您可以在 SA 之后进行 2-opt 回合,以确保没有交叉。
def swap_for_2opt( route,i,k):
"""
Helper for 2-opt search
"""
route_copy = route.copy()
index_of_cut_left = i
index_of_cut_right = k
route_copy[ index_of_cut_left:index_of_cut_right ] = np.flip(route_copy[ index_of_cut_left:index_of_cut_right ])
return route_copy
def local_search_2opt( route ):
"""
Local Optimum with 2-opt
https://en.wikipedia.org/wiki/2-opt
"""
steps_since_improved = 0
still_improving = True
route = route.copy()
while still_improving :
for i in range( route.size - 1 ):
for k in np.arange( i + 1,route.size ):
alt_route = swap_for_2opt(route,k)
if objective_function(alt_route) < objective_function(route):
route = alt_route.copy()
steps_since_improved = 0
steps_since_improved += 1
if steps_since_improved > route.size + 1:
still_improving = False
break
return route
并使用冷冻剂进行 SA
import frigidum
local_opt = frigidum.sa(random_start=random_start,objective_function=objective_function,neighbours=[random_swap,vertex_insert,block_reverse],copy_state=frigidum.annealing.naked,T_start=10**5,alpha=.95,T_stop=0.001,repeats=10**2,post_annealing = local_search_2opt)
返回的路由几乎都是 1145。
我已在 frigidum 主页上发布了一般提示和技巧。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。