模拟退火算法

算法背景

退火是指将固体加热到足够高的温度,使分子呈随机排列状态,然后逐步降温使之冷却,最后分子以低能状态排列,固体达到某种稳定状态。物理退火包括一下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;二是在连续的取若干个新解都没有跳出当前最优解。

退火方式

模拟退火算法中,退火方式对算法有很大影响。如果温度下降过慢,算法的收敛速度会大大降低。如果温度下降过快,可能会丢失极值点。为了提高模拟退火算法的性能,许多学者提出了退火的各种方式,比较有代表性的有以下几种:

  1. T(t) = T0/ln(1+t)

该方式的特点是温度下降缓慢,算法收敛速度也较慢,但是最终达到全局最优的可能性是最高的

  1. T(t) = T0/ln(1+at)

式中a为可调参数,可以改善退火曲线的形态。其特点是高温区温度下降比较快,低温区下降比较慢,这种退火方式主要期望在低温区收敛到全局最优。

  1. T(t) = T0a**t

式中a为可调参数,其特点是温度下降很快,算法的收敛速度快,但是带来的损失是可能不能充分的收敛到全局最优

以上三种退火方式各有优缺点以及适用的场景,需针对具体的应用进行选择。

算法框图

模拟退火算法_第1张图片
模拟退火算法流程图

源代码

#!/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中支持函数中嵌套函数,此时嵌套函数在函数体外不能使用,即归函数私有。

你可能感兴趣的:(模拟退火算法)