獲取更多資訊,趕快關注上面的公衆號吧!
模擬退火(Simulated Annealing,SA)
本章將介紹模擬退火算法,它是受冶金中退火過程的啓發而提出的一種元啓發式優化算法。首先回顧退火算法的發展和應用,然後描述物理退火的過程及其與退火算法的映射關係,並詳細描述算法的步驟,最後給出SA算法的僞代碼和java代碼實現。
靈感
SA由Kirkpatrick等人於1983年提出,其揭示瞭如何利用模擬固體退火過程的模型求解優化問題,在優化問題中,最小化目標函數對應於固體中的能量狀態。
在物理退火過程中,固體首先被加熱,然後慢慢冷卻,直到到達最規則的晶格排列而沒有晶格缺陷。加熱會改變物質的物理甚至是化學特性,以增加其延展性並降低其硬度。固體中的粒子具有與最穩定狀態下最小能量排列相對應的幾何構型,物理退火是通過熔化一種物質,然後緩慢降低其溫度來實現固體的低能排列的過程,值得注意的是,在冷卻階段,物質的溫度必須緩慢下降,否則,生成的固體就會凝固成存在結構缺陷的晶體。
當應用SA時,物質的不同狀態表示優化問題的不同解,該物質的能量相當於待優化的適應度函數,原子的移動引入新的解。在SA中,引入更優解的原理移動將被接受,引入更劣解的非改進移動以概率接受,此概率取決於接受函數。下表列出了模擬退火與優化算法之間的對應關係。
優化算法 | 模擬退火 |
---|---|
決策變量 | 物質原子位置 |
解 | 物質狀態 |
舊解 | 物質當前狀態 |
新解 | 物質新的狀態 |
最優解 | 最優狀態 |
適應度函數 | 物質能量 |
初始解 | 隨機位置 |
選擇 | 接受函數 |
生成新解的過程 | 原子移動 |
SA首先生成一個初始解,也就是物質的當前狀態,通過適當的機制在當前狀態的鄰域中生成新的狀態,並評估其適應度值,如果新的狀態優於當前狀態,則接受新的狀態,否則以一定概率接受,此概率與系統溫度有關,該算法在溫度逐漸降低的同時,在每個溫度下生成一定數量的新狀態,不斷嘗試一部分解,直達每個溫度下都達到熱平衡準則,此時再次降低溫度。隨着溫度的降低,選擇非改進原子移動的概率也隨之降低。整個生成新解的過程隨着系統冷卻逐漸重複,直到滿足終止條件。SA的算法流程圖如下所示。
生成初始狀態
SA生成的每個優化問題的解對應於物質原子的一種排列。系統狀態包括在N維空間中的N個決策變量,可以表示爲一個大小爲的數組,如下:
其中爲優化問題的一個解,爲解中的第個決策變量,爲決策變量的個數。對於連續問題和離散問題,決策變量值可以表示爲實數或一組預定義的值。初始時隨機生成一個初始化狀態,該狀態就是系統的當前狀態。
生成新的狀態
在退火過程中,原子移動到新的位置,以降低系統能量,實現穩定狀態。在當前狀態基礎上生成新的系統可能狀態,有多種確定性的和隨機性的機制可以根據給定的解生成鄰域解。最常用的一種機制就是隨機行走,即:
其中第個決策變量的新值,爲範圍內的隨機值,爲一個較小的數。
所有決策變量的新值在生成新解 之前先進行評估,然後新解按下式生成:
接受函數
接受函數用於確定是否使用新生成的解替換當前解,新解代表物質的一種可能的排列,其接受與否取決於舊解和新解的適應度值。當新解適應度值優於舊解時,新解替換舊解,否則新解一定概率替換舊解,該概率根據新舊解適應度值之間的差異計算得到。針對最小化問題,根據如下的接受概率函數來接受新解:
其中爲舊解,爲新生成的解,大於等於爲使用替換的概率,爲解的適應度值,爲控制參數,對應於物理退火中的溫度。如果大於等於,則使用替換,否則就不接受新解。式(4)和式(5)定義的接受函數爲玻爾茲曼分佈,當適應度值差異較大時,概率將變小,因此適應度值差異較小時更容易被接受。同理,較大時非改進改變更容易被接受,即當較大時選擇壓力較小。參數對於算法正確收斂起着重要的作用,算法初期應該較大,以避免陷入局部最優,隨着算法逐漸進行其也應該逐漸下降。
熱平衡
當在每一溫度下發生大量的鄰域運動時,系統在每一溫度下達到熱平衡,可以證明,在熱平衡時,系統狀態的概率分佈服從玻爾茲曼分佈。SA通過嘗試在每個溫度下的多個鄰域移動來進行,換言之,對於每個溫度,在溫度降低之前,需要生成多個新的狀態(不管是接受還是拒絕),這些新的狀態需要通過接受函數進行測試,至於需要生成多少個新的狀態,是用戶定義的算法參數,通常表示爲。當生成了個新解並由接受函數測試後,就滿足了熱平衡。
溫度下降
在測試多個新的狀態後,系統溫度逐漸下降:
其中爲算法迭代次數。
兩種常見的降低的方法分別是線性法和幾何法。
線性法使用下式在每次迭代中更改:
其中爲初始溫度,爲第次迭代時的溫度,爲總迭代次數,爲冷卻因子。
系統冷卻的幾何法如下:
幾何法的優點在於其不需要指定算法的最大迭代次數。算法的終止條件可以是迭代的最大次數,也可以是其他終止條件,如運行時間。在這種情況下,T的值是未知的,SA算法將繼續執行,直到滿足停止條件(例如,運行時間)。
終止條件
終止條件決定何時終止SA算法。選擇合適的終止準則對算法的正確收斂有重要作用。迭代次數、連續迭代之間目標函數的增量改進和運行時間是SA算法實現中常用的終止條件。
僞代碼和Java實現
開始
輸入算法參數和初始數據;
生成初始解並進行評估;
While(終止條件未滿足)
For =1 to
生成新解,並進行評估;
If 新解優於舊解
令
Otherwise
計算,生成[0,1]內的隨機數;
If >
令
End if
End if
Next
降低溫度
End while
輸出解
End
下面結合代碼具體說一下算法執行流程,java源碼關注公衆號後回覆“模擬退火”即可獲取。
- 初始參數設置。主要設置最大迭代次數 maxGen,求解問題維度 dim,初始溫度T0,溫度下降率alpha,熱平衡beta和目標函數 objectiveFun 等。
ObjectiveFun objectiveFun = new ObjectiveFun();
SANormal sa = SANormal.builder().maxGen(400).dim(2).T0(10).beta(200).objectiveFun(objectiveFun).build();
- 生成初始解。
private Substance genInitSolution() {
// TODO Auto-generated method stub
Substance substance = new Substance(new Position(dim, objectiveFun.getRange()));
substance.setEnergy(objectiveFun.getObjValue(substance.getPosition().getPositionCode()));
return substance;
}
- 生成新的解。採用隨機遊走的方式對舊解進行擾動。
private Substance genNewSolution(Substance oldSubstance) {
// TODO Auto-generated method stub
Substance newSubstance = new Substance(new Position(dim, objectiveFun.getRange()));
double[] newPositionCode = IntStream.range(0, this.dim)
.mapToDouble(ind -> oldSubstance.getPosition().getPositionCode()[ind]
+ 0.1 * objectiveFun.getRange().getScale()[ind] * (random.nextDouble() - 0.5))
.toArray();
newSubstance.getPosition().setPositionCode(newPositionCode);
newSubstance.setEnergy(this.objectiveFun.getObjValue(newSubstance.getPosition().getPositionCode()));
return newSubstance;
}
- 接受新解與否。
// 針對最大化問題
if (newSubstance.getEnergy() > bestSubstance.getEnergy()) {
substance = newSubstance;
} else {
double deldaF = Math.abs(newSubstance.getEnergy() - bestSubstance.getEnergy());
double p = Math.pow(Math.E, -deldaF / T);
double rand = random.nextDouble();
if (rand <= p) {
substance = newSubstance;
}
}
- 更新迭代器。迭代次數加1,溫度降低。
public void incrementIter() {
iterator++;
T = T * alpha;
}
- 循環。
while (iterator < maxGen) {
for (int j = 0; j < beta; j++) {
Substance newSubstance = genNewSolution(substance);
// 針對最大化問題
if (newSubstance.getEnergy() > bestSubstance.getEnergy()) {
substance = newSubstance;
} else {
double deldaF = Math.abs(newSubstance.getEnergy() - bestSubstance.getEnergy());
double p = Math.pow(Math.E, -deldaF / T);
double rand = random.nextDouble();
if (rand <= p) {
substance = newSubstance;
}
}
}
if (substance.getEnergy() > bestSubstance.getEnergy()) {
bestSubstance.getPosition().setPositionCode(substance.getPosition().getPositionCode());
bestSubstance.setEnergy(substance.getEnergy());
}
incrementIter();
System.out.println("**********第" + iterator + "代最優解:" + bestSubstance + "**********");
T = T * alpha;
}
測試中使用的目標函數(最大化)方程和圖像分別如下:
exp(-(x-4)^2-(y-4)^2)+exp(-(x+4)^2-(y-4)^2)+2*exp(-x^2-(y+4)^2)+2*exp(-x^2-y^2)
實驗結果如下: