模擬退火算法簡介

模擬退火算法

簡介:它用來尋找全局最優解 ; 它是一種貪心算法,但是與基礎貪心算法不同的是以一定的概率來接受一個比當前解要差的解,因此有可能會跳出這個局部的最優解,達到全局的最優解;  它是一種隨機算法(蒙特卡洛算法),採樣越多,越近似最優解

 

例:

對於C點,很容易找到A是一個局部最優解,如果是一般的貪心算法,到A就停止了;但是對於模擬退火算法,有機率跳出A,到E,到D,最後到達B。

 

模擬退火思想:

模擬退火的思想來源於金屬冶煉的退火過程(概率的計算)。

根據熱力學的原理,在溫度爲T時,出現能量差爲dE的降溫的概率爲P(dE),表示爲:P(dE) = exp( dE/(kT) )

  • 因爲是退火,所以dE<0
  • 當前溫度越高,出現一次能量差爲dE的降溫的概率就越大;當前溫度越低,則出現降溫的概率就越小。
  • 溫度越高,降溫概率大,當發生降溫,降溫的機率減少,最終趨於穩定

這個算法的關鍵在於大量的隨機,最後導致變化概率很低的時候,就難以發生變化

如果想要進一步提高模擬退火的性能,就需要考慮其參數的設置。

 

算法核心描述:

前提:計算F(x)在一個區間的最大值。

  • 當到達狀態i,那麼如何選取第i+1個狀態?

可以採用隨機法,在狀態i附近隨意選取一個狀態。 (因此模擬退火要求大量地採樣)

  •  對於狀態i和狀態i+1,如何採取動作?

如果F(i+1)>F(i),那麼說明應該變爲狀態轉變爲i+1  ; (在這裏要求尋找最大值)

如果F(i+1)<F(i),那麼當概率滿足一定條件時進行移動,而且這個概率隨着時間推移降低(降低才能趨於穩定)

  • 這個概率的計算以及概率要滿足什麼條件時,才能進行移動?

概率的計算公式:P(dE) = exp( dE/(kT) )

  • k是一個常數,一般選1(如果不要太考慮算法的性能,一般對常數選取一般值就OK)
  • dE=F(i+1)-F(i)
  • T:跟前面提到的穩定性有關,越到後面T越小,因爲dE爲負數,所以P(dE)越小;
  • 因爲dE爲負數,所以P(dE)是一個在0到1之間的小數
  • 要理解這個公式,最好理解一下它的物理含義。

概率滿足的條件:大於一個隨機產生的概率就行。

  • 既然需要大量地採樣(循環),什麼時候結束?

對於概率計算的參數T,T越小,變化的概率越小,越穩定,但是T如果太小,需要消耗大量的資源,規定一個合適的臨界值,當T小於該臨界值,那麼結束。T可以通過乘以一個比例係數或者減法進行減小,這個減小的幅度不要太大。T減小的條件:可以每一次都減小,也可以只有當發生退火(從i狀態跳到i+1狀態的時候)的情況進行減小。

 

我認爲,如果不太在意算法的性能,到這裏,模擬退火的基本情況已經OK了。

算法僞代碼:

/*
以求最大值爲例

T:概率計算公式的T
T_min:截止條件,T越小越穩定,到達這裏說明T<T_min,說明已經很穩定(最優解)
dE:差值

*/
while(T>T_min){
    //通過i獲得i+1狀態

    dE=F(i+1)-F(i)
    
    //更貼近最大值,選擇最新的狀態
    if(dE>0){
        F(i)=F(i+1)  
    }

    else{
        // 當計算得到的概率(能夠下降的概率)大於一個隨機值,選取最新的狀態
        if(exp(dE/T)>rand(0,1)){
            F(i)=F(i+1)
        }
        // 否則,保持當前狀態
    }

    /* 
    r略小於1,不能太小,太小,下降的太快,可能找不到最優解; 
    另外可以對這個T=T*r在符合某些條件的情況下執行,這樣下降的更慢
    */
    T=T*r    
}

 

算法實例:

"""
模擬退火法計算F(x)=sin(x*x)+2*cos(2x)在[5,8]的最大值


"""
import numpy as np
import math

# 定義域x從5到8閉區間
BOUND = [5, 8]

# temper,temper_lower決定模擬退火的範圍;
# alpha決定模擬退火的速率; temper=temper*alpha
# beta控制模擬退火的自變量的變化程度;
temper = 1e2
temper_lower = 1e-3
alpha = 0.98
beta = 1

# 計算F(x)的值
def F(x):
    return math.sin(x * x) + 2.0 * math.cos(2.0 * x)

# 根據dE和T(f和f_new)
def simulatedAnnealing(dE,T):
    if dE > 0:
        return 1
    else:
        # 比較概率對象選擇rand,exp(de/T)體現了穩定程度(越穩定,概率越低,越接近最值)
        if math.exp(dE / T) > np.random.rand():
            return 1
        else:
            return 0


if __name__=="__main__":
    # 一個初始隨機值
    x = np.random.rand() * (BOUND[1] - BOUND[0]) + BOUND[0]
    f = F(x)
    counter = 0  # 記錄模擬退火的次數

    # 退出條件:temper <= temper_lower
    while temper > temper_lower:

        # x到x_new的變化,隨機變化,可左可右; 如果一個方向OK?
        delta = (np.random.rand() - 0.5) * beta
        x_new = x + delta
        # 保證x_new在BOUND[0]~BOUND[1]之間
        if x_new < BOUND[0]:
            x_new = x_new + BOUND[1] - BOUND[0]
        if x_new > BOUND[1]:
            x_new = x_new - BOUND[1] + BOUND[0]

        # 比較f_new與f,進行決策,flag=1,變化,flag=0,不變
        f_new = F(x_new)
        dE = f_new - f
        flag = simulatedAnnealing(dE, temper)
        if (flag):
            f = f_new
            x = x_new

        # 當dE>0,滿足解的情況temper下降
        # if dE > 0:
            temper = temper * alpha

        counter += 1
        # 經過測試,後面的概率接近於0(1e-100)
        print('current x {}, y {},tmp {},counter {}'.format(x, f, temper, counter))
"""
問題:f(x)=x*sin(10π*x)+1.0,當x在[-1,2]範圍內的最大值
"""

from numpy import *

# 自變量範圍
ROUND = [-1, 2]

# 模擬退火的Temper和下降速度
temper=1e2
temper_lower=1e-3
alpha=0.98


# 計算f(x)的值
def f(x):
    return x*sin(10*pi*x)+1.0


def simulatedAnnealing(dE,T):
    if dE>0:
        return 1
    else:
        if exp(dE/T)>random.rand():
            return 1
        else:
            return 0



if __name__ == '__main__':
    # 計算初始值
    x=random.rand()*(ROUND[1]-ROUND[0])+ROUND[0]
    y=f(x)

    count=0 # 統計次數

    while temper>temper_lower:
        x_new=(random.rand()-0.5)+x

        if x_new<ROUND[0]:
            x_new=x_new+ROUND[1]-ROUND[0]
        if x_new>ROUND[1]:
            x_new=x_new+ROUND[0]-ROUND[1]

        y_new=f(x_new)
        dE=y_new-y

        if simulatedAnnealing(dE,temper):
            x=x_new
            y=y_new

        if dE>0 :
            temper=temper*alpha

        count+=1
        print("current x="+str(x)+",y="+str(y)+",count="+str(count))




 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章