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

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进一步研究了模拟退火算法运行过程中数据的变化。


http://www.kler.cn/a/557166.html

相关文章:

  • Jetson Orin Nano烧录系统
  • 【多语言生态篇一】【DeepSeek×Java:Spring Boot微服务集成全栈指南 】
  • 1.3 AI量化炒股的基本流程
  • flink系列之:使用flink cdc3从mysql数据库同步数据到doris和starrocks
  • docker查看正在运行的容器详细教程
  • java八股文-消息队列
  • AtCoder Beginner Contest (ABC)394(ABCD)
  • MongoDB学习
  • Python爬虫selenium验证-中文识别点选+图片验证码案例
  • vue中的watch 和 computed 的区别
  • 软件架构设计:软件工程
  • Golang通过 并发计算平方 示例演示并发
  • 【Postgresql】Linux 部署 Postgresql 数据库 (图文教程)
  • 直角三角堰计算公式
  • 开发指南103-jpa的find**/get**全解
  • 数据结构:队列queue和栈stack
  • UE5中按钮圆角,设置边框
  • CSDN如何设置付费专栏
  • [ TypeScript ] “undefined extends xxx“ 总是为 true 的 bug
  • 深入解析C++函数指针与指针函数:从原理到实战