蚁群算法及其python实现(针对TSP问题)

1.定义

蚁群算法(Ant Colony Optimization, ACO)是由Marco Dorigo于1992年在他的博士论文“Ant system: optimization by a colony of cooperating agents”中提出,其灵感来源于蚂蚁在寻找食物过程中发现路径的行为。

2.原理简述

  蚂蚁在寻找食物或者运动过程中,会在移动的行进的路径上留下一种称之为信息素(pheromone)的物质。该物质有两种比较重要的特性:

  • 会随着时间逐渐挥发;
  • 信息素对蚂蚁有吸引力。

而蚂蚁在选择路径的时候也会有一定的特性:

  • 蚂蚁往往倾向于选择信息素浓度更高的方向行进,但也会有蚂蚁特立独行;
  • 在没有信息素作为参考的地方,蚂蚁一般随机选择行进方向,并带有一定惯性。

因此受信息素机制的影响,并在正反馈的作用下,蚁群觅食往往会形成一条长长的队伍。

  如图1所示,在两点AB之间有两条路径。蚁群准备在AB之间来回搬运食物,由于刚开始路径1和路径2上都没有信息素,因此蚁群随机选择路径,两条路径各占50%。然后由于路径1比路径2短,因此单位时间内,经过路径1的蚂蚁更多(假如R_2 = 2R_1,那么路径1上的蚂蚁一个来回后,路径2上的蚂蚁才刚到B),因此路径1上的信息素也相对更浓。在这种正反馈机制的作用下,最终大部分蚂蚁都会选择走更短的路径1。

图1. 信息素作用机制

3.蚁群算法简析

原理

受蚁群觅食等行为的启发,有学者将其引入到算法中用于解决类似问题,并提出了蚁群算法。下面,我们针对一个具体的有N个城市的TSP问题,构建一个基础的ACS蚁群算法来求解。

  1. 首先设定一个启发值\eta并给他初始化一个常数,这里我们另 \eta= \frac{1}{L_{ij}},其中L_{ij}为城市i与j之间的距离。然后再设置一个参数\tau_{ij},该参数表示城市i和j之间的路径信息素浓度,算法一开始将其初始化为一个较小常数。

  2. 对于N个城市的TSP问题,蚁群算法的基本框架就是将M(M>=N,一般可设为1.5N)只蚂蚁随机分配到N个城市上,然后总共将算法迭代C次,求得最终所需的最优路径。对于第k只蚂蚁,它下一个选择去往哪个城市由如下公式决定:
    p^{k}_{ij} = \begin{cases} \frac{\tau_{ij}^\alpha * \eta_{ij}^\beta}{\sum_{z = J_k(i)}\tau_{iz}^\alpha * \eta_{iz}^\beta},\quad if \ \ j\in J_k(i)\\\\0, \quad \quad otherwise\end{cases}
    这里,p^{k}_{ij}表示第k只蚂蚁A_k从城市i运动到到城市j的概率,J_k(i)表示第k只蚂蚁未经过的城市集合(其中i表示蚂蚁A_k当前所处的城市)。\alpha 和\beta分别代表信息浓度\tau与启发因子\eta的权重,这里我们设置为\alpha=1, \beta=5

  3. 信息素的更新。蚁群算法的核心之一在于信息素的更新策略,有部分改进的蚁群算法就是针对其信息素更新策略做了优化。这里,当M只蚂蚁完成一次路径遍历即算法迭代一次后,我们用如下公式进行信息素更新:
    \tau_{ij} = (1-\rho)*\tau_{ij} + \Delta\tau_{ij} , \quad \forall (i,j) \in E该公式表示信息素的挥发机制,其中E为N个城市的链接集合,\rho表示信息素的挥发率。其中\Delta\tau_{ij}的定义为:\Delta\tau_{ij} = \sum_{k=1}^{M}\Delta\tau_{ij} ^k即每只蚂蚁在城市i和j之间残留的信息素之和。

ACS - Python实现
  1. 首先实现一个函数用于生成TSP问题所需的城市集合。
import numpy as np
import matplotlib.pyplot as plt

def creat_city(num, scale):
    """
    input:
        num: 城市数量
        scale: 城市坐标范围x,y in (0, scale)
    return:
        V:城市的坐标集合
        E:城市的邻接矩阵
    """
    x = np.random.choice(scale, num)
    y = np.random.choice(scale, num)
    
    V = np.stack((x,y), axis=1)
    inner = -2 *  V.dot(V.T)
    xx = np.sum(V**2, axis=1, keepdims=True)
    E = xx + inner + xx.T
    E = E**0.5
    index = [i for i in range(num)]
    #为了防止蚂蚁出现自旋,邻接矩阵上的对角线取值尽量大一点。
    E[index,index] = 9999999
    return V,E

