2022年 11月 9日

蚁群算法讲解python

简介

蚁群算法(Ant Clony Optimization, ACO)作为一个启发式群智能算法,它是由一群无智能或有轻微智能的个体通过相互协作而表现出智能行为,从而为求解复杂问题提供了一个新的可能性。

ACO是一种仿生学算法,是由自然界中蚂蚁觅食的行为而启发的。在自然界,蚂蚁觅食过程中,蚁群总能够按照寻找到一条从蚁巢和食物源的最优路径,这也就是蚁群算法的由来

注:然而蚁群算法去做路径规划优化智能算法或机器学习算法却不太一样。

路径规划的蚁群算法优化步骤(典型tsp问题):

  1. 初始化一些参数。α——信息素重要程度因子;β——启发函数重要程度因子;ρ——信息素挥发因子;Q为常数;m为蚁群数量;城市规模num_city;迭代次数iter_max,信息素矩阵\tau _{ij}等。

  2. 计算概率。P_{ij}^{k}(t)=\begin{cases} \frac{\tau _{ij}(t)}{\sum_{s\in allowed_{k}}^{}\tau _{is}(t)}& \text{ if } j= allowed_{k} \\ 0& \text{ if } j\neq allowed_{k} \end{cases},其中allowed_{k}为第k只蚂蚁后续可以走的城市集合;P_{ij}^{k}(t)为第k只蚂蚁在t时刻从城市i转移到城市j的概率。
  3. 更新信息素浓度。\begin{cases} \tau \left ( t+1 \right )=\left ( 1-\rho \right )\tau _{ij}\left ( t \right )+\sum_{k=1}^{m}\Delta\tau _{ij}^{k} & \text{ if }0< \rho < 1 \\ \Delta \tau _{ij}^{k}=\begin{cases} \frac{Q}{L_{k}}& \text{ if } L_{k}=distant(s_{ij}^{k}) \\ 0& \text{ if } else \end{cases}& \end{cases},Lk为第k只蚂蚁曾经走过的路径。
  4. 判断是否达到最大迭代次数,达到从而结束算法。
  1. import random
  2. import math
  3. import numpy as np
  4. import matplotlib.pyplot as plt
  5. class ACO(object):
  6. def __init__(self, num_city, data):
  7. self.m = 50 # 蚂蚁数量
  8. self.alpha = 0.5 # 信息素重要程度因子
  9. self.beta = 5 # 启发函数重要因子
  10. self.rho = 0.1 # 信息素挥发因子
  11. self.Q = 1 # 常量系数
  12. self.num_city = num_city # 城市规模
  13. self.location = data # 城市坐标
  14. self.Tau = np.zeros([num_city, num_city]) # 信息素矩阵
  15. self.Table = [[0 for _ in range(num_city)] for _ in range(self.m)] # 生成的蚁群
  16. self.iter = 1
  17. self.iter_max = 500
  18. self.dis_mat = self.compute_dis_mat(num_city, self.location) # 计算城市之间的距离矩阵
  19. self.Eta = 10. / self.dis_mat # 启发式函数
  20. self.paths = None # 蚁群中每个个体的长度
  21. # 存储存储每个温度下的最终路径,画出收敛图
  22. self.iter_x = []
  23. self.iter_y = []
  24. # self.greedy_init(self.dis_mat,100,num_city)
  25. def greedy_init(self, dis_mat, num_total, num_city):
  26. start_index = 0
  27. result = []
  28. for i in range(num_total):
  29. rest = [x for x in range(0, num_city)]
  30. # 所有起始点都已经生成了
  31. if start_index >= num_city:
  32. start_index = np.random.randint(0, num_city)
  33. result.append(result[start_index].copy())
  34. continue
  35. current = start_index
  36. rest.remove(current)
  37. # 找到一条最近邻路径
  38. result_one = [current]
  39. while len(rest) != 0:
  40. tmp_min = math.inf
  41. tmp_choose = -1
  42. for x in rest:
  43. if dis_mat[current][x] < tmp_min:
  44. tmp_min = dis_mat[current][x]
  45. tmp_choose = x
  46. current = tmp_choose
  47. result_one.append(tmp_choose)
  48. rest.remove(tmp_choose)
  49. result.append(result_one)
  50. start_index += 1
  51. pathlens = self.compute_paths(result)
  52. sortindex = np.argsort(pathlens) # argsort()是将X中的元素从小到大排序后,提取对应的索引index,然后输出到y
  53. index = sortindex[0]
  54. result = result[index]
  55. for i in range(len(result)-1):
  56. s = result[i]
  57. s2 = result[i+1]
  58. self.Tau[s][s2] = 1
  59. self.Tau[result[-1]][result[0]] = 1
  60. # for i in range(num_city):
  61. # for j in range(num_city):
  62. # return result
  63. # 轮盘赌选择
  64. def rand_choose(self, p):
  65. x = np.random.rand()
  66. for i, t in enumerate(p):
  67. x -= t
  68. if x <= 0:
  69. break
  70. return i
  71. # 生成蚁群
  72. def get_ants(self, num_city):
  73. for i in range(self.m):
  74. start = np.random.randint(num_city - 1)
  75. self.Table[i][0] = start
  76. unvisit = list([x for x in range(num_city) if x != start])
  77. current = start
  78. j = 1
  79. while len(unvisit) != 0:
  80. P = []
  81. # 通过信息素计算城市之间的转移概率
  82. for v in unvisit:
  83. P.append(self.Tau[current][v] ** self.alpha * self.Eta[current][v] ** self.beta)
  84. P_sum = sum(P)
  85. P = [x / P_sum for x in P]
  86. # 轮盘赌选择一个一个城市
  87. index = self.rand_choose(P)
  88. current = unvisit[index]
  89. self.Table[i][j] = current
  90. unvisit.remove(current)
  91. j += 1
  92. # 计算不同城市之间的距离
  93. def compute_dis_mat(self, num_city, location):
  94. dis_mat = np.zeros((num_city, num_city))
  95. for i in range(num_city):
  96. for j in range(num_city):
  97. if i == j:
  98. dis_mat[i][j] = np.inf
  99. continue
  100. a = location[i]
  101. b = location[j]
  102. tmp = np.sqrt(sum([(x[0] - x[1]) ** 2 for x in zip(a, b)]))
  103. dis_mat[i][j] = tmp
  104. return dis_mat
  105. # 计算一条路径的长度
  106. def compute_pathlen(self, path, dis_mat):
  107. a = path[0]
  108. b = path[-1]
  109. result = dis_mat[a][b]
  110. for i in range(len(path) - 1):
  111. a = path[i]
  112. b = path[i + 1]
  113. result += dis_mat[a][b]
  114. return result
  115. # 计算一个群体的长度
  116. def compute_paths(self, paths):
  117. result = []
  118. for one in paths:
  119. length = self.compute_pathlen(one, self.dis_mat)
  120. result.append(length)
  121. return result
  122. # 更新信息素
  123. def update_Tau(self):
  124. delta_tau = np.zeros([self.num_city, self.num_city])
  125. paths = self.compute_paths(self.Table)
  126. for i in range(self.m):
  127. for j in range(self.num_city - 1):
  128. a = self.Table[i][j]
  129. b = self.Table[i][j + 1]
  130. delta_tau[a][b] = delta_tau[a][b] + self.Q / paths[i]
  131. a = self.Table[i][0]
  132. b = self.Table[i][-1]
  133. delta_tau[a][b] = delta_tau[a][b] + self.Q / paths[i]
  134. self.Tau = (1 - self.rho) * self.Tau + delta_tau
  135. def aco(self):
  136. best_lenth = math.inf # math.inf返回浮点正无穷大
  137. best_path = None
  138. for cnt in range(self.iter_max):
  139. # 生成新的蚁群
  140. self.get_ants(self.num_city) # out>>self.Table
  141. self.paths = self.compute_paths(self.Table)
  142. # 取该蚁群的最优解
  143. tmp_lenth = min(self.paths)
  144. tmp_path = self.Table[self.paths.index(tmp_lenth)]
  145. # 可视化初始的路径
  146. if cnt == 0:
  147. init_show = self.location[tmp_path]
  148. init_show = np.vstack([init_show, init_show[0]])
  149. # 更新最优解
  150. if tmp_lenth < best_lenth:
  151. best_lenth = tmp_lenth
  152. best_path = tmp_path
  153. # 更新信息素
  154. self.update_Tau()
  155. # 保存结果
  156. self.iter_x.append(cnt)
  157. self.iter_y.append(best_lenth)
  158. print(cnt,best_lenth)
  159. return best_lenth, best_path
  160. def run(self):
  161. best_length, best_path = self.aco()
  162. return self.location[best_path], best_length
  163. # 读取数据
  164. def read_tsp(path):
  165. lines = open(path, 'r').readlines()
  166. assert 'NODE_COORD_SECTION\n' in lines
  167. index = lines.index('NODE_COORD_SECTION\n')
  168. data = lines[index + 1:-1]
  169. tmp = []
  170. for line in data:
  171. line = line.strip().split(' ')
  172. if line[0] == 'EOF':
  173. continue
  174. tmpline = []
  175. for x in line:
  176. if x == '':
  177. continue
  178. else:
  179. tmpline.append(float(x))
  180. if tmpline == []:
  181. continue
  182. tmp.append(tmpline)
  183. data = tmp
  184. return data
  185. data = read_tsp('data/st70.tsp')
  186. data = np.array(data)
  187. data = data[:, 1:]
  188. # 加上一行因为会回到起点
  189. show_data = np.vstack([data, data[0]])
  190. aco = ACO(num_city=data.shape[0], data=data.copy())
  191. Best_path, Best = aco.run()
  192. print(Best)
  193. Best_path = np.vstack([Best_path, Best_path[0]])
  194. plt.plot(Best_path[:, 0], Best_path[:, 1])
  195. plt.title('st70:aco-tsp')
  196. plt.show()

蚁群算法优化智能算法(以优化函数为例): 

  1. 初始化种群pop_size=100 ,最大迭代次数NGEN=50,信息素挥发因子rou=0.8,Q常量为1。
  2. 随机产生蚂蚁初始位置,计算适应度函数值,设为初始信息素,计算信息素转移概率
    pi[i] = (t_max - t[i]) / t_max

  3. 依据概率计算下一时刻位置
    1. if pi[i] < np.random.uniform(0, 1):
    2. self.pop_x[i][j] = self.pop_x[i][j] + np.random.uniform(-1, 1) * lamda
    3. else:
    4. self.pop_x[i][j] = self.pop_x[i][j] + np.random.uniform(-1, 1) * (
    5. self.bound[1][j] - self.bound[0][j]) / 2

  4. 信息素更新。
    t[i] = (1 - rou) * t[i] + Q * self.fitness(self.pop_x[i])

  5. 反复迭代,直到达到最大迭代次数

完整python代码:

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. class ACO:
  4. def __init__(self, parameters):
  5. # 初始化
  6. self.NGEN = parameters[0] # 迭代的代数
  7. self.pop_size = parameters[1] # 种群大小
  8. self.var_num = len(parameters[2]) # 变量个数
  9. self.bound = [] # 变量的约束范围
  10. self.bound.append(parameters[2])
  11. self.bound.append(parameters[3])
  12. self.pop_x = np.zeros((self.pop_size, self.var_num)) # 所有蚂蚁的位置
  13. self.g_best = np.zeros((1, self.var_num)) # 全局蚂蚁最优的位置
  14. # 初始化第0代初始全局最优解
  15. temp = -1
  16. for i in range(self.pop_size):
  17. for j in range(self.var_num):
  18. self.pop_x[i][j] = np.random.uniform(self.bound[0][j], self.bound[1][j])
  19. fit = self.fitness(self.pop_x[i])
  20. if fit > temp:
  21. self.g_best = self.pop_x[i]
  22. temp = fit
  23. def fitness(self, ind_var):
  24. """
  25. 个体适应值计算
  26. """
  27. x1 = ind_var[0]
  28. x2 = ind_var[1]
  29. x3 = ind_var[2]
  30. y = 4*x1 ** 2 + 2*x2 + x3 ** 3
  31. return y
  32. def update_operator(self, gen, t, t_max):
  33. """
  34. 更新算子:根据概率更新下一时刻的位置
  35. """
  36. rou = 0.8 # 信息素挥发系数
  37. Q = 1 # 信息释放总量
  38. lamda = 1 / gen
  39. pi = np.zeros(self.pop_size)
  40. for i in range(self.pop_size):
  41. for j in range(self.var_num):
  42. pi[i] = (t_max - t[i]) / t_max
  43. # 更新位置
  44. if pi[i] < np.random.uniform(0, 1):
  45. self.pop_x[i][j] = self.pop_x[i][j] + np.random.uniform(-1, 1) * lamda
  46. else:
  47. self.pop_x[i][j] = self.pop_x[i][j] + np.random.uniform(-1, 1) * (
  48. self.bound[1][j] - self.bound[0][j]) / 2
  49. # 越界保护
  50. if self.pop_x[i][j] < self.bound[0][j]:
  51. self.pop_x[i][j] = self.bound[0][j]
  52. if self.pop_x[i][j] > self.bound[1][j]:
  53. self.pop_x[i][j] = self.bound[1][j]
  54. # 更新t值
  55. t[i] = (1 - rou) * t[i] + Q * self.fitness(self.pop_x[i])
  56. # 更新全局最优值
  57. if self.fitness(self.pop_x[i]) > self.fitness(self.g_best):
  58. self.g_best = self.pop_x[i]
  59. t_max = np.max(t)
  60. return t_max, t
  61. def main(self):
  62. popobj = []
  63. best = np.zeros((1, self.var_num))[0]
  64. for gen in range(1, self.NGEN + 1):
  65. if gen == 1:
  66. tmax, t = self.update_operator(gen, np.array(list(map(self.fitness, self.pop_x))),
  67. np.max(np.array(list(map(self.fitness, self.pop_x)))))
  68. else:
  69. tmax, t = self.update_operator(gen, t, tmax)
  70. print('############ Generation {} ############'.format(str(gen)))
  71. print(self.g_best)
  72. print(self.fitness(self.g_best))
  73. if self.fitness(self.g_best) > self.fitness(best):
  74. best = self.g_best.copy()
  75. popobj.append(self.fitness(best))
  76. print('最好的位置:{}'.format(best))
  77. print('最大的函数值:{}'.format(self.fitness(best)))
  78. print("---- End of (successful) Searching ----")
  79. plt.figure()
  80. plt.title("Figure1")
  81. plt.xlabel("iterators", size=14)
  82. plt.ylabel("fitness", size=14)
  83. t = [t for t in range(1, self.NGEN + 1)]
  84. plt.plot(t, popobj, color='b', linewidth=2)
  85. plt.show()
  86. if __name__ == '__main__':
  87. NGEN = 100
  88. popsize = 50
  89. low = [1, 1, 1]
  90. up = [30, 30, 30]
  91. parameters = [NGEN, popsize, low, up]
  92. aco = ACO(parameters)
  93. aco.main()

 效果不好时,大家记得调节参数。

总结:

蚁群算法缺点:

  1. 收敛速度慢
  2. 易于陷入局部最优

参数作用:

  1. 蚂蚁数量:种群数量越多,得到的最优解就越精确,但是会有大量蚂蚁重复路过同一路径,运行时间增多。
  2. alpha  信息素重要程度因子:alpha值过大,蚂蚁选择之前走过的路径可能性就越大,蚁群搜索路径的随机性减弱。alpha值过小,蚁群搜索范围就会减少,易陷入局部最优解。
  3. beta 启发函数因子:beta值增大,蚁群更容易选择局部较短路径,能加快算法的运行速度,但是可能陷入局部最优解。
  4. ruo 信息素挥发因子:当ruo很小的时候,每条路径的残留信息很多,就会反复进行搜索,运算时间增加;ruo很大的时候,会放弃搜索很多有效路径,可能会丢失最优值。