天天看點

蟻群算法的優化和評估

目錄

  • 任務要求
  • 實踐
    • 代碼
    • 思路
  • 參考

任務要求

寫論文和報告(論文流程:問題——算法——評估——結論)

實踐

擷取連結

代碼

solutionbase.py

#此為蟻群算法基線模型
import numpy as np
import random as ra

def TSP(net):
    \'\'\'初始化\'\'\'
    n = net.shape[0]
    h = 0.1 #揮發率
    m = 3 #螞蟻數量
    t0 = m / TanXin(n, net) #初始資訊素濃度
    a = 1
    b = 2
    #存儲結構
    R = np.ones([m, n+1], dtype=int) #螞蟻路徑矩陣
    T= np.ones([n, n]) * t0   #資訊濃度矩陣
    C = np.ones(m)        #路徑長度序列
    global_best = float(\'inf\') #全局最優值
    global_bestR = None #全局最優路徑

    \'\'\'循環\'\'\'
    for turn in range(50):
        for k in range(m):  # 螞蟻k
            for p in range(n + 1):
                if p == 0:
                    R[k][0] = ra.randint(0, n-1)
                elif p == n:
                    R[k][n] = R[k][0]
                else:
                    i = R[k][p - 1]  # 前一個結點
                    js = neighbor(n, net, i)
                    js = list(set(js) - set(R[k][:p]))  # 下一個可能節點
                    tjs = [T[i, j]**a * (1 / net[i, j])**b for j in js]
                    sum_tjs = sum(tjs)
                    pjs = [tj / sum_tjs for tj in tjs]  # 下一個可能節點的機率
                    # 輪盤算法求下一個節點j
                    r = ra.random()
                    for z in range(len(pjs)):
                        if r <= pjs[z]:
                            j = js[z]
                        else:
                            r -= pjs[z]
                    R[k][p] = j  # 把j加入k螞蟻的路徑
            C[k] = sum([net[R[k][i], R[k][i+1]] for i in range(n)])
        # 更新t
        for i in range(n):
            for j in range(n):
                T[i, j] = (1-h)* T[i, j]
                for k in range(m):
                    if j == R[k][list(R[k]).index(i)+1]:
                        T[i, j] += 1 / C[k]
        #更新全局最優值
        local_best = min(C) #局部最優值
        bestk = list(C).index(local_best) #局部最優值對應的螞蟻編号
        if local_best < global_best:
            global_best = local_best
            global_bestR = R[bestk].copy()
            

    # \'\'\'輸出\'\'\'
    # print(\'最短路徑為:\', global_best)
    # print(\'最短路徑長度:\', global_bestR)
    return global_best


def TanXin(n, net):
    R = np.ones(n+1, dtype=int)
    while True:
        for p in range(n+1):
            if p == 0:
                R[0] = ra.randint(0, n-1)
            elif p == n:
                R[n] = R[0]
            else:
                i = R[p-1]
                js = neighbor(n, net, i)
                js = list(set(js)-set(R[:p]))
                ljs = [net[i, j] for j in js]
                j = js[ljs.index(min(ljs))]
                R[p] = j
        C = sum([net[R[i], R[i+1]] for i in range(n)])
        if C != float(\'inf\'):
            break
    return C

def neighbor(n, net, i):
    return [j for j in range(n) if j != i if net[i][j] != float(\'inf\')]

if __name__ == \'__main__\':
    net = np.array([[float(\'inf\'), 3, 1, 2],
                    [3, float(\'inf\'), 5, 4],
                    [1, 5, float(\'inf\'), 2],
                    [2, 4, 2, float(\'inf\')]])
    TSP(net)
           

solution.py

#此為蟻群算法的改進模型
\'\'\'
改進:
(1) 資訊素初始化按根據距離配置設定,加快收斂(具體使得平均值為t0,但是具體每一個值按路徑長度配置設定)
(2) 設定穩定時刻點turn0,如果在超過規定時間(y)全局最優值沒有發生變化,那麼此時為turn0時刻
turn0時刻後的資訊素揮發速率變快,蟻群産生資訊素減少,以此增加路徑選擇的發散性
具體為:資訊素保留率:(1-h) * 1 / (turn-turn0)
       一隻螞蟻在(i, j)上生産的資訊素: 1 / C[k] * 1 / (turn-turn0)
(3) 設定重來時刻點turn1,如果在turn0後的一段時間(x)後全局最優值沒有發生變化,那麼此時為turn1時刻
此時重新初始化,融斷最多發生u次
\'\'\'

import numpy as np
import random as ra

def TSP(net):
    \'\'\'初始化\'\'\'
    n = net.shape[0]
    h = 0.1 #揮發率
    m = 3 #螞蟻數量
    t0 = m / TanXin(n, net) #初始資訊素濃度
    a = 1
    b = 2
    #存儲結構
    R = np.ones([m, n+1], dtype=int) #螞蟻路徑矩陣
    T = np.ones([n, n])  #資訊濃度矩陣 
    #初始化資訊濃度矩陣
    sum_edge = 0
    num_edge = 0
    for i in range(n):
        for j in range(n):
            if i == j or net[i, j] == float(\'inf\'):
                continue
            sum_edge += 1.0 / net[i, j]
            num_edge += 1;
    for i in range(n):
        for j in range(n):
            if i == j or net[i, j] == float(\'inf\'):
                continue
            T[i, j] = T[i, j] * num_edge * t0 * 1 / net[i, j] / sum_edge
    C = np.ones(m)        #路徑長度序列
    global_best = float(\'inf\') #全局最優值
    global_bestR = None #全局最優路徑

    #改進部分 
    y = 5
    x = 5
    u = 5
    turn0times = 0  #計時器1(記錄到turn0前未變化的次數)
    turn1times = 0  #計時器2 (記錄到turn0後為變化的次數)
    turn1num = 0 #計數器2 (記錄重來的次數)
    turn0 = None #穩定時刻點
    isturn0 = False #是否到達穩定時刻點
    isturn1 = True #是否到達重來時刻點
  


    \'\'\'循環\'\'\'
    turn = 0
    while turn <= 50:
        if isturn0:
            turn1times += 1
        for k in range(m):  # 螞蟻k
            for p in range(n + 1):
                if p == 0:
                    R[k][0] = ra.randint(0, n-1)
                elif p == n:
                    R[k][n] = R[k][0]
                else:
                    i = R[k][p - 1]  # 前一個結點
                    js = neighbor(n, net, i)
                    js = list(set(js) - set(R[k][:p]))  # 下一個可能節點
                    tjs = [T[i, j]**a * (1 / net[i, j])**b for j in js]
                    sum_tjs = sum(tjs)
                    pjs = [tj / sum_tjs for tj in tjs]  # 下一個可能節點的機率
                    # 輪盤算法求下一個節點j
                    r = ra.random()
                    for z in range(len(pjs)):
                        if r <= pjs[z]:
                            j = js[z]
                        else:
                            r -= pjs[z]
                    R[k][p] = j  # 把j加入k螞蟻的路徑
            C[k] = sum([net[R[k][i], R[k][i+1]] for i in range(n)])
        #更新T
        for i in range(n):
            for j in range(n):
                T[i, j] = (1-h)* T[i, j]
                if isturn0:
                    T[i, j] *= 1.0 / (turn-turn0)
                for k in range(m):
                    if j == R[k][list(R[k]).index(i)+1]:
                        deltaT = 1 / C[k]
                        if isturn0:
                            deltaT *= 1.0 / (turn-turn0)
                        T[i, j] += deltaT
        #更新全局最優值
        local_best = min(C) #局部最優值
        bestk = list(C).index(local_best) #局部最優值對應的螞蟻編号
        if local_best < global_best:
            global_best = local_best
            global_bestR = R[bestk].copy()
            turn0times = 0
            if isturn0:
                isturn1 = False

        else:
            turn0times += 1

        if turn0times == 5:
            turn0 = turn
            isturn0 = True

        turn += 1

        if turn1times == x and isturn1 and turn1num <= u:
            turn1num += 1
            sum_edge = 0
            num_edge = 0
            for i in range(n):
                for j in range(n):
                    if i == j or net[i, j] == float(\'inf\'):
                        continue
                    sum_edge += 1.0 / net[i, j]
                    num_edge += 1;
            for i in range(n):
                for j in range(n):
                    if i == j or net[i, j] == float(\'inf\'):
                        continue
                    T[i, j] = T[i, j] * num_edge * t0 * 1 / net[i, j] / sum_edge
            C = np.ones(m)    
            global_best = float(\'inf\') 
            global_bestR = None 
            turn0times = 0 
            turn1times = 0  
            turn0 = None 
            isturn0 = False
            isturn1 = True 
            turn = 0           

    # \'\'\'輸出\'\'\'
    # print(\'最短路徑為:\', global_best)
    # print(\'最短路徑長度:\', global_bestR)
    return global_best


def TanXin(n, net):
    R = np.ones(n+1, dtype=int)
    while True:
        for p in range(n+1):
            if p == 0:
                R[0] = ra.randint(0, n-1)
            elif p == n:
                R[n] = R[0]
            else:
                i = R[p-1]
                js = neighbor(n, net, i)
                js = list(set(js)-set(R[:p]))
                ljs = [net[i, j] for j in js]
                j = js[ljs.index(min(ljs))]
                R[p] = j
        C = sum([net[R[i], R[i+1]] for i in range(n)])
        if C != float(\'inf\'):
            break
    return C

def neighbor(n, net, i):
    return [j for j in range(n) if j != i if net[i][j] != float(\'inf\')]

if __name__ == \'__main__\':
    net = np.array([[float(\'inf\'), 3, 1, 2],
                    [3, float(\'inf\'), 5, 4],
                    [1, 5, float(\'inf\'), 2],
                    [2, 4, 2, float(\'inf\')]])
    TSP(net)
           

思路

TSP問題描述

TSP問題又稱旅行商問題、貨郎擔問題,是數學領域的著名問題,也是計算機中的NP難問題。TSP問題的具體描述為:給定一系列城市和每對城市之間的距離,求解通路每一座城市一次并回到起始城市的最短回路。

蟻群算法的描述

蟻群算法的思想來自于自然界螞蟻覓食,螞蟻在尋找食物源時,會在路徑上留下螞蟻獨有的路徑辨別——資訊素,一條路徑越短,該路徑的資訊素濃度就越高。螞蟻會感覺其他螞蟻在各條路徑上留下的資訊素,更傾向于選擇資訊素濃度高的路徑。随着時間的推移,越來越多的螞蟻選擇某一條路徑,該路徑的資訊素濃度就越高,越能吸引螞蟻,形成正回報機制。最終大部分螞蟻集中于最短的路徑上。

基線模型

對于TSP問題,設蟻群中螞蟻數量為\(m\),結點數為\(n\),結點i和結點j的距離為\(net_{ij}\), t時刻結點i和j之間連接配接的路徑的資訊素濃度為\(T_{ij}(t)\)。初始時刻,所有邊的資訊素濃度設定為一個固定值,螞蟻随機從不同的結點出發,按照一定機率選擇路徑。不妨設\(P_{ij}^k(t)\)為在t時刻處于結點i的螞蟻k選擇結點j作為下一個結點的機率。螞蟻選擇下一個結點的機率收到兩方面的影響,一是目前資訊素濃度,二是通路下一個結點的長度,是以定義:

\[P_{ij}^k(t)=\frac{{[T_{ij}(t)]^a}\cdot{[\frac{1}{net_{ij}}]^b}}{\sum_{j\in allow_k}({[T_{ij}(t)]^a}\cdot{[\frac{1}{net_{ij}}]^b})}

\]

其中\(allow_k\)為螞蟻k下一步可以到達的結點集合,随着時間的推移,\(allow_k\)中的元素會越來越少,最終變為空集;\(a\)和\(b\)為重要程度因子

随着時間的推移,路徑上資訊素濃度會不斷揮發,而螞蟻行走也會增加路徑上的資訊素濃度,是以定義資訊素的狀态轉移方程為:

\[\left\{

\begin{aligned}

& T_{ij}(t+1) = (1-h) * T_{ij}(t) + \sum_{k=1}^m(\bigtriangleup T_{ij}^k(t)) \\

& \bigtriangleup T_{ij}^k(t) = 1 / C^k(t)

\end{aligned}

\right.

\]

其中\(h\)為資訊素揮發率;\(\bigtriangleup T_{ij}^k(t)\)為t時刻第k隻螞蟻在邊(i, j)上增加的資訊素濃度;\(C^k(t)\)為在t時刻第k隻螞蟻經過路徑的總長度。

改進模型

初始資訊素濃度的改進:

在基線模型中,初始資訊素濃度賦予相同的指,我認為這并不是一個好的方式,原因如下:

(1)一開始的資訊素濃度不具有指導性,使得收斂速度下降

(2)可能使得螞蟻一開始向着錯誤的路徑前進,資訊素在錯誤的路徑上轉移,容易陷入局部最優

是以我們根據路徑大小給資訊素濃度賦予不同的初值:

\[T_{ij}(0) = Q \cdot numEdge \cdot \frac{1/net_{ij}}{\sum_{i,j}(1/net_{ij})}

\]

其中,Q為初始濃度值,根據螞蟻數量m除以貪心選擇的路徑長度得到;numEdge為網絡的邊數。該資訊素初始化方法使得初始資訊素濃度的平均值為Q,但是邊的長度會影響具體的取值——邊越短,初始資訊素濃度越大。

資訊素更新政策的改進:

在基線模型中,資訊素濃度的更新政策是不變的。我們知道,運作一段時間後,蟻群會集中于某條路徑,目前最優解往往就固定不變了,容易陷入局部最優。為了增加算法的全局搜尋能力,減小局部最優發生的機率,需要一種更合理的資訊更新政策。當疊代次數較小時,需要保證螞蟻在路徑上留下的資訊素濃度足夠高,以確定算法可以更快的收斂,而當算法收斂到一定程度時,如果多次疊代中算法所求得最有路徑長度一直未發生變化,那麼算法既有可能得到正确的結果,也有可能陷入局部最優,為了避免算法陷入局部最優的機率,需要适當增加資訊素揮發速率和減少螞蟻走過路徑留下的資訊素,避免資訊素過于集中,使得螞蟻能有更大機率去探索潛在的最優路徑,提高算法的全局搜尋能力。為了友善算法的描述,給出如下定義:

定義:如果存在某個時間節點t0,在一定固定的時長内,目前最優路徑未發生變化,則稱時間節點t0為穩定時刻點。

\[\left\{

\begin{aligned}

&T_{ij}(t+1) = (1-h) * T_{ij}(t) + \sum_{k=1}^m(\bigtriangleup T_{ij}^k(t)) & t < t0\\

&T_{ij}(t+1) = \frac{(1-h) * T_{ij}(t)}{t-t0} + \frac{\sum_{k=1}^m(\bigtriangleup T_{ij}^k(t))}{t-t0} & t >= t0

\end{aligned}

\right.

\]

資訊素矩陣的重置

為了進一步增加全家搜尋的能力,在這裡我們引入融斷機制。當進入穩定時刻點t0後的一段時間内,全局最優解仍然沒有發生變化,那麼我們會将資訊素矩陣重新初始化.

算法的流程圖

蟻群算法的優化和評估

算法評估

下面通過TSPLIB提供的三個資料集,來對比基線算法和改進算法的優劣。

資料集為:rand75.tsp eil76.tsp rat99.tsp

初始參數為: a=1, b=2, h=0.1 m=3

重複次數為:10

基線模型和改進模型的平均相對誤差

資料集 基線模型 改進模型
資料集rand75.tsp 129% 57%
資料集eil76.tsp 180% 79%
資料集rat99.tsp 165% 73%

平均誤差計算公式:\(error = \frac{predicted_value - measured_value}{measured_value}\)

改進模型較基線模型相對誤差減少量

資料集 平均相對誤差減少量
資料集rand75.tsp 72%
資料集eil76.tsp 101%
資料集rat99.tsp 92%

注意:資料的準确性不行,因為資料集大小不夠,重複次數也不夠,但是電腦不給力,時間不等人,隻能這樣了。

結論

可以看出,改進的模型相較與基線模型有一定的提升,但是依然存在一些問題:

(1)算法的時間消耗較大

(2)算法的結果相較于真實值還存在較大的相對誤差。

(3)算法的參數沒有缺少更多的實驗

(4)具體的改進函數缺少更多的實驗

參考

改進蟻群算法在TSP問題中的應用