智能優化算法之模擬退火(Simulated Annealing,SA)-附源碼

在這裏插入圖片描述
獲取更多資訊,趕快關注上面的公衆號吧!

模擬退火(Simulated Annealing,SA)

本章將介紹模擬退火算法,它是受冶金中退火過程的啓發而提出的一種元啓發式優化算法。首先回顧退火算法的發展和應用,然後描述物理退火的過程及其與退火算法的映射關係,並詳細描述算法的步驟,最後給出SA算法的僞代碼和java代碼實現。

靈感

SA由Kirkpatrick等人於1983年提出,其揭示瞭如何利用模擬固體退火過程的模型求解優化問題,在優化問題中,最小化目標函數對應於固體中的能量狀態。

在物理退火過程中,固體首先被加熱,然後慢慢冷卻,直到到達最規則的晶格排列而沒有晶格缺陷。加熱會改變物質的物理甚至是化學特性,以增加其延展性並降低其硬度。固體中的粒子具有與最穩定狀態下最小能量排列相對應的幾何構型,物理退火是通過熔化一種物質,然後緩慢降低其溫度來實現固體的低能排列的過程,值得注意的是,在冷卻階段,物質的溫度必須緩慢下降,否則,生成的固體就會凝固成存在結構缺陷的晶體。

當應用SA時,物質的不同狀態表示優化問題的不同解,該物質的能量相當於待優化的適應度函數,原子的移動引入新的解。在SA中,引入更優解的原理移動將被接受,引入更劣解的非改進移動以概率接受,此概率取決於接受函數。下表列出了模擬退火與優化算法之間的對應關係。

優化算法 模擬退火
決策變量 物質原子位置
物質狀態
舊解 物質當前狀態
新解 物質新的狀態
最優解 最優狀態
適應度函數 物質能量
初始解 隨機位置
選擇 接受函數
生成新解的過程 原子移動

SA首先生成一個初始解,也就是物質的當前狀態,通過適當的機制在當前狀態的鄰域中生成新的狀態,並評估其適應度值,如果新的狀態優於當前狀態,則接受新的狀態,否則以一定概率接受,此概率與系統溫度有關,該算法在溫度逐漸降低的同時,在每個溫度下生成一定數量的新狀態,不斷嘗試一部分解,直達每個溫度下都達到熱平衡準則,此時再次降低溫度。隨着溫度的降低,選擇非改進原子移動的概率也隨之降低。整個生成新解的過程隨着系統冷卻逐漸重複,直到滿足終止條件。SA的算法流程圖如下所示。

圖1 SA流程圖

生成初始狀態

SA生成的每個優化問題的解對應於物質原子的一種排列。系統狀態包括在N維空間中的N個決策變量,可以表示爲一個大小爲1×N1\times N的數組,如下:

State=X=(x1,x2,,xi,,xN)(1)\text {State}=X=\left(x_{1}, x_{2}, \ldots, x_{i}, \ldots, x_{N}\right)\tag {1}

其中XX爲優化問題的一個解,xix_{i}爲解中XX的第ii個決策變量,NN爲決策變量的個數。對於連續問題和離散問題,決策變量值(x1,x2,,xi,,xN)\left(x_{1}, x_{2}, \ldots, x_{i}, \ldots, x_{N}\right)可以表示爲實數或一組預定義的值。初始時隨機生成一個初始化狀態,該狀態就是系統的當前狀態。

生成新的狀態

在退火過程中,原子移動到新的位置,以降低系統能量,實現穩定狀態。在當前狀態基礎上生成新的系統可能狀態,有多種確定性的和隨機性的機制可以根據給定的解生成鄰域解。最常用的一種機制就是隨機行走,即:

xi=xi+Rnd(ε,ε),i=1,2,,N(2)x_{i}^{\prime}=x_{i}+ Rnd(-\varepsilon, \varepsilon), \quad i=1,2, \ldots, N\tag{2}

其中xix_{i}ii個決策變量的新值,Rnd(a,b)Rnd(a,b)[a,b][a,b]範圍內的隨機值,ε\varepsilon爲一個較小的數。

所有決策變量的新值在生成新解 之前先進行評估,然後新解按下式生成:
X(new)=(x1,x2,,xi,,xN)(3)X^{(n e w)}=\left(x_{1}^{\prime}, x_{2}^{\prime}, \ldots, x_{i}^{\prime}, \ldots, x_{N}^{\prime}\right)\tag{3}

接受函數

接受函數用於確定是否使用新生成的解替換當前解,新解代表物質的一種可能的排列,其接受與否取決於舊解和新解的適應度值。當新解適應度值優於舊解時,新解替換舊解,否則新解一定概率替換舊解,該概率根據新舊解適應度值之間的差異計算得到。針對最小化問題,根據如下的接受概率函數來接受新解:
P(X,X(new))={1 if F(X(new))<F(X)eΔFλ Otherwise }(4)P\left(X, X^{(n e w)}\right)=\left\{\begin{array}{ll} 1 & \text { if } F\left(X^{(n e w)}\right)<F(X) \\ e^{\frac{-\Delta F}{\lambda}} & \text { Otherwise } \end{array}\right\}\tag{4}

