1. 程式人生 > 其它 >「筆記」模擬退火

「筆記」模擬退火

目錄

寫在前面

感謝 caq 的傾情講解

模擬退火是個隨機化演算法,正確性有一定保證,但如果你想我一樣臉黑的話......

實測模擬退火做多了 rp 會掉

正文

簡介

模擬退火是一種隨機化演算法,當一個問題的方案數量極大(甚至是無窮的)而且不是一個單峰函式時,我們常使用模擬退火求解。- Oi-wiki

什麼是退火?

退火是一種金屬熱處理工藝,指的是將金屬緩慢加熱到一定溫度,保持足夠時間,然後以適宜速度冷卻。目的是降低硬度,改善切削加工性;消除殘餘應力,穩定尺寸,減少變形與裂紋傾向;細化晶粒,調整組織,消除組織缺陷。準確的說,退火是一種對材料的熱處理工藝,包括金屬材料、非金屬材料。而且新材料的退火目的也與傳統金屬退火存在異同。---百度百科

扯遠了。

這個演算法就是在溫度不斷降低的過程中,不斷地從當前位置尋找別的位置進行計算,溫度越低,也就是它的動能越小時,位置就會變化的越小,最後逐漸停留在最優解(或者附近)

演算法流程

每次隨機一個新的狀態,如果狀態更優就更新答案,否則以一定概率接受這個狀態。

Metropolis準則

以求最小值為例。

  • 如果 \(\Delta E < 0\),說明當前解更優,直接更新即可

  • 否則,如果

\[e^{\frac{-\Delta E}{T}} > \frac{\text{rand()}}{\text{RAND_MAX}} \]

就接受這個狀態。

  • 否則 直接跳過。

為什麼?
第一步因為是最優解所以一定選擇更新答案
第二步後邊的是一個隨機值我們暫且不論。
考慮整個退火過程,
假設溫度 \(T\)

不變,新的解越劣,\(\Delta E\) 越大,左項的值越小,接受的概率也越小。
假設 \(\Delta E\) 不變,隨著溫度的下降,求解的範圍也趨於穩定,\(T\) 越小,左項得值也越小,接受的概率也越小

扔一張圖可能會更好理解:

聽上去很扯 ,但它還是有一定的正確性的。

SA 函式

通常降溫係數 \(d\) 是一個很接近 \(1\) 的數,終止溫度 \(T_0\) 是一個很接近 \(0\) 的數

這裡給一個虛擬碼:

const double lim = ... // 溫度最小值,通常為 1e-10 左右
const double d = ... // 變化係數,通常為 0.996 左右
void SA() {
    double T = ... // 初始溫度,通常為 2021 左右
    while(T > lim) {
        ... // 獲取一個隨機的位置
        now = calc(); // 計算當前位置的答案 
        del = now - ans; // 計算 變化量
        if(del < 0) { // 以最小值為例
            ans = now; // 更新答案
            ...  // 更新答案和中間量的狀態
        } else if(exp(-del/T) > (double)rand()/RAND_MAX) {
            ...  // 一定概率選擇當前當前狀態
        } 
        T *= d; // 降溫
    }
}

計算函式 calc

依據題目而定,這裡不給出

一些技巧

如果想要隨機一個無限大平面內的一個點,可以這樣:

double nowx = limx + ((rand() << 1) - RAND_MAX) * T;
double nowy = limy + ((rand() << 1) - RAND_MAX) * T;

其中 nowx,nowy 是我們隨機的位置, limx, limy 是我們一箇中間狀態的位置(注意不是答案的位置),
後面的那一坨剛好對應著溫度越小變化越小的實際情況。


我們有時為了使得到的解更有質量,會在模擬退火結束後,以當前溫度在得到的解附近多次隨機狀態,嘗試得到更優的解(其過程與模擬退火相似)。


模擬退火是個隨機的演算法,執行次數越多獲得的解越有可能更優,所以我們可以執行多遍 SA 函式。至於如何控制時間?

while((double)clock()/CLOCKS_PER_SEC < 0.90) SA();

上面這個程式碼控制時間在 \(0.90s\) 左右,如果時間限制為 \(1s\),而每次 SA 函式執行時間略長時,就要小心可能會 \(\text{T}\) 掉了。


如果一個程式碼不行,就考慮換個種子吧。

srand(...);

為了獲得更精確的解,也可以把 \(d\)\(T_0\) 調的更精準一點

const double d = 0.996 -> 0.99996;
const double lim = 1e-10 -> 1e-15;

還有,隨機亂搞一些 初溫,終溫,降溫係數 也是可以的。

例題

咕咕咕。。。