模拟退火算法(SA):零基础小白入门指南
如果你是零基础,想了解什么是模拟退火算法,看完这篇就能懂!我们会用最简单的语言,结合具体例子和代码,带你一步步认识这个强大的优化工具。
一、什么是模拟退火算法?
模拟退火算法(Simulated Annealing,简称 SA)是一种找最优解的方法,就像在迷宫里找出口 —— 它不仅能探索眼前的路,还会偶尔尝试绕远路,避免困在死胡同(局部最优解),最终找到全局最优解。
它的灵感来自物理退火过程:金属加热后慢慢冷却时,原子会逐渐排列成能量最低的稳定状态。算法模仿这个过程:先 “高温” 时大胆探索,再 “降温” 时逐渐稳定,最终找到最优解。
二、核心概念用大白话讲
- 最优解:我们要找的目标。比如 “如何让函数值最小”“如何让成本最低”。
- 适应度函数:判断当前解好不好的标准。比如求函数最小值时,函数值就是适应度,值越小越好。
- 温度:算法 “大胆程度” 的指标。温度高时,敢尝试差的解(绕远路);温度低时,只接受好的解(走捷径)。
- 邻域函数:生成 “附近的解” 的方法。比如当前位置是(2,3),邻域函数可能生成(2.1,3.2)这样的附近点。
- 降温:温度逐渐降低(比如每次乘以 0.95),让算法从 “探索” 慢慢转向 “收敛”。
三、算法到底在做什么?(以例子为例)
模拟退火算法的步骤就像这样:
- 随便找个起点:比如随机选 x1=0.5,x2=40,x3=60,算出此时的函数值(适应度)。
- 看看附近有没有更好的点:用邻域函数生成一个附近的点(比如 x1=0.52,x2=39.8,x3=60.1),算它的函数值。
- 决定要不要去新点:
- 如果新点的函数值更小(更好),就直接过去;
- 如果新点的函数值更大(更差),也不是完全不去 —— 温度高时可能 “冒险” 过去(比如有 30% 的概率),温度低时就几乎不去了。
- 慢慢降温:每次迭代后温度降低一点(比如从 1000 降到 950,再降到 902.5……),让 “冒险” 的概率越来越小。
- 记下来最好的点:每次迭代都记录目前找到的最小函数值,直到迭代结束,这个值就是结果。
四、代码实现与关键部分解析
下面我们用 Python 代码实现上述过程,每一步都配有详细注释,帮助理解:
# 导入必要的库
import numpy as np # 用于数值计算(生成随机数、数组操作等)
import math # 用于数学运算(比如指数函数)
import matplotlib.pyplot as plt # 用于绘制迭代过程图
1. 适应度函数(目标函数)
适应度函数是我们判断 “解好不好” 的标准,这里就是要最小化的函数
# ----------------------
# 1. 定义要优化的目标函数(适应度函数)
# 这里以最小化函数 f(x) = -10x₁ - e^(-x₂/10 - x₃) 为例
# ----------------------
def fitness(x):
# x是一个包含3个变量的数组:[x₁, x₂, x₃]
x1, x2, x3 = x # 分别取出三个变量
# 计算函数值(目标是让这个值越小越好)
return -10 * x1 - math.exp(-x2/10 - x3)
2. 邻域函数(生成附近的解)
邻域函数的作用是在当前解的基础上,生成一个 “附近” 的新解,就像在当前位置 “小范围探索”。
# ----------------------
# 2. 定义邻域函数(生成当前解附近的新解)
# ----------------------
def generate_neighbor(current_x, lower_bound, upper_bound, step=0.1):
# current_x:当前解(比如 [0.5, 40, 60])
# lower_bound/upper_bound:变量的取值范围(避免新解超出范围)
# step:扰动幅度(步长),控制新解与当前解的距离
# 给当前解加一点随机小扰动(比如0.5→0.52或0.48)
perturbation = np.random.uniform(-step, step, size=current_x.shape)
new_x = current_x + perturbation
# 确保新解在规定范围内(比如x₁不能小于0或大于1)
new_x = np.clip(new_x, lower_bound, upper_bound)
return new_x
3. 降温函数(控制温度变化)
温度是算法 “大胆程度” 的核心,降温函数让温度逐渐降低,使算法从 “大胆探索” 转向 “稳定收敛”。
# ----------------------
# 3. 定义温度更新函数(降温)
# ----------------------
def cool_down(temperature, alpha=0.95):
# 每次迭代后温度降低一点(比如1000→950→902.5...)
# alpha:降温系数(0<alpha<1,越接近1降温越慢)
return temperature * alpha
4. 模拟退火算法主函数(核心逻辑)
主函数整合了上述所有部分,实现 “生成新解→判断是否接受→降温→记录最优解” 的完整流程。
"""
----------------------
# 4. 模拟退火算法主函数
# 功能:实现模拟退火算法的完整流程,从初始解开始搜索,通过温度控制实现全局优化
# 参数说明:
# fitness_func: 函数,适应度函数(目标函数),用于评估解的优劣
# lower_bound: 数组,各变量的取值下限(如 [0, 1, 0] 表示x1≥0, x2≥1, x3≥0)
# upper_bound: 数组,各变量的取值上限(如 [1, 80, 120] 表示x1≤1, x2≤80, x3≤120)
# initial_temp: 数值,初始温度(控制探索强度的起点,越高初始探索越自由)
# alpha: 数值,降温系数(0<alpha<1,如0.95表示每次温度变为原来的95%)
# max_iter: 整数,最大迭代次数(算法终止条件之一,控制搜索的总步数)
# 返回值:
# best_x: 数组,找到的最优解(使目标函数最小的变量组合)
# best_fitness: 数值,最优解对应的函数值(目标函数的最小值)
# ----------------------
"""
def simulated_annealing(fitness_func,
lower_bound,
upper_bound,
initial_temp=1000,
alpha=0.95,
max_iter=100):
# 初始化参数
# 随机生成一个初始解(在变量范围内均匀采样,作为搜索的起点)
# 例如:x1在[0,1]、x2在[1,80]、x3在[0,120]中随机取值
current_x = np.random.uniform(lower_bound, upper_bound)
# 计算初始解的函数值(用适应度函数评估初始解的优劣)
current_fitness = fitness_func(current_x)
# 记录最优解(算法运行过程中始终保存目前找到的最好解)
# 初始状态下,最优解就是初始解
best_x = current_x.copy() # 用copy避免后续修改影响最优解记录
best_fitness = current_fitness # 记录最优解对应的函数值
# 初始化温度(从初始温度开始,控制算法的探索-收敛过程)
temperature = initial_temp
# 记录每次迭代的最优函数值(用于后续绘制迭代曲线,直观展示算法收敛过程)
fitness_history = [best_fitness] # 初始值加入历史记录
# 开始迭代搜索(核心循环,重复执行"生成新解→判断是否接受→更新最优解→降温"流程)
for i in range(max_iter):
# 生成新解:通过邻域函数在当前解附近生成一个候选解
# 新解与当前解的距离由邻域函数的步长控制,确保在变量范围内
new_x = generate_neighbor(current_x, lower_bound, upper_bound)
# 评估新解:计算新解的函数值,判断其优劣
new_fitness = fitness_func(new_x)
# 判断是否接受新解(遵循Metropolis准则,核心机制)
# 情况1:新解更好(函数值更小,因为我们要找最小值)→ 直接接受新解
if new_fitness < current_fitness:
current_x = new_x # 更新当前解为新解
current_fitness = new_fitness # 更新当前解的函数值
# 情况2:新解更差(函数值更大)→ 以一定概率接受,避免陷入局部最优
else:
# 计算接受概率:温度越高、新解与当前解的差距越小,接受概率越大
# 公式来源:模拟退火的物理背景(玻尔兹曼分布)
probability = math.exp((current_fitness - new_fitness) / temperature)
# 生成0-1之间的随机数,若小于接受概率则接受新解
if np.random.rand() < probability:
current_x = new_x # 接受较差的新解,保持探索性
current_fitness = new_fitness
# 更新最优解:如果当前解比历史最优解更好,则更新记录
if current_fitness < best_fitness:
best_x = current_x.copy() # 保存新的最优解
best_fitness = current_fitness # 保存新的最优函数值
# 降温:按照降温系数降低温度,使算法从"探索"逐渐转向"收敛"
# 温度降低后,接受较差解的概率减小,搜索更聚焦于优质区域
temperature = cool_down(temperature, alpha)
# 记录当前迭代的最优值:用于后续绘图分析算法性能
fitness_history.append(best_fitness)
# 打印迭代信息:实时展示算法进展,方便调试和观察收敛情况
print(f"第{i+1}次迭代,当前最优函数值:{best_fitness:.4f}")
# 绘制迭代过程图:直观展示最优函数值随迭代次数的变化
plt.plot(fitness_history)
plt.xlabel("迭代次数")
plt.ylabel("最优函数值")
plt.title("模拟退火算法迭代过程")
plt.show()
# 返回最终找到的最优解和对应的函数值
return best_x, best_fitness
# 绘制迭代过程图(看函数值如何变化)
plt.plot(fitness_history)
plt.xlabel("迭代次数")
plt.ylabel("最优函数值")
plt.title("模拟退火算法迭代过程")
plt.show()
return best_x, best_fitness
5. 运行算法并查看结果
最后,我们设置参数并运行算法,看看它如何找到最优解:
# ----------------------
# 5. 主程序:运行算法
# ----------------------
if __name__ == "__main__":
# 定义变量的取值范围(x₁: [0,1],x₂: [1,80],x₃: [0,120])
lower = np.array([0, 1, 0]) # 下限
upper = np.array([1, 80, 120]) # 上限
# 算法参数
initial_temperature = 1000 # 初始温度
cooling_rate = 0.95 # 降温系数
max_iterations = 100 # 最大迭代次数
# 运行模拟退火算法
best_solution, best_value = simulated_annealing(
fitness_func=fitness,
lower_bound=lower,
upper_bound=upper,
initial_temp=initial_temperature,
alpha=cooling_rate,
max_iter=max_iterations
)
# 输出结果
print("\n最终结果:")
print(f"最优解(x₁, x₂, x₃):{best_solution}")
print(f"最优函数值:{best_value:.4f}")
python语法基础补充
if __name__ == "__main__":
—— 主程序入口判断
这是 Python 中用于区分 “模块运行” 和 “模块导入” 的经典语法:
当这个脚本被直接运行时(比如
python script.py
),__name__
变量会被自动赋值为"__main__"
,此时if
条件成立,内部代码会执行。当这个脚本被作为模块导入到其他脚本时(比如
import script
),__name__
会被赋值为模块名("script"
),此时if
条件不成立,内部代码不会执行。
作用:让脚本既能作为独立程序运行,又能作为模块被其他程序导入(导入时不执行主程序逻辑)。
五、结果能说明什么?
在上述例子中,迭代 100 次后,最终找到的最优解通常接近函数值 -10.0。从迭代过程的图表(适应度随迭代次数变化)能看到:
- 一开始函数值波动较大(温度高,敢尝试差的解);
- 后来逐渐稳定在 -10.0 左右(温度低,找到最优解后不再变化)。
这说明算法成功从 “探索” 阶段过渡到 “收敛” 阶段,最终找到了全局最优解。
六、为什么要用模拟退火算法?
- 适合解决复杂问题:比如旅行商问题(找最短路线)、工厂排班、参数优化等。
- 不容易陷在局部最优:比如爬山时,它不会只盯着眼前的小山坡,而是可能先爬上一个稍高的坡,再找到更高的山。
七、如何修改代码解决自己的问题?
- 如果你想优化其他函数,只需修改
fitness
函数中的计算公式。 - 如果你有更多变量或不同的取值范围,修改
lower
和upper
数组即可(比如 2 个变量就设为np.array([a, b])
和np.array([c, d])
)。 - 可以调整
initial_temperature
(初始温度)、cooling_rate
(降温系数)、max_iterations
(迭代次数)等参数,让算法更适合你的问题。