之前的文章:
之前写完代码后,运算时,发现无法得到最佳值,首先我想到的原因是其中几个系数的选取。如信息素的挥发速率,使之前的信息素对之后的选择的效率达到一个合理的值。如蚂蚁出错的概率,一个合适的值可以使蚂蚁能够寻找最佳的路径并不收敛于一种路径,同时又能巩固已发现较短路径上的信息素残留值。
但是后来才发现,我的程序里得不到最佳值的原因是并未完全理解蚁群算法。在蚂蚁选择路径时,是选择信息素最多的路径还是选择距离较短的路径?如何使蚂蚁从信息素最多的路径转向选择距离较短的路径。蚁群寻找食物的算法中是这样解说的:
当有两条路径时,一条距离长,一条距离短,则距离短的路径上每只蚂蚁每次经过时,留下更多的信息素,因为它们来回一趟的时间更断,则在一段时间内的来回更多次,留下更多信息素,以使更多的蚂蚁来到这条路径,最后距离短的路径上的信息素浓度会超过距离长的路径上的浓度,而成为当前的较佳路径。
则在这里,我们可以发现,对于一种场景下,有两条路径时,蚂蚁绝对不是直接的去选取信息素浓度最高的路径,而是平均的分配,使一定量的蚂蚁可以沿着信息素浓度较低的新路径去寻找食物。而我之前的程序中是强制蚂蚁去选取信息素最大的路径,这样就不会出现当一条较好路径出现并逐渐取代之前路径的情况。
由此,可以发现,这里的几个参数常量的设定就更加复杂了。蚂蚁的数量应该合理,而一条当前最优的路径上信息素浓度虽然由于有较多的蚂蚁经过导致积聚很多,但是不能远大于 当一只蚂蚁找到更优路径时所留下的信息素量,否则之后的蚂蚁就会忽略这一只蚂蚁所寻找到的更优的路径,而为此,要调整蚂蚁的数量,蚂蚁的数量要多,以使每一条较优路径都应该被执行,但又不能太多,以使当前最优路径上留下的信息素远大于其他路径上的信息素,又要考虑信息素消散的速率,使其能保证较劣路径上信息素能较早消失,而又使新出现的较优路径上的信息素能狗被蚂蚁察觉。又要考虑出错的概率,出错的最好效果是对于当前信息素浓度最高的路径使其最后一次选择时留下的蚂蚁数 大于 1 而尽量小,使其即能保存最优的路径,又能尽可能多的去寻找新的路径,对于蚂蚁,前面有多条路径,而出错又该如何选择,直接随机选取任意一条路径。但是我们要从所有路径中根据信息素量来平均分配蚂蚁数,要考虑如何取值才能使较优路径上的信息素量不过原小于当前最优的路径上的信息素。这里面的参数选取是极其复杂的。接下来,我们还是以 10个 城市的情况下进行分析:
首先,我选择了 100只蚂蚁,因为蚂蚁数太少的话,会在之后的路径中对于主路径没有足够的蚂蚁去行走。再决定不出错概率为0.8,则以100只去出错10次后,还会剩下10只,较合理的选择。然后先将信息素消散速率定位 0.5 ,即每个循环后信息素量为之前的0.5倍,这个值是随便取的。然后要考虑常数Q,对于每次路径被蚂蚁结果后,增加的信息素量应该在一个什么范围内,对于要对每次选择去按照信息素量平均分配蚂蚁,则应该忽略一些浓度较低的路径,然后统计所有路径的信息素量的总值,然后根据每一个路径所在的百分比来分配蚂蚁。令Q为2 *LK等于60,LK为估计的最佳值,Q取值为估计的最佳值的2倍左右,然后Q/LK就会在(0-2),当已有一个当前最优解时,假设最初全部蚂蚁都在这条路径上,取信息素最大值MPhe,则MPhe*0.5 + (100 * 0.8)*Q / LK < MPhe 使路径上信息素叠加有个最大值,防止无限增大使无法进行新路径的检测,则计算可得MHpe为320.但是,这是最大值的情况,一般来说第一层上的信息素量的和一般为300左右,而除了当前最优路径,其他路径一般都应该有2个蚂蚁,则其上信息素量为(2 – 4)则还是会高于 1%,即可以被蚂蚁选中的。当然,如果计算时,将信息素量小于2的情况忽略,则效果更好。然后,此时,我们选的值,已可以使信息素不会过度积聚,且对新的路径敏感的不错的选择。
当有两条路径时,一条是之前的最优解,现在绝大多数蚂蚁都从上通过,譬如99只,而之后有一只蚂蚁突变找到了一条新的路径,该路径更优但是只有一只蚂蚁通过,则两条路径上产生的信息素的量是悬殊的,然后当则100只蚂蚁再次从头出发去寻找路径时,最多只会有一只蚂蚁从上通过,每次循环对这条路径上增加的信息素量为 Q/LX + Dis*Phe -Phe 则约等于 dis *Phe,但由于每次还有突变的可能,甚至导致这一点点的增加都不能保证,则这样做是不科学的。对于蚂蚁犯错不应该是这些路径上仅有的一只蚂蚁出错,而应该是在当前浓度最高路径上的大量蚂蚁出错,才能保证新发现的较优路径能有更多的蚂蚁加入。 但这是在如果蚁群已经完成向一个路径靠拢的过程后发生的结果,但如果每次都由蚂蚁数最多的路径进行变异,又会导致这个可能较优的路径无法实现集聚,无法吸引更多蚂蚁并是其他较劣的路径被淘汰。然后如何实现平衡?
这是一个相互矛盾的问题,如果在每个蚂蚁在每次选择时都有固定的出错概率的情况下。因为你要使蚁群能够进行收敛以便下一次的搜索,就必须是 出错概率较小,使其能尽早收敛。但又要保证收敛不过快过度,导致之后偶然的新路径无法被选取,又要使更多的新路径可能被创造出来。这里的结构是错综复杂的,系数的选取是要思考全局的,而最后我写的代码也有许多与上面的设想 所不同的地方。
稍微进行了些改进,但是效果不是很好,收敛与发散的协调不是很好,导致必须较高次的循环才能得到最优解,而这个循环的次数 为 const int K = 20000; 对于这么多的循环次数,显然,这个程序的效率低的有点吓人了,但是要寻找一个好的搭配或者说 更合理的做法的话还是有些困难的。
代码是在之前的代码基础上进行修改的,但是并没有对每个注释进行修改,可能会有些注释与写的代码不符。
using namespace std;
//使用10个蚂蚁,进行10个城市的TSP问题求解。
const int MMax = 9999;//蚂蚁数量,蚂蚁数量根据城市数量决定。
const int NMax = 500;//城市数量最大数量,超过出错
int m,n;//蚂蚁数量与城市数量
const double Q = 40 ;//常量
const int K = 20000;//循环总次数
int D[NMax][NMax];//路径长度
double Phe[NMax][NMax];//边对应的信息素浓度
int LK;//蚂蚁此循环中的总路径长度
int Path[MMax][NMax];//记录蚂蚁的路径,用于防止走重复的路。记录的是点
int ant;//蚂蚁当前所在点
int i,j,k,p,q;//循环使用
double Dis = 0.5;//每次信息素 消失的速率
int sameNum,samePhe[NMax];//每次去寻找信息素最多的边,如初始情况,信息素量都相同时,要
//随机的去从中选取边。
int bugNum,bugTry[NMax];//出错情况下进行的选择
double bugP = 0.95;//每一次操作的出错概率
//后来发现,出错概率要结合蚂蚁数与城市数进行判断,而定值Q为估计距离
int start;//出发点,城市编号从0 - n-1.
double Max;//用来选取最多信息素的边
bool Passed[NMax];//用来判断城市是否已经经过,是否可以选取
int bNum,Branch[NMax];//所有可以被选择的分支数量与编号
double rPhe,BPhe[NMax];//对应分支所对应的信息素浓度,BPhe[n]=Phe[i][Branch[0]]+...+PPhe[i][Branch[n]]
//然后判断 rPhe = rand() / PheAll 位于何处。。。即为随机确定的分支,这里本来应该按比例分配的直接使用随机数进行分配
double MIN = 2;//当Phe小于2时,进行忽略
int main()
{
//载入数据
fstream f("data.txt",ios::in);
f >> n;
if( n > NMax)//本来想直接赋值,后来又决定从文件中读入。
return 0;
for(i = 0;i<n;i++)
for(j = 0;j<n ;j++)
if(j != i)
f>>D[i][j];
for(i = 0;i < n;i++)
D[i][i] = 0;
f >> start;
if(start > n-1)return 0;//没必要的检测,如果文件未正确书写,发生意外的错误,概不负责。
f.close();
for(i = 0;i < n;i++)
for(j = 0; j < n;j++)
Phe[i][j] = 3;//初始化每条边上的信息素浓度
for(i = 0; i < n;i++)
Phe[i][i] = 0;
for(i = 0;i< m;i++)
Path[i][0] = start;//每只蚂蚁的出发点都固定
m = 100;//蚂蚁数为城市数的2倍,后来发现不够
for(k = 0;k < K;k++){
srand((unsigned)time(NULL));
for(i = 0;i < m;i++){//对于每只蚂蚁,进行一次循环
ant = start;
for(j = 0;j < n;j++)
Passed[j] = false;
Passed[ant] = true;
for(j = 1;j < n;j++){//每只蚂蚁选n-1次边
bNum = 0;
bugNum = 0;
rPhe = 0;
for(p = 0; p < n ;p ++){
if(!Passed[p]){
if(Phe [ant][p] > MIN){
rPhe += Phe[ant][p] ;
BPhe [bNum] = rPhe;
Branch [bNum++] = p ;//将这些数据读入并保存
}else{
bugTry[bugNum++] = p;
}
}
}
if(bugNum == 0){
for(p = 0;p < n;p++)
if(!Passed [p])
bugTry[bugNum++] = p;
}
Max = 0;
for(p =0;p < n;p++)
Max = Max > Phe[ant][p]?Max:Phe[ant][p];//取最大值
rPhe = (double) rand()/32765 *rPhe;
for(p = 0; p < bNum - 1;p++)
if(rPhe < BPhe[p])//使用概率进行分配
break;
q = Branch[p];
///if(bNum < 1)
// cout<<endl;
Max = Max / 4 ;
if(bNum == 0 )
q = bugTry[ rand() % bugNum];
else if(Phe[ant][q] > Max){//只有当蚂蚁准备选择最大边时,才会出现变异
if( (double)rand() / 32765 >bugP)
q = bugTry[ rand() % bugNum];
}
ant = q;
Passed[ant] = true;//路径经过
Path[i][j] = ant;//保存路径
}
}
for(i = 0;i < n;i++)
for(j = 0; j < n;j++)
Phe[i][j] *= Dis ;//每次循环后,进行信息素的消逝
//完成对每一个蚂蚁的操作后,进行增加信息素的操作,使用Ant-Circle System
p = 99999;
for(i = 0; i < m;i++){
LK = 0 ;
for(j = 0; j < n-1;j++)
LK += D[Path[i][j]][Path[i][j+1]];//计算一次循环中蚂蚁的总路程
LK += D[Path[i][j]][Path[i][0]];//回到初始点
for(j = 0; j < n-1;j++)
Phe[Path[i][j]][Path[i][j+1]] += Q/LK ;
Phe[Path[i][j]][Path[i][0]] += Q/LK ;//初始点
if(LK < p){
p = LK;
q = i;
}
}
}
p = 0xfffff;//虽然已经完成操作,但我们要直观的从所有现有路径中找出最短的路径。
for(i = 0;i < m;i++){
LK = 0;
for(j = 0;j < n-1;j++)
LK += D[Path[i][j]][Path[i][j+1]];//计算一次循环中蚂蚁的总路程
LK += D[Path[i][j]][Path[i][0]];//回到初始点
if(LK < p){
p = LK;
start = i;
}
}
for(i = 0;i < n; i++)
cout << Path[start][i]<<"->";
cout << Path[start][0]<<'\n'<<p<<endl;
return 0;
}
这里,我对与如何使足够量的蚂蚁去出错,而又不使新路径上的蚂蚁误入歧途,选择使 信息素浓度高于一定值的路径上的蚂蚁才能去出错。这个限制,感觉不是很好,因为对于蚁群来说,是没有这个规则的。
最后,这个续写得也是很好,这个问题并没有真正得到解决。但是,我只能做到这里了,具体正确的做法是如何进行的,正确的算法是什么样的,留着给以后心情好的时候在去百度吧。
以后有空,再来写 续的 续。