- 抽象來源:模仿自然界中螞蟻的覓食行為。
- 核心思想:蟻群覓食過程中,每只螞蟻在所走過的路徑上均會釋放出一種信息素,該信息素隨時間的推移逐漸揮發。因此,每條路徑上的信息素同時存在正負反饋兩種機制。正反饋:螞蟻每次經過該路徑均會釋放信息素使得該路徑上的信息素濃度增加;負反饋:每條路徑上的信息素隨時間推移會逐漸揮發。由此,我們可以判斷,在起點與終點之間,當相同數量的螞蟻初始同時經過兩條不同的路徑時,路徑上初始信息素的濃度是相同的;不過,當路徑越短時,信息素揮發時間也越短,殘留信息素濃度也將越高。隨后的螞蟻將根據路徑上殘留信息素濃度的大小對路徑進行選擇 --- 濃度越高,選擇概率越大。最終導致信息素濃度越高的路徑上螞蟻的選擇數目越多,而更多的螞蟻也將同時導致該路徑上殘留信息素濃度越高(即高者越高,低者越低)。因此,在理想情況下,整個蟻群將逐漸向信息素濃度最高的路徑(即最短路徑)進行轉移。
- 迭代公式:
- 時刻t,螞蟻k由節點i向節點j的狀態轉移概率:
\begin{equation}
p_{ij}^{k}(t) = \begin{cases}
\frac{[\tau_{ij}(t)]^{\alpha }\cdot [\eta_{ij}(t)]^{\beta}}{\sum_{s\in allowed_{k}}[\tau_{is}(t)]^{\alpha }\cdot [\eta_{is}(t)]^{\beta}} & \text{ if } j\in allowed_{k}\\
0 & \text{ if } j\notin allowed_{k}
\end{cases}
\end{equation}
其中$\alpha$為信息啟發式因子,$\beta$為期望啟發式因子,$\tau$為路徑殘留信息素,$\eta$為啟發函數 - 殘留信息素迭代更新方式(在完成一次迭代后集體進行更新):
\begin{equation}
\tau_{ij}(t+1) = (1-\rho)\cdot \tau_{ij}(t)+\Delta\tau_{ij}(t)
\end{equation}
其中$\rho$為信息素揮發速度 --- 負反饋相關,$\Delta\tau_{ij}$為信息素增量 --- 正反饋相關
信息素增量:
$\Delta\tau_{ij}(t) = \sum^{m}_{k=1}\Delta\tau_{ij}^{k}(t)$
每只螞蟻對信息素增量的貢獻:
$\Delta\tau_{ij}^{k}(t) = \begin{cases}
Q/L_k & \text{ if the k_th ant goes through the nodes labeled with i and j on the current iteration} \\
0 & \text{ else }
\end{cases}$
其中,$L_k$代表第k只螞蟻在當前迭代過程中完整所走過的總路程 - 啟發函數表達式:
\begin{equation}
\eta_{ij}(t) = \frac{1}{d_{ij}}
\end{equation}
其中$d_{ij}$為節點i至節點j之間的距離
- 時刻t,螞蟻k由節點i向節點j的狀態轉移概率:
- Python代碼實現:
View Code1 import numpy as np 2 import matplotlib.pyplot as plt 3 4 5 # 建立“螞蟻”類 6 class Ant(object): 7 def __init__(self, path): 8 self.path = path # 螞蟻當前迭代整體路徑 9 self.length = self.calc_length(path) # 螞蟻當前迭代整體路徑長度 10 11 def calc_length(self, path_): # path=[A, B, C, D, A]注意路徑閉環 12 length_ = 0 13 for i in range(len(path_)-1): 14 delta = (path_[i].x - path_[i+1].x, path_[i].y - path_[i+1].y) 15 length_ += np.linalg.norm(delta) 16 return length_ 17 18 @staticmethod 19 def calc_len(A, B): # 靜態方法,計算城市A與城市B之間的距離 20 return np.linalg.norm((A.x - B.x, A.y - B.y)) 21 22 23 # 建立“城市”類 24 class City(object): 25 def __init__(self, x, y): 26 self.x = x 27 self.y = y 28 29 30 # 建立“路徑”類 31 class Path(object): 32 def __init__(self, A): # A為起始城市 33 self.path = [A, A] 34 35 def add_path(self, B): # 追加路徑信息,方便計算整體路徑長度 36 self.path.append(B) 37 self.path[-1], self.path[-2] = self.path[-2], self.path[-1] 38 39 40 # 構建“蟻群算法”的主體 41 class ACO(object): 42 def __init__(self, ant_num=50, maxIter=300, alpha=1, beta=5, rho=0.1, Q=1): 43 self.ants_num = ant_num # 螞蟻個數 44 self.maxIter = maxIter # 蟻群最大迭代次數 45 self.alpha = alpha # 信息啟發式因子 46 self.beta = beta # 期望啟發式因子 47 self.rho = rho # 信息素揮發速度 48 self.Q = Q # 信息素強度 49 ########################### 50 self.deal_data('coordinates.dat') # 提取所有城市的坐標信息 51 ########################### 52 self.path_seed = np.zeros(self.ants_num).astype(int) # 記錄一次迭代過程中每個螞蟻的初始城市下標 53 self.ants_info = np.zeros((self.maxIter, self.ants_num)) # 記錄每次迭代后所有螞蟻的路徑長度信息 54 self.best_path = np.zeros(self.maxIter) # 記錄每次迭代后整個蟻群的“歷史”最短路徑長度 55 ########################### 56 self.solve() # 完成算法的迭代更新 57 self.display() # 數據可視化展示 58 59 def deal_data(self, filename): 60 with open(filename, 'rt') as f: 61 temp_list = list(line.split() for line in f) # 臨時存儲提取出來的坐標信息 62 self.cities_num = len(temp_list) # 1. 獲取城市個數 63 self.cities = list(City(float(item[0]), float(item[1])) for item in temp_list) # 2. 構建城市列表 64 self.city_dist_mat = np.zeros((self.cities_num, self.cities_num)) # 3. 構建城市距離矩陣 65 for i in range(self.cities_num): 66 A = self.cities[i] 67 for j in range(i, self.cities_num): 68 B = self.cities[j] 69 self.city_dist_mat[i][j] = self.city_dist_mat[j][i] = Ant.calc_len(A, B) 70 self.phero_mat = np.ones((self.cities_num, self.cities_num)) # 4. 初始化信息素矩陣 71 # self.phero_upper_bound = self.phero_mat.max() * 1.2 ###信息素濃度上限 72 self.eta_mat = 1/(self.city_dist_mat + np.diag([np.inf]*self.cities_num)) # 5. 初始化啟發函數矩陣 73 74 def solve(self): 75 iterNum = 0 # 當前迭代次數 76 while iterNum < self.maxIter: 77 self.random_seed() # 使整個蟻群產生隨機的起始點 78 delta_phero_mat = np.zeros((self.cities_num, self.cities_num)) # 初始化每次迭代后信息素矩陣的增量 79 ########################################################################## 80 for i in range(self.ants_num): 81 city_index1 = self.path_seed[i] # 每只螞蟻訪問的第一個城市下標 82 ant_path = Path(self.cities[city_index1]) # 記錄每只螞蟻訪問過的城市 83 tabu = [city_index1] # 記錄每只螞蟻訪問過的城市下標,禁忌城市下標列表 84 non_tabu = list(set(range(self.cities_num)) - set(tabu)) 85 for j in range(self.cities_num-1): # 對余下的城市進行訪問 86 up_proba = np.zeros(self.cities_num-len(tabu)) # 初始化狀態遷移概率的分子 87 for k in range(self.cities_num-len(tabu)): 88 up_proba[k] = np.power(self.phero_mat[city_index1][non_tabu[k]], self.alpha) * \ 89 np.power(self.eta_mat[city_index1][non_tabu[k]], self.beta) 90 proba = up_proba/sum(up_proba) # 每條可能子路徑上的狀態遷移概率 91 while True: # 提取出下一個城市的下標 92 random_num = np.random.rand() 93 index_need = np.where(proba > random_num)[0] 94 if len(index_need) > 0: 95 city_index2 = non_tabu[index_need[0]] 96 break 97 ant_path.add_path(self.cities[city_index2]) 98 tabu.append(city_index2) 99 non_tabu = list(set(range(self.cities_num)) - set(tabu)) 100 city_index1 = city_index2 101 self.ants_info[iterNum][i] = Ant(ant_path.path).length 102 if iterNum == 0 and i == 0: # 完成對最佳路徑城市的記錄 103 self.best_cities = ant_path.path 104 else: 105 if self.ants_info[iterNum][i] < Ant(self.best_cities).length: self.best_cities = ant_path.path 106 tabu.append(tabu[0]) # 每次迭代完成后,使禁忌城市下標列表形成完整閉環 107 for l in range(self.cities_num): 108 delta_phero_mat[tabu[l]][tabu[l+1]] += self.Q/self.ants_info[iterNum][i] 109 110 self.best_path[iterNum] = Ant(self.best_cities).length 111 112 self.update_phero_mat(delta_phero_mat) # 更新信息素矩陣 113 iterNum += 1 114 115 def update_phero_mat(self, delta): 116 self.phero_mat = (1 - self.rho) * self.phero_mat + delta 117 # self.phero_mat = np.where(self.phero_mat > self.phero_upper_bound, self.phero_upper_bound, self.phero_mat) # 判斷是否超過濃度上限 118 119 def random_seed(self): # 產生隨機的起始點下表,盡量保證所有螞蟻的起始點不同 120 if self.ants_num <= self.cities_num: # 螞蟻數 <= 城市數 121 self.path_seed[:] = np.random.permutation(range(self.cities_num))[:self.ants_num] 122 else: # 螞蟻數 > 城市數 123 self.path_seed[:self.cities_num] = np.random.permutation(range(self.cities_num)) 124 temp_index = self.cities_num 125 while temp_index + self.cities_num <= self.ants_num: 126 self.path_seed[temp_index:temp_index + self.cities_num] = np.random.permutation(range(self.cities_num)) 127 temp_index += self.cities_num 128 temp_left = self.ants_num % self.cities_num 129 if temp_left != 0: 130 self.path_seed[temp_index:] = np.random.permutation(range(self.cities_num))[:temp_left] 131 132 def display(self): # 數據可視化展示 133 plt.figure(figsize=(6, 10)) 134 plt.subplot(211) 135 plt.plot(self.ants_info, 'g.') 136 plt.plot(self.best_path, 'r-', label='history_best') 137 plt.xlabel('Iteration') 138 plt.ylabel('length') 139 plt.legend() 140 plt.subplot(212) 141 plt.plot(list(city.x for city in self.best_cities), list(city.y for city in self.best_cities), 'g-') 142 plt.plot(list(city.x for city in self.best_cities), list(city.y for city in self.best_cities), 'r.') 143 plt.xlabel('x') 144 plt.ylabel('y') 145 plt.savefig('ACO.png', dpi=500) 146 plt.show() 147 plt.close() 148 149 150 ACO()
筆者所用數據文件名為coordinates.dat,相應坐標信息如下:
View coordinates.dat565.0 575.0 25.0 185.0 345.0 750.0 945.0 685.0 845.0 655.0 880.0 660.0 25.0 230.0 525.0 1000.0 580.0 1175.0 650.0 1130.0 1605.0 620.0 1220.0 580.0 1465.0 200.0 1530.0 5.0 845.0 680.0 725.0 370.0 145.0 665.0 415.0 635.0 510.0 875.0 560.0 365.0 300.0 465.0 520.0 585.0 480.0 415.0 835.0 625.0 975.0 580.0 1215.0 245.0 1320.0 315.0 1250.0 400.0 660.0 180.0 410.0 250.0 420.0 555.0 575.0 665.0 1150.0 1160.0 700.0 580.0 685.0 595.0 685.0 610.0 770.0 610.0 795.0 645.0 720.0 635.0 760.0 650.0 475.0 960.0 95.0 260.0 875.0 920.0 700.0 500.0 555.0 815.0 830.0 485.0 1170.0 65.0 830.0 610.0 605.0 625.0 595.0 360.0 1340.0 725.0 1740.0 245.0
- 結果展示:

- 參考:https://blog.csdn.net/golden1314521/article/details/45059719
