模拟退火算法

   本文讲述了模拟退火算法,并提供了示例代码(Python版本)…

1. 算法由来

   模拟退火算法的思想最早由Metropolis于1953年提出,Kirkpatrick于1983年第一次使用模拟退火算法求解组合优化问题。该算法的提出用于解决NP复杂性问题,克服优化过程陷入局部最优解及克服对初值的依赖性。

2. 算法思想

   模拟退火算法以优化问题的求解过程与物理系统退火过程的相似性为基础,利用Metropolis算法并适当地控制温度的下降过程来实现模拟退火,从而达到求解全局最优问题的目的。

2.1 物理退火

   退火是指将固体物质(如金属),加热到足够高的温度,使其分子(原子)呈随机排列状态。然后逐步降温,使之缓慢地冷却,以争取足够的时间,让大量的分子(原子)在丧失可动性之前进行重新分布。最后,分子(原子)以低能状态排列,物质的能量达到最低能量状态。简言之,就是将固体加热至足够高的温度,再让其缓慢地冷却,以保证物体达到最低能量状态。
   物理退火过程可分为三部分:加温过程等温过程冷却过程

  • 加温过程: 增强组成固体物质的粒子的热运动,使其偏离平衡状态。当温度足够高时,固体物质熔化为液体,从而消除系统原先存在的非均匀态;
  • 等温过程:对于与周围环境交换热量而温度不变的封闭系统,系统状态的自发变化总是朝着自由能减少的方向进行。当自由能达到最小时,系统达到平衡态;
  • 冷却过程:减弱粒子的热运动,粒子的排列逐渐趋于有序,系统能量逐渐下降,从而得到低能量的晶体结构。

    2.2 模拟退火

       使用物理退火过程模拟组合优化问题时,对应关系如下:

    粒子状态 –> 解
    能量最低态时粒子状态 –> 最优解
    能量 –> 目标函数
    熔解过程 –> 设定初温
    等温过程 –> Metropolis采样过程
    冷却过程 –> 控制参数温度T的下降

3. Metropolis接受准则 – 以概率接受新状态

   若在温度T下,状态i –> 新状态j:

若 $$ E_j < E_i $$ 则接受新状态j;

若 $$ E_j \geq E_i $$
当 $$ exp(- \frac{E_2-E_1}{T}) > \beta $$
beta 为[0,1)之间的随机数, 则接受新状态j,否则,保持原状态i。

4. 算法参数

  • 状态产生函数:用于产生候选解,决定了解的邻域结构。通常由两部分组成:产生候选解的方式和产生候选解的概率分布。
  • 初温:初温越高,获得高质量解的几率越大,且Metropolis的接受率约为1,但是计算时间过长。
  • 退温函数(温度更新函数):用于修改温度值。常用的退温函数为指数退温函数,即$ T_t+1 = KT_t$, 其中$ 0<K<1 $ ,接近于1。
  • Markov链长度L:在等温条件下,进行迭代优化的次数,即每个温度下迭代的次数。一般选取100-1000。

5. 算法流程

  1. 初始化 – 给定初温和初始解,Markov链长度L。
  2. 产生候选解(新解)– 状态产生函数
  3. 接受或舍弃候选解 – Metropolis接受准则
  4. 在当前温度下是否充分搜索 – 迭代次数是否大于L。
  5. 是否满足停止准则 – 若满足,则结束程序。
  6. 降低温度T, 跳转到2 – 退温函数

6. 代码示例 – Python

注意:计算结果主要与状态产生函数有关,与T,K,L关系不大。

1
2
3
4
# Import libs
import numpy as np
import random as rd
import matplotlib.pyplot as plt
1
2
3
# Constant variables
MIN = [-5, -5]
MAX = [5, 5]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# SA
class SA():

def __init__(self,T=100,K=0.99,L=50,dim=2):
'''
Initiate the parameters of SA.
:param T: Current temperature.
:param K: Attenuation coefficient.
:param L: Markov chain length.
:param dim: Dimension.
'''

self.T = T
self.K = K
self.L = L
self.dim = dim

self.s = np.array([0.0]*dim) # State, namely current solution
self.bs = np.array([0.0]*dim) # Best state
self.trace = list()

def init_val(self):
'''
Set the initial solutions.
:return:
'''

for i in range(self.dim):
self.s[i] = rd.uniform(MIN[i], MAX[i])
self.bs = self.s

def energy(self, s):
'''
Energy function, namely objective function.
:param s: state, namely candidate solution.
:return E:
'''

# Objective function
E = 5*np.cos(s[0]*s[1])+s[0]*s[1]+s[1]**3
return E

def update_T(self, t):
'''
Update temperature.
:param t: current temperature.
:return T: the next temperature.
'''

T = self.K * t
return T

def state_generated(self):
'''
Generate the candidate solutions.
:return new_s: The candidate solutions.
'''

new_s = np.array([0.0]*self.dim)
for i in range(self.dim):
detaS = rd.uniform(0,1) * 2 - 1
if self.s[i]+detaS <= MAX[i] and self.s[i]+detaS >= MIN[i]:
new_s[i] = self.s[i]+detaS
else:
new_s[i] = self.s[i]-detaS
return new_s

def state_accepted(self, new_s):
'''
Based on the Metropolis laws.
:param new_s: The candidate solutions.
:return:
'''

accepted_s = np.array([0.0]*self.dim)
# Metropolis
if self.energy(new_s) < self.energy(self.s):
accepted_s = new_s
else:
delta_E = self.energy(new_s)-self.energy(self.s)
alpha = np.exp(-delta_E/self.T)
if alpha > np.random.uniform(0,1):
accepted_s = new_s
else:
accepted_s = self.s
self.s = accepted_s
if self.energy(self.s) < self.energy(self.bs):
self.bs = self.s

def run(self):
'''
Run the SA algorithm.
:return:
'''

self.init_val()
while self.T > 1e-03 :
for i in range(self.L):
new_s = self.state_generated()
self.state_accepted(new_s)
#
self.trace.append(self.energy(self.bs))
self.T = self.update_T(self.T)
1
2
3
4
5
6
7
8
9
10
11
12
13
# main
def main():

sa = SA(dim=2)
sa.run()
#
print('Optimal solution', sa.bs)
print('Function value', sa.energy(sa.bs))
#
plt.plot(sa.trace)
title = 'SA: ' + str(sa.energy(sa.bs))
plt.title(title)
plt.show()
有何不可 wechat
Subscribe to my blog by scanning my wechat account~
书山有路勤为径,学海无涯苦作舟~