我们使用下面的代码在400*400的范围内生成20个城市,其生成的的效果如图2所示:

V, E = creat_city(20, 400)
plt.scatter(V[:,0], V[:,1], alpha=0.6, c = "r")  # 绘制散点图,透明度为0.6(这样颜色浅一点,比较好看)
plt.show()
图2. 城市分布图
  1. 算法所需的其他必备函数
  • 依概率采样函数
import heapq
import random

def a_res(samples, m):
    """
    :samples: [(item, weight), ...]
    :k: number of selected items
    :returns: [(item, weight), ...]
    """

    heap = [] # [(new_weight, item), ...]
    for sample in samples:
        wi = sample[1]
        if wi==0:
            continue
        ui = random.uniform(0, 1)
        ki = ui ** (1/wi)

        if len(heap) < m:
            heapq.heappush(heap, (ki, sample))
        elif ki > heap[0][0]:
            heapq.heappush(heap, (ki, sample))

            if len(heap) > m:
                heapq.heappop(heap)

    return [item[1] for item in heap]
  • 由信息素浓度获得蚂蚁去往下一个城市的概率。
def possibility(eta, gamma, other_city, cur_city):
    """
    返回候选城市集合中,从start到各候选城市的概率,只返回有路径的
    """   
    alpha = 1
    beta = 5
    start_city = cur_city[-1]

    t_i = gamma[start_city]  
    n_i = eta[start_city]
    
    temp = (t_i**alpha * n_i**beta)
    temp[cur_city] = 0
    add = temp.sum()
    p_ij = temp/add
    
    return p_ij
  • 其他函数
def rotate(l, n):
    '''
    旋转列表。
    '''
    return l[n:] + l[:n]

def get_path_dis(root, E):
    """
    获取该路径距离。
    """
    dis = E[root[:-1], root[1:]].sum()
    return dis + E[root[0],root[-1]]
  1. 蚁群算法实现
def ACS(V, E, M, num):
    """
    Ant system
    V : 点集
    E: 邻接矩阵,点之间的连接性,
    M: 蚂蚁数量
    num:迭代次数
    """
    #相关参数
    global_best_path=None   #当前最优路径
    global_best_dis = 99999999
    cur_city = None
    other_city = [i for i in range(len(V))]
    lo = 0.5   #信息素挥发率
    
    
    #信息素启发值
    eta = 1/E
    eta[np.isinf(eta)] = 0
    
    #信息素浓度
    E_mean = E[E>0].mean()
    gamma = np.full(E.shape, 1/len(V))
    
    V_index = [i for i in range(len(V))]

    for i in range(num):
        epoch_gamma = np.zeros_like(gamma) #保存每一轮的各路径信息素累积量
        local_best_path=None   #每一次迭代当前最优路径
        local_best_dis = 99999999
        for j in range(M):
            cur_city = [j%len(V)]
            other_city = [i for i in range(len(V))]
            other_city.remove(cur_city[-1])
            while(other_city):
                p_ij = possibility(eta, gamma, other_city, cur_city)
 
                next_city = int(a_res(np.stack((V_index,p_ij),axis=1), 1)[0][0])
                
                epoch_gamma[cur_city[-1],next_city] += gamma[cur_city[-1],next_city]
                cur_city.append(next_city)
                other_city.remove(next_city)
            epoch_dis = get_path_dis(cur_city, E)
            if epoch_dis < local_best_dis:
                local_best_dis = epoch_dis
                local_best_path = cur_city

        if local_best_dis < global_best_dis:
            global_best_dis = local_best_dis
            global_best_path = local_best_path
            
        gamma = (1 - lo) * gamma + epoch_gamma
    
    print("The shortest distance is {}m and the best path is: ".format(global_best_dis), end="")
    best_path = rotate(global_best_path, global_best_path.index(0))
    for index in best_path:
        print(" city_" + str(index) + " ->", end="")
    print("city_0\n")
    
    return best_path
  1. 算法计算结果
