模拟退火算法
应用
通过模拟物体退火的过程,寻找问题的最优解,可用用来求解函数最值或者旅行商问题(旅行商问题,常被称为旅行推销员问题,是指一名推销员要拜访多个地点时,如何找到在拜访每个地点一次后再回到起点的最短路径)。由于这个算法基于蒙特卡洛方法,所以每次求出的结果可能不一致,只能求出近似的最优值。
原理
简单来说模拟退火算法有点像一个贪心算法,只是贪心算法是将比当前好的解来替代当前的解,直到找到最优解,但这就有容易陷入局部最优解。而模拟退火算法以一定的概率来接受一个比当前解要差的解,因此有可能会跳出这个局部的最优解,达到全局的最优解。
关于此这篇文章提到了有一个有趣的比喻:
- 普通算法:兔子朝着比现在低的地方跳去。它找到了不远处的最低的山谷。但是这座山谷不一定最低的。
- 模拟退火:兔子喝醉了。它随机地跳了很长时间。这期间,它可能走向低处,也可能踏入平地。但是,它渐渐清醒了并朝最低的方向跳去。这就是模拟退火。
过程
- 设定一个初始温度,求出当前温度的解 f i f_i fi,这位最优解
- 进行降温,再算出现在的解 f i + 1 f_{i+1} fi+1
- 如果 f i + 1 f_{i+1} fi+1要比 f i f_i fi更好,接受 f i + 1 f_{i+1} fi+1为最优解;如果 f i + 1 f_{i+1} fi+1要比 f i f_i fi更差,有一定概率接受 f i + 1 f_{i+1} fi+1,这个概率随着温度降低越来越低
- 重复以上步骤,知道温度到达最低
粒子在温度
T
T
T时趋于平衡的概率为
e
−
Δ
E
k
T
e^{\frac{-ΔE}{kT}}
ekT−ΔE,其中
E
E
E为温度
T
T
T时的内能,
Δ
E
ΔE
ΔE为其改变数,为
E
(
x
n
e
w
)
−
E
(
x
o
l
d
)
E(x_{new})-E(x_{old})
E(xnew)−E(xold),
k
k
k为常数。
M
e
t
r
o
p
o
l
i
s
Metropolis
Metropolis准则常表示为
p
=
{
e
−
Δ
E
k
T
,
Δ
E
≥
0
1
,
Δ
E
<
0
p=\left\{ \begin{aligned} e^{\frac{-ΔE}{kT}} & ,ΔE≥0 \\ 1 & ,ΔE<0 \end{aligned} \right.
p={ekT−ΔE1,ΔE≥0,ΔE<0
这条公式就表示:温度越高,出现一次能量差为
Δ
E
ΔE
ΔE的降温的概率就越大;温度越低,则出现降温的概率就越小。又由于
Δ
E
ΔE
ΔE总是小于0(因为退火的过程是温度逐渐下降的过程),因此
Δ
E
k
T
<
0
\frac{ΔE}{kT} < 0
kTΔE<0 ,所以
P
(
Δ
E
)
P(ΔE)
P(ΔE)的函数取值范围是(0,1) 。随着温度T的降低,
P
(
Δ
E
)
P(ΔE)
P(ΔE)会逐渐降低。
问题
[0 , 0 ], [1 , 10 ], [2 , 7.2 ], [15 , 5 ], [4.3 , 5 ], [12.5, 9 ], [3.4 , 3 ], [6 , 3.4 ], [9 , 6.8 ], [8.1 , 2 ], [15 , 16.5], [13 , 18.8], [11.7, 18.4], [2.9 , 14.7], [9.6 , 20.4]这是几个城市的坐标,现在要如何走才能将这些城市都经过一次且没有重复的路线,要这个距离最短。
python代码
# 模拟退火算法
import numpy as np
import matplotlib.pyplot as plt
import math
import random
city_id = np.array([[0 , 0 ],
[1 , 10 ],
[2 , 7.2 ],
[15 , 5 ],
[4.3 , 5 ],
[12.5, 9 ],
[3.4 , 3 ],
[6 , 3.4 ],
[9 , 6.8 ],
[8.1 , 2 ],
[15 , 16.5],
[13 , 18.8],
[11.7, 18.4],
[2.9 , 14.7],
[9.6 , 20.4]]) # 城市坐标
# 计算城市的总长度
def len_dic(city, city_n):
dic = 0
for i in range(city_n-1):
dic += math.sqrt(np.sum(np.power(city[i+1,:]-city[i,:], 2)))
return dic
# 交换城市位置
def updata(city, city_n):
i, j = 0, 0
while True:
i = math.floor(random.random()*city_n)
j = math.floor(random.random()*city_n)
if i != j:
break # 随机获取两个城市
city[[i,j],:] = city[[j,i],:] # 两行交换
return city
# 准则
def rule(dlt, tmp):
pr = 0
if dlt < 0:
pr = 1
else:
p = math.exp(-dlt/tmp)
if p > random.random():
pr = 1
else:
pr = 0
return pr
# 处理流程
def SA(city, tmp=1e6, tmp_min=1e-3, alpha=0.95, up_rate=0.75):
ct_n = len(city_id) # 城市数量
city_old = city.copy()
dic_old = len_dic(city_old, ct_n)
city_new = city.copy()
dic_new = dic_old
k = 0 # 循环次数
road_dic = []
tmps = []
tmps.append(tmp)
while tmp >= tmp_min and k < 5000000:
city_new = updata(city_old, ct_n) # 更换路线
dic_new = len_dic(city_new, ct_n)
dlt = dic_new - dic_old
if rule(dlt, tmp) == 1:
road_dic.append(dic_new)
city_old = city_new
dic_old = dic_new
if dlt < 0:
tmp = tmp*alpha
tmps.append(tmp)
k = k + 1
if k % 100000 == 0:
print('循环第'+str(int(k/100000))+'次——本时刻的温度为:'+str(tmp)+',当前最短距离为:'+str(dic_old)) # 记没10W次为1次
return city_old, dic_old, road_dic, temps
# 画图
def plot_road(record_length, tmps):
plt.plot(record_length, label='distance')
plt.legend()
plt.show()
plt.plot(tmps, label='temperature')
plt.legend()
plt.show()
cities, dics, road_dic, tmps = SA(city_id)
print('最短距离为:'+str(dics))
print('路径为:')
print(cities)
plot_road(road_dic, tmps)
# 路线
plt.scatter(cities[:,0], cities[:,1], label='city')
for i in range(len(cities)-1):
plt.plot([cities[i][0], cities[i+1][0]], [cities[i][1], cities[i+1][1]], 'r--')
plt.legend()
plt.show()
结果如下
循环第1次——本时刻的温度为:1.3497229680547014,当前最短距离为:80.35644966452612
循环第2次——本时刻的温度为:1.3497229680547014,当前最短距离为:80.35644966452612
循环第3次——本时刻的温度为:1.3227285086936074,当前最短距离为:76.91166987167794
循环第4次——本时刻的温度为:1.3227285086936074,当前最短距离为:79.6010711793955
循环第5次——本时刻的温度为:1.3227285086936074,当前最短距离为:79.6010711793955
循环第6次——本时刻的温度为:1.2962739385197353,当前最短距离为:78.927264413122
循环第7次——本时刻的温度为:1.2962739385197353,当前最短距离为:78.927264413122
循环第8次——本时刻的温度为:1.2962739385197353,当前最短距离为:78.927264413122
循环第9次——本时刻的温度为:1.2962739385197353,当前最短距离为:78.927264413122
循环第10次——本时刻的温度为:1.2703484597493406,当前最短距离为:78.0900639357398
循环第11次——本时刻的温度为:1.2703484597493406,当前最短距离为:79.24609883302985
循环第12次——本时刻的温度为:1.2703484597493406,当前最短距离为:79.24609883302985
循环第13次——本时刻的温度为:1.2703484597493406,当前最短距离为:79.24609883302985
循环第14次——本时刻的温度为:1.2449414905543539,当前最短距离为:76.3729975348194
循环第15次——本时刻的温度为:1.2200426607432668,当前最短距离为:75.52671953418854
循环第16次——本时刻的温度为:1.2200426607432668,当前最短距离为:75.52671953418854
循环第17次——本时刻的温度为:1.2200426607432668,当前最短距离为:75.52671953418854
循环第18次——本时刻的温度为:1.2200426607432668,当前最短距离为:75.52671953418854
循环第19次——本时刻的温度为:1.2200426607432668,当前最短距离为:75.52671953418854
循环第20次——本时刻的温度为:1.2200426607432668,当前最短距离为:75.52671953418854
循环第21次——本时刻的温度为:1.2200426607432668,当前最短距离为:75.52671953418854
循环第22次——本时刻的温度为:1.2200426607432668,当前最短距离为:75.52671953418854
循环第23次——本时刻的温度为:1.1956418075284014,当前最短距离为:78.05300602025468
循环第24次——本时刻的温度为:1.1956418075284014,当前最短距离为:78.05300602025468
循环第25次——本时刻的温度为:1.1956418075284014,当前最短距离为:78.05300602025468
循环第26次——本时刻的温度为:1.1956418075284014,当前最短距离为:79.13880140384566
循环第27次——本时刻的温度为:1.1956418075284014,当前最短距离为:79.13880140384566
循环第28次——本时刻的温度为:1.1717289713778334,当前最短距离为:78.32208672838217
循环第29次——本时刻的温度为:1.1717289713778334,当前最短距离为:78.32208672838217
循环第30次——本时刻的温度为:1.1717289713778334,当前最短距离为:78.32208672838217
循环第31次——本时刻的温度为:1.1717289713778334,当前最短距离为:78.32208672838217
循环第32次——本时刻的温度为:1.1717289713778334,当前最短距离为:78.38225229736688
循环第33次——本时刻的温度为:1.1717289713778334,当前最短距离为:78.38225229736688
循环第34次——本时刻的温度为:1.1717289713778334,当前最短距离为:78.38225229736688
循环第35次——本时刻的温度为:1.1482943919502766,当前最短距离为:79.90575188198459
循环第36次——本时刻的温度为:1.1482943919502766,当前最短距离为:79.90575188198459
循环第37次——本时刻的温度为:1.1482943919502766,当前最短距离为:79.90575188198459
循环第38次——本时刻的温度为:1.125328504111271,当前最短距离为:70.7238412425356
循环第39次——本时刻的温度为:1.125328504111271,当前最短距离为:70.7238412425356
循环第40次——本时刻的温度为:1.125328504111271,当前最短距离为:70.7238412425356
循环第41次——本时刻的温度为:1.125328504111271,当前最短距离为:70.7238412425356
循环第42次——本时刻的温度为:1.125328504111271,当前最短距离为:70.7238412425356
循环第43次——本时刻的温度为:1.125328504111271,当前最短距离为:70.7238412425356
循环第44次——本时刻的温度为:1.125328504111271,当前最短距离为:70.7238412425356
循环第45次——本时刻的温度为:1.125328504111271,当前最短距离为:70.7238412425356
循环第46次——本时刻的温度为:1.125328504111271,当前最短距离为:70.7238412425356
循环第47次——本时刻的温度为:1.125328504111271,当前最短距离为:70.7238412425356
循环第48次——本时刻的温度为:1.125328504111271,当前最短距离为:70.7238412425356
循环第49次——本时刻的温度为:1.125328504111271,当前最短距离为:70.7238412425356
循环第50次——本时刻的温度为:1.125328504111271,当前最短距离为:70.7238412425356
最短距离为:70.7238412425356
路径为:
[[ 8.1 2. ]
[13. 18.8]
[ 9.6 20.4]
[ 2. 7.2]
[ 1. 10. ]
[15. 5. ]
[ 2.9 14.7]
[ 0. 0. ]
[ 3.4 3. ]
[ 9. 6.8]
[ 6. 3.4]
[12.5 9. ]
[15. 16.5]
[ 4.3 5. ]
[11.7 18.4]]
在多次运行得到过的最小距离大致都在70.几到75.几之间。然鹅事实上最短是多少不知道。模拟退火算法只能求出近似的最优值,而且每次求出的结果可能不一致。从distance这个图我们还是可以看到随着温度下降距离逐渐趋向最小。应用时可以改变参数看能不能得到更好的解。