1.算法简介
模拟退火算法(Simulated Annealing,SA)是一种模拟物理退火的过程而设计的随机优化算法,结合爬山法和随机行走算法,同时避免算法进入局部最优,早期用于组合优化,后来发展成一种通用的优化算法。它的基本思想最早在1953年就被Metropolis提出,但直到1983年Kirkpatrick等人才设计出真正意义上的模拟退火算法并进行应用。

该算法采用类似于物理退火的过程,先在一个高温状态下(相当于算法随机搜索),然后逐渐退火,在每个温度下(相当于算法的每一次状态转移),徐徐冷却(相当于算法局部搜索),最终达到物理基态(相当于算法找到最优解)。

高温过程——增强粒子的热运动,使其偏离平衡位置,目的是消除系统原先可能存在的非均匀态;
等温过程——退火过程中要让温度慢慢降低,在每一个温度下要达到热平衡状态,对于与环境换热而温度不变的封闭系统满足自由能较少定律,系统状态的自发变化总是朝自由能减少的方向进行,当自由能达到最小时,系统达到平衡态;
冷却过程——使粒子热运动减弱并渐趋有序,系统能量逐渐下降,从而得到低能的晶体结构。当液体凝固为固体的晶态时退火过程完成。

因此模拟退火算法从某一高温出发,在高温状态下计算初始解,然后以预设的邻域函数产生一个扰动量,从而得到新的状态,即模拟粒子的无序运动,比较新旧状态下的能量,即目标函数的解。如果新状态的能量小于旧状态,则状态发生转化;如果新状态的能量大于旧状态,则以Metropolis接受准则发生转化。当状态稳定后,便可以看作达到了当前状态的最优解,便可以开始降温,在下一个温度继续迭代,最终达到低温的稳定状态,便得到了模拟退火算法产生的结果

该算法的关键点如下:
1、对固体退火过程的模拟;
2、采用Metropolis接受准则;
3、用冷却进度表控制算法进程,使算法在多项式时间里给出一个近似解。
固体退火过程是SAA的物理背景;Metropolis接受准则使算法跳离局部最优 “险井”;而冷却进度表的合理选择是算法应用的前提。

下面依次介绍上述三个关键点

1.1 固体退火过程:
对于该部分如果不理解,可以先简单跳过。
固体退火过程的数学表述:
在温度T,分子停留在状态r满足Boltzmann概率分布

1.2 Metropolis准则
Metropolis准则(1953)——以概率接受新状态,固体在恒定温度下达到热平衡的过程可以用Monte Carlo方法(计算机随机模拟方法)加以模拟。

Monte Carlo模拟退火过程:蒙特卡罗(Monte Carlo)方法,或称计算机随机模拟方法,是一种基于“随机数”的计算方法。

若在温度T,当前状态i → 新状态j
若Ej<Ei,则接受 j 为当前状态;
否则,若概率 p=e x p [ − ( E j − E i ) / k B T ] exp[-(Ej-Ei)/k_BT]exp[−(Ej−Ei)/k B

 T](也就是状态j与状态i概率的比值) 大于[0,1)区间的随机数,则仍接受状态 j 为当前状态;若不成立则保留状态 i 为当前状态。

可以看出,高温下可接受与当前状态能差较大的状态为重要状态,而在低温下只能接受与当前状态能差较小的新状态为重要状态。在温度趋于零时,就不再接受Ej>Ei的新状态j了.如此反复,达到系统在此温度下的热平衡。这个过程称作Metropolis抽样过程。上面这种方法被称为重要性采样法,该方法以概率形式接受新状态,避免局部最优。

1.3 冷却进度表
在Metropolis抽样过程中温度T缓慢的降低,模拟退火过程就是通过T参数的变化使状态收敛于最小能量处。因而,T参数的选择对于算法最后的结果有很大影响。初始温度和终止温度设置的过低或过高都会延长搜索时间。降温步骤太快,往往会漏掉全局最优点,使算法收敛至局部最优点。降温步骤太慢,则会大大延长搜索全局最优点的计算时间,从而难以实际应用。因此选择一个合适的降温函数非常重要,冷却进度表就是指的从某一高温状态T向低温状态冷却时的降温函数。

pytorch模拟退火算法 python模拟退火的库_最优解

import math
from random import random
import matplotlib.pyplot as plt
"""
第一步是由一个产生函数从当前解产生一个位于解空间的新解;为便于后续的计算和接受,减少算法耗时,通常选择由当前新解经过简单地变换即可产生新解的方法,如对构成新解的全部或部分元素进行置换、互换等,注意到产生新解的变换方法决定了当前新解的邻域结构,因而对冷却进度表的选取有一定的影响。
第二步是计算与新解所对应的目标函数差。因为目标函数差仅由变换部分产生,所以目标函数差的计算最好按增量计算。事实表明,对大多数应用而言,这是计算目标函数差的最快方法。
第三步是判断新解是否被接受,判断的依据是一个接受准则,最常用的接受准则是Metropolis准则: 若ΔT<0则接受S′作为新的当前解S,否则以概率P接受S′作为新的当前解S。
第四步是当新解被确定接受时,用新解代替当前解,这只需将当前解中对应于产生新解时的变换部分予以实现,同时修正目标函数值即可。此时,当前解实现了一次迭代。可在此基础上开始下一轮试验。而当新解被判定为舍弃时,则在原当前解的基础上继续下一轮试验。

"""
def func(x, y):  # 函数优化问题
    res = 4 * x ** 2 +2*y**2
    return res


# x为公式里的x1,y为公式里面的x2
class SA:
    def __init__(self, func, iter=100, T0=100, Tf=0.01, alpha=0.99):
        self.func = func
        self.iter = iter  # 内循环迭代次数,即为L =100
        self.alpha = alpha  # 降温系数,alpha=0.99
        self.T0 = T0  # 初始温度T0为100
        self.Tf = Tf  # 温度终值Tf为0.01
        self.T = T0  # 当前温度
        self.x = [random() * 11 - 5 for i in range(iter)]  # 随机生成100个x的值列表形式
        self.y = [random() * 11 - 5 for i in range(iter)]  # 随机生成100个y的值
        self.most_best = []
        """
        random()这个函数取0到1之间的小数
        如果你要取0-10之间的整数(包括0和10)就写成 (int)random()*11就可以了,11乘以零点多的数最大是10点多,最小是0点多
        该实例中x1和x2的绝对值不超过5(包含整数5和-5),(random() * 11 -5)的结果是-6到6之间的任意值(不包括-6和6)
        (random() * 10 -5)的结果是-5到5之间的任意值(不包括-5和5),所有先乘以11,取-6到6之间的值,产生新解过程中,用一个if条件语句把-5到5之间(包括整数5和-5)的筛选出来。
        """
        self.history = {'f': [], 'T': []}

    def generate_new(self, x, y):  # 扰动产生新解的过程
        while True:
            x_new = x + self.T * (random() - random())
            y_new = y + self.T * (random() - random())
            if (-5 <= x_new <= 5) & (-5 <= y_new <= 5):
                break  # 重复得到新解,直到产生的新解满足约束条件
        return x_new, y_new

    def Metrospolis(self, f, f_new):  # Metropolis准则,新的小于旧的
        if f_new <= f:
            return 1
        else:
            p = math.exp((f - f_new) / self.T)   #返回e^x,x表示参数,e是欧拉常数
            if random() < p:
                return 1
            else:
                return 0

    def best(self):  # 获取最优目标函数值
        f_list = []  # f_list数组保存每次迭代之后的值
        for i in range(self.iter):
            f = self.func(self.x[i], self.y[i])
            f_list.append(f)
        f_best = min(f_list)

        idx = f_list.index(f_best)
        return f_best, idx  # f_best,idx分别为在该温度下,迭代L次之后目标函数的最优解和最优解的下标

    def run(self):
        count = 0
        # 外循环迭代,当前温度小于终止温度的阈值
        while self.T > self.Tf:             #T根据降温系数不断减小,但为防止直接达到最优,内部循环100次

            # 内循环迭代100次
            for i in range(self.iter):

                f = self.func(self.x[i], self.y[i])  # f为迭代一次后的值
                x_new, y_new = self.generate_new(self.x[i], self.y[i])  # 产生新解
                f_new = self.func(x_new, y_new)  # 产生新值
                if self.Metrospolis(f, f_new):  # 判断是否接受新值
                    self.x[i] = x_new  # 如果接受新值,则把新值的x,y存入x数组和y数组
                    self.y[i] = y_new
            # 迭代L次记录在该温度下最优解
            ft, _ = self.best()
            self.history['f'].append(ft)
            self.history['T'].append(self.T)
            # 温度按照一定的比例下降(冷却)
            self.T = self.T * self.alpha
            count += 1

            # 得到最优解
        f_best, idx = self.best()                #分别为在该温度下,迭代L次之后目标函数的最优解和最优解的下标
        print(f"F={f_best}, x={self.x[idx]}, y={self.y[idx]}")
        print(count)


sa = SA(func)
sa.run()

plt.plot(sa.history['T'], sa.history['f'])
plt.title('SA')
plt.xlabel('T')
plt.ylabel('f')
plt.gca().invert_xaxis()
plt.show()

结果:F=3.591534016505581e-05, x=0.0010217709965204412, y=-0.003983671440250457

pytorch模拟退火算法 python模拟退火的库_迭代_02

def demo2_func(x):
    x1, x2, x3 = x
    return x1**2+x2**2+x3**2
#模拟退火算法
from sko.SA import SAFast
sa_fast = SAFast(func=demo2_func, x0=[1, 1, 1], T_max=1, T_min=1e-9, q=0.99, L=300, max_stay_counter=150)
sa_fast.run()
print('Fast Simulated Annealing: best_x is ', sa_fast.best_x, 'best_y is ', sa_fast.best_y)