前言
*模拟退火算法是机器学习算法中的一种简单的算法。
在进行随机梯度下降时,采用模拟退火更新学习率是选择之一。
概述
模拟退火算法来源于固体退火原理,是一种基于概率的算法,将固体加温至充分高,再让其徐徐冷却,加温时,固体内部粒子随温升变为无序状,内能增大,而徐徐冷却时粒子渐趋有序,在每个温度都达到平衡态,最后在常温时达到基态,内能减为最小。(来源:百度百科)
模拟退火基于贪心算法。与同样基于贪心算法的爬山算法(Hill Climbing)不同的是,模拟退火算法在一些情况下可能会克服陷入局部最优解而非找到全局最优解的情况。
爬山算法(Hill Climbing)的缺陷
以寻找最大值为例。上图中曲线最大值处于B点。如果采用爬山算法,会陷入这样一种困境:如果从上图曲线最左端点开始寻找,最后会找到A点,因为A点任意方向的临近值都小于它。同样的,如果从上图曲线最右端点开始寻找,最后会找到C点,因为C点任意方向的临近值都小于它。但是A、C点都只是局部最优,而非全局最优。
Metropolis算法
上面这种缺陷是由贪心算法导致的。
对此,N.Metropolis等人提出一种方法:
借助以上所说的基于概率的思想,我们可以在爬山时(例如此时处于下图P点)以一定的概率选择比当前所处位置低的点,从而让程序获得摆脱这种陷阱的概率到达B点。
模拟退火算法
再来巩固一遍:
模拟退火算法基于这样一种物理原理:一个高温物体降至常温,温度越高时降温的概率越大(降温越快),温度越低时降温的概率越小(降温越慢)。
模拟退火算法基于此种思想搜索,即多次降温(迭代),直到获得一个可行解。
在迭代过程中,模拟退火算法随机选择下一个状态,存在两种可能:
1.新状态更优,则接受新状态;
2.新状态更差,则以一定概率接收该状态,不过这个概率随时间推移逐渐降低。
主要步骤如下:
1.设置参数:温度
T
T
T、降温系数
d
T
dT
dT、评价函数
F
(
x
)
F(x)
F(x)。
2.迭代退火,直到温度降到所设温度(一般为0度)。
代码框架如下:
double T = 10000; // 起始温度
double dT = 0.98; // 降温系数,一般取 0.95 ~ 0.99 之间的数
const double eps = 1e-10; // 用来判断零,控制精度
double func(double x){ //评价函数
return (...);
}
double SA(){ // 模拟退火过程
double x = rand(); // 随机选择一个 x,根据情况需要控制范围
double n = func(x); // x 处的值
double ans = n;
while(T > eps){
double newx = x + (2 * rand() - RAND_MAX) * T; // [-RAND_MAX, RAND_MAX] 之间的随机数 * T 作为跳跃幅度
while(newx < 0 && newx > 100) newx = x + (2 * rand() - RAND_MAX) * T; // 确定新的 x 的范围,比如这里我要保证 x 介于 0 ~ 100 之间
double next = func(newx); // 新的 x 值对应的 y 值
ans = max(n, next); // 视情况而定何种比较,此处选取最大值
if(next - n > eps){ // 新状态更优,则接受新状态
x = newx;
n = next;
}else if(exp((next - n) / T) * RAND_MAX > rand()){ // 新状态更差,则以一定概率接收该状态,不过这个概率随时间推移逐渐降低
x = newx;
n = next;
}
T *= dT; // 降温
}
return ans;
}
值得注意的是,以上代码中
exp((next - n) / T) * RAND_MAX > rand()
是根据科学家们推断出来的判断概率的方法(简化后),即
w
=
e
d
e
l
t
a
K
T
w=e^{\frac{delta}{KT}}
w=eKTdelta
其中
d
e
l
t
a
delta
delta 即为
n
e
x
t
−
n
next-n
next−n,需是负数(
w
w
w 之所以乘以
R
A
N
D
_
M
A
X
RAND_\_MAX
RAND_MAX 是为了让其落在区间 [0, RAND_MAX] 中以便能够与rand()比较。因为C++中rand()产生一个介于区间 [0, RAND_MAX] 中的随机整数,此时
w
w
w 属于(0, 1)区间内)。
K
K
K 在这里取1即可。
应用
模拟退火算法的典型应用就是求最值。
如给定常数
c
c
c,求
F
(
x
)
=
l
n
(
x
)
x
+
c
F(x)=\frac{ln(x)}{x}+c
F(x)=xln(x)+c
在[0, 100]上的最大值。
代码如下:
#include <bits/stdc++.h>
using namespace std;
double T = 100;
double dT = 0.98;
const double eps = 1e-10;
double y;
double func(double x)
{
return (1.0 * log(x) / x) + y;
}
double SA()
{
double x = rand() % 100;
double n = func(x);
double ans = n;
while(T > eps)
{
double newx = x + (2 * rand() - RAND_MAX) * T;
//while(newx < 0 && newx > 100) newx = x + (2 * rand() - RAND_MAX) * T;
if(newx >= 0 && newx <= 100)
{
double next = func(newx);
ans = max(n, next);
if(next - n > eps)
{
x = newx;
n = next;
}
else if(exp((next - n) / T) * RAND_MAX > rand())
{
x = newx;
n = next;
}
}
T *= dT;
}
return ans;
}
int main()
{
while(scanf("%lf", &y) != EOF)
{
printf("%.4f\n", SA());
}
return 0;
}
结果:
66
66.3679
^Z
Process returned 0 (0x0) execution time : 3.703 s
Press any key to continue.
验证:
结果 1 e + 66 \frac{1}{e}+66 e1+66 约等于66.3679。