模拟退火算法是现代优化算法的一种,是一种启发式算法。即上一次的计算结果会对下一次结果造成影响。模拟退火算法得益于材料的统计力学结果。结构力学表明材料中离子的不同结构对应于粒子的不同能量水平。在高温条件下,粒子的能量较高,可以自由运动和重新排列;而在低温条件下,粒子的能量较低。若从高温开始,非常缓慢地降温(即退火),粒子就可以在某个温度下达到热平衡,当系统完全冷却时,形成能量最低形态的晶体。
采用Metropolis算法可以用一个很简单的数学模型描述退火过程。
假设材料在状态 i i i之下的能量为 E ( i ) E(i) E(i),那么材料在温度 T T T时从状态 i i i进入状态 j j j就会遵循以下规律:
(1)若 E ( j ) ≤ E ( i ) E(j) \leq E(i) E(j)≤E(i),接受该状态被转换;
(2)若 E ( j ) > E ( i ) E(j)>E(i) E(j)>E(i),以一定的概率 p p p接受状态变换。
其中, p = e E ( i ) − E ( j ) K T p=e^{\frac{E(i)-E(j)}{K T}} p=eKTE(i)−E(j)。
因此在求解优化问题时,即可通过该思想来对模型进行求解。
若一个组合优化问题:优化函数为: f : x → R + f: x \rightarrow R^{+} f:x→R+,其中 x ∈ S x\in S x∈S为优化问题的一个可行解, R + = { y ∣ y ∈ R , y > 0 } R^{+}=\{y \mid y \in R, y>0\} R+={y∣y∈R,y>0},且 S S S表示函数定义域。
在进行求解时,首先给定一个初始温度 T 0 T_0 T0和一个初始解 x ( 0 ) x(0) x(0),并由该初始解形成一个在其邻域内的新解 x ′ x' x′,是否接受该新截遵循以下公式:
P ( x ( 0 ) → x ′ ) = { 1 若 f ( x ′ ) < f ( x ( 0 ) ) e − f ( x ′ ) − f ( x ( 0 ) ) T 0 其它 P\left(x(0) \rightarrow x^{\prime}\right)=\left\{\begin{array}{ll}1 & \text { 若 } f\left(x^{\prime}\right)
而对于其他任一温度 T i T_i Ti时,接受新解的概率为:
P ( x ( k ) → x ′ ) = { 1 若 f ( x ′ ) < f ( x ( k ) ) e − f ( x ′ ) − f ( x ( k ) ) T i 其它 P\left(x(k) \rightarrow x^{\prime}\right)=\left\{\begin{array}{ll}1 & \text { 若 } f\left(x^{\prime}\right)
现利用模拟退火算法解决一个简单的二次函数极值问题。
代码如下:
#模拟退火算法求函数极值
import numpy as np
def func(x):
return 21.5+x[:,0]*np.sin(20*np.pi*x[:,0]) + x[:,1]*np.sin(20*np.pi*x[:,1])
#定义变量
var = 2 #变量个数
T0 = 1000 #初始温度
Tt = T0 #t时刻温度
maxi = 1000 #迭代次数
times = 200 #每个温度下的迭代次数
alfa = 0.95 #衰减系数
xlow = np.array([[-3,4.1]]) #下界
xhigh = np.array([[12.1,5.8]]) #上界
#生成初始解
x0 = np.zeros((1,var))
for i in range(var):
x0[:,i] = xlow[:,i] + (xhigh[:,i]-xlow[:,i])*np.random.rand()
it_x,it_y = [x0],[func(x0)]
#模拟退火过程
for i in range(maxi):
for j in range(times):
y0 = func(x0)
y = np.random.randn(1,var)
z = y / np.sqrt(np.sum(y*y))
xnew = x0 + z*Tt
for k in range(var):
if xnew[:,k] < xlow[:,k]:
r = np.random.rand()
xnew[:,k] = r*xhigh[:,k] + (1-r)*x0[:,k]
elif xnew[:,k] > xhigh[:,k]:
r = np.random.rand()
xnew[:,k] = r*xlow[:,k] + (1-r)*x0[:,k]
x1 = xnew
y1 = func(x1)
if y1 > y0:
print('接受新解:{}'.format(x1))
x0 = x1
it_x.append(x1)
it_y.append(y1)
else:
p = np.exp(-(y0-y1)/Tt)
if np.random.rand() > p:
print('新解较小,但仍接受新解:{}'.format(x1))
else:
print('保留旧解')
x0 = x1
Tt = alfa*Tt
print(max(it_y))
最终求解结果为极值:39.15003305。
下面是相同的问题的另一种代码,思想一样,但是编写方式稍微有些不同:
import random
import numpy as np
def ob(x):
return 21.5 + x[:,0]*np.sin(20*np.pi*x[:,0]) + x[:,1]*np.sin(20*np.pi*x[:,1])
def main(T, x0, J):
global fB
n = 1
xl = np.array([[-3,4.1]])
xu = np.array([[12.1,5.8]])
while n <= 501:
fA = ob(x0)
print("旧解{}".format(fA))
a = np.random.randn(1,2)
zi = a / (sum(a ** 2) ** 0.5)
xn = x0 + T*zi
for k in range(2):
if xn[:,k] < xl[:,k]:
r = np.random.rand()
xn[:,k] = r*xu[:,k] + (1-r)*x0[:,k]
elif xn[:,k] > xu[:,k]:
r = np.random.rand()
xn[:,k] = r*xl[:,k] + (1-r)*x0[:,k]
x1 = xn
fB = ob(x1)
if fB >= fA:
print("第{}次操作,接受新解{}".format((i-1) * 500 + n, fB))
fA, x0 = fB, x1
elif fB < fA:
P = np.exp(-(abs(fB - fA) / T))
r = random.uniform(0, 1)
if P >= r:
print("第{}次操作,虽然比较小,但我们接受新解{}".format(i * n, fB))
fA, x0 = fB, xn
else:
print("第{}次操作,仍使用旧解{}".format((i-1) * 500 + n, fA))
J.append(fB)
n += 1
return xn
T0 = 100
x0 = np.zeros((1,2))
J = []
for i in range(1,201):
T = 0.95**i*T0
main(T, x0, J)
xn = main(T,x0,J)
print("{:*^25}".format("分割线"))
print("最优解是{}".format(max(J)))
第一种方式是我自己写的,第二种是我老婆写的。最后的结果比较相近,求解得极值为:39.10768688。
https://www.bilibili.com/video/BV1hK41157JL