[关闭]
@Plams 2018-06-03T13:14:58.000000Z 字数 10964 阅读 791

ACO

首先采用原始未优化的蚁群算法,求解eil51.tsp问题。
eil51.tsp 含有51个点,最优解总长度426。


一个较好的结果, 约为最优解的105%。

可以看出,仍有交叉的路径,并非最优解。

2-opt ACO

配合2-opt的local search。
一个比较好的解答, 不超过最优解的100.7%。

  1. Shortest path found:
  2. [ 4 37 10 31 0 21 1 15 49 8 29 33 20 28 19 34 35 2 27 30 7 25 6 42
  3. 23 22 47 5 26 50 45 11 46 3 17 13 24 12 40 39 18 41 43 16 36 14 44 32
  4. 38 9 48 4]
  5. Minimum distance found = 428.981647

使用邻域搜索的一个附带好处是 可以大幅度减少蚂蚁数,同时不会严重影响求解质量

接下来尝试使用2-OPT_ACO来求解一个规模稍大的问题 tsp225 (3919)

  1. Shortest path found:
  2. [ 22 21 20 19 202 18 17 16 15 14 13 12 11 10 9 8 39 40
  3. 7 6 5 4 2 0 199 197 3 196 194 42 41 43 45 193 217 192
  4. 44 47 195 191 190 223 198 132 204 189 224 206 48 49 50 56 55 54
  5. 51 52 53 57 58 1 46 188 26 187 116 186 185 184 183 119 121 120
  6. 174 118 117 114 112 113 222 115 59 60 61 62 63 111 110 109 64 65
  7. 66 67 68 69 70 71 72 73 75 74 97 98 99 100 101 102 103 219
  8. 104 105 106 107 108 123 122 170 172 181 180 179 178 177 176 175 173 171
  9. 169 168 167 124 211 213 149 150 151 148 147 146 145 152 153 154 155 156
  10. 143 144 142 200 141 138 137 139 140 158 159 160 161 162 136 135 182 134
  11. 157 212 133 214 163 164 165 166 125 126 127 128 131 221 129 210 130 85
  12. 84 81 82 83 209 86 87 88 89 90 91 92 93 220 80 79 94 208
  13. 95 96 77 78 76 201 29 28 27 203 25 33 32 34 205 216 218 215
  14. 30 31 38 37 36 35 24 207 23 22]
  15. Minimum distance found = 4028.625948

Code

原始ACO

main.py

  1. import math
  2. from aco import ACO, Graph
  3. from plot import plot
  4. def distance(city1: dict, city2: dict):
  5. return math.sqrt((city1['x'] - city2['x']) ** 2 + (city1['y'] - city2['y']) ** 2)
  6. def main():
  7. cities = []
  8. points = []
  9. with open('eil51.txt') as f:
  10. for line in f.readlines():
  11. city = line.split(' ')
  12. cities.append(dict(index=int(city[0]), x=int(city[1]), y=int(city[2])))
  13. points.append((int(city[1]), int(city[2])))
  14. cost_matrix = []
  15. rank = len(cities)
  16. for i in range(rank):
  17. row = []
  18. for j in range(rank):
  19. row.append(distance(cities[i], cities[j]))
  20. cost_matrix.append(row)
  21. aco = ACO(10, 2000, 1.0, 10.0, 0.5, 10, 2)
  22. graph = Graph(cost_matrix, rank)
  23. path, cost = aco.solve(graph)
  24. print('cost: {}, path: {}'.format(cost, path))
  25. plot(points, path)
  26. if __name__ == '__main__':
  27. for _ in range(20):
  28. main()

aco.py

  1. import random
  2. class Graph(object):
  3. def __init__(self, cost_matrix: list, rank: int):
  4. """
  5. :param cost_matrix:
  6. :param rank: rank of the cost matrix
  7. """
  8. self.matrix = cost_matrix
  9. self.rank = rank
  10. # noinspection PyUnusedLocal
  11. self.pheromone = [[1 / (rank * rank) for j in range(rank)] for i in range(rank)]
  12. class ACO(object):
  13. def __init__(self, ant_count: int, generations: int, alpha: float, beta: float, rho: float, q: int,
  14. strategy: int):
  15. """
  16. :param ant_count:
  17. :param generations:
  18. :param alpha: relative importance of pheromone
  19. :param beta: relative importance of heuristic information
  20. :param rho: pheromone residual coefficient
  21. :param q: pheromone intensity
  22. :param strategy: pheromone update strategy. 0 - ant-cycle, 1 - ant-quality, 2 - ant-density
  23. """
  24. self.Q = q
  25. self.rho = rho
  26. self.beta = beta
  27. self.alpha = alpha
  28. self.ant_count = ant_count
  29. self.generations = generations
  30. self.update_strategy = strategy
  31. def _update_pheromone(self, graph: Graph, ants: list):
  32. for i, row in enumerate(graph.pheromone):
  33. for j, col in enumerate(row):
  34. graph.pheromone[i][j] *= self.rho
  35. for ant in ants:
  36. graph.pheromone[i][j] += ant.pheromone_delta[i][j]
  37. # noinspection PyProtectedMember
  38. def solve(self, graph: Graph):
  39. """
  40. :param graph:
  41. """
  42. best_cost = float('inf')
  43. best_solution = []
  44. for gen in range(self.generations):
  45. # noinspection PyUnusedLocal
  46. ants = [_Ant(self, graph) for i in range(self.ant_count)]
  47. for ant in ants:
  48. for i in range(graph.rank - 1):
  49. ant._select_next()
  50. ant.total_cost += graph.matrix[ant.tabu[-1]][ant.tabu[0]]
  51. if ant.total_cost < best_cost:
  52. best_cost = ant.total_cost
  53. best_solution = [] + ant.tabu
  54. # update pheromone
  55. ant._update_pheromone_delta()
  56. self._update_pheromone(graph, ants)
  57. # print('generation #{}, best cost: {}, path: {}'.format(gen, best_cost, best_solution))
  58. return best_solution, best_cost
  59. class _Ant(object):
  60. def __init__(self, aco: ACO, graph: Graph):
  61. self.colony = aco
  62. self.graph = graph
  63. self.total_cost = 0.0
  64. self.tabu = [] # tabu list
  65. self.pheromone_delta = [] # the local increase of pheromone
  66. self.allowed = [i for i in range(graph.rank)] # nodes which are allowed for the next selection
  67. self.eta = [[0 if i == j else 1 / graph.matrix[i][j] for j in range(graph.rank)] for i in
  68. range(graph.rank)] # heuristic information
  69. start = random.randint(0, graph.rank - 1) # start from any node
  70. self.tabu.append(start)
  71. self.current = start
  72. self.allowed.remove(start)
  73. def _select_next(self):
  74. denominator = 0
  75. for i in self.allowed:
  76. denominator += self.graph.pheromone[self.current][i] ** self.colony.alpha * self.eta[self.current][
  77. i] ** self.colony.beta
  78. # noinspection PyUnusedLocal
  79. probabilities = [0 for i in range(self.graph.rank)] # probabilities for moving to a node in the next step
  80. for i in range(self.graph.rank):
  81. try:
  82. self.allowed.index(i) # test if allowed list contains i
  83. probabilities[i] = self.graph.pheromone[self.current][i] ** self.colony.alpha * \
  84. self.eta[self.current][i] ** self.colony.beta / denominator
  85. except ValueError:
  86. pass # do nothing
  87. # select next node by probability roulette
  88. selected = 0
  89. rand = random.random()
  90. for i, probability in enumerate(probabilities):
  91. rand -= probability
  92. if rand <= 0:
  93. selected = i
  94. break
  95. self.allowed.remove(selected)
  96. self.tabu.append(selected)
  97. self.total_cost += self.graph.matrix[self.current][selected]
  98. self.current = selected
  99. # noinspection PyUnusedLocal
  100. def _update_pheromone_delta(self):
  101. self.pheromone_delta = [[0 for j in range(self.graph.rank)] for i in range(self.graph.rank)]
  102. for _ in range(1, len(self.tabu)):
  103. i = self.tabu[_ - 1]
  104. j = self.tabu[_]
  105. if self.colony.update_strategy == 1: # ant-quality system
  106. self.pheromone_delta[i][j] = self.colony.Q
  107. elif self.colony.update_strategy == 2: # ant-density system
  108. # noinspection PyTypeChecker
  109. self.pheromone_delta[i][j] = self.colony.Q / self.graph.matrix[i][j]
  110. else: # ant-cycle system
  111. self.pheromone_delta[i][j] = self.colony.Q / self.total_cost

plot.py

  1. import operator
  2. import matplotlib.pyplot as plt
  3. def plot(points, path: list):
  4. x = []
  5. y = []
  6. for point in points:
  7. x.append(point[0])
  8. y.append(point[1])
  9. # noinspection PyUnusedLocal
  10. y = list(map(operator.sub, [max(y) for i in range(len(points))], y))
  11. plt.plot(x, y, 'co')
  12. for _ in range(1, len(path)):
  13. i = path[_ - 1]
  14. j = path[_]
  15. # noinspection PyUnresolvedReferences
  16. plt.arrow(x[i], y[i], x[j] - x[i], y[j] - y[i])
  17. # noinspection PyTypeChecker
  18. plt.xlim(0, max(x) * 1.1)
  19. # noinspection PyTypeChecker
  20. plt.ylim(0, max(y) * 1.1)
  21. plt.show()

2-OPT ACO

aco_2opt.py

  1. from math import sqrt
  2. import argparse
  3. import os
  4. import numpy as np
  5. from matplotlib import pyplot as plt
  6. import time
  7. #Global variables. Still not sure what these should be
  8. SCENT_MIN = 0.1
  9. ALPHA = 1
  10. BETA = 2
  11. EVAP_COEFF = 0.01
  12. class Ant(object):
  13. def __init__(self, node):
  14. self.node = node
  15. self.path = np.array([node])
  16. self.visited = {}
  17. class Node(object):
  18. def __init__(self, id, x, y):
  19. self.id = int(id) - 1 #added -1
  20. self.x = float(x)
  21. self.y = float(y)
  22. def build_graph(filename):
  23. with open (filename, "r") as myfile:
  24. data = myfile.readlines()
  25. node_list = []
  26. length = len(data)
  27. print "Number of cities = %d" % length
  28. for i in xrange(length):
  29. line = data[i].split()
  30. node = Node(line[0], line[2], line[1])
  31. node_list.append(node)
  32. distance = np.empty([length, length], dtype=float)
  33. heuristic = np.empty([length, length], dtype=float)
  34. scent = np.empty([length, length], dtype=float)
  35. scent.fill(SCENT_MIN)
  36. (distance, heuristic) = init_distances(node_list, distance)
  37. return (node_list, distance, heuristic, scent, length)
  38. def init_distances(node_list, distances):
  39. for node1 in node_list:
  40. for node2 in node_list:
  41. dist = sqrt((node1.x - node2.x)**2 + (node1.y - node2.y)**2)
  42. distances[node1.id][node2.id] = dist #removed -1's
  43. np.fill_diagonal(distances, np.nan)
  44. heuristic = 1/distances
  45. return (distances, heuristic)
  46. def update_probability(scent, heuristic):
  47. probability = ((scent**ALPHA) * (heuristic**BETA)) / \
  48. np.nansum((scent**ALPHA) * (heuristic**BETA), axis=1)[:,np.newaxis]
  49. return probability
  50. def update_scents(ants, scent, distance):
  51. num_nodes = len(ants[0].path)
  52. path_travelers = {}
  53. for ant in ants:
  54. for i in xrange(num_nodes - 1):
  55. src = ant.path[i]
  56. dst = ant.path[i + 1]
  57. if (src,dst) in path_travelers:
  58. path_travelers[(src,dst)] = path_travelers[(src,dst)] + 1
  59. path_travelers[(dst,src)] = path_travelers[(dst,src)] + 1
  60. else:
  61. path_travelers[(src,dst)] = 1
  62. path_travelers[(dst,src)] = 1
  63. dist = distance[src][dst]
  64. for key, value in path_travelers.iteritems():
  65. src = key[0]
  66. dst = key[1]
  67. updated_scent = (1 - EVAP_COEFF) * scent[src][dst] + 100 * value * (1/distance[src][dst])
  68. scent[src][dst] = max(updated_scent, SCENT_MIN)
  69. return scent
  70. def path_distance(path, distance):
  71. dist = 0
  72. num_cities = len(path)
  73. for i in xrange(num_cities - 1):
  74. #print "i = %d" % i
  75. src = path[i]
  76. dst = path[i+1]
  77. dist = dist + distance[src][dst]
  78. return dist
  79. def update_paths(ants, probability):
  80. num_nodes = probability.shape[0]
  81. for ant in ants:
  82. new_arr = np.copy(probability)
  83. curr_node = ant.path[-1]
  84. for i in xrange(num_nodes):
  85. for node in ant.path:
  86. new_arr[curr_node][node] = 0
  87. row_sum = np.nansum(new_arr[curr_node])
  88. if row_sum != 0:
  89. new_arr[curr_node] = new_arr[curr_node] / row_sum
  90. next_node = np.random.choice(np.arange(0, num_nodes), p = new_arr[curr_node])
  91. ant.path = np.append(ant.path, next_node)
  92. else:
  93. ant.path = np.append(ant.path, ant.path[0])
  94. return ants
  95. def update_greedy_paths(ants, distance):
  96. for ant in ants:
  97. curr_node = ant.path[-1]
  98. def place_ants_randomly(num_ants, num_cities):
  99. ants = np.empty(num_ants, dtype=object)
  100. for i in xrange(num_ants):
  101. ants[i] = Ant(np.random.random_integers(0, num_cities - 1))
  102. return ants
  103. def shortest_path(ants, min_dist, min_path, distance):
  104. for ant in ants:
  105. #print ant.path
  106. dist = 0
  107. for i in xrange(ant.path.shape[0] - 1):
  108. src = ant.path[i]
  109. dst = ant.path[i + 1]
  110. dist = dist + distance[src][dst]
  111. if dist < min_dist:
  112. min_path = ant.path
  113. min_dist = dist
  114. #print min_path
  115. return (min_dist, min_path)
  116. def plot(nodes, path):
  117. locations = {}
  118. for node in nodes:
  119. locations[node.id] = (node.x, node.y)
  120. x = []
  121. y = []
  122. nums = []
  123. for i in path:
  124. x.append(locations[i][0])
  125. y.append(locations[i][1])
  126. plt.scatter(x,y)
  127. plt.plot(x,y)
  128. plt.xlabel("X Position")
  129. plt.ylabel("Y Position")
  130. plt.title("Shortest path found")
  131. plt.hold(True)
  132. plt.show()
  133. def two_opt_swap(route, i, k):
  134. route_len = route.shape[0]
  135. new_route = np.empty(route_len, dtype=int)
  136. new_route[0] = route[0]
  137. j = 1
  138. for a in xrange(1, i):
  139. new_route[j] = route[a]
  140. j += 1
  141. """route[i] to route[k] in reverse"""
  142. for a in reversed(xrange(i, k+1)):
  143. new_route[j] = route[a]
  144. j += 1
  145. for a in xrange(k+1, route_len):
  146. new_route[j] = route[a]
  147. j += 1
  148. return new_route
  149. def two_opt_iter(curr_path, distance):
  150. num_nodes = curr_path.shape[0]
  151. best_distance = path_distance(curr_path, distance)
  152. for i in xrange(1, num_nodes - 1):
  153. for k in xrange(i + 1, num_nodes - 1):
  154. new_route = two_opt_swap(curr_path, i, k)
  155. new_dist = path_distance(new_route, distance)
  156. if (new_dist < best_distance):
  157. print new_dist
  158. return (new_route, True)
  159. return (curr_path, False)
  160. def two_opt(curr_path, distance):
  161. num_nodes = curr_path.shape[0]
  162. improvement = True
  163. while (improvement):
  164. (curr_path, improvement) = two_opt_iter(curr_path, distance)
  165. return curr_path
  166. def local_search(ants, distance):
  167. """Local search on each solution"""
  168. for ant in ants:
  169. ant.path = two_opt(ant.path, distance)
  170. return ants
  171. def main():
  172. #parse arguments
  173. parser = argparse.ArgumentParser(description='Short sample app')
  174. parser.add_argument('-a', action='store',dest='num_ants',default=10, type=int)
  175. parser.add_argument('-i', action='store',dest='iterations',default=10, type=int)
  176. parser.add_argument('-f', action='store',dest='file',type=str)
  177. args = parser.parse_args()
  178. #Intialize graph
  179. num_ants = args.num_ants
  180. iterations = args.iterations
  181. file_path = args.file
  182. (nodes, distance, heuristic, scent, num_cities) = build_graph(file_path)
  183. #main loop
  184. min_path = []
  185. min_dist = float('inf')
  186. for a in xrange(iterations):
  187. ants = place_ants_randomly(num_ants, num_cities)
  188. probability = update_probability(scent, heuristic)
  189. ants = update_paths(ants, probability)
  190. ants = local_search(ants, distance)
  191. (min_dist, min_path) = shortest_path(ants, min_dist, min_path, distance)
  192. min_dist = path_distance(min_path, distance)
  193. scent = update_scents(ants, scent, distance)
  194. print "Shortest path found:"
  195. print min_path
  196. print "Minimum distance found = %f" % min_dist
  197. plot(nodes,min_path)
  198. if __name__ == '__main__':
  199. main()

Reference

  1. 秦东各, 王长坤. 一种基于 2-opt 算法的混合型蚁群算法[J]. 工业控制计算机, 2018, 1: 042.
  2. "高级蚁群算法求解1000以上城市的TSP问题(旅行商),附大量TSPLIB数据!" http://club.excelhome.net/thread-825313-1-1.html
  3. https://github.com/ppoffice/ant-colony-tsp
  4. https://github.com/djzurawski/aco-tsp
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注