作者:阳羡 原文链接:mp.weixin.qq.com/s?__biz=Mzg…
背景&目标
之前已经完成了一次人工智能相关算法的分享 人工神经网络算法入门,相信大部分同学并没有听懂。这是因为我抱着一种分享兴趣的态度去做的分享,主要目标是给大家讲一个概念,感兴趣的同学才会去深入了解。
然而,在最近的会议讨论上,PM 提了一句智能化的事情,这代表我们未来也许需要做一些智能化的尝试。从大的方向说,现在的风口逐步从“互联网+”转换至“人工智能+”,凡是我们耳熟能详的产品,后面大多有人工智能相关技术进行撑腰。因此,了解人工智能的优先级提高了。
对于前端来说,了解人工智能就和了解后端一样,一方面有益于和未来可能的算法同学合作,另一方面可能会像 BFF 一样,需要编写我们自己的人工智能算法。
本文的目的是,通过“蚁群算法”这个非常简单的例子,去系统地了解一个人工智能算法是由哪些部分组成的,以及它的基本原理。
人工智能导论
数学作为一门基础理论学科,总是能够领先其他更偏实际应用的学科。
对于一个具体问题,数学家会把它抽象成一个抽象问题,然后给出方程组进行解答。
数学总是能够给出正确的解答,然而会耗费大量的算力,并且难以给出有效的估算,或者中间结果。
举个例子,作为抖音来说,数学上只需要看 15 个视频就能算出你最喜欢什么,算的非常非常准,但是需要一个月,在这一个月里,什么结论都不会有。这是不能接受的,我们更能接受的结果是,每天都告诉我,你喜欢什么,可以算的不是那么准,可以迭代,这才能商业化。
对,迭代,人工智能作为从数学中脱胎而出的可以实际应用的学科,它和数学相比,最大的特点就是迭代。
迭代,计算机中可能更愿意使用循环 for-loop,是一切计算机程序的基石。
比如说一个经典的 win32 程序,它的结构就如下所示:
int main() { init() while(auto message = getWindowMessage()) { switch(message.type) { xxx } }}
用流程图表示就是
任何一个人工智能算法也是如此,分为初始化和循环两部分。只不过由于面向对象等软件工程思想的应用,导致分得不是这么明显。
人工智能的初始化分为两个步骤,初始化常量和初始化变量。
常量是指在每次循环中不会改变的量,比如节点层数、每层的节点数量。根据所使用算法不同,所要解决的问题不同,常量可能需要人工手动调整,这一步被称为“调参”。
变量只是在每次迭代中会优化的量,比如权重、偏移,通常是由算法自动校准。
当然,随着人工智能算法的进一步发展,这两个的界限越来越模糊,很多人工智能已经学会了自动调参。
我认为,人工智能的循环通常分为两个核心步骤:计算、校准。
计算就是指根据常量和变量去计算出问题的答案,比如在人工神经网络中,这一步的专业名词叫做“正向传播”。
校准就是指看看上一步计算出的答案和标准答案相比有哪些出入,根据这些出入去调整变量,在人工神经网络中,这一步叫“反向传播”,标准答案有时候我们叫它“训练集”。在有的算法中,会在校准前存在“评价”这一步,用于评价的函数通常叫做 cost 代价函数。
对于一些实际项目来说,可能会在若干次循环的前后存在保存和调整场景两步。
首先是保存,比如对于抖音来说,一天是一个计算周期的话,那每一天迭代完都会输出预测明天喜欢的视频。这一步是人工智能算法能够商业化的核心。对于我们演示的 demo 来说,因为不复杂,就不做保存这个功能了,以 console.log
作为替代。
然后是调整场景,通常可能是调整训练集,即所谓的标准答案,也可能是调整其他参数。比如明天你会看到的视频,等于昨天算法的结果加上今天你点喜欢的视频。当然,并不是每个问题都有标准答案的,比如这次我们要讲的蚁群算法,某种程度上也算是没有标准答案。
关于一个问题有没有训练集,也就是有没有标准答案,学术上将其分为“无监督学习”和“监督学习”,有训练集的叫做“监督学习”,无训练集的叫做“无监督学习”。当然还有“半监督学习”,不过相信大家已经猜到是什么意思了。
通俗点来说,就是数学和玄学。数学讲究确定性,必须得有确定的输入和输出,不存在概率和意外。玄学讲究不确定性,不一定要有确定的输入和输出,某种程度上鼓励意外发生。
之前分享的人工神经网络算法属于数学,而这次要分享的蚁群算法算是属于玄学。
我们不一定要非左即右,可以视实际情况把玄学和数学结合起来,从而能够更好地解决问题。
蚁群算法
蚁群算法的目标是为了解决 旅行推销员 TSP 问题,简单说就是地图上有若干个城市,有一位旅行推销员想不重复地经过每座城市,求最短路径。
这个问题本身有很多解决方案,但是经典的数学方案它无法迭代,导致在问题规模特别大的情况下容易导致数字溢出和时间太长两个主要问题,并且因为不存在“迭代”,所以在计算完成前无法输出有效的结论。
比如说传统遍历算法的复杂度是 O(n!),注意这里有个感叹号,代表阶乘。这意味着当 n = 32 左右的时候,遍历次数就已经大于 int64 的最大值了,非常恐怖。
蚁群算法,顾名思义,就是灵感来自于蚁群的算法。蚂蚁在行走时会留下信息素,之后的蚂蚁会大概率沿着上一只蚂蚁留下的信息素前进,信息素会随着时间挥发。为了方便计算和理解,在这里我们假定每只蚂蚁携带的信息素是固定的,并且均匀地洒落在它走过的每段路上。
初始化
我们首先要编写初始化代码,但是作为尚处于学习过程的我们来说,我们很难知道它到底需要哪些参数,多半是会在编写循环代码中发现漏了某个参数,再回来补上。
首先对于每个人工智能算法来说,一定有迭代条件,当不满足迭代条件时,停止迭代。比如说上次分享的人工神经网络算法,当 cost 代价函数返回 0 时,代表它已经完全学会了,可以停止迭代了。对于商业化来说,一般没有明确的停止条件,会一直迭代学习下去。对于我们的 demo 来说,会加上迭代次数这个常量,当迭代了这么多次后就停止了,方便演示。
剩下的参数会散落在后文中,感兴趣的同学可以整理下。
计算(正向传播)
既然是蚁群,那肯定不止一只蚂蚁了。我们需要 new 出一群蚂蚁,把它们随机散落在各个点上。
这里有几个常量:蚂蚁的数量、点的位置和数量。点的位置和数量就是问题,不过为了演示更加随机,我们把这个问题也作为常量的一部分,让它随机地初始化。
然后开始迭代每一只蚂蚁。
我们使用轮盘赌算法来决定蚂蚁的下个目的地。
顾名思义,轮盘赌算法来自轮盘赌,如下图所示:
奖项 | 一等奖 | 二等奖 | 三等奖 | 参与奖 |
---|---|---|---|---|
因子 | 10 | 20 | 30 | 40 |
某个概率 = 某个因子 除以 所有因子之和
比如一等奖的因子是10,所有因子之和是 100,则一等奖的概率是 10%
学会了轮盘赌算法,让我们回到蚁群算法。
那么现在我们的目标是计算出蚂蚁去下个目的地的因子。因为蚂蚁走过的路上会留下信息素,信息素越高,选择这条路的概率越高,所以假定 因子 = 信息素。在这里,信息素就是一种变量,会在后续的反向传播环节中进行校准。
但是这样会带来一个问题,当第一次迭代完成后,走过的路上留下了信息素,没走过的路上没有信息素,导致之后的蚂蚁会更偏向于之前已经走过的路,而之前已经走过的路不一定是最优路线。
因此我们需要给因子再加一个参数,因为我们的问题是求最短路径,所以我们把两点之间的距离引入进来。从贪心的角度来说,两点之间距离越短,则选择这条路线的概率越大,所以我们乘上距离的倒数。
现在,因子 = 信息素 X (距离的倒数)
因子在受到多个参数影响后,我们希望能够调整各个参数的权重,在之前的人工神经网络中,我们是通过乘法的形式把权重和参数结合,即:
但是在这里我们已经使用了乘法,所以在处理权重时,我们可以使用指数,即:
因子信息素距离
这边使用 alpha 和 beta 分别代表 信息素 和 距离 的权重,这两个值属于常量,调参时可以灵活调整。
在求得每段路程的因子之后,每只蚂蚁使用轮盘赌算法去选择它要去的下个节点,遍历的时候顺便计算走过的路程,毕竟我们要求的就是最短路径。
地点 | A | B | C | D |
---|---|---|---|---|
A | / | 40 | 60 | 30 |
B | 40 | / | 15 | 70 |
C | 60 | 15 | / | 45 |
D | 30 | 70 | 45 | / |
假如说,我们求得的每段路程的因子如上图所示。有一只蚂蚁,它现在位于A点,已经去过了D点,那么它现在只能去B点或者去C点。A点到B点和A点到C点的因子之和为100,所以它会去B点的概率为 40%,会去C点的概率为 60%。
校准(反向传播)
校准这一步的目的是,帮助上一步计算越来越准。
在蚁群算法中,我们的策略是更新信息素。像蚂蚁一样,信息素存在挥发和增加两步,即:
信息素挥发因子信息素信息素增加量
挥发因子是一个常量,调参时可以灵活调整。
假定每只蚂蚁携带特定的信息素,并且这些信息素均匀地散落在每段路上,则对于每只蚂蚁来说:
信息素增加量携带的信息素量走过的总路程
当然,信息素增加公式本身也属于常量的一部分,可以在调参时灵活调整。
把一轮迭代中每只蚂蚁的信息素增加量相加,就是在一次迭代中需要增加的总信息量。
优化
现在这个算法已经可以运行了,但是仍然存在一定的优化空间。
首先在进行多轮迭代后,经常被蚂蚁行走的路径会拥有非常高的信息素,而其他路径会有非常低的信息素,趋近于零。这样会导致结果飞快地收敛,虽然算法收敛快是好事情,但是不一定会收敛到正确结果上,也可能是收敛到错误结果上。
为了解决这个问题,我们可以引入“最大最小蚂蚁系统”,即信息素拥有最大值和最小值,这样再罕见的路径也有概率被选中,从而解决正确结果藏在罕见路径中的问题。
但是引入最大值最小值的代价就是,降低了收敛速度。为了使结果能够更快地收敛到正确结果上,我们可以引入“精英蚂蚁系统”,即每次完成迭代后,让有史以来走的最短的那只蚂蚁再多走两圈,留下更多信息素,从而引导后续蚂蚁有更高概率选择这条路径,以此来提高收敛速度。
演示(翻车)环节
演示一下调整场景和保存
思考
蚁群算法当然不是仅仅是为了解决 TSP 问题而生的,了解蚁群算法的本质能够让我们更好地把它应用到其他问题上。
蚁群问题能够解决一个离散的大问题,而这个离散的大问题能够被拆解为一系列离散的小问题。试想,如果蚂蚁的选择不是下个点,而是 0 ~ 10 之内的实数,是不是很难记录其中的信息素了。
每个问题,包括小问题和大问题,都有一个值标记着这个问题解决得好不好。这个值也许叫做 cost 代价、lost 损失、fitness 适应度,以及这里的 distance 距离,我们的目标就是追求这个值的最大化或者最小化,并使用信息素记录追求的过程。小问题的最优解并不一定是大问题的最优解。
像这样模拟一个群体去做运算,从而在试错率和收敛速度中取得平衡的算法,就叫群集智能(Swarm intelligence, SI) ,其他好玩的群集智能算法,之后有时间再给大家分享。
在明白蚁群算法的本质后,留下两个问题交给读者思考:
1. 【简单】如何使用蚁群算法解决背包问题
2. 【困难】如何使用蚁群算法优化 NN,从而避免 NN 落入极小值
参考资料
参考资料:zhuanlan.zhihu.com/p/35451395
其他蚁群算法的资料:www.cnblogs.com/bokeyuancj/…