ΔF=F(X(new))F(X)(5)\Delta F=\left|F\left(X^{(n e w)}\right)-F(X)\right|\tag{5}

其中XX爲舊解,X(new)X^{(new)}爲新生成的解,P(X,X(new))P\left( {X,{X^{(new)}}} \right)大於等於Rand[0,1]Rand\in[0,1]爲使用X(new)X^{(new)}替換XX的概率,F(X)F(X)爲解XX的適應度值,λ\lambda爲控制參數,對應於物理退火中的溫度。如果P(X,X(new))P\left( {X,{X^{(new)}}} \right)大於等於Rand[0,1]Rand\in[0,1],則使用X(new)X^{(new)}替換XX,否則就不接受新解。式(4)和式(5)定義的接受函數爲玻爾茲曼分佈,當適應度值差異較大時,概率將變小,因此適應度值差異較小時更容易被接受。同理,λ\lambda較大時非改進改變更容易被接受,即當λ\lambda較大時選擇壓力較小。參數λ\lambda對於算法正確收斂起着重要的作用,算法初期λ\lambda應該較大,以避免陷入局部最優,隨着算法逐漸進行其也應該逐漸下降。

熱平衡

當在每一溫度下發生大量的鄰域運動時,系統在每一溫度下達到熱平衡,可以證明,在熱平衡時,系統狀態的概率分佈服從玻爾茲曼分佈。SA通過嘗試在每個溫度下的多個鄰域移動來進行,換言之,對於每個溫度λ\lambda,在溫度降低之前,需要生成多個新的狀態(不管是接受還是拒絕),這些新的狀態需要通過接受函數進行測試,至於需要生成多少個新的狀態,是用戶定義的算法參數,通常表示爲β\beta。當生成了β\beta個新解並由接受函數測試後,就滿足了熱平衡。

溫度下降

在測試多個新的狀態後,系統溫度逐漸下降:

limt+λt=0,t>0(6)\lim _{t \rightarrow+\infty} \lambda_{t}=0, \quad t>0\tag{6}
其中tt爲算法迭代次數。

兩種常見的降低λ\lambda的方法分別是線性法和幾何法。

線性法使用下式在每次迭代中更改λ\lambda
λt=λ0α×t(7)\lambda_{t}=\lambda_{0}-\alpha \times t\tag{7}

α=λ0λTT(8)\alpha=\frac{\lambda_{0}-\lambda_{T}}{T}\tag{8}

其中λ0\lambda_{0}爲初始溫度,λt\lambda_{t}爲第tt次迭代時的溫度,TT爲總迭代次數,α\alpha爲冷卻因子。

系統冷卻的幾何法如下:
λt=λ0×αt,0<α<1(9)\lambda_{t}=\lambda_{0} \times \alpha^{t}, \quad 0<\alpha<1\tag{9}

幾何法的優點在於其不需要指定算法的最大迭代次數。算法的終止條件可以是迭代TT的最大次數,也可以是其他終止條件,如運行時間。在這種情況下,T的值是未知的,SA算法將繼續執行,直到滿足停止條件(例如,運行時間)。

終止條件

終止條件決定何時終止SA算法。選擇合適的終止準則對算法的正確收斂有重要作用。迭代次數、連續迭代之間目標函數的增量改進和運行時間是SA算法實現中常用的終止條件。

僞代碼和Java實現

開始

  輸入算法參數和初始數據;

  生成初始解XX並進行評估;

  While(終止條件未滿足)

    For jj=1 to β\beta

      生成新解X(new)X^{(new)},並進行評估;

      If 新解(X(new))(X^{(new)})優於舊解(X)(X)

        令X=X(new)X=X^{(new)}

      Otherwise

        計算P(X,X(new))P\left( {X,{X^{(new)}}} \right),生成[0,1]內的隨機數RandRand;

        If P(X,X(new))P\left( {X,{X^{(new)}}} \right)>RandRand

          令X=X(new)X=X^{(new)}

        End if

      End if

    Next jj

    降低溫度

  End while

  輸出解XX

End

下面結合代碼具體說一下算法執行流程,java源碼關注公衆號後回覆模擬退火即可獲取。

  1. 初始參數設置。主要設置最大迭代次數 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();
  1. 生成初始解
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;
}
  1. 生成新的解。採用隨機遊走的方式對舊解進行擾動。
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;
}
  1. 接受新解與否
// 針對最大化問題
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. 更新迭代器。迭代次數加1,溫度降低。
public void incrementIter() {
  iterator++;
  T = T * alpha;
}
  1. 循環
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)

實驗結果如下:

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