注: 本博客仅做本人笔记参考使用。
推荐博客:
https://blog.csdn.net/lyxleft/article/details/82982567#%E6%A8%A1%E6%8B%9F%E9%80%80%E7%81%AB%E7%AE%97%E6%B3%95%E6%A6%82%E8%BF%B0
https://www.cnblogs.com/Qling/p/9326109.html
https://www.cnblogs.com/CH42e/p/12262851.html
模拟退火算法(Simulated Annealing,简称SA)的思想最早是由Metropolis等提出的。其出发点是基于物理中固体物质的退火过程与一般的组合优化问题之间的相似性。模拟退火法是一种通用的优化算法,其物理退火过程由以下三部分组成:
(1) 加温过程。其目的是增强粒子的热运动,使其偏离平衡位置。当温度足够高时,固体将熔为液体,从而消除系统原先存在的非均匀状态。
(2) 等温过程。对于与周围环境交换热量而温度不变的封闭系统,系统状态的自发变化总是朝自由能减少的方向进行的,当自由能达到最小时,系统达到平衡状态。
(3) 冷却过程。使粒子热运动减弱,系统能量下降,得到晶体结构。
加温过程相当于对算法设定初值,等温过程对应算法的Metropolis抽样过程,冷却过程对应控制参数的下降。这里能量的变化就是目标函数,我们要得到的最优解就是能量最低态。其中Metropolis准则是SA算法收敛于全局最优解的关键所在,Metropolis准则以一定的概率接受恶化解,这样就使算法跳离局部最优的陷阱。
SA算法的Metropolis准则允许接受一定的恶化解,具体来讲,是以一定概率来接受非最优解。举个例子,相当于保留一些“潜力股”,使解空间里有更多的可能性。对比轮盘赌法,从概率论来讲,它是对非最优解给予概率0,即全部抛弃。
模拟退火本身是求一个最小值问题,但可以转化为求最大值问题,只需要对目标函数加个负号或者取倒数。
初始化:取初始温度T0足够大,令T = T0,任取初始解S1。
对比GA、ACA、PSO之类的群优化算法,需要在解空间中产生多个个体,再继续寻优。而SA算法只需要一个点即可。
对当前温度T,重复第(3)~(6)步。
对当前解S1随机扰动产生一个新解S2。
此处随机的扰动没有定义。结合实际例子做选择。
计算S2的增量df = f(S2) - f(S1),其中f(S1)为S1的代价函数。
代价函数相当于之前群优化算法中讲的适应度函数。
若df < 0,则接受S2作为新的当前解,即S1 = S2;否则,计算S2的接受概率exp(-df/T), T是温度。随机产生(0,1)区间上均匀分布的随机数rand,若exp(-df/T) > rand,也接受S2作为新的当前解S1 = S2,否则保留当前解S1。
这是SA算法的核心部分,即有一定概率接受非最优解。
如果满足终止条件Stop,则输出当前解S1为最优解,结束程序,终止条件Stop通常取为在连续若干个Metropolis链中新解S2都没有被接受时终止算法或者是设定结束温度。否则按衰减函数衰减T后返回第(2)步,即被接受的新的解一直在产生,则我们要对问题进行降温,使得非最优解被接受的可能不断降低,结果愈发收敛于最优解。
• 与遗传算法、粒子群优化算法和蚁群算法等不同,模拟退火算法不属于群优化算法,不需要初始化种群操作。
• 收敛速度较慢。因为1)它初始温度一般设定得很高,而终止温度设定得低,这样才符合物体规律,认为物质处于最低能量平衡点;2)它接受恶化解,并不是全程都在收敛的过程中。这一点可以类比GA中的变异,使得它不是持续在收敛的,所以耗时更多一些。
• 温度管理(起始、终止温度)、退火速度(衰减函数)等对寻优结果均有影响。比如T的衰减速度如果太快,就会导致可能寻找不到全局最优解。
首先看下面这个图,假如我有一个函数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), 以一定概率接收该移动,并随时间推移概率降低。
而这个概率,就是参考了金属冶炼的退火过程,这也正是这个算法被称为模拟退火算法的原因。
在金属冶炼的退火过程中,在温度为T时,出现能量差为dE的粒子的降温的概率为P(dE),表示为:
P(dE)=exp(dEkT)
其中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后面。
以下代码是模拟退火算法用于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。我们不能保证这个答案一定是最短的,这就是这个算法的缺点了。如果你迭代的次数够多,得到的结果就越接近正确的结果。但是迭代次数过多就会需要很长的计算时间,所以要适当选择这个迭代次数。
最开始随机选的路径和算出来的最优解的一个路径分别如下:
另外,随着迭代次数的增加,总长度的变化图如下:
可以看出,随着我们不断的迭代,距离是一直在不断下降的。因此模拟退火算法,从某种意义上而言,在近似解决一些问题方面,还是起了很大的作用。。
求一元函数:x = [1,2]范围内 y = sin(10pix) / x 的极值
二元函数:在x,y都是[-5,5]范围内找z = x.^2 + y.^2 - 10cos(2pix) - 10cos(2piy) + 20 的极值
上面是我们提前获得的ground-truth(用main.m,代码在下文)。下面我们假装不知道这个结果的,用模拟退火方法来搜索:
首先定义我们在SA算法中需要用到的代价函数fitness.m:
function fitnessVal = fitness( x )
%一元函数优化:
fitnessVal = sin(10*pi*x) / x; %求最小值
% fitnessVal = -1 * sin(10*pi*x) / x; 用模拟退火求最大值,可以加个负号或者弄个倒数!
%二元函数优化:
% fitnessVal = -1 * (x(1)^2 + x(2).^2 - 10*cos(2*pi*x(1)) - 10*cos(2*pi*x(2)) + 20);
end
主程序main.m,用于我们直观看图先获得ground-truth:
%% I. 清空环境变量
clear all
clc
%% II. 一元函数优化
x = 1:0.01:2;
y = sin(10*pi*x) ./ x;
figure
plot(x,y,'linewidth',1.5)
ylim([-1.5, 1.5])
xlabel('x')
ylabel('y')
title('y = sin(10*pi*x) / x')
hold on
%%
% 1. 标记出最大值点
[maxVal,maxIndex] = max(y);
plot(x(maxIndex), maxVal, 'r*','linewidth',2)
text(x(maxIndex), maxVal, {[' X: ' num2str(x(maxIndex))];[' Y: ' num2str(maxVal)]})
hold on
%%
% 2. 标记出最小值点
[minVal,minIndex] = min(y);
plot(x(minIndex), minVal, 'ks','linewidth',2)
text(x(minIndex), minVal, {[' X: ' num2str(x(minIndex))];[' Y: ' num2str(minVal)]})
%% III. 二元函数优化
[x,y] = meshgrid(-5:0.1:5,-5:0.1:5);
z = x.^2 + y.^2 - 10*cos(2*pi*x) - 10*cos(2*pi*y) + 20;
figure
mesh(x,y,z)
hold on
xlabel('x')
ylabel('y')
zlabel('z')
title('z = x^2 + y^2 - 10*cos(2*pi*x) - 10*cos(2*pi*y) + 20')
%%
% 1. 标记出最大值点
maxVal = max(z(:));
[maxIndexX,maxIndexY] = find(z == maxVal);
for i = 1:length(maxIndexX)
plot3(x(maxIndexX(i),maxIndexY(i)),y(maxIndexX(i),maxIndexY(i)), maxVal, 'r*','linewidth',2)
text(x(maxIndexX(i),maxIndexY(i)),y(maxIndexX(i),maxIndexY(i)), maxVal, {[' X: ' num2str(x(maxIndexX(i),maxIndexY(i)))];[' Y: ' num2str(y(maxIndexX(i),maxIndexY(i)))];[' Z: ' num2str(maxVal)]})
hold on
end
现在正式开始模拟退火算法环节。在MATLAB命令中输入:optimtool,打开工具箱。
boltzmann就是对应Metropolis准则的退火方式。
选择可视化的输出的项目:
对于二元函数,在工具箱中的设置方法大同小异:记得先在fitness.m中修改我们的目标函数!
运行后即可得到结果。