万字长文了解模拟退火算法原理及求解复杂约束问题(源码实现)

模拟退火算法原理

    退火这个词,其实是铁匠发明的。它的意思很简单,就是将铁匠炉烧热后,再把下边的火撤掉,让金属在炉子里边慢慢冷却。人们发现,这个缓慢的降温过程能消除金属内部的各种缺陷,使得其恢复能量最低的状态。后来,受到退火工艺的启发,研究人员把将退火的缓慢的降温思路推广开来,用于寻找复杂函数的全局最优值。举个简单的例子,氦原子在金属中空位附近运动时,其势能函数大概长这样:
    举个简单的例子,氦原子在金属中空位附近运动时,其势能函数大概长这样:

    把势能函数看成一个轨道,再把氦看成一个小球放到这个轨道上。那么,小球的高度就是它的重力势能。求这个函数的全局最小值,其实就是让小球滚到最深那个坑过程。

    由于函数中存在非常多局域极小值(浅坑),常见的贪心算法(如梯度下降法,就是闭着眼睛沿坡往下滚)会陷入最近的局域极小值,很错过到全局最小值(深坑)。
    那么,怎么才能让滚进深坑呢?
    我们先晃一晃整个体系,让小球随机动起来。我们把晃动的剧烈程度称为温度T。在足够长的时间内。小球出现在i点的概率呈玻尔兹曼分布,与i点的高度(势能Ei)和温度T密切相关:

    温度为0时,小球出现在全局最小值的概率为100%。
    那么,把温度设为0不就行了?
    当然不行。上面的概率成立的前提是提供无穷长的时间,使得小球能够遍历整个函数。
    实际中,无穷长的时间当然是不现实的。小球从低概率区向高概率区转移,这个过程是需要消耗时间的。
    高温下,转移速度快。但另一方面,高温下,小球落在各个坑里的概率差别比较小。
    为了让小球能够尽快的转移到最低点,我们可以采取一个降温策略:

  • 先设定一个较高的温度T,使得小球有足够的能量越过能垒,小球到处乱跑的概率比较大。
  • 随机移动小球的位置,检查其移动前后的能量差 ΔE。
  • 若 ΔE<0 ,小球能量降低,则接受此次移动。
  • 若 ΔE>0,小球能量升高,有一定概率接受此次移动,概率为:exp(-ΔE/(KBT)) 。这个判定方法称为Metropolis判据。
  • 逐渐降低温度T,使得小球滑向低处的概率逐渐增大,直到小球缓缓收敛到最低点,不再晃动。

    通过这样一个逐渐降温的过程,小球最终有很大概率(并不是一定)能找到全局最低值。这个算法就是模拟退火算法。
    模拟退火算法最大的优势在于其通用性:函数形式如何复杂我不管,我就一步步瞎跑,然后按Metropolis判据概率性的接受这一步就行。

流程图见下:


算例

算例1

    计算函数f(x)=∑x^2(-20≤x≤20)的最小值,其中个体x的维数n= 10。 这是一个简单的平方和函数,只有一个极小点x=(0, 0, …0), 理论最小值f(0,0, .,. 0)=0

MATLAB版求解

%%%%%%%%%%%%%%%%%%%%%%模拟退火算法解决函数极值%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%初始化%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
clear all;                      %清除所有变量
close all;                      %清图
clc;                            %清屏
D=10;                           %变量维数 
Xs=20;                          %上限                                
Xx=-20;                         %下限
%%%%%%%%%%%%%%%%%%%%%%%%%%%冷却表参数%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
L = 200;                        %马可夫链长度
K = 0.998;                      %衰减参数
S = 0.01;                       %步长因子
T=100;                          %初始温度
YZ = 1e-8;                      %容差
P = 0;                          %Metropolis过程中总接受点
%%%%%%%%%%%%%%%%%%%%%%%%%%随机选点 初值设定%%%%%%%%%%%%%%%%%%%%%%%%%
PreX = rand(D,1)*(Xs-Xx)+Xx;
PreBestX = PreX;
PreX =  rand(D,1)*(Xs-Xx)+Xx;
BestX = PreX;
%%%%%%%%%%%每迭代一次退火一次(降温), 直到满足迭代条件为止%%%%%%%%%%%%
deta=abs( func1( BestX)-func1(PreBestX));
while (deta > YZ) && (T>0.001)
    T=K*T; 
    %%%%%%%%%%%%%%%%%%%%%在当前温度T下迭代次数%%%%%%%%%%%%%%%%%%%%%%
    for i=1:L  
        %%%%%%%%%%%%%%%%%在此点附近随机选下一点%%%%%%%%%%%%%%%%%%%%%
            NextX = PreX + S* (rand(D,1) *(Xs-Xx)+Xx);
            %%%%%%%%%%%%%%%%%边界条件处理%%%%%%%%%%%%%%%%%%%%%%%%%%
            for ii=1:D
                if NextX(ii)>Xs | NextX(ii)<Xx
                    NextX(ii)=PreX(ii) + S* (rand *(Xs-Xx)+Xx);
                end
            end            
        %%%%%%%%%%%%%%%%%%%%%%%是否全局最优解%%%%%%%%%%%%%%%%%%%%%%
        if (func1(BestX) > func1(NextX))
            %%%%%%%%%%%%%%%%%%保留上一个最优解%%%%%%%%%%%%%%%%%%%%%
            PreBestX = BestX;
            %%%%%%%%%%%%%%%%%%%此为新的最优解%%%%%%%%%%%%%%%%%%%%%%
            BestX=NextX;
        end
        %%%%%%%%%%%%%%%%%%%%%%%% Metropolis过程%%%%%%%%%%%%%%%%%%%
        if( func1(PreX) - func1(NextX) > 0 )
            %%%%%%%%%%%%%%%%%%%%%%%接受新解%%%%%%%%%%%%%%%%%%%%%%%%
            PreX=NextX;
            P=P+1;
        else
            changer = -1*(func1(NextX)-func1(PreX))/ T ;
            p1=exp(changer);
            %%%%%%%%%%%%%%%%%%%%%%%%接受较差的解%%%%%%%%%%%%%%%%%%%%
            if p1 > rand        
                PreX=NextX;
                P=P+1;         
            end
        end
    trace(P+1)=func1( BestX);    
    end
    deta=abs( func1( BestX)-func1 (PreBestX)); 
end
disp('最小值在点:');
BestX
disp( '最小值为:');
func1(BestX)
figure
plot(trace(2:end))
xlabel('迭代次数')
ylabel('目标函数值')
title('适应度进化曲线')

%%%%%%%%%%%%%%%%%%%%%%%%%适应度函数%%%%%%%%%%%%%%%%%%%%%%%
function result=func1(x)
summ=sum(x.^2);
result=summ;
end

从图中可以看到迭代次数太多,是个体寻优,需要花费很多时间。相比其他群体寻优方法,模拟退火算法很差。

python版求解


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Author: yudengwu(余登武)
# @Date  : 2021/6/21
#@email:1344732766@qq.com

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib; matplotlib.use('TkAgg')
mpl.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题


#=================初始化参数============
D=10                           #变量维数
Xs=20                          #上限
Xx=-20                         #下限
#==冷却表参数==
L = 200                       #马可夫链长度 #在温度为t情况下的迭代次数
K = 0.998                     #衰减参数
S = 0.01                      #步长因子
T=100                         #初始温度
YZ = 1e-7                     #容差
P = 0                         #Metropolis过程中总接受点
#====随机选点 初值设定====
PreX = np.random.uniform(size=(D,1))*(Xs-Xx)+Xx
PreBestX = PreX#t-1代的全局最优X
PreX =  np.random.uniform(size=(D,1))*(Xs-Xx)+Xx
BestX = PreX#t时刻的全局最优X

#==============目标函数=============
def func1(x):
    return np.sum([i**2 for i in x])


#====每迭代一次退火一次(降温), 直到满足迭代条件为止===
deta=np.abs(func1(BestX)-func1(PreBestX))#前后能量差

trace=[]#记录
while (deta > YZ) and (T>0.1):#如果能量差大于允许能量差 或者温度大于阈值
    T = K * T#降温
    print(T)
    #=在当前温度T下迭代次数=
    for i in range(L):#
        #=在此点附近随机选下一点==
        NextX = PreX + S * (np.random.uniform(size=(D, 1)) * (Xs - Xx) + Xx)
        #===边界条件处理
        for ii in range(D):#遍历每一个维度
            while NextX[ii]>Xs or NextX[ii]<Xx:
                NextX[ii]=PreX[ii] + S* (np.random.random() *(Xs-Xx)+Xx)

        #===是否全局最优解 ===
        if (func1(BestX) > func1(NextX)):
            #保留上一个最优解
            PreBestX = BestX
            #此为新的最优解
            BestX = NextX

        #====Metropolis过程===
        if (func1(PreX) - func1(NextX) > 0):#后一个比前一个好
           #接受新解
           PreX = NextX
           P = P + 1
        else:
            changer = -1 * (func1(NextX) - func1(PreX)) / T
            p1 = np.exp(changer)
            #接受较差的解
            if p1 > np.random.random():
                PreX = NextX
                P = P + 1
        trace.append(func1(BestX))
    deta = np.abs(func1(BestX)- func1(PreBestX))#修改前后能量差



print('最小值点\n',BestX)
print('最小值\n',func1(BestX))
plt.plot(trace,label='迭代曲线')
plt.xlabel('迭代次数')
plt.ylabel('目标函数值')
plt.show()


复杂约束求解:算例2

复杂约束求解:python版求解

在约束求解中,添加了惩罚项。


import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib; matplotlib.use('TkAgg')
mpl.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题


#=================初始化参数============
D=2                           #变量维数 ,对应x,y
Xs=1                          #搜索区间上限
Xx=-1                         #搜索区间下限
#==冷却表参数==
L = 300                       #马可夫链长度 #在温度为t情况下的迭代次数
K = 0.98                     #衰减参数
S = 0.01                      #步长因子
T=100                         #初始温度
YZ = 1e-8                     #容差(运行t-1,,t时刻的误差)
P = 0                         #Metropolis过程中总接受点
eloss=0.1                     #允许的惩罚项误差
#====随机选点 初值设定====
PreX = np.zeros(shape=(D,1))
PreX[0]=np.random.uniform(1,2,1)#X范围【1,2】
PreX[1]=np.random.uniform(-1,0,1)#y范围【-1,0】
PreBestX = PreX#t-1代的全局最优X

PreX = np.zeros(shape=(D,1))
PreX[0]=np.random.uniform(1,2,1)#X范围【1,2】
PreX[1]=np.random.uniform(-1,0,1)#y范围【-1,0】
BestX = PreX#t时刻的全局最优X


#==============目标函数=============
def func1(X):
    A = 10
    pi = np.pi
    x = X[0]
    y = X[1]
    return 2 * A + x ** 2 - A * np.cos(2 * pi * x) + y ** 2 - A * np.cos(2 * pi * y)
#==========惩罚项===========
def calc_e(X):
    """计算个体的目惩罚项,X 的维度是 size * 2 """
    ee = 0
    """计算第一个约束的惩罚项"""
    e1 = X[0] + X[1] - 6
    ee += max(0, e1)
    """计算第二个约束的惩罚项"""
    e2 = 3 * X[0] - 2 * X[1] - 5
    ee += max(0, e2)
    return ee


#====每迭代一次退火一次(降温), 直到满足迭代条件为止===
deta=np.abs(func1(BestX)-func1(PreBestX))#前后能量差


trace=[]#记录
while (deta > YZ) and (T>0.1):#如果能量差大于允许能量差 或者温度大于阈值
    T = K * T#降温
    print(T)
    # =在当前温度T下迭代次数=
    for i in range(L):  #
        # =在此点附近随机选下一点==
        NextX = PreX + S * (np.random.uniform(size=(D, 1)) * (Xs - Xx) + Xx)
        # ===边界条件处理
        while NextX[0] > 2 or NextX[0] < 1:#x属于【1,2】
            NextX[0] =PreX[0] + S* (np.random.uniform() * (Xs - Xx) + Xx)
        while NextX[1] > 0 or NextX[0] < -1:  # y属于【-1,0】
            NextX[1] = PreX[1] + S* (np.random.uniform() * (Xs - Xx) + Xx)

        # ===是否全局最优解 ===
        if (func1(BestX) > func1(NextX)) and (calc_e(BestX)<eloss) and (calc_e(NextX)<eloss):#两者都没有违反约束,下一代适应度更好时选下一代
            # 保留上一个最优解
            PreBestX = BestX
            # 此为新的最优解
            BestX = NextX
        elif (calc_e(BestX)>eloss) and (calc_e(NextX)>eloss) and (func1(BestX) > func1(NextX)):#两者都违反约束,下一代适应度更好时(选下一代)
            # 保留上一个最优解
            PreBestX = BestX
            # 此为新的最优解
            BestX = NextX
        elif (calc_e(BestX)>eloss) and (calc_e(NextX)<eloss) :#上一代惩罚项不满足约束,下一代惩罚项满足约束(选下一代)
            # 保留上一个最优解
            PreBestX = BestX
            # 此为新的最优解
            BestX = NextX


        # ====Metropolis过程  当代温度T 前后个体判断===
        if (func1(PreX) - func1(NextX) > 0) and (calc_e(PreX)<eloss) and(calc_e(NextX)<eloss) :  # 两个惩罚项都满足约束,后一个的适应度更小(选后一个)
            # 接受新解
            PreX = NextX
            P = P + 1
        elif  (calc_e(PreX)>eloss) and(calc_e(NextX)>eloss) and (func1(PreX) - func1(NextX) > 0) :#两个惩罚项都不满足时,后一个适应度比前一个小(选后一个)
            # 接受新解
            PreX = NextX
            P = P + 1
        elif (calc_e(PreX)>eloss) and(calc_e(NextX)<eloss) :#前一个惩罚项不满足,后一个满足(选后一个)
            # 接受新解
            PreX = NextX
            P = P + 1

        else:
            changer = -1 * (func1(NextX) - func1(PreX)) / T
            p1 = np.exp(changer)
            # 接受较差的解
            if p1 > np.random.random():
                PreX = NextX
                P = P + 1
        trace.append(func1(BestX))
    deta = np.abs(func1(BestX) - func1(PreBestX))  # 修改前后能量差



print('最优值X\n',BestX)
print('最优目标函数值\n',func1(BestX))
print('最优值对应的惩罚项',calc_e(BestX))
plt.plot(trace,label='迭代曲线')
plt.xlabel('迭代次数')
plt.ylabel('目标函数值')
plt.show()

在这里插入图片描述

作者:电气-余登武

  • 8
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

总裁余(余登武)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值