root = ACS(V, E, 20, 50)
path = V[root]
path = np.append(path, [path[0]], axis=0)
plt.plot(path[:,0], path[:,1], marker="o", mfc="r")
plt.show()
图3. ACS计算结果
MMAS(最大-最小蚁群系统)
  1. MMAS算法简介
    我们从图3中可以看到,ACS算法对于20个城市的TSP问题已经能够得到相对较优的结果了。但是ACS仍然有一些缺点,比如收敛速度比较慢,获取容易陷入局部最小值等等。针对这些缺陷有学者又提出了最大-最小蚁群系统。该改进算法与基本的蚁群算法有以下几点不同
  • \tau的值被限制在一定范围内[\tau_{min}, \tau_{max}],这也正是该算法名字的由来。通常该最大 最小值有我们自己设定。
  • 算法开始将城市之间的信息素浓度都初始化为\tau_{max}
  • 每次迭代信息素更新时只针对最优路径进行更新,而不考虑其他路径。其中最优路径可为局部最优路径(本次迭代最优)或全局最优路径(所有迭代最优)。具体更新公式如下:
    \tau_{ij} = (1 - \rho)*\tau_{ij} , \quad \forall (i,j) \in E \tau_{ij} = \tau_{ij} + \Delta\tau_{ij}^{best},\quad \forall (i,j) \in Route^{best}其中,Route^{best}指本次迭代最优路径,\Delta\tau_{ij}^{best}为本次迭代最优路径中从城市i到城市j的信息素增量。增量的定义为\Delta\tau_{ij}^{best} = \frac{Q}{L^{best}},其中Q为一个自己设置的常数,L^{best}是本次迭代最优路径的长度。
  1. MMAS算法python实现
def MMAS(V, E, M, num, islocal=True):
    """
    最大最小蚁群算法
    V : 点集
    E: 邻接矩阵,点之间的连接性,
    M: 蚂蚁数量
    num:迭代次数
    """
    #相关参数
    global_best_path=None   #当前最优路径
    global_best_dis = 99999999
    cur_city = None
    other_city = [i for i in range(len(V))]
    lo = 0.8   #信息素挥发率
    e = num #精英路径权重
    
    tao_min = 0.1 / num
    tao_max = 1

    #信息素启发值
    eta = 1/E
    eta[np.isinf(eta)] = 0
    
    #信息素浓度
    E_mean = E[E>0].mean()
    gamma = np.full(E.shape,tao_max) 
    
    V_index = [i for i in range(len(V))]

    for i in range(num):
        epoch_gamma = np.zeros_like(gamma) #保存每一轮的各路径信息素累积量
        local_best_path=None   #每一次迭代当前最优路径
        local_best_dis = 99999999
        for j in range(M):
            cur_city = [j%len(V)]
            other_city = [i for i in range(len(V))]
            other_city.remove(cur_city[-1])
            while(other_city):
                p_ij = possibility(eta, gamma, other_city, cur_city)
                next_city = int(a_res(np.stack((V_index,p_ij),axis=1), 1)[0][0])
                if next_city not in other_city:
                    next_city = int(a_res(np.stack((V_index,p_ij),axis=1), 1)[0][0])
                
                epoch_gamma[cur_city[-1],next_city] += gamma[cur_city[-1],next_city]
                cur_city.append(next_city)
                other_city.remove(next_city)
            epoch_dis = get_path_dis(cur_city, E)
            if epoch_dis < local_best_dis:
                local_best_dis = epoch_dis
                local_best_path = cur_city

        if local_best_dis < global_best_dis:
            global_best_dis = local_best_dis
            global_best_path = local_best_path
         #信息素更新   
        gamma = (1 - lo) * gamma
        if islocal:
            for i,j in np.stack((local_best_path[1:] + local_best_path[:1], local_best_path), axis=1):
                gamma[i,j] += e / local_best_dis
        else:
            for i,j in np.stack((global_best_path[1:] + global_best_path[:1], global_best_path), axis=1):
                gamma[i,j] += e / global_best_dis
        gamma[gamma>tao_max] = tao_max
        gamma[gamma<tao_min] = tao_min
    
    print("The shortest distance is {}m and the best path is: ".format(global_best_dis), end="")
    best_path = rotate(global_best_path, global_best_path.index(0))
    for index in best_path:
        print(" city_" + str(index) + " ->", end="")
    print("city_0.\n")
    
    return best_path
  1. MMAS算法运行结果
    从图4可以看到,MMAS对于TSP问题能得到很好的解,并且收敛速度也比较快。因此一般对于TSP类问题使用MMAS来求解是比较合适的。


    图4. MMAS结果
蚁群算法的其他优化版本

这里我们限于篇幅仅仅简单介绍了ACS和MMAS,其实对于蚁群算法还有精英系统(EAS)、排序蚁群(ASrank)、连续正交蚁群(COAC)等等。此外,我们这里仅仅是用TSP算法来举例,实际上,蚁群算法可以被应用到许多问题上,比如调度问题、设置问题、分配问题、车辆路径问题等等。读者朋友如果有兴趣可以去扩展这些知识,相信会很有收获的!

4.参考资料

https://zh.wikipedia.org/wiki/%E8%9A%81%E7%BE%A4%E7%AE%97%E6%B3%95
https://www.voidking.com/dev-aco/
https://zhuanlan.zhihu.com/p/45985636
https://blog.csdn.net/zuochao_2013/article/details/71872950
https://blog.csdn.net/acsunqi/article/details/76060669
https://cloud.tencent.com/developer/article/1369213

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容