蟻群算法(1) - Python實現


  • 抽象來源:模仿自然界中螞蟻的覓食行為。
  • 核心思想:蟻群覓食過程中,每只螞蟻在所走過的路徑上均會釋放出一種信息素,該信息素隨時間的推移逐漸揮發。因此,每條路徑上的信息素同時存在正負反饋兩種機制。正反饋:螞蟻每次經過該路徑均會釋放信息素使得該路徑上的信息素濃度增加;負反饋:每條路徑上的信息素隨時間推移會逐漸揮發。由此,我們可以判斷,在起點與終點之間,當相同數量的螞蟻初始同時經過兩條不同的路徑時,路徑上初始信息素的濃度是相同的;不過,當路徑越短時,信息素揮發時間也越短,殘留信息素濃度也將越高。隨后的螞蟻將根據路徑上殘留信息素濃度的大小對路徑進行選擇 --- 濃度越高,選擇概率越大。最終導致信息素濃度越高的路徑上螞蟻的選擇數目越多,而更多的螞蟻也將同時導致該路徑上殘留信息素濃度越高(即高者越高,低者越低)。因此,在理想情況下,整個蟻群將逐漸向信息素濃度最高的路徑(即最短路徑)進行轉移。
  • 迭代公式
    • 時刻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之間的距離

  • Python代碼實現
      1 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()
    View Code

    筆者所用數據文件名為coordinates.dat,相應坐標信息如下:

    565.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
    View coordinates.dat

 

  • 結果展示

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM