Simulate Anneal模擬退火算法,是一種用於獲得最優解的隨機化算法。c++
若是能夠打一手漂亮的隨機化搜索,也許當你面對束手無策的神仙題時就有一把趁手的兵器了。算法
這篇題解將教你什麼?SA的基本思路,何時能用SA。函數
標題是淺談,因此本篇博客參雜了些許我的簡介,如有疑問或異議,歡迎提出指正。學習
我也很感謝大家給出的建議,它們真的能讓我變好、變強。優化
那麼咱們進入本篇正題。spa
1. 什麼是模擬退火:設計
模擬退火是一種在廣大的搜索空間尋找最優解的隨機化算法。咱們看名字就明白,這個算法實在模擬物理中退火的過程。知識不少時候都是相通的,咱們學習的大部分知識都是有用的。code
爲何經過模擬退火的過程能夠得出最優解呢?在物理中,固體物質的退火過程和通常的組合優化問題有着很高的類似度。blog
2. 模擬退火基本要素:遊戲
知足這些你就能夠將SA算法愉快地嵌入你的程序裏了。
第一個要素就是狀態空間和狀態的產生函數,咱們須要有一個搜索空間,並且範圍較大。
什麼是搜索空間呢?咱們學習搜索的時候應該接觸過,咱們的搜索算法本質就是在問題的求解空間中進行遍歷,尋找最優解。模擬退火的狀態空間也相似,它就是咱們自定義出來的最優解的有限集合。
咱們光有狀態空間還不夠啊,咱們真正須要的是狀態。學函數的時候,老師就說過了,一個函數要有「域」。咱們的狀態空間就是狀態產生函數的「域」。而咱們若是想寫一個好的退火,咱們的搜索空間要足夠大,足夠讓函數生成不一樣的新解。
第二個就是候選解,咱們在狀態空間內經過生成的隨機數在必定密度內隨機選擇咱們的候選解。
其實還有一條是機率分佈,大部分採用均勻分佈,少數狀況咱們會用到指數分佈。
至於狀態轉移機率,我目前接觸到的SA算法通通採用了Metropolis準則,我接下來會介紹它。
3. 模擬退火基本流程:
一. 由一個狀態產生函數從當前解產生一個新解,通常採用增量構造(即由原解加上/減去產生函數產生的值)來獲得。
二. 計算新解的目標函數差Δt'。
三. 判斷新解是否被接受,
這裏介紹Metropolis準則:若Δt'<0,咱們接受它,不然以exp(-Δt'/T)的多項式機率接受它。
**T是咱們模擬退火過程當中的溫度**
四. 當新解被接受時,用新解代替當前解,不然繼續下一輪試驗。
4. 模擬退火中的參數控制:
調參能夠說是SA算法最難的部分,也是決定你的算法得分率的部分。
這裏分享一下神仙FlashHu(LCT導師Orz)寫SA時調參的經驗,我總結了一下就是這樣:
對於eps,能夠根據數據範圍和精度要求粗略獲得eps的大概大小,手動微調能夠獲得最終的eps。
對於T和ΔT(溫度的變化率,通常在0.95-0.99間),先開大一點跑出最優解,而後一邊退火一邊輸出當前的T、ans等信息,大體感覺解的降低速率,越均勻表示參數越好(神仙稱之爲觀察法)
5. 注意事項:
SA可能會陷入當前解卡在局部最小的狀況,也就是這樣:
圖中,紅色是咱們的當前解,藍色是咱們的最優解,紅色的線表明SA算法獲得的新解,咱們會發現,若是參數不佳,咱們就有可能出現卡在局部最優解的狀況。自己算法的設計就有避免這一種狀況,還記得嗎?Metropolis準則,就算這不是最優解,咱們也以必定機率接受它(答案不更新),就是爲了防止這種狀況,然而在參數不佳的狀況下,它仍可能發生。因此咱們要改進對溫度的控制方式。
這裏仍是以那道經典到不能再經典的模擬退火模板作例題:平衡點/吊打xxx
這裏直接貼上代碼,關於算法中須要注意的地方我會打上註釋。
#include<bits/stdc++.h> #define down 0.997//ΔT,模擬徐徐降溫 using namespace std; inline int read(){ int data=0,w=1;char ch=0; while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar(); if(ch=='-')w=-1,ch=getchar(); while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar(); return data*w; } int n; struct point{ int x,y,w; }object[2019];//物體信息 double ansx,ansy,answ;//答案 double energy(double x,double y){//物理學知識:能量總和越小越穩定 double r=0,dx,dy; for(int i=1;i<=n;i++){ dx=x-object[i].x;dy=y-object[i].y; r+=sqrt(dx*dx+dy*dy)*object[i].w;//力臂乘重力 } return r; } void SA(){ double t=3e3+10;//初始溫度要高 while(t>1e-15){//exp略大於0 double ex=ansx+(rand()*2-RAND_MAX)*t; double ey=ansy+(rand()*2-RAND_MAX)*t; double ew=energy(ex,ey); double de=ew-answ; if(de<0){//此答案更優 ansx=ex;ansy=ey;answ=ew; }else if(exp(-de/t)*RAND_MAX>rand()){//Metropolis準則,以多項式機率接受 ansx=ex;ansy=ey; }t*=down;//逐步降溫 } } void solve(){ SA();SA();SA();SA();SA(); } int main(){ n=read(); for(int i=1;i<=n;i++){ object[i].x=read();object[i].y=read();object[i].w=read(); ansx+=object[i].x;ansy+=object[i].y; } ansx/=n;ansy/=n;//設平均值爲初值 answ=energy(ansx,ansy); solve(); printf("%.3lf %.3lf\n",ansx,ansy); return 0; }
遊戲結束。