算法实践-模拟退火(SA)
模拟退火(SA)-原理简介
1983年,模拟退火算法(SA)被Kirkpatrick等首次提出。
‘退火’是物理学术语,指对物体加温在冷却的过程。我们知道在分子和原子的世界中,能量越大,意味着分子和原子越不稳定,当能量越低时,原子越稳定。模拟退火算法来源于晶体冷却的过程,如果固体不处于最低能量状态,给固体加热再冷却,随着温度缓慢下降,固体中的原子按照一定形状排列,形成高密度、低能量的有规则晶体,对应于算法中的全局最优解。而如果温度下降过快,可能导致原子缺少足够的时间排列成晶体的结构,结果产生了具有较高能量的非晶体,这就是局部最优解。
SA的搜索策略简单来说就是:刚开始处于高温,搜索范围宽广,有更高概率搜索到高质量解,同时每一次搜索的计算成本也很高;随着温度的下降,搜索范围不断收窄,每一次搜索的效率也提高,最后温度达到退火温度后结束搜索。
模拟退火(SA)的流程图:
Metropolis:
SA实践
为了形象的理解,这里还是以一个具体的函数求解为例。问题如下:
f
(
x
)
=
4
x
sin
(
10
x
)
−
5
x
cos
(
3
x
)
,
0
<
x
<
10
f(x) = 4x\sin (10x) - 5x\cos (3x),0 < x < 10
f(x)=4xsin(10x)−5xcos(3x),0<x<10,求函数在定义域内极小值。函数图像如下:
python 实现代码如下:
import numpy as np
import matplotlib.pyplot as plt
class SA():
def __init__(self,T_init,T_min,count):
self.T_init = T_init # 初始化温度
self.Tmin = T_min # 退火温度
self.T = T_init # 动态的温度变化
self.count = count # 每个温度下的循环次数
self.t = 0 # 温度变化次数
def y_fun(self,x):
"""
目标函数 : f(x)=4*x*sin(10x)-5*x*cos(3x),0<x<10
"""
x=np.array(x)
return 4*x*np.sin(10*x)-5*x*np.cos(3*x)
def generate(self,x,interval):
"""
根据当前温度随机产生一个新的解
input: x, 初始解,默认为0
interval,定义域范围
output: xNew , 新的解
"""
xNew = x + np.random.uniform(low=-0.1,high=0.1)*self.T # 新解的搜索范围随着温度下降而减小
if xNew < interval[0] or xNew > interval[1] : #如果超出边界就在定义域内随机搜索
xNew = np.random.uniform(low=interval[0],high=interval[1])
return xNew
def SA_process(self,x,interval):
"""
input: x, 初始解,默认为0
interval,定义域范围
output: sol,所有搜索过的解
"""
sol = []
while self.T >= self.Tmin:
for i in range(self.count):
y=self.y_fun(x)
xNew = self.generate(x,interval) # 在x的邻域搜索解,范围需要被指定
if (interval[0]<=xNew and xNew<=interval[1]):
yNew=self.y_fun(xNew)
if yNew-y<0:
x=xNew
else: #以一定概率接受劣解
p = np.exp(-(yNew-y)/self.T) # metropolis principle
r = np.random.uniform(low=0,high=1)
if r < p:
x=xNew
sol.append(x)
plt.plot(x,self.y_fun(x),'*')
self.t+=1 # 温度循环次数加1
self.T=self.T_init/(1+self.t) # 降温模式
print('迭代次数 :',self.t)
return sol
def f_plot(self):
"""绘制函数图像"""
x= np.array(range(1000))/100
y = self.y_fun(x)
plt.plot(x,y)
if __name__ == "__main__":
x = np.random.uniform(low=0,high=10) # 初始化x
Sa_example = SA(T_init=500,T_min=2,count=100) # 初始温度500,结束温度2度,每次迭代100次
sol = Sa_example.SA_process(x,[0,10])
Sa_example.f_plot()
print("函数最小值:",np.min(Sa_example.y_fun(sol)))
plt.show()
总结
模拟退火算法的应用很广泛,可以用于求解NP完全问题,但其参数依赖人工经验设置,其主要问题有以下三点:
(1) 初始状态设置。
初始温度T和退火温度T_min的设置是影响模拟退火算法全局搜索性能的重要因素之一,初始温度高,温度变化范围大,则搜索到全局最优解的可能性大,但因此要花费大量的计算时间;反之,则可节约计算时间,但全局搜索性能可能受到影响。初始温度的设置一般需要依据实验结果进行若干次调整。
(2) 退火策略设计。
退火速度即温度从初始温度T变化到退火温度T_min的速度,与T和T_min相关。模拟退火算法的全局搜索性能也与退火速度密切相关。一般来说,同一温度下的“充分”搜索(退火)是相当必要的,但这需要计算时间。在实际应用中,针对具体问题的性质和特征设置合理的降温条件是主要问题之一,即从温度T降到T_min过程中是降温方式、以及需要降温的次数?
常用的降温方式:
T
(
n
)
=
λ
T
(
n
)
,
λ
<
1
T(n) = \lambda T(n),\lambda < 1
T(n)=λT(n),λ<1
T ( n ) = T ( 0 ) log ( 1 + t ) T(n) = \frac{{T(0)}}{{\log (1 + t)}} T(n)=log(1+t)T(0),t为当前温度
T ( n ) = T ( 0 ) 1 + t T(n) = \frac{{T(0)}}{{1 + t}} T(n)=1+tT(0),t为当前温度
(3) 状态产生函数的设计
简单来说就是解的搜索范围,每一次搜索产生新解时,如何确定搜索范围和边界?
(4)避免迂回搜索
盲目的随机搜索效率低,容易重复搜索,如何提高搜索效率?