算法简介
模拟退火算法是一种优化算法,该算法得益于材料统计力学的研究成果。统计力学表明材料中的粒子在高温条件下能量较高,可以自由运动和重新排列;在低温条件下,粒子能量较低,运动的剧烈程度也低。如果从高温开始非常缓慢地降温(退火),粒子就可以在每个温度下达到热平衡,当系统完全冷却时,最终形成处于低能状态的晶体。
假设材料在状态$i$下的状态为$E(i)$,则材料在温度T时从状态$i$进入状态$j$遵循如下规律:
- 如果$E(j)\leq E(i)$,则接受该状态
- 如果$E(j)>E(i)$,则以概率$e^\frac{E(i)-E(j)}{KT}$接受该状态。其中$e$是自然对数的底数,$K$是玻尔兹曼常数,$T$是材料温度。
模拟退火算法需要确定优化目标,通过迭代的退火过程,得到最后的最优解。具体的算法步骤如下:
- 确定优化目标。优化目标通常是求一个或多个函数的最大值或最小值
- 任选一初始状态$s_0$作为初始解$s(0)=s_0$,并设初始温度为$T_0$,令$i$等于0
- 按某一种规定的方式对当前解$s(i)$扰动,产生一个新解$s’$
- 对新解$s’$进行评价
- 若新解$s’$优于当前解$s(i)$,则将其作为下一个当前解$s(i+1)$
- 否则依据概率$e^{-\frac{\Delta C}{T}}$接受该解,将其作为下一个当前解. $\Delta C$是优化目标函数值之差的绝对值
- 按一定方式将T降温,令$i=i+1$,$T_i=w(T_i)$
- 检查退火过程是否结束,若未结束则跳转到3,否则跳转到7
- 以当前解$s_i$作为最优解输出
需要注意:
模拟退火算法求得的最优解通常为近似解,并非精确解
该算法在每一次迭代中产生的新解$s’$是随机选择当前解的一个临近子集中的值,随机选择的范围与温度有关,因此并不能确保新解会命中精确的最优解。但随着迭代次数的提高与温度的降低,新解会不断靠近最优解。
模拟退火算法求得的最优解理论上为全局最优解
该算法在评价新解$s’$是否作为下一个当前解$s(i+1)$时,在新解$s’$未优于当前解$s(i)$的情况下并没有抛弃新解,而是依概率接受新解,这样可以确保每次迭代的解都能双向移动。概率与$s’$和$s(i)$优化程度的差值有关,也与当前温度有关。求出局部最优解的可能性仍然存在。
如上图,当前解为$x$时,若产生新解为$x_2$,尽管新解未优于当前解,但很明显全局最优解在$x$左侧,若直接舍弃$x_2$,则会导致新解的一直落在当前解的右侧,最终导致陷入局部最优解。
优化问题
单变量函数优化
问题:对于给定的$n$,求$\sqrt{n}$。
设$x$为$\sqrt{n}$的最优解,当$x^2$与$n$越接近,x的值就越精确。因此问题的优化目标如下:
初始温度设为$1000$,温度变化率设为$1\%$,最低温度边界为$10^{-5}$,初始最优解可以是定义域内的任意值,本文取$x_0=0$. 程序如下:
1 | import random |
当n取10时,程序的结果为3.16227766,f非常接近$\sqrt{10}$。如果增加初始温度T
,减小温度变化率dT
和温度边界Tep
可提升精度。但增加迭代次数将会导致算法时间开销增加。
多变量函数优化
多变量函数优化需要考虑对每一个变量都进行扰动,因此在每个温度状态下将新增一个内层循环。在内层循环的每次迭代过程中,既可以随机对某一变量进行扰动,其他变量不变;也可以对每个变量同时扰动。该过程是一个Markov过程,每个变量的取值仅与前一个变量的取值相关,内层循环的迭代次数为Markov链的长度。
以智能优化算法的标准测试函数Schwefel为例。Schwefel的形式为:
![](https://pic.imgdb.cn/item/6304e0fc16f2c2beb1ee2d22.png)
1 | import numpy as np |
当变量个数为2时,筛选出最优结果为:1
2[420.92878528 421.02752236]
0.000662878735056438
输出结果的值是随机的,但大部分围绕标准解波动。程序有时会输出偏差较大的值,说明此时的结果落在了局部最优解附近,这种情况与温度及其变化率的设置有关,也与对变量扰动的处理方法有关。该程序内层循环的每次迭代仅对一个变量进行扰动,且扰动幅度相同,这可能导致变量之间的偏差较大,从而偏离标准解。
约束条件的处理
此处所说的约束条件是指不同变量之间的约束关系,而非单个变量的上下限约束。
对于不等式约束条件,可在模拟退火算法的迭代过程中将每次产生的新解代入约束函数,如果满足约束条件则保留;如果不满足约束条件则舍弃该解并重新生成新解并检验,直到满足所有约束条件为止。对于等式约束,可以采用等式代换的方法进行消元,仅保留不等式约束,再按以上方法对新解进行检验。
这种检验法思路简单,但在约束条件繁多且苛刻的情况下,该方法的时间开销将会相当大。下文将介绍另一种通用的方法。
惩罚函数法
惩罚函数法(SUMT)又称为序列无约束最优化方法,它通过对约束条件加权将约束优化问题转化为无约束优化问题。
对于以下的约束优化问题:
根据惩罚函数法,将约束最优化问题转换为增广目标函数极小值问题:
其中$\lbrace M_t\rbrace$为一单调增加正序列,即$M_1
带约束条件的单目标优化
有非线性单目标目标优化如下:
利用惩罚函数法,将约束条件转化为惩罚函数,其中定义$P_1(X)=(\min(x_1x_2x_3x_4-25,0))^2$,$P_2(X)=(x_1^2+x_2^2+x_3^2+x_4^2-40)^2$,此时构造新的增广目标函数$min\ F(X,M_t)=f(X)+M_t(P_1(X)+P_2(X))$,其中$M_t$为惩罚因子。
惩罚因子$M_t$随外层循环逐渐增大,在内层循环保持不变。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
60import numpy as np
import random
import math
class SA:
def __init__(self):
self.tInit = 10000 # 初始温度
self.tFinal = 1e-15 # 温度边界
self.dt = 0.999 # 降温系数
self.markov = 100 # 马尔可夫链长
self.X = [1, 1, 1, 1] # 变量
self.xMin = [1, 1, 1, 1] # 变量下限
self.xMax = [5, 5, 5, 5] # 变量上限
self.F = 0 # 目标函数值
self.M = 1 # 惩罚因子
self.delta = 1.01 # 惩罚因子增长系数
self.scale = 0.5 # 内循环搜索步长
def optimize(self, X):
# 惩罚函数
P0 = (min(0, X[0]*X[1]*X[2]*X[3]-25))**2
P1 = (X[0]**2 + X[1]**2 + X[2]**2 + X[3]**2 - 40)**2
# 优化目标函数
fx = X[0] * X[3] * (X[0] + X[1] + X[2]) + X[2]
# 新的优化目标
F = fx + self.M*(P0 + P1)
return F
def run(self):
xNew = [1, 1, 1, 1]
t = self.tInit
self.F = self.optimize(self.X)
while t > self.tFinal:
for i in range(self.markov):
v = random.randint(0, 3)
xNew[v] = self.X[v] + self.scale*random.normalvariate(0, 1)
while xNew[v] < self.xMin[v] or xNew[v] > self.xMax[v]:
xNew[v] = self.X[v] + self.scale*random.normalvariate(0, 1)
fNew = self.optimize(xNew)
if fNew < self.F:
self.F = fNew
self.X[:] = xNew[:]
# self.scale *= self.dt
elif math.exp(-(fNew - self.F)/t) > random.random():
self.F = fNew
self.X[:] = xNew[:]
# 减小搜索步长
self.scale *= self.dt
# 降温
t *= self.dt
# 增加惩罚因子
self.M *= self.delta
def display(self):
print('X:', self.X)
print('F(x)', self.F)
print('opt', self.X[0] * self.X[3] * (self.X[0] + self.X[1] + self.X[2]) + self.X[2])
print('M', self.M)
if __name__ == '__main__':
sa = SA()
sa.run()
sa.display()
程序经过多次运行后筛选最优值为$x_1=2.0962,x_2=1.0982,x_3=1.4561,x_4=3.0498$,优化目标函数值为$31.187$。但是该结果与最优结果仍有较大差距,这说明程序有欠缺之处,也与模拟退火算法的随机性有关。