深度學習經典算法 | 模擬退火算法詳解

模擬退火算法基本思想

現代的模擬退火算法形成於20世紀80年代初,其思想源於固體的退火過程,即將固體加熱至足夠高的溫度,再緩慢冷卻。升溫時,固體內部粒子隨溫度升高變爲無序狀,內能增大,而緩慢冷卻時粒子又逐漸趨於有序,從理論上講,如果冷卻過程足夠緩慢,那麼冷卻中任一溫度時固體都能達到熱平衡,而冷卻到低溫時將達到這一低溫下的內能最小狀態。

在這一過程中, 任一恆定溫度都能達到熱平衡是個重要步驟, 這一點可以用MonteCarlo算法模擬,不過其需要大量採樣,工作量很大。但因爲物理系統總是趨向於能量最低,而分子熱運動則趨向於破壞這種低能量的狀態,故而只需着重取貢獻比較大的狀態即可達到比較好的效果, 因而1953年Metropolis提出了這樣一個重要性採樣的方法, 即設從當前狀態i生成新狀態j.若新狀態的內能小於狀態i的內能(即Ej<Ei),則接受新狀態j作爲新的當前狀態; 否則,以概率接受狀態j, 其中k爲Boltzmann常數, 這就是通常所說的Metropolis準則。

1953年, Kirkpatrick把模擬退火思想與組合最優化的相似點進行類比, 將模擬退火應用到了組合最優化問題中,在把模擬退火算法應用於最優化問題時,一般可以將溫度T當做控制參數,目標函數值f視爲內能E,而固體在某溫度T時的一個狀態對應一個解。然後算法試圖隨着控制參數T的降低,使目標函數值f(內能E)也逐漸降低,直至趨於全局最小值(退火中低溫時的最低能量狀態),就像固體退火過程一樣。

其他一些參數的說明

退火過程由一組初始參數, 即冷卻進度表(cooling schedule) 控制, 它的核心是儘量使系統達到準平衡,以使算法在有限的時間內逼近最優解。冷卻進度表包括:

  • ①控制參數的初值T。:冷卻開始的溫度。

  • ②控制參數T的衰減函數:因計算機能夠處理的都是離散數據,因此需要把連續的降溫過程離散化成降溫過程中的一系列溫度點,衰減函數即計算這一系列溫度的表達式。

  • ③控制參數T的終值T,(停止準則)。

  • ④Markov鏈的長度L.:任一溫度T的迭代次數。

算法基本步驟

①令T=T。,即開始退火的初始溫度,隨機生成一個初始解工,並計算相應的目標函數值E(x0)。

②令T等於冷卻進度表中的下一個值Ti。

③根據當前,進行擾動(擾動方式可以參考後面的實例),產生一個新解、計算應的目標函數值E(),得到△E=E()一E()。

④若△E<0,則新解被接受,作爲新的當前解;若△E>0,則新解,按概率exp(一△E/) 接受,爲當前溫度。

⑤在溫度下,重複L,次的擾動和接受過程,即執行步驟③與④。

⑥判斷T是否已到達,是,則終止算法;否,則轉到步驟②繼續執行。

算法實質分兩層循環,在任一溫度隨機擾動產生新解,並計算目標函數值的變化,決定是否被接受。由於算法初始溫度比較高,這樣,使E增大的新解在初始時也可能被接受.因而能跳出局部極小值,然後通過緩慢地降低溫度,算法就最終可能收斂到全局最優解。還有一點要說明的是,雖然在低溫時接受函數已經非常小了,但仍不排除有接受更差的解的可能,因此一般都會把退火過程中碰到的最好的可行解(歷史最優解)也記錄下來,與終止算法前最後一個被接受解一併輸出。

幾點說明

爲了更好地實現模擬退火算法,還需要注意以下一些方面。

狀態表達

上文已經提到過,SA算法中優化問題的一個解模擬了(或說可以想象爲)退火過程中固體內部的一種粒子分佈情況。這裏狀態表達即指實際問題的解(即狀態)如何以一種合適的數學形式被表達出來,它應當適用於SA的求解、又能充分表達實際問題,這需要仔細地設計。可以參考遺傳算法和禁忌搜索中編碼的相關內容。常見的表達方式有:揹包問題和指派問題的0-1編碼, TSP問題和調度問題的自然數編碼:還有用於連續函數優化的實數編碼等。

新解的產生

新解產生機制的基本要求是能夠儘量遍及解空間的各個區域,這樣、在某一恆定溫度不斷產生新解時,就可能跳出當前區域的極小以搜索其他區域,這是模擬退火算法能夠進行廣域搜索的一個重要條件。

收斂的一般性條件

收斂到全局最優的一般性條件是:

  • ①初始溫度足夠高:

  • ②熱平衡時間足夠長;

  • ③終止溫度足夠低;

  • ④降溫過程足夠緩慢。但上述條件在應用中很難同時滿足。

參數的選擇

(1)控制參數T的初值T。

求解全局優化問題的隨機搜索算法一般都採用大範圍的粗略搜索與局部的精細搜索相結合的搜索策略。只有在初始的大範圍搜索階段找到全局最優解所在的區域,才能逐漸縮小搜索的範圍.最終求出全局最優解。模擬退火算法是通過控制參數T的初值T。和其衰減變化過程來實現大範圍的粗略搜索和局部精細搜索。

