1、算法简介
模拟退火算法来源于固体退火原理,将固体加温至充分高,再让其徐徐冷却,加温时,固体内部粒子随温升变为无序状,内能增大,而徐徐冷却时粒子渐趋有序,在每个温度都达到平衡态,最后在常温时达到基态,内能减为最小。需要注意的是一定要徐徐冷却,也就是物体和外界温度相差越低,退火时间越长退火的效果就越好。这也就是这算法名字的由来。
2、基本步骤
(1)生成初始解T0
(2)生成新解Tn+1
(3)使用评价函数评价新解Tn+1,
(4)如果新解Tn+1更优秀则接受新解。
(5)否则以一定概率p接受Tn+1
(6)重复2-5步骤,直到解足够优秀。
3、关键点
算法中有4个部分是对算法质量有显著影响的。初始解,产生新解的方法,接受差解的概率p,以及评价函数。
通常要求初始解比较差,也就是被加热的温度很高。如果初始解就比较好,很容易坠入局部最优解。
产生的新解与原来的解差距比较要,也就是要徐徐的降温,降温过快就会成淬火了。
接受差解的概率p,概率不能过高也不能过低,过低会导致很容易坠入局部最优解,过高会导致收敛速度大幅下降。
通常取p=exp(-Δt′/T)。
评价函数,显而易见的评价函数能否有效表示解的优秀程度直接关系解的质量。
4、举个例子
下面以求非线性方程组的解为例,展示如何使用模拟退火算法(虽然通常解非线性方程通常不用模拟退火算法,但是用这个举例比较适合,毕竟简单)
(1)方程
funs.Add((double i,double j) =>
{
return (Math.Sin(i) * j);
});
funs.Add((double i, double j) =>
{
return (Math.Exp(i)-j);
});
这里我用一个List保存方程组的各个函数。这里有两个函数,分别是
f1(x,y)=ysinx=0;
f2(x,y)=e^x-y=0;
(2)产生初始解,这里为-50 ~ 50中的随机数。
double a, b;
Random rd = new Random();
a = rd.NextDouble()*100-50;
b = rd.NextDouble()*100-50;
(3)迭代过程。
while(true)
{
t++;
doublet1=funs[0](a,b);
doublet2=funs[1](a,b);
//接受新解
if(Ack(t1,t2)<ak)
{
ba=a;
bb=b;
ak=Ack(t1,t2);
}
//以一定概率接受差解
else
{
if(p(t,Ack(t1,t2))<rd.NextDouble())
{
ba=a;
bb=b;
ak=Ack(t1,t2);
}
}
//迭代结束,输出
if(Ack(t1,t2)<=0.0001)
{
Console.WriteLine("t:{0}a:{1}b:{2}ak:{3}",t,ba,bb,ak);
return;
}
//产生新解
a=ba+(rd.NextDouble()-0.5)*step;
b=bb+(rd.NextDouble()-0.5)*step;
}
其中评价函数直接去了两个函数计算结果的模长。
5、总结
模拟退火算法是一种通过随机搜索寻求最优解的方法,所以其不一定能够收敛的最优解,是存在一定概率收敛在局部最优解中的。并且解的质量与计算时间有关,不过只要参数设置得当,会取得高质量解,并且速度十分迅速。