模拟退火算法
算法背景
退火是指将固体加热到足够高的温度,使分子呈随机排列状态,然后逐步降温使之冷却,最后分子以低能状态排列,固体达到某种稳定状态。物理退火包括一下3个过程:
1.加温过程——增强粒子的热运动,消除系统原先可能存在的非均匀态;
2.等温过程——对于与环境换热而温度不变的封闭系统,系统状态的自发变化总是朝自由能减少的方向进行,当自由能达到最小时,系统达到平衡态;
3.冷却过程——使粒子热运动减弱并渐趋有序,系统能量逐渐下降,从而得到低能的晶体结构。
算法原理
基本原理
模拟退火算法分为三部分:初始解、解空间以及目标函数,分别对应物理退火过程中的初始温度、降温以及最终温度。
1.初始解:初始解释算法迭代的起点,试验表明,模拟退火算法是健壮的,即最终解的求得最优解并不十分依赖初始解的选取,从而可任意选择一个初始解。当然,如果初始解选择得当可以加快找到全局最优解。
2.解空间:一般是离散的可行解的集合。
3.目标函数:对优化目标的量化描述,是解空间到某个数集的一个映射,通常表示为若干优化目标的一个和式,应正确体现问题的整体优化要求且较易计算,当解空间包含不可行解时还应包括罚函数。
4.算法从初始解或称为初始状态开始,在解空间中进行启发式搜索(通常是随机搜索的方式),最终搜索到最优的目标值。
基本过程
初始化:设定初始温度T,初始解状态S,终止温度T0;
降温过程:如果T>T0,则循环执行3至6步;
在解空间中随机搜索一个新解S’;
计算增量ΔE=E(S′)-E(S),其中C(S)为解S对应的目标函数值或称为评价函数;
若ΔE<0则接受S′作为新的当前解,否则以概率exp(-ΔE/T)接受S′作为新的当前解;
如果满足终止条件则输出当前解作为最优解,结束程序。终止条件可以有两种情况,一是温度已经达到了最低温度T0;二是在连续的取若干个新解都没有跳出当前最优解。
退火方式
模拟退火算法中,退火方式对算法有很大影响。如果温度下降过慢,算法的收敛速度会大大降低。如果温度下降过快,可能会丢失极值点。为了提高模拟退火算法的性能,许多学者提出了退火的各种方式,比较有代表性的有以下几种:
- T(t) = T0/ln(1+t)
该方式的特点是温度下降缓慢,算法收敛速度也较慢,但是最终达到全局最优的可能性是最高的
- T(t) = T0/ln(1+at)
式中a为可调参数,可以改善退火曲线的形态。其特点是高温区温度下降比较快,低温区下降比较慢,这种退火方式主要期望在低温区收敛到全局最优。
- T(t) = T0a**t
式中a为可调参数,其特点是温度下降很快,算法的收敛速度快,但是带来的损失是可能不能充分的收敛到全局最优
以上三种退火方式各有优缺点以及适用的场景,需针对具体的应用进行选择。
算法框图
模拟退火算法流程图源代码
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import matplotlib.pyplot as plt
import math
import numpy as np
class SA():
def __init__ (self, max_steps):
self.T_max = 100 #最高温度
self.T_min = 1 #最小温度
self.T = self.T_max #温度初始化
self.t=1 #初始化时间
self.max_steps = max_steps # 同一温度下迭代次数
self.dim = 9 # 搜索空间的维度
self.x_bound_low = np.full(shape = self.dim, fill_value = -10) #变量取值下边界
self.x_bound_high = np.full(shape = self.dim, fill_value = 10) #变量取值上边界
def function_fitness (self, x):
return np.sum(np.square(x))
def update_value (self, x, y, new_x, new_y,best_x, best_y, flag):
"""要接受这个y_new为当前温度下的理想值,要满足y_new>y_old 或 math.exp(-(y - new_y) / self.T)>random.random()"""
if (new_y < y) or (math.exp(-(new_y - y) / self.T) > np.random.random()):
x = new_x.copy()
y = new_y
if new_y < best_y:
flag = 1
best_x = new_x.copy()
best_y = new_y
return x, y, new_x, new_y,best_x, best_y, flag
def solve (self):
x = np.random.uniform(self.x_bound_low, self.x_bound_high,size=(self.dim)) # 初始化自变量位置
y = self.function_fitness(x)
best_x = x.copy() #矩阵之间相互赋值需要特别注意,赋值后两者对应的地址还是一样的
best_y = y
new_x = x.copy()
new_y = y
while self.T > self.T_min:
flag = 0 #用以判断有无新值被接受
for step in range(self.max_steps):
delta_x = np.random.uniform(low=-0.055,high=0.055,size=(self.dim))*self.T
middle_value = x+delta_x
for i in range(self.dim):
if (self.x_bound_low[i] <= middle_value[i]) and (middle_value[i] <= self.x_bound_high[i]):
new_x[i] = x[i] + delta_x[i]
new_y = self.function_fitness(new_x)
x, y, new_x, new_y, best_x, best_y, flag = self.update_value ( x, y, new_x, new_y, best_x, best_y, flag)
if flag == 1:
x = best_x
y = best_y
self.t += 1
self.T = self.T_max/(50+self.t)
print(best_x, best_y)
sa = SA(100)
sa.solve()
python tips
当对数组进行运算和操作时,其数据有时会被拷贝到一个新的数组而有时又不会拷贝。对应有三种情况:完全不拷贝(b=a,两者对应内存地址相同)、视图或浅拷贝(c = a.view(),c随着a变,c不拥有数据,但改变其中任意一个值,两者都会变,矩阵切片是浅拷贝)、深拷贝(d=a.copy(),d和a已经没有任何关系了)!
本程序中的退火方式为T = T0/(a+t),与随机梯度下降法的学习率取相同的变化规律,仍有比较好的效果。
python中支持函数中嵌套函数,此时嵌套函数在函数体外不能使用,即归函数私有。