基本原理

蚂蚁觅食,通常是一种群体行为,当然一只小蚂蚁也是可以找到食物的, 辛苦点、慢点而已;

蚁群觅食,每只蚂蚁在经过的路上会留下一种化学物质,称为 信息素信息素 会随着时间 逐渐 挥发,也就是说找到食物越慢,这条路上的信息素 残留 越少;

其他蚂蚁可以 感受到 信息素的存在,并且能测量信息素的浓度;

通常 蚂蚁 会 沿着 信息素 最浓的方向 行走,毕竟是前人经验嘛,这样这条路上的信息素 越来越浓;

但也会 突发奇想,试着走走 信息素没那么浓,甚至 没有信息素的路径,可能更快找到了食物,那么新路径 信息素浓度很大,也可能找不到食物,信息素挥发完毕;

 

如此反复,经过大量蚂蚁 重复多次的探索,最终摸索出一条 最短的 路径;当然 不一定是 全局最短,这点就不解释了;

整个过程如下图

蚁群算法【1】

整个过程最重要的两点就是 接下来选择哪条路 和 记录并更新每条路信息素的浓度

 

状态转移概率

决定了 接下来选择哪条路;

在城市 i 时 选择 城市 j 的概率,有了所有 可选城市 的概率后,可采用 轮盘赌 等方式 选出 下一个城市;

也可采用其他方式选择下一个城市,比如 模拟退火算法代替 轮盘赌,就实现了 混合优化算法

蚁群算法【1】

 ???????????????? 状态转移概率,代表 第????只蚂蚁在城市????选择城市????的概率;

allowed 代表 可选择的城市;

???????????? 城市???? ???? 之间存留的信息素;

????信息素启发因子,控制着信息素???????????? 对路径选择的影响程度,值越大,越依赖信息素,探索性降低,值越小,蚁群搜索的范围减少,容易陷入局部最优;

η???????? 城市 ???? ???? 之间的能见度,反映了由城市 ????到城市 ???? 的启发程度,一般取 ???????????? 的倒数;

???????????? 城市 ???? ???? 之间的距离;

????期望值启发因子,控制着η???????? 能见度的影响程度,其大小反应了在道路搜索中 先验性、确定性等因素的强弱;

????信息素挥发系数,影响信息素挥发的快慢;1-????信息素残留系数;(1-????)????????????  在该回合前城市 ???? ???? 间残留的信息素, ∆????????????k 该回合新增的信息素;

???? 代表第????只蚂蚁;

 

信息素更新策略

信息素更新公式,符号意义同上

蚁群算法【1】

m 表示 蚂蚁数量

 

根据 Δ????????????k (t, t+1) 的更新方式不同,可将蚁群算法分为3类:蚁密算法、蚁量算法、蚁周算法

1. 蚁密算法(Ant-Density模型)

每只蚂蚁经过城市 i j 时,对边 eij 所贡献的信息素为常量,每个单位长度为 Q

 蚁群算法【1】

2. 蚁量算法(Ant-Quantity模型)

每只蚂蚁在经过城市 i j 时,对边 eij 所贡献的信息素为变量,Q/dij,dij 表示 城市 i j 间的距离

 蚁群算法【1】

3. 蚁周算法(Ant-Cycle模型)

上述两种模型,对两城市之间 eij 边上信息素贡献的增量在蚂蚁经过边的同时完成,而蚁周模型对边信息素的增量是在本次循环结束时才进行更新调整

一只蚂蚁在经过城市i j 时,对边上信息素贡献的增量为每单位长度 Q/Lk,Lk为蚂蚁在本次循环走出路径的长度      【经测试,这里的 每单位长度 可以忽略,直接每条边 增加 Q/L 就行了】

 蚁群算法【1】

 

一只小蚂蚁的寻路经历

简单起见,我们只用一只小蚂蚁觅食,原理懂了,代码只是浮云,偷个懒;

代码其实 是经典的旅行商问题,从 起点出发,经过所有城市后,回到起点,找最短路径

import cv2 as cv
import numpy as np
import matplotlib.pylab as plt


ALPHA = 1
BETA = 1
RHO = 0.5
Q = 100.

city_num = 50
ant_num = 50

distance_x = [
    178, 272, 176, 171, 650, 499, 267, 703, 408, 437, 491, 74, 532,
    416, 626, 42, 271, 359, 163, 508, 229, 576, 147, 560, 35, 714,
    757, 517, 64, 314, 675, 690, 391, 628, 87, 240, 705, 699, 258,
    428, 614, 36, 360, 482, 666, 597, 209, 201, 492, 294]
distance_y = [
    170, 395, 198, 151, 242, 556, 57, 401, 305, 421, 267, 105, 525,
    381, 244, 330, 395, 169, 141, 380, 153, 442, 528, 329, 232, 48,
    498, 265, 343, 120, 165, 50, 433, 63, 491, 275, 348, 222, 288,
    490, 213, 524, 244, 114, 104, 552, 70, 425, 227, 331]

position = list(zip(distance_x, distance_y))

distance = np.ones(shape=(city_num, city_num))
pheromone = np.ones(shape=(city_num, city_num))
for i in range(city_num):
    for j in range(city_num):
        distance[i, j] = round(np.sqrt(pow(position[i][0] - position[j][0], 2) +
                                       pow(position[i][1] - position[j][1], 2)), 1)
print(distance)


class ANT(object):
    # 蚂蚁
    def __init__(self, id, start, train=False, method='Cycle'):
        self.id = id
        self.start = start                  # 起点
        self.mile = 0
        self.city_index = start
        self.passed = [self.city_index]     # 经过的站点  index
        self.allowed = [True] * city_num    # 可行站点
        self.allowed[self.city_index] = False
        self.train = train
        self.method = method
        if method not in ['Density', 'Quantity', 'Cycle']:
            raise 'method is not supported'

    def select_next_city(self):
        # pijk
        p_allowed = []
        city_allowed = []
        for ind, city in enumerate(self.allowed):
            if city:    # 可行走
                p = pow(pheromone[self.city_index, ind], ALPHA) * pow(1 / distance[self.city_index, ind], BETA)
                p_allowed.append(p)
                city_allowed.append(ind)

        # 无路可走
        if not p_allowed: return

        if self.train:
            # 轮盘赌
            index = np.random.choice(city_allowed, size=1, replace=True, p=np.array(p_allowed) / sum(p_allowed))[0]
            return index
        else:
            return city_allowed[np.argmax(p_allowed)]   # 最优路径

    def move(self, city_index):
        '''
        city_index: 新的站点, None 表示回到起点
        '''
        if city_index is not None:  # city_index 可能等于0
            self.allowed[city_index] = False  # 新的城市不再可行
        else:
            city_index = self.start
        self.passed.append(city_index)     # 新的城市加入已经过序列

        # 单步更新信息素
        if self.train:
            if self.method == 'Density':
                pheromone[self.city_index, city_index] = pheromone[self.city_index, city_index] * RHO + Q
            if self.method == 'Quantity':
                pheromone[self.city_index, city_index] = pheromone[self.city_index, city_index] * RHO + \
                                                     Q / distance[self.city_index, city_index]

        self.mile += distance[self.city_index, city_index]  # 更新里程
        self.city_index = city_index       # 更新节点

    def path(self):
        global pheromone
        while 1:
            index = self.select_next_city()
            self.move(index)
            if index is None:   # 全部节点走完
                break

    def update_pheromone(self):
        # 回合更新信息素
        global pheromone
        for ind, val in enumerate(self.passed[ :-1]):
            pheromone[val, self.passed[ind + 1]] = pheromone[val, self.passed[ind + 1]] * RHO \
                                                   + (Q / self.mile) # * distance[val, self.passed[ind + 1]]
            # 注意下面这句,表示正反权重相同,可根据实际情况调整
            pheromone[self.passed[ind + 1], val] = pheromone[val, self.passed[ind + 1]]

