近似算法
我們的問題是,給定一個亂七八糟的函數,求它在某個區域內的最大值(最小值)。
模擬退火算法
爬山
爬山算法是純粹的貪心算法。給定一個起始點,我們能爬到一個極大值。
while(1) { if(f(x+0.001) - f(x-0.001) > eps) x+=0.001; //如果向右走有利,則向右走 else if(f(x+0.001) - f(x-0.001) < -eps) x+=0.001; //如果向左走有利,則向左走 else goto finish; //已經爬到極大值 }
爬山的缺陷在於,它會陷入局部最優解,而難以爬到全局最優解。例如下圖。
我們把上面的x+0.001
之類的操作稱作“移動”。
經典模擬退火
模擬退火的思想在於,如果一個移動會使答案變得更優,我們就接受這個移動;否則我們以一定的概率接受這個移動。
聽起來很玄學。根據物理的那套理論,我們定義兩個東西:
- 溫度$(T)$。它隨着時間推移而逐漸降低。
- 增量$(E)$。它描述一次移動獲得的好處。從$x$移動到$x'$的增量定義為$f(x')-f(x)$,增量越大,往$x'$移動的優勢越大。
在模擬退火中,如果增量大於$0$,則直接接受這次移動;否則按下面的概率接受移動:
$$P = \exp(\frac{E}{T})$$
聽起來十分的玄學。然而它竟然可以得出精度比較好的解。偽代碼如下:
T=100.0; //初始溫度 for(int i=0;i<100;i++) //控制迭代次數 { tar=getPos(); //在x的周圍選一個點 E=f(tar)-f(x); if(E > eps) x=tar; //直接移動 else if(exp(E/T) > random(0,1)) x=tar; //接受移動 T=T*0.99; //降溫 }
遺傳算法
不妨假設有一大群兔子,它們均勻地分布在各種地方。
由於一些黑惡勢力的影響,每年只有位置最高的那100只兔子能活下來。
位置最高的那些兔子們繁衍生息,它們的后代有些比它們站得高,於是這些后代活了下來;其他后代被黑惡勢力搞死了。每年都只有站得最高的100只兔子能活下來。
在無盡的歲月后,這100只兔子想必都站在了世界上最高的山峰。
這個算法聽起來比模擬退火靠譜。現在它的實現過程如下:
- 先隨機產生100個點,均勻地分布在所求區間上。
- 取每兩個點的中位數,這樣我們共獲得了10000個點。
- 取出最高的100個點,然后開始新一輪迭代。
但是這會引發一個問題。例如下圖:
這樣的話,無論我們迭代多少次,總是找不到最高點(因為是取中位數)。這搞屁。
所以我們引入變異。每個數都有二進制表示,我們產生一個數之后,對它進行變異操作:二進制的每一位都以$p$的概率翻轉。
這樣的話,由於變異的存在,迭代若干次之后,最高點是找得到的。
那么問題來了。$p$到底取多少?
恭喜您打開了黑暗世界的大門——玄學調參數。由於我們很難給出一個很妙的$p$,遺傳算法變得比模擬退火還不靠譜。
如何玄學調參數呢?我們造一些區間比較小的數據,暴力求出答案,然后根據這些數據來調整$p$。猜很多個$p$的值,看哪個最好。我們只需要考慮$p<0.5$的情況,因為以p的概率翻轉
,和以p的概率不翻轉
是本質相同的。