一般來說,只有足夠大的T。才能滿足算法要求(但對不同的問題“足夠大”的含義也不同,有的可能T。=100就可以,有的則要1010)。在問題規模較大時,過小的T。往往導致算法難以跳出局部陷阱而達不到全局最優。但爲了減少計算量,T。不宜取得過大,而應與其他參數折中選取。

(2)控制參數T的衰減函數

衰減函數可以有多種形式,一個常用的衰減函數是

其中.a是一個常數,可以取爲0.5~0.99,它的取值決定了降溫的過程。小的衰減量可能導致算法進程迭代次數的增加,從而使算法進程接受更多的變換,訪問更多的鄰域,搜索更大範圍的解空間,返回更好的最終解。同時由於在值上已經達到準平衡,則在時只需少量的變換就可達到準平衡。這樣就可選取較短長度的Markov鏈來減少算法時間。

(3) Markov鏈長度

Markov鏈長度的選取原則是:在控制參數T的衰減函數已選定的前提下, 應能使在控制參數T的每一取值上達到準平衡。從經驗上來說,對簡單的情況可以令=100n,n爲問題規模。

算法停止準則:對Metropolis準則中的接受函數分析可知,在T比較大的高溫情況下,指數上的分母比較大,而這是一個負指數,所以整個接受函數可能會趨於1,即比當前解x,更差的新解工,也可能被接受,因此就有可能跳出局部極小而進行廣域搜索,去搜索解空間的其他區域;而隨着冷卻的進行,T減小到一個比較小的值時,接受函數分母小了,整體也小了,即難以接受比當前解更差的解,也就是不太容易跳出當前的區域。如果在高溫時,已經進行了充分的廣域搜索,找到了可能存在最好解的區域,而在低溫再進行足夠的局部搜索,則可能最終找到全局最優了。因此,一般T,應設爲一個足夠小的正數,比如0.01~5,但這只是一個粗糙的經驗,更精細的設置及其他的終止準則可以查閱文獻。

Python實現

函數:

import numpy as np
import matplotlib.pyplot as plt
import random

class SA(object):

    def __init__(self, interval, tab='min', T_max=10000, T_min=1, iterMax=1000, rate=0.95):
        self.interval = interval                                    # 給定狀態空間 - 即待求解空間
        self.T_max = T_max                                          # 初始退火溫度 - 溫度上限
        self.T_min = T_min                                          # 截止退火溫度 - 溫度下限
        self.iterMax = iterMax                                      # 定溫內部迭代次數
        self.rate = rate                                            # 退火降溫速度
        #############################################################
        self.x_seed = random.uniform(interval[0], interval[1])      # 解空間內的種子
        self.tab = tab.strip()                                      # 求解最大值還是最小值的標籤: 'min' - 最小值;'max' - 最大值
        #############################################################
        self.solve()                                                # 完成主體的求解過程
        self.display()                                              # 數據可視化展示

    def solve(self):
        temp = 'deal_' + self.tab                                   # 採用反射方法提取對應的函數
        if hasattr(self, temp):
            deal = getattr(self, temp)
        else:
            exit('>>>tab標籤傳參有誤:"min"|"max"<<<')
        x1 = self.x_seed
        T = self.T_max
        while T >= self.T_min:
            for i in range(self.iterMax):
                f1 = self.func(x1)
                delta_x = random.random() * 2 - 1
                if x1 + delta_x >= self.interval[0] and x1 + delta_x <= self.interval[1]:   # 將隨機解束縛在給定狀態空間內
                    x2 = x1 + delta_x
                else:
                    x2 = x1 - delta_x
                f2 = self.func(x2)
                delta_f = f2 - f1
                x1 = deal(x1, x2, delta_f, T)
            T *= self.rate
        self.x_solu = x1                                            # 提取最終退火解

    def func(self, x):                                              # 狀態產生函數 - 即待求解函數
        value = np.sin(x**2) * (x**2 - 5*x)
        return value

    def p_min(self, delta, T):                                      # 計算最小值時,容忍解的狀態遷移概率
        probability = np.exp(-delta/T)
        return probability

    def p_max(self, delta, T):
        probability = np.exp(delta/T)                               # 計算最大值時,容忍解的狀態遷移概率
        return probability

    def deal_min(self, x1, x2, delta, T):
        if delta < 0:                                               # 更優解
            return x2
        else:                                                       # 容忍解
            P = self.p_min(delta, T)
            if P > random.random(): return x2
            else: return x1

    def deal_max(self, x1, x2, delta, T):
        if delta > 0:                                               # 更優解
            return x2
        else:                                                       # 容忍解
            P = self.p_max(delta, T)
            if P > random.random(): return x2
            else: return x1

    def display(self):
        print('seed: {}\nsolution: {}'.format(self.x_seed, self.x_solu))
        plt.figure(figsize=(6, 4))
        x = np.linspace(self.interval[0], self.interval[1], 300)
        y = self.func(x)
        plt.plot(x, y, 'g-', label='function')
        plt.plot(self.x_seed, self.func(self.x_seed), 'bo', label='seed')
        plt.plot(self.x_solu, self.func(self.x_solu), 'r*', label='solution')
        plt.title('solution = {}'.format(self.x_solu))
        plt.xlabel('x')
        plt.ylabel('y')
        plt.legend()
        plt.savefig('SA.png', dpi=500)
        plt.show()
        plt.close()


if __name__ == '__main__':
    SA([-5, 5], 'max')

結果展示

參考文獻

《matlab在數學建模中的應用》

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