模拟退火

今天闲来无事,写点东西吧

模拟退火

  • 首先模拟退火是个什么东西呢?

    模拟退火算法(Simulated Annealing,SA)最早的思想是由N. Metropolis 等人于1953年提出。1983 年,S. Kirkpatrick 等成功地将退火思想引入到组合优化领域。它是基于Monte-Carlo迭代求解策略的一种随机寻优算法,其出发点是基于物理中固体物质的退火过程与一般组合优化问题之间的相似性。模拟退火算法从某一较高初温出发,伴随温度参数的不断下降,结合概率突跳特性在解空间中随机寻找目标函数的全局最优解,即在局部最优解能概率性地跳出并最终趋于全局最优。模拟退火算法是一种通用的优化算法,理论上算法具有概率的全局优化性能,目前已在工程中得到了广泛应用,诸如VLSI、生产调度、控制工程、机器学习、神经网络、信号处理等领域。
    ——来自百度
    也就是说,模拟退火是一种模拟物理上固体物质退火原理的一种求解最优解的算法,准确的说,模拟退火算法是一种全局优化的贪心算法,它会在贪心的时候一定概率接受一个更差的解,具有很好的跳出局部最优的能力,在算法后期则有良好的收敛性。

  • 那么模拟退火有什么用呢?

    模拟退火一般用于求解最优解,而且一般比较适合于小数据的最优解和大数据的近似最优解,总之就是解题的一个万能挂

  • 那么模拟退火算法的原理是什么呢?

    我们知道贪心算法是一个很方便写的算法,但是它也有很大的局限性:不一定是全局最优解。这个时候我们跳脱出来,就有可能跳出这个‘坑’,最终找到全局最优解,而‘跳出来’的步骤就是模拟退火的精髓,有可能的接受差解。

  • 那么模拟退火的实现?

首先来看一下模拟退火的主要步骤:

  1. 先初始化温度,当前解和当前答案
  2. 如果温度小于最终温度,跳7;否则跳3
  3. 重复执行4~5步L次
  4. 由当前解生成一个临时的新解,并计算新的答案
  5. 判断是否接受该临时解,接受则更新解和答案,不接受则回退到上个解
  6. 降温,跳2
  7. 结束

对于差解的判断和 这个解有多差 以及 当前温度 有关,解越差我们越不想要它,接受它的概率就小一些;贪心往往会在开始的时候陷入局部最优解,我们就要在开始的时候跳出局部最优,也就是通过走向较差的解,所 以开始的时候接受差解的概率要大一些,快结束的时候我们需要稳定在当前的最优解,接受差解的概率就小一些,所以我们模拟物理的退火原理,温度从高逐渐降低,对于接受差解概率的计算公式e^(delta/T)来说,新解答案的差异值为分子delta,是个负数(如果正数取负就行),答案越差,delta绝对值越大,指数越小,概率也越小;温度越低,指数越小,概率也越小,这就符合了我们求解的需要。
这样就可以写出伪代码了:

init();
while(T>T_end) do
    for(i:=1 to L)
        产生新解
        计算新解的函数值
        if(接受新解)    更新答案
        else    回退回上一个解
    降温

一般模拟退火的时候,可以再单独维护一个当前最优解(这次模拟退火中遇到过的最优解),我们还可以在求解问题时,多跑几遍模拟退火,取所有模拟退火的答案的最优值。

  • 关于模拟退火的产生新解

这是个难题,因为这就决定了模拟退火能解决的问题的种类多样性,也就是能不能做到每个题都能用模拟退火写一写,而且产生新解的方法也影响到算法的收敛速度,一个好的产生新解的方法对模拟退火算法有很大提高,具体的话还是要看具体题目的,但也可以总结出一些方法和规律,这里的话先留个坑,日后总结好了补上吧。

  • 关于模拟退火一个解的函数值的计算

这就根据具体题目决定了,一般来说是一个计算式或者模拟,也有一些题用到和其它算法的结合,比如DP,贪心,二分等等,可以看一下这道题。总之这个的话还是看你对题目本身的理解了。

  • 关于模拟退火的调参

模拟退火的参数主要有四个:初始温度T0,最终温度T_end,降温速度D,每个温度迭代次数一般T0要足够大,T_end较小,D较接近1但小于1,D越大,退火速度越慢,一般D每差一个数量级,程序运行时间是10倍左右,T0和T_end对程序耗时影响不大,如果程序过早陷入局部最优,可以考虑增大T0和D,如果最后精度不够可以减小T_end,还有一个玄学调参法,T0 * D ^ L * 0.7/理论最优解=0.002,具体证明参考这篇文章

最后放个参考的TSP问题的程序帮助大家理解qwq。

#include<cstdio>
#include<cstdlib>
#include<time.h>
#include<cmath>
#include<algorithm>
#define sqr(_) ((_)*(_))
using namespace std;
const int N=20;
const double T0=1e5;
const double T_end=1e-4;
const double D=1-3e-3;
const int L=100;
int n,now[N],st=clock();
double x[N],y[N],ans=1e9;
double dis(int a,int b) {return sqrt(sqr(x[a]-x[b])+sqr(y[a]-y[b]));}
double calc() {
    double res=0;
    for(int i=2;i<=n;++i)res+=dis(now[i-1],now[i]);
    return res+dis(now[n],now[1]);
}
double rand_f() {return double(rand())/double(RAND_MAX);}
bool RP_up(double delta,double t) {return delta>0||rand_f()<exp(delta/t);}
void SA() {
    for(int i=1;i<=n;++i)   now[i]=i;
    double T=T0,sum=calc();
    while(T>T_end) {
        for(int i=0;i<L;++i) {
            int u=rand()%n+1,v=rand()%n+1;
            swap(now[u],now[v]);
            double tsum=calc();
            if(RP_up(sum-tsum,T)) sum=tsum,ans=min(ans,sum);
            else    swap(now[u],now[v]);
        }T*=D;
    }
}
int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;++i)   scanf("%lf%lf",&x[i],&y[i]);
    while(clock()-st<0.95*CLOCKS_PER_SEC)SA();
    printf("%.2lf",ans);return 0;
}

蒟蒻第一次写博客,写的不好的地方请见谅,有错误和可以改进之处欢迎大家指出。

参考文献及推荐文章:
https://blog.csdn.net/georgesale/article/details/80631417
https://www.cnblogs.com/rvalue/p/8678318.html

posted @ 2018-10-02 20:33  こんにゃく  阅读(3008)  评论(1编辑  收藏  举报