相信学过算法的童鞋都听说过一个很经典的问题:TSP问题,这个问题是NP问题,无法在多项式时间内进行求解。当问题规模较小时,还可以用穷举的方法进行求解,但是当城市一旦变多,穷举的时间将会指数级增加。就算采用启发式搜索,估计也很难求解。
但是这个问题是可以尝试解决的,人工智能给我们提供了强大的武器,也许尽管无法求得全局最优解,但我们也能得到一个很不错的解。最主要的是,我们可以在可以忍耐的时间内得到一个解。下面给出人工智能中对TSP问题求解的几种方法,这也是刘峡壁老师在课堂上反复强调的。
1、使用霍普菲尔德网络求解TSP问题
首先我们要把TSP的图转换为二维矩阵,就像下面这样,这里是一个以城市为元素的矩阵,同时每个元素都对应一个神经网络的神经元:
限制条件是:
- 每行仅有一个神经元处于活跃状态
- 每列仅有一个神经元处于活跃状态
- 对n个城市的问题,需要有n个神经元处于活跃状态
例如针对五个城市,列代表到达某个城市的时间,行代表城市,则可以如下表示:
接下来我们就可以按照霍普菲尔德网络求解的一般做法进行处理,我们可以如下定义能量函数:
然后可以把上面的式子转换为符合二维霍普菲尔德网络的形式:
最后我们只需要随机给定初始值,对神经元进行扰动,当网络能量达到最小时会稳定下来,我们就会得到需要的解,下面是别人针对4个城市的一个求解过程:
2、使用遗传算法求解TSP问题
遗传算法主要有三个操作,选择,交叉和变异。个人觉得在求解TSP问题时,遗传算法的优势就是能在全局和局部直接达到平衡,它不像穷举算法那样耗费很多时间进行全局搜索,而是能较快确定解的范围,当确定范围以后,它又能对局部进行比较细致的搜索,找到一个比较好的解。
全局探索(Exploration):交叉重组与突变是算法全局探测能力的主要构成要素。
局部探测(Exploitation):对种群个体的选择是算法局部探测能力的主要构成要素。
平衡方法主要通过调节各要素的随机变化的参数实现,较大突变概率具有较强的全局探测能力,较大的选择概率意味着较强局部搜索能力。
主要数据结构:
基因表示方法:城市遍历顺序,如G =[1,3,2],表示遍历顺序为1->3->2->1
class Map{
private:
int **distance;//用于存储城市之间的距离
public:
int Distance(int city_1,city_2);//返回两个城市的距离
}
class Population{
private:
int Max_number;
int RanParNum;//每次随机选择的父体个数
double pro;//变异概率
int **GenList;//用于存储所有群体基因
int **RandomParent; //用于存储随机选择的父母群体
int **BestTwoParentFromRP;//用于存储从RandomParent中选取的最好的两个个体
int *NewChild;//用于存储即将进行变异和更新群体的孩子个体
int *Solution;
public:
static int GenLen;
Population(int m=100,int RPN=5,double p=0.8,int i=0,int **GL=NULL,
int **RP=NULL,int **BTPFRP=NULL,int *NC=NULL,int *S=NULL)
:Max_number(m),RanParNum(RPN),pro(p),icount(i),
GenList(GL),RandomParent(RP),
BestTwoParentFromRP(BTPFRP),NewChild(NC),Solution(S){}//构造参数
void randominit();//初始化群体及相关参数
bool Evlote(int MaxTime);
void GetParents();//选取少量个体
void SelectBestParent();//从少量个体中选取两个作为父母
void ReConbination();//基因重组,并保证子个体是合法的
void Swap();//交换变异
int Replace();//更新群体
void Display();//输出状态
int fit(int* person);//计算适应度,这里为对应方案的路程的倒数
};
//主要进化算法:
for(i=0;i<MaxTime;i++)//进化过程
{
GetParents();
SelectBestParent();
int *AnotherChild = new int[GenLen+1];//用于暂时存储重组后的一个孩子个体,另一个存储在Population的NewChild中,用于即将进行的个体变异
ReConbination(&AnotherChild);//重组
Swap();//变异
Replace();//更新群体
for(int i=0;i<=GenLen;i++)//将AnotherChild赋值给NewChild进行个体变异
{
NewChild[i]=AnotherChild[i];
}
delete AnotherChild;
Swap();//变异
Replace();//更新群体
}
更多内容,请参看这里: https://www.ads.tuwien.ac.at/raidl/tspga/TSPGA.html
3、使用蚁群算法求解TSP问题
对于蚁群算法不想过多说明,具体看这里, Ant colony optimization algorithms 总之你可以小看一只蚂蚁,但不能小看一群蚂蚁,《 哥德尔、艾舍尔、巴赫集异璧之大成》这本奇书中也提到了这一点。
用蚁群求解TSP问题主要是利用蚁周模型,即蚂蚁每完成一次城市的遍历后才更新所有路径上的信息素,求解过程如下:
下面是一个四个城市的例子:
这里共有两只蚂蚁,一只沿着四条边走,另一只沿着对角线走,它们经过的距离肯定是不一样的,由于蚁周模型中信息素的变化是依据下面这个式子:
所以前一只蚂蚁通过后在每条边留下的信息素为1/4=0.25 ;后一只蚂蚁为1/4.8≈0.21
经过叠加以后,上下两条边的信息素得到的加强,相比之下对角线的信息素值最小。随着时间推移和信息素的挥发,信息素值越大的路线上走过的蚂蚁越多,而蚂蚁的增多也促进了信息素的增加,相反信息素值越小将会越来越没有蚂蚁走,最终废弃。针对这个例子,最终的结果就是走正方形的四条边。
//主要数据结构:
public class ACO {
private Ant[] ants; //蚂蚁
private int antNum; //蚂蚁数量
private int cityNum; //城市数量
private int MAX_GEN; //运行代数
private float[][] pheromone; //信息素矩阵
private int[][] distance; //距离矩阵
private int bestLength; //最佳长度
private int[] bestTour; //最佳路径
//三个参数
private float alpha;
private float beta;
private float rho;
public void init(String filename);//从文件初始化数据
}
//主要算法:
for (int g = 0; g < MAX_GEN; g++) {
//蚂蚁依据信息素寻找解
for (int i = 0; i < antNum; i++) {
for (int j = 1; j < cityNum; j++) {
ants[i].selectNextCity(pheromone);//选择下一个城市,并从禁忌表去除该城市
}
ants[i].getTabu().add(ants[i].getFirstCity());
//更新最好Tour
if (ants[i].getTourLength() < bestLength) {
bestLength = ants[i].getTourLength();
for (int k = 0; k < cityNum + 1; k++) {
bestTour[k] = ants[i].getTabu().get(k).intValue();
}
}
//计算此次释放的信息素
for (int j = 0; j < cityNum; j++) { ants[i].getDelta()[ants[i].getTabu().get(j).intValue()][ants[i].getTabu().get(j+1).intValue()] = (float) (1./ants[i].getTourLength());
ants[i].getDelta()[ants[i].getTabu().get(j+1).intValue()][ants[i].getTabu().get(j).intValue()] = (float) (1./ants[i].getTourLength());
}
}
//更新信息素
//信息素挥发
for(int i=0;i<cityNum;i++)
for(int j=0;j<cityNum;j++)
pheromone[i][j]=pheromone[i][j]*(1-rho);
//信息素更新
for(int i=0;i<cityNum;i++){
for(int j=0;j<cityNum;j++){
for (int k = 0; k < antNum; k++) {
pheromone[i][j] += ants[k].getDelta()[i][j];
}
}
}
//重新初始化蚂蚁
for(int i=0;i<antNum;i++){
ants[i].init(distance, alpha, beta);
}
}
同样这里也存在全局和局部搜索问题
全局探索(Exploration):挥发系数(rho)越大;信息素的更新策略越均匀,收敛越慢,全局探索能力越强。
局部探测(Exploitation):与全局探索对应,挥发系数(rho)越小;信息素的更新策略越不均匀,收敛越快,局部探测能力越强。
可以使用一些有效的参数和更新策略选取算法,对多组参数和更新策略进行比较、验证,以获得效果理想的参数和更新策略。
小结:以上的三种方法分别对应于人工智能的三个子领域(神经网络,进化计算,群智能),在这里我们能看到使用多种方法求解某一类问题的妙处,这三种方法中究竟哪种方法最好并没有一个权威的论断,不过显然既然是从不同角度出发肯定都有各自的优缺点,难分伯仲。还是要说,人工智能是很有意思的一个领域,这个领域很宽泛,面极其广阔,包含了太多的东西,包括认知科学,机器学习,自然语言处理,机器人学,计算机博弈,自动定理证明,模式识别,计算机视觉等等等等,希望更多的人能涌入这个领域,AI前景无限美好。