def show(path):
    img = np.zeros(shape=(np.max(distance_y) + 50, np.max(distance_x) + 50, 3)).astype(np.uint8)
    for i in position:
        cv.circle(img, i, 2, [0, 0, 150], 8)    # 描点
        cv.putText(img, '%s,%s' % i, i, cv.FONT_ITALIC, 0.3, [255, 100, 100])
    cv.circle(img, position[path[0]], 4, [0, 255, 0], 8)    # 起点

    for ind, val in enumerate(path[ :-1]):
        cv.line(img, position[val], position[path[ind + 1]], [255, 255, 255], 2)    # 划线
        cv.imshow('temp', img)
        cv.waitKey(1)


if __name__ == '__main__':
    ### test ANT

    # test select_next_city
    ant = ANT(2, 10)
    index = ant.select_next_city()
    print(index)

    start = 20  # 起点
    miles = []
    # train
    for _ in range(2000):
        ant = ANT(3, start, train=True, method='Cycle')   # Density Quantity Cycle
        ant.path()
        if ant.method == 'Cycle':
            ant.update_pheromone()
        # print(pheromone)

        # test
        ant = ANT(3, start, train=False)
        ant.path()
        miles.append(ant.mile)
    # 收敛性
    plt.plot(miles, '+-')
    plt.show()
    # 最优路径
    show(ant.passed)
    cv.waitKey(0)

收敛速度

蚁群算法【1】蚁群算法【1】蚁群算法【1】

最优路径

蚁群算法【1】

 

特点

采用 一种 正反馈 机制,使得算法收敛

1. 每个蚂蚁可以实时改变周围环境,蚁密模型 和 蚁量模型 都是 实时改变 信息素的,单步更新,蚁周算法是 回合更新

2. 整个搜索过程每只蚂蚁完全独立,可采用分布式计算方式,提高搜索效率 

 

3. 蚁群算法 容易陷入 局部最优,早熟现象

 

总结

[1];

设置了早熟停止迭代,因此可能出现城市数与耗时不成正比的情况;

[3](P113)比Ant-Quantity模型优,因为Ant-Cycle模型信息素的计算是不受路径中的某段路长短的影响,但现实中很少有这种奇怪性质的“病态问题”;

算法实现是比较粗糙的,如最后评价函数应该是:模型寻优次数+寻优迭代步数+运行时间,因此关于细节方面,感兴趣的同学可详读下文提供的参考文献;

 

优化方向

[5]等等,数不胜数; 
其中贪婪式交叉、旋转交叉、混合蛙跳、DPX本人均实现过,开始的性能和寻优能力不及蚁群算法,而遗传算法组合能力是非常强的,比较容易跳出局部最优值,可优化空间大,长期迭代中,各方面指标是优于蚁群算法,因此实际工业应用场景之中主要是以启发式遗传算法为主;
启发式遗传算法不但需要有强算法能力,还需非常熟悉工业场景,直接进场的话,实现效果可能不佳,因此,长期工业应用首选启发式遗传算法,短期快速上线首选蚁群算法(ps,在路径规划专项任务下,还是蚁群算法简单好用,易于实现);

爬山算法+蚁群算法,模拟退火和爬山算法跳出局部最优值的好帮手,而且计算快,算法组合后,性能受影响小,笔者用类似的方法解决了背包问题+商旅问题;

 

融合算法 也称 双层启发式算法,具体用法见我的其他博客

 

 

 

参考资料:

https://www.jianshu.com/p/6d16573ef675  蚁群算法

https://www.jianshu.com/p/9ef24ad65191  蚁群算法及其应用实例

https://www.jianshu.com/p/e6a20de60797  数学建模学习笔记(一) 蚁群算法(MATLAB)

https://blog.csdn.net/qq_33829154/article/details/85258615  蚁群算法

https://zhuanlan.zhihu.com/p/351466641  蚁群算法(ant colony algorithm)python实现专项解决商旅问题(TSP)    理论较强

https://www.cnblogs.com/bokeyuancj/p/11798635.html    精英蚂蚁系统

https://www.jianshu.com/p/a8acd65aba79  python3使用蚁群+邻域搜索算法解决带有起点和终点的TSP问题    代码

相关文章:

  • 2021-09-12
  • 2021-12-07
  • 2021-11-16
猜你喜欢
  • 2021-04-12
相关资源
相似解决方案