模拟退火算法
Metropolis准则
在温度为 T T T 时,接受能量从 E ( X o l d ) E(X o l d) E(Xold) 到 E ( X n e w ) E(X n e w) E(Xnew) 的概率为 P P P :
p = { 1 if E ( x new ) < E ( x old ) exp ( − E ( x new ) − E ( x old ) T ) if E ( x new ) ≥ E ( x old ) p=\left\{\begin{array}{cll} 1 & \text { if } & E\left(x_{\text {new }}\right)<E\left(x_{\text {old }}\right) \\ \exp \left(-\frac{E\left(x_{\text {new }}\right)-E\left(x_{\text {old }}\right)}{T}\right) & \text { if } & E\left(x_{\text {new }}\right) \geq E\left(x_{\text {old }}\right) \end{array}\right. p={1exp(−TE(xnew )−E(xold )) if if E(xnew )<E(xold )E(xnew )≥E(xold )
模拟退火原理
模拟退火算法(Simulated Annealin, SA)是一种元启发式的优化算法,用于寻找函数的全局最值,其工作原理为:
- 从一个状态 S S S移动到一个新的状态 S + 1 S + 1 S+1
- 若 f ( S ) < f ( S + 1 ) f(S) < f(S + 1) f(S)<f(S+1),则接受该状态作为新解,若 f ( S ) > f ( S + 1 ) f(S) > f(S + 1) f(S)>f(S+1),则以一定概率接受该解,且概率随时间推移而降低,这里是随温度的降低来体现的
- 对初始温度降温, T 0 = r T 0 T_{0}= rT_{0} T0=rT0, r ∈ ( 0 , 1 ) r \in (0, 1) r∈(0,1) , r r r越大,温度降低越快,与神经网络的"学习率"有异曲同工之妙
- 重复以上三步直到 T 0 < T m i n T_{0}< T_{min} T0<Tmin
应用
求解旅行商(TSP)问题
题目描述
一个旅行商要前往 n n n个城市推销他的商品,然后回到他所在的城市,请找出一条路线让他能经过每一个城市且总路径最短
分析
该题的本质是寻找一副完全无向有权图中边权和最小的哈密顿回路,我们设从城市 i i i到城市 j j j的路程为 d i j d_{ij} dij,那么我们的暴力的解决方法显然是枚举出所有城市的全排列,然后依次计算出每一个排列的总距离,从中找出路程最小的排列即可,显然,暴力算法的时间复杂度 O ( n ! ) O(n!) O(n!),无法在多项式时间内解决
代码实现
import itertools
import numpy as np
import pandas as pd
import graphviz as gz
# 随机生成城市的距离
class TSP:
def __init__(self, size):
self.G_SIZE = size
self.path = None
self.D = np.random.randint(1, 20, (self.G_SIZE, self.G_SIZE))
self.D = self.D - np.diag(self.D.diagonal())
self.D = np.triu(self.D) + np.triu(self.D).T
def calc_distance(self, arr):
dis = 0;
for i in range(len(arr)-1):
j = i + 1
dis += self.D[arr[i]][arr[j]]
return dis
# 暴力搜索
def dfs(self):
ans = None
path = None
arr = np.array(list(itertools.permutations(np.arange(self.G_SIZE))))
arr = np.hstack([arr,arr[:, 0].reshape(-1, 1)])
# 计算距离
for i in range(len(arr)):
res = self.calc_distance(arr[i])
if ans is not None:
if ans > res:
ans = res
path = arr[i]
else:
ans = res
path = arr[i]
return ans, path
# 模拟退火
# 传入当前温度, 降温系数,最小温度,和距离矩阵
def simulatedAnnealing(self, T, r, T_min):
old_dis = None
best_path = None
while(T >= T_min):
# 现在考虑状态转移的方式,出于方便,我们使用numpy的随机排列函数
arr = np.arange(0, self.G_SIZE)
np.random.shuffle(arr)
# 尾部插入首元素成环
arr = np.append(arr, arr[0])
# 计算总距离
dis = self.calc_distance(arr)
if old_dis is not None:
if(old_dis >= dis):
old_dis = dis
best_path = arr
elif(old_dis < dis):
if(np.exp(-(dis - old_dis) / T) > np.random.rand(1)[0]):
old_dis = dis
best_path = arr
else:
old_dis = dis
best_path = arr
T *= r
if(T < T_min):
self.path = best_path
return old_dis, best_path
# 绘制图像:
def draw_graph(self, path=None):
if(self.path is not None and path is None):
path = self.path
#生成邻接矩阵记录连接情况
mat = np.zeros((self.G_SIZE, self.G_SIZE))
G = gz.Graph("TSP")
for i in range(len(path)-1):
j = i + 1;
G.edge(str(path[i]), str(path[j]), color = 'red', label=str(self.D[path[i]][path[j]]))
mat[path[i]][path[j]] = 1
mat[path[j]][path[i]] = 1
for i in range(G_SIZE):
G.node(str(i))
for j in range(i + 1, self.G_SIZE):
if mat[i][j] == 1:
continue
G.edge(str(i), str(j), label=str(self.D[i][j]))
return G
性能分析
使用%time
对暴力与模拟退火分别进行测时,得到如下结果:
暴力枚举:
输入规模n | 时间 |
---|---|
5 | 384 μ \mu μs |
10 | 20s |
15 | -(内核炸了) |
模拟退火(T = 10000, T_min = 1, r = 0.96):
输入规模n | 时间 |
---|---|
5 | 3ms |
10 | 3.77ms |
15 | 4.36ms |
⋮ \vdots ⋮ | ⋮ \vdots ⋮ |
100 | 14.6ms |
可见模拟退火算法的时间复杂度随T
, T_min
,r
的影响而变化,且随机性较强,估算为
O
(
n
l
o
g
2
n
)
O(nlog_2n)
O(nlog2n)
n=5时,解的图像如下: