一个锅底凹凸不平有很多坑的大锅,晃动这个锅使得一个小球使其达到全局最低点。一开始晃得比较厉害,小球的变化也就比较大,在趋于全局最低的时候慢慢减小晃锅的幅度,直到最后不晃锅,小球达到全局最低。
固体退火过程
模拟退火算法是由复杂组合优化问题与固体的退火过程之间的相似之处,在它们之间建立联系而提出来的。固体的退火过程是一种物理现象,属于热力学和统计物理学的研究范畴。
固体退火过程能最终达到最小能量的一个状态,从理论上来说,必须满足4个条件:
- 初始温度必须足够高;
- 在每个温度下,状态的交换必须足够充分;
- 温度的下降必须足够缓慢;
- 最终温度必须足够低。
模拟退火算法
组合优化问题与固体退火过程的类比
组合优化问题与固体退火过程有其类似性,将组合优化问题类比为固体的退火过程,便提出了求解组合优化问题的模拟退火算法。
在求解组合优化问题时,首先给定一个比较大的t值,这相当于一个比较高的温度T。随机给定一个问题的解i,作为问题的初始解。在给定的 t 下,随机产生一个问题的解 j ,j ∈ N(i),其中 N(i) 是 i 的邻域。从解 i 到新解 j 的转移概率,按照 Metropolis 准则确定:
P t ( i ⇒ j ) = { 1 f ( j ) < f ( i ) e − j ( j ) t , f ( i ) other P_{t}(i \Rightarrow j)=\left\{\begin{array}{ll} 1 & f(j)<f(i) \\ e^{-\frac{j(j)}{t}, f(i)} & \text { other } \end{array}\right. Pt(i⇒j)={1e−tj(j),f(i)f(j)<f(i) other
如果新解 j 被接受,则以解 j 代替解i,否则继续保持解 i 。重复该过程,直到在该控制参数 t 下达到平衡。
与退火过程中的温度T缓慢下降相对应,在进行足够多的状态转移之后,控制参数t要缓慢下降,并在每个参数t下,重复以上过程,直到控制参数 t 降低到足够小为止。最终得到的是该组合优化问题的一个最优解
用退火算法解决旅行商问题
1. 原理
设有n个城市,城市间的距离用矩阵D=[dij](i,j=1,2,…,n)
表示,当问题对称时,有dij=dji
。
-
解空间,其空间规模为
(n-1)!
-
指标函数
f ( π 1 , … , π n ) = ∑ i = 1 n d π i π i + 1 f\left(\pi_{1}, \ldots, \pi_{n}\right)=\sum_{i=1}^{n} d \pi_{i} \pi_{i+1} f(π1,…,πn)=i=1∑ndπiπi+1
-
新解的产生,采用两个城市间的逆序交换方式得到问题的一个新解。
-
指标函数差:
Δ f = ( d π u π v − 1 + d π u + 1 π v ) − ( d π u π u + 1 + d π v − 1 π v ) \Delta f=\left(d_{\pi_{u} \pi_{v-1}}+d_{\pi_{u+1} \pi_{v}}\right)-\left(d_{\pi_{u} \pi_{u+1}}+d_{\pi_{v-1} \pi_{v}}\right) Δf=(dπuπv−1+dπu+1πv)−(dπuπu+1+dπv−1πv)
-
新解的接受准则:
A t = { 1 ; Δ f < 0 e − Δ f t ; other A_{t}=\left\{\begin{array}{cc} 1 & ; \Delta f<0 \\ e^{-\frac{\Delta f}{t}} & ; \text {other} \end{array}\right. At={1e−tΔf;Δf<0;other
2. 网上的办法
设置初始温度,停止温度与降温系数
tmp = 1e5
tmp_min = 1e-3
alpha = 0.98
直到 tmp < tmp_min 的时候停止,产生新解的办法是交换两个城市的顺序。
# -*- coding: utf-8 -*-
"""
Created on Mon Mar 23 21:38:00 2020
@author: Rical
"""
import numpy as np
import matplotlib.pyplot as plt
len_plot = []
city_map = np.array([
[ 0. , 2538.94 , 2873.8 , 2575.27 , 2318.1 , 2158.71 , 2216.58 , 3174.04 , 3371.13 , 3540.24 ],
[2538.94 , 0. , 1073.54 , 111.288, 266.835, 395.032, 410.118, 637.942, 853.554, 1055. ],
[2873.8 , 1073.54 , 0. , 964.495, 988.636, 1094.32 , 1382.73 , 1240.15 , 1460.25 , 1687. ],
[2575.27 , 111.288, 964.495, 0. , 262.053, 416.707, 503.563, 624.725, 854.916, 1068.42 ],
[2318.1 , 266.835, 988.636, 262.053, 0. , 163.355, 395.14 , 885. , 1110.86 , 1318.19 ],
[2158.71 , 395.032, 1094.32 , 416.707, 163.355, 0. , 338.634, 1030.34 , 1248.58 , 1447.69 ],
[2216.58 , 410.118, 1382.73 , 503.563, 395.14 , 338.634, 0. , 984.068, 1160.26 , 1323.7 ],
[3174.04 , 637.942, 1240.15 , 624.725, 885. , 1030.34 , 984.068, 0. , 243.417, 473.768],
[3371.13 , 853.554, 1460.25 , 854.916, 1110.86 , 1248.58 , 1160.26 , 243.417, 0. , 232.112],
[3540.24 , 1055. , 1687. , 1068.42 , 1318.19 , 1447.69 , 1323.7 , 473.768, 232.112, 0. ]
], dtype='float').reshape([10, 10])
def calc_len(path):
# best = 8916
length = city_map[0, path[0]] + city_map[path[-1], 0]
for i in range(len(path) - 1):
length += city_map[path[i], path[i+1]]
return length
def get_2_randint():
i = j = 0
while i == j:
i, j = np.random.randint(0, 9, 2)
return i, j
def judge(dE, tmp):
if dE < 0:
return True
else:
d = np.exp(-(dE / tmp))
return d > np.random.random()
def main():
# 初始温度,停止温度与降温系数
tmp = 100
tmp_min = 1e-3
alpha = 0.98
# 从 0 出发回到 0
TSP_path = [1, 2, 3, 4, 5, 6, 7, 8, 9]
ori_len = calc_len(TSP_path)
for k in range(1000):
if tmp < tmp_min:
print('End Normal!')
return
i, j = get_2_randint()
TSP_path[i], TSP_path[j] = TSP_path[j], TSP_path[i]
new_len = calc_len(TSP_path)
dE = new_len - ori_len
if judge(dE, tmp):
print("Length: {:.3f}, path: {}".format(new_len, TSP_path))
len_plot.append(new_len)
ori_len = new_len
tmp = tmp * alpha
else:
TSP_path[i], TSP_path[j] = TSP_path[j], TSP_path[i]
plt.plot(len_plot)
main()
运行结果:
3. 老师教的方法
初始温度 t0=280
在每个温度下采用固定迭代次数 Lk=100n,n为城市数
温度衰减系数a=0.92,即tk+1=0.92×tk
算法终止准则为当相邻两个温度得到的解无任何变化时算法停止。
代码如下:
# -*- coding: utf-8 -*-
"""
Created on Mon Mar 23 21:38:00 2020
@author: Rical
"""
import numpy as np
import matplotlib.pyplot as plt
len_plot = []
n = 10
def calc_len(path, cmap):
# best = 8916
length = 0
for i in range(len(path)-1):
length += cmap[path[i], path[i+1]]
len_plot.append(length)
print("Length: {:.3f}, path: {}".format(length, path))
return length
def get_2_randint():
i = j = 0
# 为了确保 i 比 j 小,且 i 与 j 之间至少相隔两个元素
while i >= j - 2 or j - i == 10:
i, j = np.random.randint(0, n+1, 2)
return i, j
def judge(dE, tmp):
if dE < 0:
return True
else:
d = np.exp(-(dE / tmp))
return d > np.random.random()
def main():
# 生成地图
cmap = np.array([
[ 0. , 2538.94 , 2873.8 , 2575.27 , 2318.1 , 2158.71 , 2216.58 , 3174.04 , 3371.13 , 3540.24 ],
[2538.94 , 0. , 1073.54 , 111.288, 266.835, 395.032, 410.118, 637.942, 853.554, 1055. ],
[2873.8 , 1073.54 , 0. , 964.495, 988.636, 1094.32 , 1382.73 , 1240.15 , 1460.25 , 1687. ],
[2575.27 , 111.288, 964.495, 0. , 262.053, 416.707, 503.563, 624.725, 854.916, 1068.42 ],
[2318.1 , 266.835, 988.636, 262.053, 0. , 163.355, 395.14 , 885. , 1110.86 , 1318.19 ],
[2158.71 , 395.032, 1094.32 , 416.707, 163.355, 0. , 338.634, 1030.34 , 1248.58 , 1447.69 ],
[2216.58 , 410.118, 1382.73 , 503.563, 395.14 , 338.634, 0. , 984.068, 1160.26 , 1323.7 ],
[3174.04 , 637.942, 1240.15 , 624.725, 885. , 1030.34 , 984.068, 0. , 243.417, 473.768],
[3371.13 , 853.554, 1460.25 , 854.916, 1110.86 , 1248.58 , 1160.26 , 243.417, 0. , 232.112],
[3540.24 , 1055. , 1687. , 1068.42 , 1318.19 , 1447.69 , 1323.7 , 473.768, 232.112, 0. ]
], dtype='float').reshape([10, 10])
# 康立山
tmp = 280
alpha = 0.92
path = [0] + [i+1 for i in range(n-1)] + [0]
print(path)
count = 0
for k in range(100 * n):
count += 1
i, j = get_2_randint()
# 只更换 i,j 中间部分,不包含 i j
dE = cmap[path[i], path[j-1]] + cmap[path[i+1], path[j]] - cmap[path[i], path[i+1]] - cmap[path[j-1], path[j]]
if dE == 0:
calc_len(path, cmap)
print(count, i, j)
return
if judge(dE, tmp):
path[i+1:j] = path[i+1:j][::-1]
calc_len(path, cmap)
tmp = tmp * alpha
print(count)
plt.plot(len_plot)
main()
运行结果: