当前位置: 首页 > news >正文

python学智能算法(二)|模拟退火算法:进阶分析

【1】引言

前序学习进程中,已经对模拟退火算法进行了原理解释和最小值求解学习,相关文章链接为:

python学智能算法(一)|模拟退火算法:原理解释和最小值求解-CSDN博客

在此基础上,今天将进一步学习具体的数值处理退火过程,实现灵活运用该算法进行各式各样目标值求解。

【2】代码设计

在原理解释和最小值求解学习文章中,主要研究方向是把模拟退火原理用代码展示出来,这里面涉及到的操作步骤满足:

图1   模拟退火操作步骤

实际上,初始温度和最低温度都不参与计算,它们只是辅助决定要计算多少次。

真正实施的计算过程,是在初始温度时,从初始解开始,对初始解周围的区域进行搜索。当搜索次数达到预设数量时,温度降低。

在这个过程中,温度不参与计算,只是相当于一个条件,只要温度还没有到最低,搜索过程就要进行。

类似于温度就是一个跑道,计算过程就是粒子在奔跑。只要这个跑道还有直径,里面的粒子就要绕着跑道运动,且每个直径的跑道上,粒子运动的圈数相同。

图2   模拟退火说明

在此分析基础上,仅仅获得最有分析结果不再是唯一目标。

将代码搜寻过程中,选取的最优解和最优目标值同时展示出来,会更有助于更清晰地展示退火过程。

在前述文章中,算法运行的目标是获得最小值,使用的模拟退火算法核心代码为:

while temperature > final_temperature:
    for _ in range(num_iterations_per_temp):
        # 在当前解的邻域内生成新解
        new_solution = current_solution + np.random.uniform(-0.1, 0.1)
        # 计算新解的目标函数值
        new_energy = objective_function(new_solution)
        # 计算能量差
        delta_energy = new_energy - current_energy

        # 如果新解更优,直接接受
        if delta_energy < 0:
            current_solution = new_solution
            current_energy = new_energy
            # 更新最优解
            if new_energy < best_energy:
                best_solution = new_solution
                best_energy = new_energy
        # 如果新解更差,以一定概率接受
        else:
            acceptance_probability = np.exp(-delta_energy / temperature)
            if np.random.rand() < acceptance_probability:
                current_solution = new_solution
                current_energy = new_energy

这个计算过程,实际上可以取到的参数包括:

每次计算的解

每次计算的目标函数值/能量值

每次计算的最优解

每次计算的最优目标函数值/能量值

所以,如果将上述量尽可能展示出来,具体的退火过程会更为清晰。

【3】代码测试

要想展示各个具体的解和对应能量值,只需要定义相关的列表存储各个值即可,在这依然是先修改核心代码:

# 模拟退火算法
def simulated_annealing(initial_solution, initial_temperature, final_temperature, alpha, num_iterations_per_temp):
    # 当前解
    current_solution = initial_solution
    # 当前解的目标函数值
    current_energy = objective_function(current_solution)
    # 最优解
    best_solution = current_solution
    # 最优解的目标函数值
    best_energy = current_energy
    # 当前温度
    temperature = initial_temperature

    # 用于记录每一步的最优目标函数值
    energy_history = [best_energy]
    current_solution_history = [current_solution]
    current_energy_history = [current_energy]
    best_energy_history = [best_energy]
    best_solution_history =[best_solution]

    while temperature > final_temperature:
        for _ in range(num_iterations_per_temp):
            # 在当前解的邻域内生成新解
            new_solution = current_solution + np.random.uniform(-0.1, 0.1)
            # 计算新解的目标函数值
            new_energy = objective_function(new_solution)
            # 计算能量差
            delta_energy = new_energy - current_energy

            # 如果新解更优,直接接受
            if delta_energy < 0:
                current_solution = new_solution
                current_energy = new_energy
                # 更新最优解
                if new_energy < best_energy:
                    best_solution = new_solution
                    best_energy = new_energy
            # 如果新解更差,以一定概率接受
            else:
                acceptance_probability = np.exp(-delta_energy / temperature)
                if np.random.rand() < acceptance_probability:
                    current_solution = new_solution
                    current_energy = new_energy

            # 记录当前最优目标函数值
            energy_history.append(best_energy)
            # 更新当前解和能量的历史记录
            current_solution_history.append(current_solution)
            current_energy_history.append(current_energy)
            best_energy_history.append(best_energy)
            best_solution_history.append(best_solution)

        # 降低温度
        temperature *= alpha

    return best_solution, best_energy, energy_history, current_solution_history, current_energy_history, best_energy_history, best_solution_history

非常清晰,这里只是在两处位置分别新增了四行代码:

# 用于记录每一步的最优目标函数值
energy_history = [best_energy]
current_solution_history = [current_solution]
current_energy_history = [current_energy]
best_energy_history = [best_energy]
best_solution_history =[best_solution]

...

# 记录当前最优目标函数值
energy_history.append(best_energy)
# 更新当前解和能量的历史记录
current_solution_history.append(current_solution)
current_energy_history.append(current_energy)
best_energy_history.append(best_energy)
best_solution_history.append(best_solution)

新增的代码对运算没有任何影响,只是将参数值用列表的形式存储起来。

之后就比较简单,用动画的形式动态展示这些参数的关系就可以:

fig, ax = plt.subplots()  # 定义要画图
# 设置坐标轴范围
#ax.set_xlim(min(current_solution_history), max(current_solution_history))
#ax.set_ylim(min(current_energy_history), max(current_energy_history))
ax.set_xlim(-10,10)
ax.set_ylim(0,100)
# 定义散点图对象
scatter1 = ax.scatter([], [], c='b', label='当前解与当前能量关系')
scatter2 = ax.scatter([], [], c='g', marker="^",label='最优解与最优能量关系')
# 定义折线图对象来表示 best_energy 的变化
line, = ax.plot([], [], 'r-', label='当前解与最优能量关系')

num_frames = len(energy_history)
x = np.linspace(-10, 10, 400)
y = objective_function(x)
plt.plot(x, y, label='目标函数')
def update(frame):
    x = current_solution_history[:frame + 1]
    y = current_energy_history[:frame + 1]
    # 更新散点图的数据
    scatter1.set_offsets(np.c_[x, y])

    # 更新折线图的数据,这里使用迭代步数作为 x 轴,best_energy 作为 y 轴
    line_x = current_solution_history[:frame + 1]
    line_y = best_energy_history[:frame + 1]
    line.set_data(line_x, line_y)

    scatter_x=best_solution_history[:frame + 1]
    scatter_y =best_energy_history[:frame + 1]
    scatter2.set_offsets(np.c_[scatter_x, scatter_y])

    return scatter1, line,scatter2

ani = animation.FuncAnimation(fig, update, repeat=True,
                              frames=num_frames, interval=50)  # 调用animation.FuncAnimation()函数画图
plt.legend()

在这段代码中,分别定义了4个图像:

  • 当前解和当前能量的关系,对应current_solution和current_energy;
  • 最优解和最优能量的关系,对应best_solution和best_energy;
  • 当前解和最优能量的关系,对应current_solution和best_energy;
  • 目标函数。

代码运行后,获得的图像为:

图3   模拟退火计算过程参数变化

在图3所示的模拟退火计算过程参数变化图像中,可以清晰看到四个图像:

目标函数图像是不会变化的线条;

当前解和当前能量动画为蓝色散点图,蓝色散点会反复出现,甚至往横坐标减小的方向变化,这就是退火过程中不断搜索的证据;

最优解和最优能量动画为绿色散点图,绿色散点图有时候不会变化,这是因为当前解在搜索过程中,不能发现更好的最优能量,所以最优解和最优能量不会更新;

当前解和最优能量动画为红色线条,红色线条会沿着坐标轴减小到方向往水平方向延展,这就是因为当前解在搜索过程中,不能发现更好的最优能量,所以最优能量不会更新,只会保持之前取到的值。

图3清晰展现了模拟退火算法运行过程中数据的变化。

此时的完整代码为:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

# 设置 matplotlib 支持中文
plt.rcParams['font.family'] = 'SimHei'
# 解决负号显示问题
plt.rcParams['axes.unicode_minus'] = False

# 定义目标函数,这里是 f(x) = x^2
def objective_function(x):
    return x ** 2

# 初始化参数
# 初始解
initial_solution = np.random.uniform(-10, 10)
# 初始温度
initial_temperature = 100
# 终止温度
final_temperature = 0.01
# 温度衰减系数
alpha = 0.95
# 每个温度下的迭代次数
num_iterations_per_temp = 5

# 模拟退火算法
def simulated_annealing(initial_solution, initial_temperature, final_temperature, alpha, num_iterations_per_temp):
    # 当前解
    current_solution = initial_solution
    # 当前解的目标函数值
    current_energy = objective_function(current_solution)
    # 最优解
    best_solution = current_solution
    # 最优解的目标函数值
    best_energy = current_energy
    # 当前温度
    temperature = initial_temperature

    # 用于记录每一步的最优目标函数值
    energy_history = [best_energy]
    current_solution_history = [current_solution]
    current_energy_history = [current_energy]
    best_energy_history = [best_energy]
    best_solution_history =[best_solution]

    while temperature > final_temperature:
        for _ in range(num_iterations_per_temp):
            # 在当前解的邻域内生成新解
            new_solution = current_solution + np.random.uniform(-0.1, 0.1)
            # 计算新解的目标函数值
            new_energy = objective_function(new_solution)
            # 计算能量差
            delta_energy = new_energy - current_energy

            # 如果新解更优,直接接受
            if delta_energy < 0:
                current_solution = new_solution
                current_energy = new_energy
                # 更新最优解
                if new_energy < best_energy:
                    best_solution = new_solution
                    best_energy = new_energy
            # 如果新解更差,以一定概率接受
            else:
                acceptance_probability = np.exp(-delta_energy / temperature)
                if np.random.rand() < acceptance_probability:
                    current_solution = new_solution
                    current_energy = new_energy

            # 记录当前最优目标函数值
            energy_history.append(best_energy)
            # 更新当前解和能量的历史记录
            current_solution_history.append(current_solution)
            current_energy_history.append(current_energy)
            best_energy_history.append(best_energy)
            best_solution_history.append(best_solution)

        # 降低温度
        temperature *= alpha

    return best_solution, best_energy, energy_history, current_solution_history, current_energy_history, best_energy_history, best_solution_history

# 运行模拟退火算法
best_solution, best_energy, energy_history, current_solution_history, current_energy_history, best_energy_history, best_solution_history= simulated_annealing(
    initial_solution, initial_temperature, final_temperature, alpha, num_iterations_per_temp)

print("最优解:", best_solution)
print("最优值:", best_energy)
print('energy_history=', energy_history)

fig, ax = plt.subplots()  # 定义要画图
# 设置坐标轴范围
#ax.set_xlim(min(current_solution_history), max(current_solution_history))
#ax.set_ylim(min(current_energy_history), max(current_energy_history))
ax.set_xlim(-10,10)
ax.set_ylim(0,100)
# 定义散点图对象
scatter1 = ax.scatter([], [], c='b', label='当前解与当前能量关系')
scatter2 = ax.scatter([], [], c='g', marker="^",label='最优解与最优能量关系')
# 定义折线图对象来表示 best_energy 的变化
line, = ax.plot([], [], 'r-', label='当前解与最优能量关系')

num_frames = len(energy_history)
x = np.linspace(-10, 10, 400)
y = objective_function(x)
plt.plot(x, y, label='目标函数')
def update(frame):
    x = current_solution_history[:frame + 1]
    y = current_energy_history[:frame + 1]
    # 更新散点图的数据
    scatter1.set_offsets(np.c_[x, y])

    # 更新折线图的数据,这里使用迭代步数作为 x 轴,best_energy 作为 y 轴
    line_x = current_solution_history[:frame + 1]
    line_y = best_energy_history[:frame + 1]
    line.set_data(line_x, line_y)

    scatter_x=best_solution_history[:frame + 1]
    scatter_y =best_energy_history[:frame + 1]
    scatter2.set_offsets(np.c_[scatter_x, scatter_y])

    return scatter1, line,scatter2

ani = animation.FuncAnimation(fig, update, repeat=True,
                              frames=num_frames, interval=50)  # 调用animation.FuncAnimation()函数画图
plt.legend()

ani.save('mnthsf.gif') #保存动画
plt.show()

【4】细节说明

上述代码中,为加快计算时间,所以减少了计算次数,每个温度下只重复计算5次。如果时间充足,可以扩大这个计算次数。

【5】总结

使用python进一步研究了模拟退火算法运行过程中数据的变化。

相关文章:

  • PHP 会话(Session)实现用户登陆功能
  • Flutter CupertinoNavigationBar iOS 风格导航栏的组件
  • 10-R数组
  • LeetCode 热题 100_在排序数组中查找元素的第一个和最后一个位置(65_34_中等_C++)(二分查找)(一次二分查找+挨个搜索;两次二分查找)
  • 独立开发者如何寻找产品设计灵感
  • 大规模 RDMA AI 组网技术创新:算法和可编程硬件的深度融合
  • 基于Spring Boot的兴顺物流管理系统设计与实现(LW+源码+讲解)
  • eclips 快捷键
  • java方法学习
  • 探索 Peewee:轻量级 Python ORM 简明指南
  • 更改visual studio 2022 默认NuGet包路径
  • 【uni-app】对齐胶囊容器组件
  • 基于SpringBoot实现的宠物领养系统平台功能七
  • 华为guass在dbever和springboot配置操作
  • 大厂出品!三个新的 DeepSeek 平替网站
  • 【AcWing】动态规划-线性DP -选数异或
  • springboot408-基于Java的樱洵宾馆住宿管理系统(源码+数据库+纯前后端分离+部署讲解等)
  • JDK源码系列(二)
  • 第44天:Web开发-JavaEE应用反射机制类加载器利用链成员变量构造方法抽象方法
  • 代码随想录刷题day28|(栈与队列篇:栈)232.用栈实现队列
  • 沙县小吃中东首店在沙特首都利雅得开业,首天营业额5万元
  • 海北州委常委、常务副州长桑本履新青海省供销社理事会主任
  • 这一次,又被南昌“秀”到了
  • 中美瑞士会谈后中国会否取消矿产出口许可要求?外交部回应
  • 复旦设立新文科发展基金,校友曹国伟、王长田联合捐赠1亿助力人文学科与社会科学创新
  • 中科院院士魏辅文已卸任江西农业大学校长