若干年前读研的时候,学院有一个教授,专门做群蚁算法的,很厉害,偶尔了解了一点点。感觉也是生物智能的一个体现,和遗传算法、神经网络有异曲同工之妙。只不过当时没有实际需求学习,所以没去研究。最近有一个这样的任务,所以就好好把基础研究了一下,驱动式学习,目标明确,所以还是比较快去接受和理解,然后写代码实现就好了。今天就带领大家走近TSP问题以及群蚁算法。
机器学习目录: 【目录】数据挖掘与机器学习相关算法文章总目录
本文原文地址: 群蚁算法理论与实践全攻略——旅行商等路径优化问题的新方法
插曲:这篇文章研究和写作前后有1个月了,今天是个特殊的日子,在发表这篇文章的时候,我正背着行囊去新的单位报道,即将走上新的工作岗位。除了感谢,还是感谢!以后的时间写文章的机会会少很多,但是要写,必出精品!谢谢大家的支持,在上海有找工作或者想换工作的朋友,也可以留言或者Q我,内推速度快,效率高,多看看,多了解未尝不可。
旅行商问题(Traveling Saleman Problem,TSP)是车辆路径调度问题(VRP)的特例,由于数学家已证明TSP问题是NP难题,因此,VRP也属于NP难题。旅行商问题(TSP)又译为旅行推销员问题、货郎担问题,简称为TSP问题,是最基本的路线问题,该问题是在寻求单一旅行者由起点出发,通过所有给定的需求点之后,最后再回到原点的最小路径成本。—— 旅行商问题百科
很明显,当节点数很少时,大多数人都会想到,问题很简单,直接穷举就OK了,但实际问题中,节点数往往很大,变得不可能。例如:对于一个仅有16个城市的旅行商问题,如果用穷举法来求问题的最优解,需比较的可行解有: 15!/2=653,837,184,000个 。在1993年,使用当时的工作站用穷举法求解此问题需时92小时。即使现在计算机速度快,但是面对复杂的问题,仍然不够。这就是所谓的“组合爆炸”,指数级的增长,所以科学家逐步寻找近似算法或者启发式算法,目的是在合理的时间范围内找到可接受的最优解。
TSP问题解决算法的发展可以分为3个部分:
1).经典精确算法:穷举法、线性规划算法、动态规划算法、分支定界算法等运筹学中的传统算法,这些算法复杂度一般都很大,只适用于求解小规模问题。
2).近似算法:当问题规模较大时,其所需的时间成级数增长,这是我们无法接受的,算法求解问题的规模受到了很大的限制,一个很自然的想法就是牺牲精确解法中的最优性,去寻找一个好的时间复杂度我们可以容忍的,同时解的质量我们可以接受的算法.基于这一思想所设计出的算法统称为近似算法。如插入算法,最邻近算法等。
3).智能算法:随着科学技术和生产的不断发展,许多实际问题不可能在合理的时间范围内找到全局最优解,这就促使了近代最优化问题求解方法的产生。随着各种不同搜索机制的启发式算法相继出现,如禁忌搜索、遗传算法、模拟退火算法、人工神经网络、进化策略、进化编程、粒子群优化算法、蚁群优化算法和免疫计算等,掀起了研究启发式算法的高潮。
具体每一种算法不再详细描述,大家可以针对性的寻找相应资料进行了解。
TSP问题在实际的生产生活中,更加实际环境不同,有很多衍生的经典问题。车辆路径调度(VRP)扩展问题是经典VRP加入各种约束条件后而形成的。例如需求约束形成的需求随机的车辆路径问题(SVRP);加入时间约束得到的带时间窗的车辆路径题(VRPTW);加入距离约束的距离约束车辆路径问题(DVRP);根据其它条件的不同,还有多配送中心车辆路径问题(MDVRP)、可切分的车辆路径问题(SDVRP);先配送再收集车辆路径问题(VRPB)、配送收集车辆路径问题(VRPPD);信息不完全的模糊车辆路径问题(FVRP)[3]。
对于VRP问题,求解算法大致可分为精确算法和人工智能算法两大类。精确性算法基于严格的数学手段,在可以求解的情况下,解的质量较好。但是由于算法严格,运算量大,特别是大规模的问题几乎无法求解。所以其应用只能是小规模的确定性问题,面对中小规模问题,人工智能算法在精度上不占优势。但规模变大时,人工智能方法基本能在可接受时间里,找到可接受的满意解,这是精确算法难以做到的。由于的实际问题,各种约束错综复杂,人工智能算法显示出了巨大的优越性,也正因为如此,实际应用中,人工智能算法要更广泛。求解车辆路径调度问题的精确算法有动态规划法、分枝定界法等。并开始寻求所得结果可接受的启发式算法,以处理大规模实际问题,一些其他学科的新一代优化算法相继出现,如禁忌搜索算法,遗传算法,人工神经网络算法,以及现在研究较多的蚁群算法等。
蚁群算法是受到对真实蚂蚁群觅食行为研究的启发而提出。生物学研究表明:一群相互协作的蚂蚁能够找到食物和巢穴之间的最短路径,而单只蚂蚁则不能。生物学家经过大量细致观察研究发现,蚂蚁个体之间的行为是相互作用相互影响的。蚂蚁在运动过程中,能够在它所经过的路径上留下一种称之为信息素的物质,而此物质恰恰是蚂蚁个体之间信息传递交流的载体。蚂蚁在运动时能够感知这种物质,并且习惯于追踪此物质爬行,当然爬行过程中还会释放信息素。一条路上的信息素踪迹越浓,其它蚂蚁将以越高的概率跟随爬行此路径,从而该路径上的信息素踪迹会被加强,因此,由大量蚂蚁组成的蚁群的集体行为便表现出一种信息正反馈现象。某一路径上走过的蚂蚁越多,则后来者选择该路径的可能性就越大。蚂蚁个体之间就是通过这种间接的通信机制实现协同搜索最短路径的目标的。我们举例简单说明蚂蚁觅食行为:
如上图a,b,c的示意图:
a图是原始状态,蚂蚁起始点为A,要到达E,中途有障碍物,要绕过才能到达。BC和BH是绕过障碍物的2条路径(假设只有2条)。各个路径的距离d已经标定。
b图是t=0时刻蚂蚁状态,各个边上有相等的信息素浓度,假设为15;
c图是t=1时刻蚂蚁经过后的状态,各个边的信息素浓度,有变化;因为大量蚂蚁的选择概率会不一样,而选择概率是和路径长度相关的。所以越短路径的浓度会越来越大,经过此短路径达到目的地的蚂蚁也会比其他路径多。这样大量的蚂蚁实践之后就找到了最短路径。所以这个过程本质可以概括为以下几点:
1.路径概率选择机制信息素踪迹越浓的路径,被选中的概率越大
2.信息素更新机制路径越短,路径上的信息素踪迹增长得越快
3.协同工作机制蚂蚁个体通过信息素进行信息交流。
从蚂蚁觅食的原理可见,单个个体的行为非常简单蚂蚁只知道跟踪信息素爬行并释放信息素,但组合后的群体智能又非常高蚂蚁群能在复杂的地理分布的清况下,轻松找到蚁穴与食物源之间的最短路径。这种特点恰恰与元启发算法的特点相一致,蚁群优化算法正是受到这种生态学现象的启发后加以模仿并改进而来,觅食的蚂蚁由人工蚁替代,蚂蚁释放的信息素变成了人工信息素,蚂蚁爬行和信息素的蒸发不再是连续不断的,而是在离散的时空中进行。
上述例子如果不好理解,我在这里贴几张PPT,个人感觉非常不错,也是我找了很多资料觉得最好理解的【来源是大连理工大学谷俊峰】,点击这里提供下载:蚁群算法基本知识.rar。
从深层意义上来讲,蚁群算法作为优化的方法之一,属于人工群集智能领域。人工群集智能,大都受自然群集智能如昆虫群和动物群等的启发而来。除了具有独特的强有力的合作搜索能力外,还可以利用一系列的计算代理对问题进行分布式处理,从而大大提高搜索效率。
我们还是采用大连理工大学谷俊峰的PPT来说明问题,重要公式进行截图计算和解释,对PPT难以理解的地方进行单独解释:
首先看看基本TSP问题的基本数学模型:
问题其实很简单,目标函数就是各个走过路径的总长度,注意的就是距离矩阵根据实际的问题不一样,长度是不一样的。
在说明群蚁算法流程之前,我们对算法原理和几个注意点进行描述:
1.TSP问题的人工蚁群算法中,假设m只蚂蚁在图的相邻节点间移动,从而协作异步地得到问题的解。每只蚂蚁的一步转移概率由图中的每条边上的两类参数决定:1. 信息素值也称信息素痕迹。2.可见度,即先验值。
2.信息素的更新方式有2种,一是挥发,也就是所有路径上的信息素以一定的比率进行减少,模拟自然蚁群的信息素随时间挥发的过程;二是增强,给评价值“好”(有蚂蚁走过)的边增加信息素。
3.蚂蚁向下一个目标的运动是通过一个随机原则来实现的,也就是运用当前所在节点存储的信息,计算出下一步可达节点的概率,并按此概率实现一步移动,逐此往复,越来越接近最优解。
4.蚂蚁在寻找过程中,或者找到一个解后,会评估该解或解的一部分的优化程度,并把评价信息保存在相关连接的信息素中。
更加我们前面的原理和上述说明,群蚁算法的2个核心步骤是 路径构建 和 信息素更新 。我们将重点对这2个步骤进行说明。
每个蚂蚁都随机选择一个城市作为其出发城市,并维护一个路径记忆向量,用来存放该蚂蚁依次经过的城市。蚂蚁在构建路径的每一步中,按照一个随机比例规则选 择下一个要到达的城市。随机概率是按照下列公式来进行计算的:
上述公式就是计算 当前点 到 每一个可能的下一个节点 的概率。分子是 信息素强度 和 能见度 的幂乘积,而分母则是所有 分子的和值。这个刚开始是很不容易理解的,我们在最后实例计算的时候,可以看得很清楚,再反过来理解公式。注意每次选择好节点后,就要从可用节点中移除选择的节点。
信息素更新是群蚁算法的核心。也是整个算法的核心所在。算法在初始期间有一个固定的浓度值,在每一次迭代完成之后,所有出去的蚂蚁回来后,会对所走过的路线进行计算,然后更新相应的边的信息素浓度。很明显,这个数值肯定是和蚂蚁所走的长度有关系的,经过一次次的迭代, 近距离的线路的浓度会很高,从而得到近似最优解。那我们看看信息素更新的过程。
初始化信息素浓度 C(0) ,如果太小,算法容易早熟,蚂蚁会很快集中到一条局部最优路径上来,因为可以想想,太小C值,使得和每次挥发和增强的值都差不多,那么 随机情况下,一些小概率的事件发生就会增加非最优路径的信息素浓度;如果C太大,信息素对搜索方向的指导性作用减低,影响算法性能。一般情况下,我们可以使用贪婪算法获取一个路径值 Cnn ,然后根据蚂蚁个数来计算 C(0) = m/Cnn ,m为蚂蚁个数
每一轮过后,问题空间中的所有路径上的信息素都会发生蒸发,然后,所有的蚂蚁根据自己构建的路径长度在它们本轮经过的边上释放信息素,公式如下:
信息素更新的作用:
1.信息素挥发(evaporation)信息素痕迹的挥发过程是每个连接上的 信息素痕迹的浓度自动逐渐减弱的过程,这个挥发过程主要用于避 免算法过快地向局部最优区域集中,有助于搜索区域的扩展。
2.信息素增强(reinforcement)增强过程是蚁群优化算法中可选的部 分,称为离线更新方式(还有在线更新方式)。这种方式可以实现 由单个蚂蚁无法实现的集中行动。基本蚁群算法的离线更新方式是 在蚁群中的m只蚂蚁全部完成n城市的访问后,统一对残留信息进行 更新处理。
迭代停止的条件可以选择合适的迭代次数后停止,输出最优路径,也可以看是否满足指定最优条件,找到满足的解后停止。最重要的是,我刚开始理解这个算法的时候,以为每一只蚂蚁走一条边就是一次迭代,其实是错的。 这里算法每一次迭代的意义是:每次迭代的m只蚂蚁都完成了自己的路径过程,回到原点后的整个过程。
使用PPT中的一个案例,非常直观,对几个符号错误进行了修改,主要是计算概率的乘号,结果没有错误:
过程总体还是比较简单的,注意理解公式,然后把公式和实例结合起来看,最好是拿笔自己手动画一画,容易理解。下面我们来看看如何编程实现TSP问题的群蚁算法代码。
百度搜索相关群蚁算法的代码,基本都是matlab的,在CSDN有一个asp.net + C#版本的实现,不过我看了之后果断决定重写,封装不够完善,同时思路也不清楚。所以自己写的过程,理解也更清楚了。经过我的简单更改,目前还说得过去吧,当然后续我还打算继续进行研究,所以先把基本程序的过程写下来,当然是利用了C# 的面向对象特性,看了别人写的 完全面向过程,理解真的很费劲。简单说说实现过程和代码吧。
我们封装了一个基础的 BaseTspAntSystem类 ,包括了一些基本属性和计算过程,后续相关改进版本可以进行直接继承。当然设计可能有缺陷,先这样进行,碰到需求再改吧。 BaseTspAntSystem类的主要属性如下:
1 /// <summary>城市数量,N</summary> 2 public Int32 NCity { get; set; } 3 /// <summary>蚂蚁数量,M</summary> 4 public Int32 AntCount { get; set; } 5 /// <summary>信息启发式因子a,表征信息素重要程度的参数</summary> 6 public Double Alpha { get; set; } 7 /// <summary>期望启发式因子b,表征启发式因子重要程度的参数</summary> 8 public Double Beta { get; set; } 9 /// <summary>信息素蒸发的参数p</summary> 10 public Double Rho { get; set; } 11 /// <summary>距离数据,不一定是对称矩阵,D=d(ij)</summary> 12 public Double[,] Distance { get; set; } 13 /// <summary>最大迭代次数NC</summary> 14 public Int32 NcMax { get; set; } 15 /// <summary>最好的解个数,取最优解列表中个数的数目,可以作为备用方案</summary> 16 public Int32 BetterPlanCount { get; set; } 17 private List<Ant> planList; 18 /// <summary>信息素浓度</summary> 19 public double[,] InfoT { get; set; }
基类有一个构造函数,对系统的初始化就是传入基本的参数,并对相关列表进行初始化,代码如下:
1 /// <summary>构造函数</summary> 2 /// <param name="m">蚂蚁数量</param> 3 /// <param name="a">信息启发式因子</param> 4 /// <param name="b">期望启发式因子</param> 5 /// <param name="p">信息素蒸发的参数</param> 6 /// <param name="distance">距离数据</param> 7 /// <param name="NCMax">最大迭代次数</param> 8 /// <param name="planCount">最优解的个数</param> 9 public BaseTspAntSystem(double[,]distance,Int32 m,double a,double b,double p,int NCMax,int planCount=10) 10 { 11 this.AntCount = m; 12 this.Alpha = a; 13 this.Beta = b; 14 this.Rho = p; 15 this.Distance = distance; 16 this.NcMax = NCMax; 17 this.BetterPlanCount = planCount; 18 planList = new List<Ant>(); 19 NCity = Distance.GetLength(0);//所有的城市个数 20 InfoT = new double[NCity, NCity];//初始化信息素矩阵 21 }
核心的是求解过程,完全按照迭代次数要求进行迭代进行,过程就是概率选择和信息素更新,我们辅助的用到了Ant蚂蚁类,目的就是让程序更加独立和容易理解。Ant类里面有蚂蚁路径寻找过程的所有信息。下一节将进行介绍。求解过程代码如下面,看看注释和对比算法进行:
1 public void TspSolution() 2 { 3 #region 初始化计算 4 //计算初始信息素的值,可直接指定,或者贪婪算法计算 5 double Cnn = GreedyAlgorithm(); 6 double t0 = (double)AntCount / Cnn;//信息素初始化 7 for (int i = 0; i < NCity; i++) 8 { 9 for (int j = 0; j < NCity; j++) 10 { 11 //每条可行的路径的信息素初始值 12 if (Distance[i, j] != 0) InfoT[i, j] = t0; 13 } 14 } 15 //为每个蚂蚁随机选择出发城市,List的长度为蚂蚁个数,内部List长度为路径 16 List<Int32> allNodes = new List<int>(); 17 for (int i = 0; i < NCity; i++) allNodes.Add(i);//所有路径节点 18 //迭代次数 19 Int32 NC = 0; 20 #endregion 21 22 while (NC<NcMax) 23 { 24 //生成蚂蚁及初始访问城市,并设置对应禁忌表和路径列表 25 List<Ant> antList = new List<Ant>(); 26 for (int i = 0; i < AntCount; i++) 27 antList.Add(new Ant(i, allNodes.DeepCopy(), false)); 28 //所有蚂蚁依次寻找下一个节点,直到本轮完成 29 antList.ForEach(n => n.NextCityUntilFinished(InfoT,Distance,Alpha,Beta,Rho)); 30 //统计最优解 31 planList.AddRange(antList);//先添加 32 planList = planList.OrderBy(n => n.CpathLength).ToList();//排序 33 //取出前面指定的几条最短路径 34 if (planList.Count > BetterPlanCount) 35 planList.RemoveRange(BetterPlanCount, planList.Count - BetterPlanCount); 36 NC++; 37 //更新信息素的值:循环所有路径,依次进行添加 38 //先挥发 39 for (int i = 0; i < NCity; i++) //挥发过程 40 { 41 for (int j = 0; j < NCity; j++) InfoT[i, j] *= (1.0 - Rho); 42 } 43 //再增强,循环所有蚂蚁 44 foreach (var item in antList) 45 { 46 var temp = 1.0 / item.CpathLength; 47 foreach (var edge in item.Edge) InfoT[edge.Key, edge.Value] += temp; 48 } 49 } 50 }
根据算法的描述,m只蚂蚁同时进行自己的工作和寻找路程,是一个并行的过程,因此也在单次过程中,蚂蚁都是独立的。蚂蚁的每一次迭代,过程都比较清楚,寻找路径过程,注意维护一些可用的节点列表,以及最后一条路径的处理。看看蚂蚁类的主要属性和构造函数:
1 public class Ant 2 { 3 #region 属性 4 /// <summary>蚂蚁编号</summary> 5 public Int32 Id { get; set; } 6 /// <summary>当前蚂蚁已经走过的路径节点列表,也就是禁忌表 7 /// 最后1个就是当前所处的位置 8 /// </summary> 9 public List<Int32> PathNodes { get; set; } 10 /// <summary>当前蚂蚁下一步可供选择的节点列表</summary> 11 public List<Int32> selectNodes { get; set; } 12 /// <summary>该蚂蚁旅行的总长度</summary> 13 public Double CpathLength { get; set; } 14 /// <summary>当前蚂蚁走过的边,key为起点,value为终点</summary> 15 public Dictionary<int, int> Edge; 16 private Random rand; 17 int N; 18 #endregion 19 20 #region 构造函数 21 /// <summary>构造函数</summary> 22 /// <param name="id">蚂蚁编号</param> 23 /// <param name="allNodes">所有节点名称列表</param> 24 /// <param name="isFixStart">是否固定起点</param> 25 public Ant(Int32 id,List<Int32> allNodes,Boolean isFixStart) 26 { 27 this.Id = id; 28 this.selectNodes = allNodes; 29 this.PathNodes = new List<int> (); 30 //isFixStart为false给蚂蚁随机1个起点,否则都为0 31 if (isFixStart) 32 { 33 this.PathNodes.Add(0); 34 this.selectNodes.RemoveAt(0); 35 } 36 else 37 { 38 var temp = new Random().Next(0, allNodes.Count - 1); 39 this.PathNodes.Add(temp); 40 this.selectNodes.RemoveAt(temp); 41 } 42 this.CpathLength = 0; 43 rand = new Random(); 44 } 45 #endregion 46 }
Ant类的核心是寻找下一个城市节点的过程,以及循环直到所有路径都完成。如下面代码,是一个循环过程:
1 #region 蚂蚁行为-依次按概率选择下一个城市,直到走完 2 public void NextCityUntilFinished(double[,] info,double[,] distance,double a,double b,double p) 3 { 4 N = distance.GetLength(0); 5 Edge = new Dictionary<int, int>();//经过的边:起点-终点 6 //为空,就不需要计算了 7 while(selectNodes.Count >0) 8 { 9 double sumt = 0;//分母的和值 10 Int32 current = PathNodes.Last(); 11 //依次计算当前点到其他点可选择点的 值 12 Dictionary<Int32, double> dic = new Dictionary<int, double>(); 13 foreach (var item in selectNodes) 14 { 15 var temp = Math.Pow(info[current, item], a) * Math.Pow(1.0 / distance[current, item], b); 16 sumt += temp; 17 dic.Add(item, temp); 18 } 19 //计算各个点的概率 20 var ratio = dic.ToDictionary(n => n.Key, n => n.Value / sumt); 21 //产生1个随机数,并按概率确定下一个城市 22 Int32 nextCity = GetNextCityByRandValue(ratio, rand.NextDouble()); 23 //修改列表 24 this.PathNodes.Add(nextCity); 25 this.selectNodes.Remove(nextCity); 26 this.CpathLength += distance[current, nextCity]; 27 Edge.Add(current, nextCity);//信息素增强辅助计算 28 } 29 //最后1条路径的问题,额外增加,直接 回原点 30 this.CpathLength += distance[PathNodes.Last(), PathNodes.First()]; 31 Edge.Add(PathNodes.Last(), PathNodes.First()); 32 this.PathNodes.Add(PathNodes.First());//最后才添加 33 } 34 35 /// <summary>按照dic中按照顺序的节点的概率值,和随机数rnd的值,确定哪一个为下一个城市</summary> 36 /// <param name="dic"></param> 37 /// <param name="rnd"></param> 38 /// <returns></returns> 39 int GetNextCityByRandValue(Dictionary<Int32,double> dic,double rnd) 40 { 41 double sum = 0; 42 foreach (var item in dic) 43 { 44 sum += item.Value; 45 if (rnd < sum) return item.Key; 46 else continue; 47 } 48 throw new Exception("无法选择城市"); 49 } 50 #endregion
后面的 GetNextCityByRandValue 是一个辅助函数,进行随机概率值的选择,确定是否选择哪一个节点。
基本TSP问题的C#代码下载: AcaSolution-150913.rar
如果下载有问题,请到本文原文下载: 群蚁算法理论与实践全攻略——旅行商等路径优化问题的新方法
[1].什么是NP问题. http://blog.csdn.net/yangtrees/article/details/8107563
[2].文永军.旅行商问题的两种智能算法[M].西安电子科技大学,2010年
[3].杨瑞臣.有时间窗和在前约束车辆路径问题的蚁群优化[M].西安建筑科技大学,2005.
[4].谷俊峰.智能算法-第七章:蚁群算法 PPT,大连理工大学,下载:蚁群算法基本知识.rar