啟發式演算法(Heuristic Algorithm)是一種基於直觀或經驗的構造的演算法,對具體的優化問題能在可接受的計算成本(計算時間、佔用空間等)內,給出一個近似最優解,這個近似解與真實最優解的偏離程度一般不能被預計。
由於 NP 問題一般的經典演算法求解效率過低甚至無法求解,從而促使了啟發式演算法的誕生。一個精心設計的啟發式演算法,通常能在較短時間內得到問題的近似最優解,對於 NP 問題也可以在多項式時間內得到一個較優解。
啟發式演算法不是一種確切的演算法,而是提供了一個尋找最優解的框架。從開頭的定義可以看出,啟發式演算法需要根據實際應用場景,建立一系列切合實際問題的啟發式規則。同時啟發式演算法存在以下問題:
-
目前缺乏統一、完整的理論體系;
-
啟發式演算法都會遭遇到區域性最優的問題,難點在於如何設計出有效跳出區域性最優的機制;
-
演算法的引數設定對效果有很大的影響,如何有效設定引數值得思考;
-
如何設定有效的迭代停止條件等。
因此值得注意的是,啟發式演算法不能保證得到最優解,效果相對不穩定,它的效果依賴於實際問題和設計者的經驗。但瑕不掩瑜,面對複雜問題啟發式演算法能以相對簡單的方式進行解決,並且它容易設計程式。
大部分啟發式演算法是通過生物現象或物理現象演變而來,我們據此將它分為以下類別:
-
仿動物類的演算法:粒子群優化演算法、蟻群演算法、魚群演算法、蜂群演算法等;
-
仿植物類的演算法:向光性演算法、雜草優化演算法等;
-
仿人類的演算法:遺傳基因演算法、神經網路、聲搜尋演算法等;
-
仿物理現象的演算法:模擬退火演算法。
接下來挑選以上類別中較為經典的啟發式演算法進行介紹。
模擬退火演算法 SA
在熱力學上,退火(annealing)現象指物體逐漸降溫的物理現象。溫度越低,物體的能量狀態越低;當能量狀態足夠低後,液體開始冷凝與結晶,在結晶狀態時,系統的能量狀態最低,也最穩定。
物理退火過程由三部分組成:
-
加溫過程:目的是增強粒子的熱運動,使其偏離平衡位置。
-
等溫過程:對於與周圍環境交換熱量而溫度不變的封閉系統,系統狀態的自發變化總是朝自由能減少的方向進行。
-
冷卻過程:使粒子熱運動減弱,系統能量下降,達到能量最低態。
模擬退火法是一種通用的啟發式演算法,我們把問題搜尋空間內每個解都想象成物體內的分子,對於搜尋空間內的每個解,如同物體分子一樣帶有「能量」,用於表示這個解對問題的合適程度。演算法以搜尋空間中的一個任意解作為初始解,每一步隨機產生一個新解,並計算從當前解到達新解的概率。其中,加溫過程對應演算法的初始溫度,等溫過程對應演算法的 Metropolis 抽樣過程,冷卻過程對應控制引數的下降。
Metropolis 準則是指以一定的概率接受惡化解,從而使演算法具有逃脫區域性極值和避免過早收斂的全域性優化能力。
能量的變化就是目標函式值的變化,能量的最低態就是最優解。其中 Metropolis 準則是 SA 演算法收斂於全域性最優解的關鍵所在,當搜尋到不好的解,Metropolis 準則會以一定概率接受這個不好的解,使演算法具備跳出區域性最優的能力。假定當前可行解為 x,迭代更新後的解為 x_new,那麼對應的「能量差」定義為:
Δf = f ( x_new ) − f ( x )
以一定概率接受不好的解,則該概率為:
p (Δf) = exp( -Δf/kT )
溫度 T 是 Metropolis 準則的影響因素之一:在高溫下,可接受與當前狀態能量差較大的新解;在低溫下,只接受與當前狀態能量差較小的新解。
模擬退火演算法的具體流程如下圖所示:
模擬退火演算法的應用廣泛,可以高效地求解 NP 完全問題,如旅行商問題、最大截問題、0-1 揹包問題等。但是其引數比較難控制,不能保證一次就收斂到最優值,大部分情況下還是會陷入區域性最優值,主要受三個關鍵引數影響:
-
初始溫度值設定
初始溫度值的設定是影響模擬退火演算法全域性搜尋效能的重要因素。初始溫度高,則搜尋到全域性最優解的可能性大,但因此要花費大量的計算時間;反之,則可節約計算時間,但全域性搜尋效能可能受到影響。 -
退火速度問題,即每個溫度值的迭代次數
模擬退火演算法的全域性搜尋效能也與退火速度密切相關。一般來說,同一溫度下的「充分」搜尋是相當必要的,但搜尋越「充分」,計算開銷自然越大。 -
溫度管理問題
溫度管理問題也是影響模擬退火演算法全域性搜尋效能的重要因素。實際應用場景下,因為需要考慮計算開銷問題,常採用溫度衰減的方式進行降溫:T=α×T.α∈(0,1)
為了保證較大的搜尋空間,α 一般取接近於 1 的值,如 0.95、0.9。
?舉個例子:買菜問題
買菜問題是一個典型的組合優化問題:假設給定一組包含n個蔬菜的集合,其中每個蔬菜都有自身的體積 w_i 和價格 v_i,買菜阿姨有一個容量為C的購物籃,買菜阿姨的任務是在不超過購物籃容量的前提下,購買一組價格最高的蔬菜放入籃中。
給定 n 個蔬菜的集合{x_1, x_2, …, x_n},x_i∈{0, 1},每個蔬菜 x_i 的具有屬性{(w_i, v_i)},購物籃容量為正整數 C,即求解如下優化問題:
假設蔬菜的體積集合 size={2,2,3,4},蔬菜的價格集合 value={5,6,7,8},阿姨的購物籃容量為 7,一般設定初始方案為 S0 = {0,0,0,0}。既然是最大化蔬菜的總價格,那麼目標函式和限制函式就是:
若阿姨每次蒙著眼睛隨機挑選一個蔬菜 i 則有三種新解產生方式:
1.如果蔬菜 i 不在購物籃中,就直接把它丟進籃子裡;
2.如果蔬菜 i 不在購物籃中,但是籃子滿了,那麼蒙著眼睛從籃子裡取出蔬菜 j,把位置讓給蔬菜 i;
3.如果蔬菜 i 已經在購物籃中,那麼先把蔬菜 i 取出來,然後蒙著眼睛挑一個蔬菜j並放進籃子裡。
根據三種新解產生的情況,那麼購物籃的價值差、體積差都會出現三種情況。
購物籃的價值差:
購物籃的體積差:
由於買菜問題是有約束的最優化問題,所以 Metropolis 準則需要做一些相應的調整:
然後,根據前面所述的計算規則,設定好初始溫度 T、溫度衰減係數 α、每個溫度的迭代次數 K,會獲得一個最優買菜方案:S = {1,1,1,0}。即 Totalsize=7,Totalvalue=18。
遺傳演算法 GA
遺傳演算法借鑑了達爾文的進化論和孟德爾的遺傳學說,將待解決的問題模擬成一個生物進化的過程,通過複製、交叉、突變等操作產生下一代的解,並逐步淘汰掉適應度函式值低的解,增加適應度函式值高的解。這樣進化 N 代後就很有可能會進化出適應度函式值很高的個體。
先看一些遺傳學的相關生物概念:
種群:生物的進化以群體的形式進行,這樣的一個群體稱為種群;
個體:組成種群的單個生物;
基因:一個遺傳因子;
染色體:包含一組的基因;
生存競爭,適者生存:對環境適應度高的個體參與繁殖的機會比較多,後代就會越來越多;適應度低的個體參與繁殖的機會比較少,後代就會越來越少;
遺傳與變異:新個體會遺傳父母雙方各一部分的基因,同時有一定的概率發生基因變異。
而對應的遺傳演算法也有以下基本組成:
-
編碼
-
適應度函式
-
遺傳運算元
-
執行引數
編碼
遺傳演算法通過某種編碼機制把物件抽象成按一定順序進行排列的字串,編碼主要分為以下種類:
-
最簡單的一種編碼方式是二進位制編碼,既是把問題的解,編碼成二進位制陣列的形式;
-
互換編碼一般用於解決排序問題:比如 TSP 問題,用一串基因編碼表示遍歷的城市順序;
-
樹形編碼用於遺傳規劃中的演化程式設計;
-
值編碼用於解決複雜的數值問題。
編碼主要遵循三個原則:
1.完備性:問題空間的所有解都能由編碼規則進行表示;
2.健全性:任何一個基因都對應一個可能解;
3.非冗餘性:問題空間和編碼規則形成的表達空間一一對應。
適應度函式
遺傳演算法對一個個體(解)的質量評估,是用適應度函式值來度量的,適應度函式的值越大,個體的質量越好。適應度函式是遺傳演算法進化過程的驅動力,也是進行自然選擇的唯一標準,需要根據求解的具體問題進行設計。
通常來說,適應度函式與目標函式是正相關的,可對目標函式作一些變形來得到適應度函式。
遺傳運算元
遺傳運算元包括選擇運算元、交叉運算元和變異運算元。
選擇運算
選擇運算是指對個體進行優勝劣汰操作。適應度高的個體被遺傳到下一代群體中的概率大;適應度低的個體,被遺傳到下一代群體中的概率小。
基本遺傳演算法(SGA)中選擇運算元採用輪盤賭的選擇方法,其基本思想是每個個體被選中的概率與其適應度函式值大小成正比。
輪盤賭選擇方法的實現步驟如下所示:
-
計算群體中所有個體的適應度值;
-
計算每個個體的選擇概率;
-
計算積累概率;
-
採用模擬賭盤操作(即生成0到1之間的隨機數,與每個個體遺傳到下一代群體的概率進行匹配,用以確定每個個體是否遺傳到下一代群體中。)
?舉個例子
假設有染色體 S1 = 3(00011)、S2 = 10(01010)、S3 = 17(10001)、S4 = 22(10110),根據適應度函式 F(S) = S^2 -2 可得出各個染色體的適應度:
-
F(S1) = 3^2 -2 = 7
-
F(S2) = 10^2 -2 = 98
-
F(S3) = 17^2 -2 = 287
-
F(S4) = 22^2 -2 = 482
假設有 4 個隨機數 r1=0.061,r2=0.242,r3=0.402,r4=0.728;再結合各個染色體的適應度可得出:
交叉運算
交叉運算是指對兩個相互配對的染色體依據交叉概率 Pc 按某種方式相互交換其部分基因,從而形成兩個新的個體。交叉運算是遺傳演算法區別於其他進化演算法的重要特徵,它在遺傳演算法中起關鍵作用,是產生新個體的主要方法。基本遺傳演算法(SGA)中交叉運算元採用單點交叉運算元,除此之外,還有雙點交叉和基於「與/或」的交叉。
單點交叉(二進位制編碼)是指選擇一個交叉點,子代在交叉點前面的基因從一個父代基因中獲得,後面的部分從另一個父代基因獲得。
雙點交叉(二進位制編碼)是選擇兩個交叉點,子代基因在兩個交叉點之間的部分從一個父代基因中獲得,剩下的部分從另外一個父代基因中獲得。
基於「與/或」的交叉(二進位制編碼)則是對兩個父代基因,進行按位「與」/「或」處理,得到子代基因。
變異運算
變異運算是指依據變異概率 Pm 改變個體編碼串中的某些基因值,從而形成新的個體。變異運算是產生新個體的輔助方法,決定遺傳演算法的區域性搜尋能力,保持種群多樣性。
交叉運算和變異運算的相互配合,共同完成對搜尋空間的全域性搜尋和區域性搜尋。基本遺傳演算法(SGA)中變異運算元採用基本位變異運算元。
基本位變異運算元是指對個體編碼串隨機指定的一位或者多位基因,進行簡單翻牌操作,即 1 變 0、0 變 1。是否接受變異,由變異概率決定。
執行引數
執行引數包含種群規模、交叉率、變異率、最大進化代數等。
種群規模指的是群體中個體的個數,比較大的種群的規模並不能優化遺傳演算法的結果,種群的大小推薦使用 15-30。交叉率一般來說應該比較大,一般使用 85%-95%。變異率一般來說應該比較小,一般使用 0.5%-1%。
如上圖所示,遺傳演算法基本流程是:
1.初始化進化代數計數器(t=0),T 是最大進化代數,然後隨機生成 M 個個體作為初始群體 P(t);
2.進行個體評價:計算 P(t)中各個個體的適應度值;
3.進行選擇運算:將選擇運算元作用於群體;
4.進行交叉運算:將交叉運算元作用於群體;
5.進行變異運算:將變異運算元作用於群體;
6.通過步驟 2-5,得到下一代群體 P(t+1);
7.終止條件判斷進化代數是否達到最大值:若 t≦T,則 t←t+1,轉到步驟2;若 t>T,終止,輸出解。
遺傳演算法有兩種效能優化方案:災變與精英主義。
遺傳演算法的區域性搜尋能力較強,但很容易陷入區域性最優的陷阱,要跳出區域性極值就必須殺死當前的優秀個體,從而讓遠離當前極值的點有充分的進化餘地,這就是災變的思想。當利用交叉和變異產生子代時,很可能在某個中間步驟丟失得到的最優解,在每次產生子代時,首先把當前最優解複製到子代中,防止進化過程中產生的最優解被交叉和變異破壞,這就是精英主義的思想。
災變與精英主義,這兩者間存在一定程度的矛盾:災變機制是把產生的優秀個體殺掉;精英主義機制則是把優秀個體保留到子代。但其實兩者其實是可以共存的,在每一代進行交叉運算前,都把最優秀的個體複製到下一代,但當接下去連續 n 代都沒有出現更優秀個體時,可能是遺傳演算法陷入區域性最優,這個時候就可以採用災變機制,幫助演算法跳出區域性最優。根據具體場景構建遺傳演算法時,可以有選擇性的採用災變和精英主義。
?再回到上文的例子:買菜問題
買菜問題是一個典型的組合優化問題:假設給定一組包含n個蔬菜的集合,其中每個蔬菜都有自身的體積 w_i 和價格 v_i,買菜阿姨有一個容量為C的購物籃,買菜阿姨的任務是在不超過購物籃容量的前提下,購買一組價格最高的蔬菜放入籃中。
給定 n 個蔬菜的集合{x_1, x_2, …, x_n},x_i∈{0, 1},每個蔬菜 x_i 的具有屬性{(w_i, v_i)},購物籃容量為正整數 C,即求解如下優化問題:
阿姨買菜問題,天然符合二進位制編碼,將待求解的 n 個蔬菜的集合{x_1, x_2, …, x_n},表示為長度為 n 的二進位制染色體。
假設蔬菜的體積集合 size={2,2,3,4},蔬菜的價格集合 value={5,6,7,8},阿姨的購物籃容量為 10,隨機生成如下種群(n=4,種群規模為4): S1 = 0001,S2 = 0101,S3 = 1000,S4 = 1111。
既然是最大化蔬菜的總價格,那麼首先計算個體的總價格,然後計算個體的總體積:
由於容量上限的限制,那麼適應度的計算需要考慮這個因素,因此加入懲罰項:
其中,引數 α 是懲罰係數,α > 1.0,數值越大,懲罰力度越大。
假設懲罰係數取 10,那麼每個個體的適應度為:
S1 = 0001,TotalSize=4,TotalValue=8,Fitness=8;
S2 = 0101,TotalSize=6,TotalValue=14,Fitness=14;
S3 = 1000,TotalSize=2,TotalValue=5,Fitness=5;
S4 = 1111,TotalSize=11,TotalValue=26,Fitness=16。
假設生成 4 個隨機數,r1=0.22,r2=0.57,r3=0.41,r4=0.79。
那麼,下一代的種群就是 T1 = 0101,T2 = 1000,T3 = 0101,T4 = 1111。接著對經過選擇後的種群進行隨機配對,然後根據交叉率隨機設定交叉點,採用單點交叉的方式,互換已配對的染色體的部分基因。
假設 T1 和 T4 配對,交叉點是第二位;T2 和 T3 配對,交叉點在第三位,那麼交叉後的新種群為:
U1 = 0111,TotalSize=9,TotalValue=21,Fitness=21;
U2 = 1001,TotalSize=6,TotalValue=13,Fitness=13;
U3 = 0100,TotalSize=2,TotalValue=6,Fitness=6;
U4 = 1101,TotalSize=8,TotalValue=19,Fitness=19。
再次假設 U3 在第三、四位上發生了變異,那麼:
U1 = 0111,TotalSize=9,TotalValue=21,Fitness=21;
U2 = 1001,TotalSize=6,TotalValue=13,Fitness=13;
U3 = 0111,TotalSize=9,TotalValue=21,Fitness=21;
U4 = 1101,TotalSize=8,TotalValue=19,Fitness=19。
至此,阿姨已經找到了最佳的買菜方案 U1和U3。
?再舉一個例子:旅遊問題(如果你懂了,可快速滑過~)
阿姨從家裡出發,遊玩所有祖國的著名城市之後回家。這又是一個經典的組合優化問題,這次的任務是選出一條路線,讓這次旅遊的總行程最短。
如上表所示,給每一個城市賦予一個數字編碼,那麼一條路線(即一條染色體)用包含 n 個城市編碼的陣列來表示,陣列元素的順序表示旅行的順序,而且陣列中的元素不會重複,因為一個城市只去玩一次。
假設:S1 = {17, 3, 21, …, 30},表示旅行的順序為:鄭州 -> 上海 -> 福州 -> … -> 成都。
阿姨的這次旅行總行程越短越好,那麼取一條路線的總行程的倒數,作為適應度函式。假設東經為 x,北緯為 y,那麼一個城市的座標即為(x, y),那麼一條路線 S_i = {(x_1, y_1), (x_2, y_2), …, (x_n, y_n)},i∈[0, m],所以:
這裡同樣採用輪盤賭的選擇方法。接著對路線隨機配對,根據交叉率隨機挑選出交叉點。對於路徑序列,不能利用單點交叉法簡單的互換父母染色體的部分基因,因為這樣容易造成子代染色體中出現重複的城市編碼。從父親中獲得交叉點的城市編碼,保持這些編碼在父親中是順序並填充到子代的頭部,剩餘的城市編碼從母親中獲取並填滿子代。比如:
父親:{1,2,3,4,5,6,…}
母親:{31,2,3,11,1,5,…}
子代:{1,3,4,5,6,…,31,2,11,…}
這裡採用的變異方式是根據變異率隨機挑選一條路線中的某兩個城市編碼,然後交換這兩個編碼的位置。比如 S = {1,3,4,5,6,…,31,2,11,…},那麼,變異後 T = {1,11,4,5,6,…,31,2,3,…}。
至此,阿姨就可以根據這個遺傳演算法尋找總行程最短的旅遊路線啦~
進化策略 ES
進化策略(Evolution Strategy)是一種求解引數優化問題的方法,模仿生物進化原理,假設不論基因發生何種變化,產生的結果(性狀)總遵循零均值、某一方差的高斯分佈。進化策略特點如下:
-
進化策略中的選擇是按照確定方式進行的,不同於遺傳演算法中的隨機選擇方式;
-
進化策略中的重組運算元,不同於遺傳演算法中的交叉,它不是簡單的將個體的某一部分進行互換,而是使個體中的每一位都發生變化。
進化策略可分成兩類,(μ+λ)-ES 和 (μ,λ)-ES。
(μ+λ)-ES
每次迭代產生 λ 個新解,通過和父代進行比較,將較好的 μ 個成為下一次迭代的父代,其他的直接捨去。
這種方式引入種群的思想,易於並行化,但容易陷入區域性最優陷阱,主要用在多目標優化。
(μ,λ)-ES
每次迭代產生 λ 個新解(λ>μ),其中較好的 μ 個成為下一次迭代的父代,其他的直接捨去。這種方式所有解都只存活一代,可較好避免陷入區域性最優。
(μ+λ) 選擇可以保證最優個體存活,使群體的進化過程呈單調上升趨勢,但是 (μ+λ) 選擇保留舊個體,容易帶來區域性最優問題。(μ, λ)-ES 通常優於 (μ+λ)-ES,是當前進化策略使用的主流。
進化策略的 DNA 不再是用二進位制進行表示, 而是用實數來代替,可以解決很多由實陣列成的實際問題。進化策略產生子代的基因交叉,和遺傳演算法類似,可是基因變異應該怎麼操作呢?由於基因是實數,所以無法像遺傳演算法那樣採用簡單的翻牌做法。
進化策略中的基因變異由變異強度來決定,正態分佈在這裡發揮了關鍵作用。
我們將父代遺傳下來的基因值看做是正態分佈的平均值,接著在這個平均值上附加一個標準差,這個時候便確定了一個正態分佈,然後使用該正態分佈產生一個數。比如在這個 8.8 位置上的變異強度為 2.5, 按照 2.5 的標準差和 8.8 的均值確定一個正太分佈,然後產生一個新的值 8.7。
子代基因每一位上的值都會經過不同的狀態分佈進行變異,這樣就會產生全新的子代 DNA。 所以,變異強度也可以被當成一組遺傳資訊從父代的 DNA 中遺傳下來,而且變異強度本身也能進行變異。
進化策略的關鍵步驟在於:交叉、變異、選擇、變異程度的變化。
和遺傳演算法一樣,交叉就是交換兩個個體的基因,主要有三種方式:
1.離散重組:先隨機選擇兩個父代個體,然後將其分量進行隨機交換,構成子代新個體的各個分量,從而得出新個體。
2.中值重組:這種重組方式也是先隨機選擇兩個父代個體,然後將父代個體各分量的平均值作為子代新個體的分量,構成新個體。
3.混雜重組:這種重組方式的特點在於父代個體的選擇上。混雜重組時先隨機選擇一個固定的父代個體,然後針對子代個體每個分量再從父代群體中隨機選擇第二個父代個體。也就是說,第二個父代個體是經常變化的。至於父代兩個個體的組合方式,既可以採用離散方式,也可以來用中值方式。
變異比較簡單,就是在每個分量上面加上零均值、某一方差的高斯分佈的變化產生新的個體。這個某一方差就是變異程度,變異程度是會變化的,演算法開始的時候變異程度比較大,當接近收斂後,變異程度會開始減小。
接著通過一個比較具體的進化策略案例來了解,如下圖所示是 (1 + 1)-ES 中的變異程度公式。
變異程度的控制這裡採用 1/5 成功規則。在還沒收斂的時候增大變異程度,快要收斂的時候就減小變異程度。判定是否收斂的條件是,如果只有 1/5 的變異比原始父代好,那麼就是快收斂了;如果有一半的變異比原始父代好, 那麼就是還沒收斂。