一、引言
哈喽大家好,看到标题大家应该知道我今天要讲什么了吧。“模拟退火算法”,怎么听起来很燃的感觉,哈哈并没有啦,一点都不燃,但是很有用!!看完这篇文章你就懂我什么意思了。
二、退火现象
首先,我们了解一下什么是“退火”。是指妖魔鬼怪快离开,火也快离开的意思吗?差不多哈哈,因为火离开了温度就低了嘛。好啦不开玩笑,讲正经点,退火就是就是将金属缓慢加热到一定温度,然后让其缓慢的冷却到室温。比如说,博主有一碗面,可是温度太高好烫吃不了,那就放在桌子上,等它慢慢降温到合适的温度就可以吃啦,这个过程就叫退火啦。
诶那我们这个模拟退火算法跟这个退火现象有什么关系呢?其实还是有点关系的,模拟退火算法的思想,其实跟退火的现象有关。具体怎么有关听我慢慢描述哈。
三、模拟退火算法
首先看下面这个图,假如我有一个函数$y = f(x)$,画出来的图就跟下图那个一样。现在,我想找到这个函数的最大值。那么该怎么做呢?模拟退火算法是这么认为的:
我们先在$x$的定义域内,取一个起始点$x = i$,如图红色虚线,得到$y = f(i)$。接着,我们可以采取如下的策略:
让红色线往右移动一个单位,然后作如下判定:
(1) 如果 f(i + 1) > f(i) ,则接收该移动
这很好理解,如果你发现移动到的新点求出来的值更大,肯定是接收这个移动啦。
现在,我们的红色线在不断重复(1)操作的过程中,来到了函数的第一个极大值。如下图:
现在,我再进行(1)步骤,就会发现:
f(i + 1) < f(i) ,那到了这里我们是不是就不接受移动了。可是在这里因为我们是知道这个图是长什么样的,我们知道只要这个红色线再经过“低谷”后就能找到一个真正的极大值了。可是电脑是不会知道图的样子的,那么该怎么做呢?
模拟退火算法的核心就在这里,它不会放弃这个新的f(i + 1),它说,f(i + 1)你可以比f(i)小,我给你机会,给你一段时间,看这个f(i + 1)能不能蜕变成“最大值”。因此对于f(i + 1) < f(i) ,执行如下操作:
(2) if f(i + 1) < f(i), 以一定概率接收该移动,并随时间推移概率降低。
而这个概率,就是参考了金属冶炼的退火过程,这也正是这个算法被称为模拟退火算法的原因。
四、Metropolis准则
在金属冶炼的退火过程中,在温度为T时,出现能量差为dE的粒子的降温的概率为P(dE),表示为:
$P \left( dE \right ) = exp\left({\frac{dE}{kT}}\right )$
其中k是一个常数,exp表示自然指数,且dE < 0。这条公式的含义是:温度越高,出现一次能量差为dE的降温的概率就越大;温度越低,则出现降温的概率就越小。又由于dE总是小于0(否则就不叫退火了),因此dE/kT < 0 ,所以P(dE)的函数取值范围是(0,1) 。
在模拟退火的算法中,我们的一定概率是这样定义的:
dC = f(i + 1) - f(i) < 0;
if exp(dC / T) >= rand,则接收该移动,否则不接收
上面这个定义称为Metropolis准则。
因此。整个算法的流程是这样的(伪代码)
//目的:求系统的最小值 S(i): 系统在状态y时的评价函数值 i: 系统当前状态 i + 1: 系统的新状态 rate: 控制降温的速率 T0: 系统的初始温度(高温状态) T_min: 温度的下限 while (T0 > T_min) { dE = S(i + 1) - S(i); if dE < 0 //接收从S(i)到S(i+1)的移动 else if (exp(-dE / T) > random(0,1)) //接收从S(i)到S(i+1)的移动 else //不接收从S(i)到S(i+1)的移动 T0 = r * T0; //降温退火(0 < r < 1 ; r越大, 降温越慢; r越小, 降温越快) i++; }
五、模拟退火算法的应用
有这么一个古老问题,被称为旅行商问题 ( TSP , Traveling Salesman Problem ) ,是数学领域中著名的问题,问题内容是这样的:有这么一个旅行商人,他要拜访n个城市,但是每次走的路径是有限制的,即每个城市只能拜访一次,并且最后要回到原来出发的城市。如何选择路径使得总路程为最小值。
之所以著名是因为这个问题至今还没有找到一个有效的算法。如果想精确的求解只能只能通过穷举所有的路径组合但是随着城市数量的增加其复杂度会很高。
不过还好,我们有模拟退火算法,当然它不能精确的求解这个问题,不过他能近似的求解这个问题。具体做法如下:
1. 设定初始温度T0,并且随机选择一条遍历路径P(i)作为初始路径,算出其长度L(P(i));
2. 随机产生一条新的遍历路径P(i+1),算出其长度L(P(i + 1));
3. 若L(P(i + 1)) < L(P(i)),则接收P(i + 1)为新路径,否则以模拟退火的概率接收P(i + 1),然后降温
4. 重复步骤1和2直至温度达到最低值Tmin。
产生新的遍历路径的方法有很多,下面列举其中3种:
1. 随机选择2个节点,交换路径中的这2个节点的顺序。
2. 随机选择2个节点,将路径中这2个节点间的节点顺序逆转。
3. 随机选择3个节点m,n,k,然后将节点m与n间的节点移位到节点k后面。
六、模拟退火算法Matlab代码
以下代码是模拟退火算法用于TSP问题的。主函数如下:
%% I. 清空环境变量 clear all clc %% II. 导入城市位置数据 X = [16.4700 96.1000 16.4700 94.4400 20.0900 92.5400 22.3900 93.3700 25.2300 97.2400 22.0000 96.0500 20.4700 97.0200 17.2000 96.2900 16.3000 97.3800 14.0500 98.1200 16.5300 97.3800 21.5200 95.5900 19.4100 97.1300 20.0900 92.5500]; %% III. 计算距离矩阵 D = Distance(X); %计算距离矩阵 n = size(D,1); %城市的个数 %% IV. 初始化参数 T0 = 1e10; % 初始温度 Tf = 1e-30; % 终止温度 %L = 2; % 各温度下的迭代次数 q = 0.9; % 降温速率 Time = ceil(double(solve([num2str(T0) '*(0.9)^x = ', num2str(Tf)]))); % 计算迭代的次数 T0 * (0.9)^x = Tf count = 0; % 初始化迭代计数 Obj = zeros(Time, 1); % 目标值矩阵初始化 path = zeros(Time, n); % 每代的最优路线矩阵初始化 %% V. 随机产生一个初始路线 S1 = randperm(n); DrawPath(S1, X) % 画出初始路线 disp('初始种群中的一个随机值:') OutputPath(S1); % 用箭头的形式表述初始路线 rlength = PathLength(D, S1); % 计算初始路线的总长度 disp(['总距离:', num2str(rlength)]); %% VI. 迭代优化 while T0 > Tf count = count + 1; % 更新迭代次数 %temp = zeros(L,n+1); % 1. 产生新解 S2 = NewAnswer(S1); % 2. Metropolis法则判断是否接受新解 [S1, R] = Metropolis(S1, S2, D, T0); % Metropolis 抽样算法 % 3. 记录每次迭代过程的最优路线 if count == 1 || R < Obj(count-1) % 如果当前温度下的距离小于上个温度的距离,记录当前距离 Obj(count) = R; else Obj(count) = Obj(count-1); end path(count,:) = S1; % 记录每次迭代的路线 T0 = q * T0; % 以q的速率降温 end %% VII. 优化过程迭代图 figure plot(1: count, Obj) xlabel('迭代次数') ylabel('距离') title('优化过程') grid on %% VIII. 绘制最优路径图 DrawPath(path(end, :), X) % path矩阵的最后一行一定是最优路线 %% IX. 输出最优解的路线和总距离 disp('最优解:') S = path(end, :); p = OutputPath(S); disp(['总距离:', num2str(PathLength(D, S))]);
其中为了更好的维护代码和使代码更加易读,对一些功能进行了函数包装,如下:
(1) Distance.m:计算两城市之间的距离
function D = Distance(citys) %% 计算两两城市之间的距离 % 输入:各城市的位置坐标(citys) % 输出:两两城市之间的距离(D) n = size(citys, 1); D = zeros(n, n); for i = 1: n for j = i + 1: n D(i, j) = sqrt((citys(i, 1) - citys(j, 1))^2 + (citys(i, 2) - citys(j, 2))^2); D(j, i) = D(i, j); end end
(2) DrawPath.m:将路径用图的形式表示出来
function DrawPath(Route, citys) %% 画路径函数 % 输入 % Route 待画路径 % citys 各城市坐标位置 %画出初始路线 figure plot([citys(Route, 1); citys(Route(1), 1)], [citys(Route, 2); citys(Route(1), 2)], 'o-'); grid on %给每个地点标上序号 for i = 1: size(citys, 1) text(citys(i, 1), citys(i, 2), [' ' num2str(i)]); end text(citys(Route(1), 1), citys(Route(1), 2), ' 起点'); text(citys(Route(end), 1), citys(Route(end), 2), ' 终点');
(3) Metropolis.m:Metropolis准则判定
function [S,R] = Metropolis(S1, S2, D, T) %% 输入 % S1: 当前解 % S2: 新解 % D: 距离矩阵(两两城市的之间的距离) % T: 当前温度 %% 输出 % S: 下一个当前解 % R: 下一个当前解的路线距离 R1 = PathLength(D, S1); % 计算S1路线长度 n = length(S1); % 城市的个数 R2 = PathLength(D,S2); % 计算S2路线长度 dC = R2 - R1; % 计算能力之差 if dC < 0 % 如果能力降低 接受新路线 S = S2; % 接受新解 R = R2; elseif exp(-dC/T) >= rand % 以exp(-dC/T)概率接受新路线 S = S2; R = R2; else % 不接受新路线 S = S1; R = R1; end end
(4) NewAnswer.m: 随机产生新解的方式
function S2 = NewAnswer(S1) %% 输入 % S1: 当前解 %% 输出 % S2: 新解 n = length(S1); S2 = S1; a = round(rand(1, 2) * (n - 1) + 1); %产生两个随机位置,用于交换 W = S2(a(1)); S2(a(1)) = S1(a(2)); S2(a(2)) = W;
(5) OutputPath.m: 用箭头的形式描绘出路径方向
function p = OutputPath(Route) %% 输出路径函数 % 输入: 路径(R) %给R末端添加起点即R(1) Route = [Route, Route(1)]; n = length(Route); p = num2str(Route(1)); for i = 2: n p = [p, ' —> ', num2str(Route(i))]; end disp(p)
(6) PathLength.m: 算出总路径的长度
function Length = PathLength(D, Route) %% 计算起点与终点之间的路径长度 % 输入: % D 两两城市之间的距离 % Route 路线 Length = 0; n = length(Route); for i = 1: (n - 1) Length = Length + D(Route(i), Route(i + 1)); end Length = Length + D(Route(n), Route(1));
运行了上述代码后,得到的结果如下:
初始种群中的一个随机值: 11 —> 10 —> 14 —> 9 —> 5 —> 3 —> 7 —> 4 —> 8 —> 1 —> 2 —> 13 —> 12 —> 6 —> 11 总距离:62.7207 最优解: 9 —> 11 —> 8 —> 13 —> 7 —> 12 —> 6 —> 5 —> 4 —> 3 —> 14 —> 2 —> 1 —> 10 —> 9 总距离:29.3405
也就是通过这个算法,它给我们找到的一个最短的距离是29.3405。我们不能保证这个答案一定是最短的,这就是这个算法的缺点了。如果你迭代的次数够多,得到的结果就越接近正确的结果。但是迭代次数过多就会需要很长的计算时间,所以要适当选择这个迭代次数。
最开始随机选的路径和算出来的最优解的一个路径分别如下:
另外,随着迭代次数的增加,总长度的变化图如下:
可以看出,随着我们不断的迭代,距离是一直在不断下降的。因此模拟退火算法,从某种意义上而言,在近似解决一些问题方面,还是起了很大的作用。。