【数学建模】模拟退火全解析

这篇具有很好参考价值的文章主要介绍了【数学建模】模拟退火全解析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

模拟退火理论(SA——Simulated Annealing)

和局部束搜索相反,模拟退火将最优化策略改变,引入随机噪声,不一定每次都是最优,但是内部机制保证了最终的走向是最优,总的过程可以理解为初期广泛探索(Exploration),逐步过渡到深挖(Exploitation)。其中机理比较复杂,我们逐步去理解。

基本思路

【数学建模】模拟退火全解析
首先声明,我们这里还是要将函数优化到最大,如果要优化最小,符号都要变。

所谓退火就是从高温逐步降温的过程。在这个过程中,初期高温,状态容易改变,不稳定,后期低温,状态稳定。我们模拟退火中进行随机变化的概率就和温度有关。这里引入温度变量T,T怎么变呢?一般来说是指数变化 T = 0.9 9 t T 0 T=0.99^tT_0 T=0.99tT0,这个0.99是退火率,可以进行修改,t是时间。之所以采用指数变化而不是什么线性递减,也有门道,后面解释。

看代码,随着循环,t增大,T以指数速率先快后慢变小。之后进行邻域中的随机选择,而不是在邻域中找最优。这时结果就只能有两种,我们通过新结果和原结果的差距 △ E \triangle E E判断:

  1. △ E > 0 \triangle E>0 E>0,更优。那我们就直接笑纳。这个效果上相当于登山算法。
  2. △ E ≤ 0 \triangle E\leq 0 E0, 相等或者更差,总之要么确实是不好的结果,要么再走走可能会碰到更好的结果,先苦后甜。那到底怎么抉择呢,这可能是个机遇,但是也有风险,所以我们要计算概率。

概率公式初步理解

接受坏结果的概率 P = e △ E T P=e^{\frac{\triangle E}{T}} P=eTE,解析一下这个式子。

  1. 首先指数部分一定是负的,所以保证P是介于0-1之间的。
  2. 初期T大,不管E怎么变,比值总是接近于0,概率较大,所以很容易接受所有改动
  3. 中期T适中,由 ∣ △ E ∣ |\triangle E| E的大小决定是否接受,delta绝对值越大,接受概率越小,因为巨大的退步代表巨大的风险,合理。
  4. 后期T小,一般情况比值都更接近于负无穷,只有在 ∣ △ E ∣ |\triangle E| E特别小时能有接近于0的比值,才能接受,也就是说后期要么就是更优,直接接受,要么就是产生一个更差结果,我大概率不接受而保持原状,所以后期就相当于一个局部搜索。这里可见指数优点,在低温区变化够慢,可以保证足够的时间达到局部最优,保证收敛。其实还可以采取别的,比如现实世界的温度变化函数,但是规律一定是先快后慢。

如何理解步长逐渐缩小

我们进一步从步长的角度理解,这个步长是指 ∣ △ E ∣ |\triangle E| E,而这个常常和解空间中点的距离有较大的关系,所以我们就叫他步长:

  1. 刚开始T大,所以 ∣ △ E ∣ |\triangle E| E即使很大,也可以走出去,所以初期步长可大可小,可以接受各种结果。
  2. 但是到了后期,T变小, ∣ △ E ∣ |\triangle E| E一旦变大,给的概率就会变小,步子越大,越不容易接受,所以T越小,小步长的概率就越大,从这个角度理解,随着T减小,平均步长是会越来越小的。

宏观理解

再进行宏观理解,理解下这个算法的合理性与有效性:

  1. 从宏观角度理解,如果你把每次计算出来的概率输出,你会发现接受较差结果的概率是越来越小。
  2. 合理性。不需要担心算法质量越来越差,因为概率保证了质量。因为是随机搜索的,所以假定产生好结果的概率是0.5,那么产生坏结果的前提下再接受坏结果的概率肯定是小于0.5的,这就保证了在任何时候,总体上接受好结果的概率要大于接受坏结果的概率,保证方向是正确的。
  3. 收敛性。初期对坏结果的接受度很高,所以属于横冲直撞的广泛探索,不会收敛。后期温度降低,且保持足够长时间的低温。低温状态下,基本只会朝着优化方向走,所以在低温状态下相当于一个登山算法,而登山算法只要迭代次数够多,必然能达到局部最优。所以一定会收敛。
  4. 收敛较慢的特性保证了解的优秀性。

局部搜索实践:北理工最优化大作业——流水车间调度

流水车间问题入门
基于改进模拟退火算法的大规模置换流水车间调度_黎阳

n个器件,在m个机器上加工。
每个器件在一个机器上加工的时间都不同,会给出一个矩阵,告诉你哪个器件在哪个机器上的耗时。
求最好的顺序。

为什么顺序会影响时间呢?那是因为一个器件的加工,要等自己在前一个机器的加工完成,又要等上一个器件在当前机器的加工完成,这两个等待时间和顺序有关。

对于一种顺序,时间计算是很容易的,我后面也有现成的代码。

初探:邻域如何确定

问题来了,状态转移比较难。人家八皇后可以通过冲突数来确定状态变化,我们通过什么确定呢?或者准确说,我们在什么邻域里搜索呢?

既然是顺序,那么我每次变化就只能变化顺序,必须保证不冲突,就没办法对一个器件进行单独变化。但是可以明确的是,每次变化一定是交换两个工件的顺序,所以如何选定两个工件就是问题。是否存在一种启发性算法能找出合理的工件呢?我们还得从目标上下手。

经过我的一通查找,也看了一些论文,最后发现是有启发性算法的,但是比较麻烦,而且效果也就好那么一丢丢,性价比太低。所以最后的结论就是:不需要使用启发性算法,直接就进行邻域随机搜索。

你可能会怀疑,这和领域有什么关系呢?其实你只交换两个,或者只交换一部分,这已经是就近原则了。真正的全局纯随机,是利用np.random.shuffle函数将顺序彻底打乱,我试过这个方法,十分影响表现,所以不能用。这里也看出了,我们的三种交换策略确实和全局纯随机不一样。

【数学建模】模拟退火全解析

基本函数结构

这里给出导入的包,以及要用的函数。
其中有三种交换策略,基础的使用点交换,论文中还提到了区间颠倒和区间交换,我也都写了,经过测试保证有正确的结果。

# 解流水车间调度问题
# 函数编写
# 读取一个用例
def read():
    with open('flowshop-test-10-student.txt') as file_obj:
    #with open('one_instance.txt') as file_obj:
        line=file_obj.readline()#去除头两行
        line=file_obj.readline()
        line=file_obj.readline().split()# 获取n,m
        n=int(line[0])
        m=int(line[1])
        data=[]# 逐行读取内容
        for i in range(n):
            part=file_obj.readline().split()# 将工件数据提取出来
            part=[int(x) for x in part]# 类型装换
            part=part[1::2]# 切片
            data.append(part)
        return data   
       
# 时间到温度
def time_to_temp(time):
    return 0.99**time*100

# 动态规划计算调度时间,schedule储存顺序 
def all_time(data,schedule):
    C=np.zeros((len(data),len(data[0])))
    C[0][0]=data[schedule[0]][0]
    for j in range(1,len(data[0])):# 计算1,m
        C[0][j]=C[0][j-1]+data[schedule[0]][j]
    for i in range(1,len(data)): # 计算n,1
        C[i][0]=C[i-1][0]+data[schedule[i]][0]
    for j in range(1,len(data[0])): # 填充
        for i in range(1,len(data)): 
            C[i][j]=max(C[i-1][j],C[i][j-1])+data[schedule[i]][j]            
    #return C
    return C[-1][-1]
    
# 邻域搜索
def two_exchange(pre_schedule): # 将选定区域中工件顺序颠倒
    schedule=pre_schedule[:]
    n=len(pre_schedule)
    i=np.random.randint(0,n) # [low,high),恰好覆盖所有可能
    j=np.random.randint(0,n)
    if j<i: # 保持ij大小关系
        temp=j
        j=i
        i=temp
    for k in range(j-i+1):# 颠倒区间
        schedule[j-k]=pre_schedule[i+k]
    return schedule

def three_exchange(pre_schedule): # 交换两段子序列位置
    n=len(pre_schedule)
    nodes=np.random.randint(0,n,3)
    nodes=sorted(nodes)
    print(nodes)
    schedule=pre_schedule[:nodes[0]] # 两边要边界,中间分给右边
    schedule.extend(pre_schedule[nodes[1]:nodes[2]+1])
    schedule.extend(pre_schedule[nodes[0]:nodes[1]])
    schedule.extend(pre_schedule[nodes[2]+1:])
    return schedule

def point_exchange(pre_schedule):# 随机两点交换
    schedule=pre_schedule[:]
    n=len(schedule)
    i=np.random.randint(0,n) # [low,high),恰好覆盖所有可能
    j=np.random.randint(0,n)
    #print(f"exchange {i},{j}")
    t=schedule[i]
    schedule[i]=schedule[j]
    schedule[j]=t
    return schedule

登山搜索框架

用例:
这是一个简单的用例,用的时候保存成txt放在目录下,自己调整一下代码里的文件路径就好, 1 1 11 11^{11} 1111的解空间也还不算特别大,勉强可以遍历,实际后面我们会在50个工件级别的问题中计算,所以趁早死了遍历的心。

instance 0
optimal-result:7038
11 5
0 375 1  12 2 142 3 245 4 412
0 632 1 452 2 758 3 278 4 398
0  12 1 876 2 124 3 534 4 765
0 460 1 542 2 523 3 120 4 499
0 528 1 101 2 789 3 124 4 999
0 796 1 245 2 632 3 375 4 123
0 532 1 230 2 543 3 896 4 452
0  14 1 124 2 214 3 543 4 785
0 257 1 527 2 753 3 210 4 463
0 896 1 896 2 214 3 258 4 259
0 532 1 302 2 501 3 765 4 988

代码:

# 登山搜索
# 编码和初始化
data=read()
print(data)
schedule=list(range(len(data)))# 初始顺序调度

# 循环
t=0
T=time_to_temp(t)
epsilon=0.1 # 阈值温度
min_time=[] # 记录每个变化节点
min_t=[]
min_time.append(all_time(data,schedule))
min_t.append(0)
while T>epsilon:
    # 多规则邻域搜索生成新解,协同比较
    new_schedule=point_exchange(schedule)
    # 判断新解情况,决定新状态
    now_time=all_time(data,new_schedule)
    if(now_time<min_time[-1]): # 耗时更少
        min_time.append(now_time)
        min_t.append(t)
        schedule=new_schedule
    # 更新时间和温度    
    t=t+1
    T=time_to_temp(t)
min_time.append(min_time[-1])
min_t.append(t)
print(f"after {t} epoch",min_time[-1],schedule)
plt.xlabel('迭代次数')
plt.ylabel('调度总时间')
plt.plot(min_t,min_time)

一次结果,优化到7038,有时候会有一点差距,这个和运气有关系:

【数学建模】模拟退火全解析

模拟退火框架

数据还是用的上面给出的数据,程序其实也仅仅是在对于新解判断的一步进行改动。

# 模拟退火
# 编码和初始化
data=read()
print(data)
schedule=list(range(len(data)))# 初始顺序调度

# 循环
t=0
T=time_to_temp(t)
epsilon=0.1 # 阈值温度
min_time=[] # 记录每个变化节点
min_t=[]
min_time.append(all_time(data,schedule))
min_t.append(0)
while T>epsilon:
    # 多规则邻域搜索生成新解,协同比较
    new_schedule=point_exchange(schedule)
    # 判断新解情况,决定新状态
    now_time=all_time(data,new_schedule)
    if(now_time<min_time[-1]): # 耗时更少
        min_time.append(now_time)
        min_t.append(t)
        schedule=new_schedule
    else: # 概率接受更差结果
        P=np.exp((min_time[-1]-now_time)/T)
        if np.random.rand()<P: # 接受差结果
            min_time.append(now_time)
            min_t.append(t)
            schedule=new_schedule
    # 更新时间和温度    
    t=t+1
    T=time_to_temp(t)
min_time.append(min_time[-1])
min_t.append(t)
print(f"after {t} epoch",min_time[-1],schedule)
plt.xlabel('迭代次数')
plt.ylabel('调度总时间')
plt.plot(min_t,min_time)

跑出来的图大概是这样,可以看到波动还是比较大的,但是最后可以达到更好的效果。
【数学建模】模拟退火全解析

完全版本项目文件

项目包含加强版源代码,数据集,实验结果图,以及我写的论文 。

百度云
提取码:cyyy

上面给出的俩程序都很简单,实际上我们还可以通过一些技巧,提高5%左右的表现。我自己就是从3500优化到了3280的,基本上优化了 220 3250 = 6.8 % \dfrac{220}{3250}=6.8\% 3250220=6.8%,这么一看还是挺可观的。

具体的改进:

  1. 程序架构。其实有11组数据,所以要用新的read函数,一次性读取出11个矩阵,然后遍历逐个处理。
  2. 10组随机重启搜索。每个测试用例都要进行10次随机重启搜索,互不干扰
  3. 增加记忆机制,选择最好的组作为结果。这样可以降低结果对出生点的依赖。
  4. 实现三种邻域搜索,每次搜索都要从当前状态进行三种搜索,选择其中最好的结果,再进行判断是否保留。
  5. 图像绘制。采用1000dpi的清晰度,记录每个优化点的时间与结果,将10组都画在一张图里。最后保存到目录里,用于论文撰写。

看看我最后的成果,从图中可以看出:

  1. 小规模问题上,登山搜索和模拟退火不会有太大差异
  2. 但是稍微上升到50个工件的规模,模拟退火就逐渐体现出优势,登山搜索10组才出来一个3280级别的,而模拟退火一眼看过去全是3280级别的,有理由认为登山搜索那个3280是偶然,也就是随机重启带来的好处,而模拟退火即使是只跑一组实验,结果也很稳定
  3. 如果上升到100工件,在结果上模拟退火应该是会更优的。

【数学建模】模拟退火全解析

【数学建模】模拟退火全解析
【数学建模】模拟退火全解析

【数学建模】模拟退火全解析
【数学建模】模拟退火全解析
【数学建模】模拟退火全解析文章来源地址https://www.toymoban.com/news/detail-432375.html

到了这里,关于【数学建模】模拟退火全解析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数学建模(三):模拟退火算法(SA)

    1、 算法简介 模拟退火算法(simulated annealing,SA)来源于固体退火原理,是一种基于概率的算法。 模拟退火算法(SA)来源于固体退火原理,是一种基于概率的算法。将固体加温至充分高的温度,再让其徐徐冷却,加温时,固体内部粒子随温升变为无序状,内能增大,分子和原

    2023年04月17日
    浏览(44)
  • 【数学建模】《实战数学建模:例题与讲解》第十四讲-模拟退火、遗传算法(含Matlab代码)

    本系列侧重于例题实战与讲解,希望能够在例题中理解相应技巧。文章开头相关基础知识只是进行简单回顾,读者可以搭配课本或其他博客了解相应章节,然后进入本文正文例题实战,效果更佳。 如果这篇文章对你有帮助,欢迎点赞与收藏~ 现代优化算法,自20世纪80年代初开

    2024年02月04日
    浏览(40)
  • Matlab数学建模算法之模拟退火算法(SA)详解

    🔗 运行环境:Matlab 🚩 撰写作者:左手の明天 🥇 精选专栏:《python》 🔥  推荐专栏:《算法研究》 🔐####  防伪水印——左手の明天 #### 🔐 💗 大家好🤗🤗🤗,我是 左手の明天 !好久不见💗 💗今天分享 matlab数学建模算法 —— 模拟退火算法 💗

    2024年01月16日
    浏览(29)
  • 模拟退火算法与遗传算法求解多目标优化问题的算法实现(数学建模)

    模拟退火算法是一种全局优化算法,解决的问题通常是找到一个最小化(或最大化)某个函数的全局最优解。它通过模拟物理退火的过程来搜索解空间,在开始时以一定的温度随机生成初始解,然后一步步降低温度,同时在当前解的周围随机搜索新的解,并根据一定概率接受

    2024年02月02日
    浏览(34)
  • 数学建模|通过模拟退火算法求解供货与选址问题:问题二(python代码实现)

    今天继续用模拟退火算法供货与选址问题的问题二,如果还没看过问题一的可以看我之前的博客 数学建模|通过模拟退火算法求解供应与选址问题:问题一(python代码实现)-CSDN博客 这里还是把题目放上来(题目来自数学建模老哥的视频): 那么我们可以分析一下,第一问和

    2024年01月16日
    浏览(38)
  • 数学建模-退火算法和遗传算法

    退火算法和遗传算法 一.退火算法 退火算法Matlab程序如下: [W]=load(\\\'D:100个目标经度纬度.txt\\\'); 二、遗传算法 [E]=xlsread(\\\'D:100个目标经度纬度\\\');  % 加载敌方 100 个目标的数据, 数据按照表格中的位置保存在纯文本文件 sj.txt 中 x=[E(:,1)]; y=[E(:,2)]; e =[x y]; d1=[70,40]; e =[d1;  e ;d1];

    2024年02月20日
    浏览(37)
  • 数学建模--退火算法求解最值的Python实现

    目录 1.算法流程简介 2.算法核心代码 3.算法效果展示

    2024年02月09日
    浏览(24)
  • 有趣的数学 数学建模入门二 一些理论基础

             现实世界中混乱的问题可以用数学来解决,从而产生一系列可能的解决方案来帮助指导决策。大多数人对数学建模的概念感到不舒服,因为它是如此开放。如此多的未知信息似乎令人望而却步。哪些因素最相关?但正是现实世界问题的这种开放性导致了解决问题

    2024年02月10日
    浏览(30)
  • 数学建模理论与实践国防科大版

    目录 1.数学建模概论 2.生活中的数学建模 2.1.行走步长问题 2.2.雨中行走问题 2.3.抽奖策略 2.4.《非诚勿扰》女生的“最优选择” 3.集体决策模型 3.1.简单多数规则 3.2.Borda数规则 3.3.群体决策模型公理和阿罗定理 1.数学模型的概念 2.数学建模的概念 3.数学建模的一般过程 自然界

    2024年03月13日
    浏览(63)
  • 数学建模-蒙特卡洛模拟

    2024年02月15日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包