今天閑來無事,寫點東西吧
模擬退火
-
首先模擬退火是個什么東西呢?
模擬退火算法(Simulated Annealing,SA)最早的思想是由N. Metropolis 等人於1953年提出。1983 年,S. Kirkpatrick 等成功地將退火思想引入到組合優化領域。它是基於Monte-Carlo迭代求解策略的一種隨機尋優算法,其出發點是基於物理中固體物質的退火過程與一般組合優化問題之間的相似性。模擬退火算法從某一較高初溫出發,伴隨溫度參數的不斷下降,結合概率突跳特性在解空間中隨機尋找目標函數的全局最優解,即在局部最優解能概率性地跳出並最終趨於全局最優。模擬退火算法是一種通用的優化算法,理論上算法具有概率的全局優化性能,目前已在工程中得到了廣泛應用,諸如VLSI、生產調度、控制工程、機器學習、神經網絡、信號處理等領域。
——來自百度
也就是說,模擬退火是一種模擬物理上固體物質退火原理的一種求解最優解的算法,准確的說,模擬退火算法是一種全局優化的貪心算法,它會在貪心的時候一定概率接受一個更差的解,具有很好的跳出局部最優的能力,在算法后期則有良好的收斂性。 -
那么模擬退火有什么用呢?
模擬退火一般用於求解最優解,而且一般比較適合於小數據的最優解和大數據的近似最優解,
總之就是解題的一個萬能掛。 -
那么模擬退火算法的原理是什么呢?
我們知道貪心算法是一個很方便寫的算法,但是它也有很大的局限性:不一定是全局最優解。這個時候我們跳脫出來,就有可能跳出這個‘坑’,最終找到全局最優解,而‘跳出來’的步驟就是模擬退火的精髓,有可能的接受差解。
-
那么模擬退火的實現?
首先來看一下模擬退火的主要步驟:
- 先初始化溫度,當前解和當前答案
- 如果溫度小於最終溫度,跳7;否則跳3
- 重復執行4~5步L次
- 由當前解生成一個臨時的新解,並計算新的答案
- 判斷是否接受該臨時解,接受則更新解和答案,不接受則回退到上個解
- 降溫,跳2
- 結束
對於差解的判斷和 這個解有多差 以及 當前溫度 有關,解越差我們越不想要它,接受它的概率就小一些;貪心往往會在開始的時候陷入局部最優解,我們就要在開始的時候跳出局部最優,也就是通過走向較差的解,所 以開始的時候接受差解的概率要大一些,快結束的時候我們需要穩定在當前的最優解,接受差解的概率就小一些,所以我們模擬物理的退火原理,溫度從高逐漸降低,對於接受差解概率的計算公式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