尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

scikit-opt实战:7种启发式算法统一接口与调参指南

scikit-opt实战:7种启发式算法统一接口与调参指南 简介这是一份封装了7种启发式算法的 Python 代码库面向需要求解优化问题与组合优化场景的算法学习者、研究人员和工程师。代码库覆盖差分进化、遗传、粒子群、模拟退火、蚁群、免疫、人工鱼群等主流算法并提供 TSP 旅行商问题的完整示例适合对比算法效果、快速集成到项目中。压缩包共83个文件以 py 源码和 md 文档为主配套 examples 演示脚本、数据文件、GPU 实现及相关说明文档整体仅96KB结构清晰便于按需查阅。已有207人学习下载。通过该资源可以拿到各算法的可运行实现、基础算子与工具箱、测试用例以及旅行商等典型问题的演示脚本借助文档目录可快速定位遗传算法、粒子群、模拟退火等模块适合作为算法教学、毕业设计或工程选型的参考资料。1. 启发式算法库 scikit-opt一个包收齐遗传、粒子群、模拟退火等 7 种算法做调度排产、路径规划或者参数寻优的时候最烦的就是每种算法都要重新写一套代码。遗传算法一套数据结构粒子群又一套蚁群再来一套光是统一接口就折腾半天。我拆解这个 scikit-opt 代码库时最直接的感受是它把 7 种启发式算法遗传算法 GA、粒子群 PSO、模拟退火 SA、蚁群 ACA、差分进化 DE、免疫算法 IA、人工鱼群 AFSA全部收敛到了同一个 Python 包里API 风格高度一致都是from sko.XA import XA这种导入方式几分钟就能从零跑通一个优化任务。对需要快速验证算法效果的从业者来说这是个能直接抄作业的工具库尤其适合做 TSP 路径规划、连续函数寻优和离散组合优化这三类常见问题。2. 环境搭建与快速跑通安装 scikit-opt 并复现官方 demo2.1 安装与依赖检查这个库的安装路径很常规项目里有setup.py直接用 pip 就能装上。我建议先建一个干净的虚拟环境避免把系统 Python 搞乱。python -m venv sko_env source sko_env/bin/activate # Windows 下用 sko_env\Scripts\activate pip install scikit-opt依赖方面项目里的requirements.txt核心就是 numpy、scipy、matplotlib 三件套。numpy 负责向量运算scipy 里有些内部工具函数会用到matplotlib 主要是在 examples 里画收敛曲线用的如果只做计算不画图不装也能跑。需要注意的坑是这个库叫 scikit-opt跟 scikit-learn 没关系别装混了。装完后可以验证一下导入是否正常from sko.GA import GA, GA_TSP from sko.PSO import PSO from sko.SA import SA, SA_TSP from sko.ACA import ACA_TSP from sko.DE import DE from sko.IA import IA from sko.AFSA import AFSA print(所有 7 种算法导入成功)这里导入的对象分两类不带_TSP后缀的是连续优化版本处理形如f(x1, x2, x3)的函数最小值问题带_TSP后缀的是离散组合优化版本专门解决旅行商这类路径问题。导入失败时先检查 numpy 和 scipy 版本Python 3.6 以下的老环境容易报语法错误。2.2 连续优化从一个 GA 实例看 API 结构项目自带的demo_func.py里有个经典的演示函数我用它来拆解 GA 的调用方式。import numpy as np from sko.GA import GA def demo_func(x): # 三个变量的平方和最小值在 (1, 2, 3) 处取到 return (x[0] - 1) ** 2 (x[1] - 2) ** 2 (x[2] - 3) ** 2 ga GA(funcdemo_func, n_dim3, size_pop50, max_iter200, lb[-10, -10, -10], ub[10, 10, 10], precision1e-7) best_x, best_y ga.run() print(最优解坐标:, best_x) print(最优目标值:, best_y)这段代码里几个参数要专门说清楚。size_pop50是种群规模就是每代保留 50 个候选解max_iter200是最大迭代代数lb和ub是每个维度的上下界写成列表形式三个变量就传三个值数组长度必须跟n_dim一致这个是新手最容易翻车的地方。precision是基因编码精度1e-7 意味着每个变量会被编码成一个足够长的二进制串来逼近这个精度想改精度直接调这个参数就行不需要手动处理编码长度。ga.run()返回两个值best_x是最优解坐标numpy 数组best_y是最优目标值标量。这个接口设计在所有算法里是统一的DE、PSO、SA 都是run()返回这两个值换算法只需要改类和初始化参数业务代码不需要重写。2.3 TSP 路径规划GA 与 ACA 的切换项目 examples 目录下有一堆 TSP demo我以经典的坐标点序列为例拆 GA_TSP 的用法。import numpy as np from scipy import spatial from sko.GA import GA_TSP num_points 15 points_coordinate np.random.rand(num_points, 2) # 生成 15 个二维坐标点 distance_matrix spatial.distance.cdist(points_coordinate, points_coordinate, metriceuclidean) def cal_total_distance(routine): # routine 是路径序列例如 [0, 5, 3, 1, ...] num_points, routine.shape return sum([distance_matrix[routine[i % num_points], routine[(i 1) % num_points]] for i in range(num_points)]) ga_tsp GA_TSP(funccal_total_distance, n_dimnum_points, size_pop50, max_iter500) best_points, best_distance ga_tsp.run() print(最优路径:, best_points) print(最短距离:, best_distance)这里的关键是把 TSP 问题包装成「给定路径序列返回总距离」的函数routine是一个 0 到 14 的排列表示访问城市的顺序。spatial.distance.cdist一次性算好所有点两两之间的欧氏距离矩阵后续每次计算路径总长只需查表不用重复算距离。路径是从最后一个点折返到第一个点的所以索引用了i % num_points来做环形取模。换成蚁群算法只需要改两行from sko.ACA import ACA_TSP aca ACA_TSP(funccal_total_distance, n_dimnum_points, size_pop50, max_iter200) best_points, best_distance aca.run()蚁群的size_pop在这里表示蚂蚁数量max_iter是迭代轮数。数据形式和距离矩阵完全复用这意味着你可以用同一份 TSP 数据在 GA、ACA 甚至 SA_TSP 之间做横向对比不用改业务层的距离计算代码。3. 七种算法的参数拆解与选型哪些参数值得调哪些是玄学3.1 算法适用场景全景对比这个包最实用的地方是提供了一张算法全景图不同问题的收敛特性差异很大选错算法等于白跑。我整理过一份对比表算法核心类擅长问题主要参数收敛特点遗传算法 GAGA,GA_TSP连续/离散通用组合优化size_pop,max_iter,prob_mut全局搜索强后期收敛慢差分进化 DEDE连续函数全局最优size_pop,max_iter,F,CR参数少且稳健首选粒子群 PSOPSO连续函数带约束寻优w,c1,c2收敛快易早熟模拟退火 SASA,SA_TSP组合优化局部精细搜索T_max,T_min,L单点搜索受初值影响大蚁群 ACAACA_TSPTSP 及路径类组合优化size_pop,max_iter,alfa,rho正反馈强参数敏感免疫算法 IAIA多峰函数多样性要求高size_pop,max_iter抗体浓度机制维持多样性人工鱼群 AFSAAFSA多峰连续优化size_pop,max_iter,try_number全局收敛稳精度一般我的选型习惯是先上 DE 和 PSO 做快速摸底DE 稳健、PSO 快如果是 TSP 或排产类组合问题直接上 GA_TSP 或 ACA_TSP如果怀疑目标函数是多峰的加一个 IA 比较抗体多样性机制AFSA 用得最少但遇到高度非线性的多峰函数时它的全局收敛性比 PSO 更可靠。3.2 差分进化 DE参数最少最不容易翻车DE 是我在这个包里用得最多的算法因为它只多两个参数F变异权重和CR交叉概率不需要理解复杂的编码机制。import numpy as np from sko.DE import DE def obj_func(p): # Rastrigin 函数多峰适合测试全局搜索 x1, x2 p return 20 x1**2 x2**2 - 10*(np.cos(2*np.pi*x1) np.cos(2*np.pi*x2)) de DE(funcobj_func, n_dim2, size_pop60, max_iter300, lb[-5, -5], ub[5, 5], F0.5, CR0.7) best_x, best_y de.run() print(DE 最优解:, best_x, best_y)F控制变异步长太大容易跳过最优区域太小容易陷入局部CR控制候选解各维度被替换的概率0.7 到 0.9 之间常见。DE 的好处是它对目标函数的光滑性要求很低即使函数有大量局部极小值只要种群规模给够通常能找到全局最优。作为对照你可以用 Rastrigin 函数跑一遍 PSO会发现 PSO 在max_iter不够时非常容易卡在某个局部峰上。3.3 粒子群 PSO调 w、c1、c2 的三个阶段PSO 的收敛速度在这 7 个算法里排前列正因如此也最容易早熟。from sko.PSO import PSO def demo_func(x): return (x[0] - 1) ** 2 (x[1] - 2) ** 2 (x[2] - 3) ** 2 pso PSO(funcdemo_func, n_dim3, pop40, max_iter150, lb[-10, -10, -10], ub[10, 10, 10], w0.8, c10.5, c20.5) best_x, best_y pso.run() print(PSO 最优解:, best_x, best_y)w是惯性权重控制粒子保持原速度的程度0.8 意味着快速飞行的粒子会逐渐减速偏向开发c1是自我认知系数拉向粒子历史最优c2是社会认知系数拉向群体最优。经验做法是前期c2设大一些比如 0.8让群体快速收敛到有希望的区域后期把c1调大比如 0.8让粒子向自己的历史最优精细搜索。pop是粒子数量跟 GA 的size_pop一个意思只是参数名不同这是 scikit-opt 的一个小坑——每个算法的种群参数名不统一抄代码时容易看漏。3.4 模拟退火 SA单点搜索的节奏控制SA 跟群体算法的区别是它只维护一个解靠温度控制接受劣质解的概率。import numpy as np from sko.SA import SA def demo_func(x): return (x[0] - 1) ** 2 (x[1] - 2) ** 2 sa SA(funcdemo_func, x0np.array([5, 5]), T_max100, T_min1, L50, max_stay_counter50) best_x, best_y sa.run() print(SA 最优解:, best_x, best_y)x0是初始解SA 对初值敏感建议先用 PSO 跑一轮拿到一个较优解再作为x0传入做精细搜索。T_max和T_min是温度上下限温度高时接受差解的概率大相当于大范围探索L是每个温度下的迭代次数max_stay_counter是连续多少步没有改进就提前结束。这个参数是 SA 的后悔药设太小会在收敛前就停设太大浪费时间。我一般设 50 到 100发现问题后再调。3.5 蚁群 ACA路径类问题的正反馈调参ACA_TSP 是专门为路径优化设计的它的核心是信息素更新机制。from sko.ACA import ACA_TSP aca ACA_TSP(funccal_total_distance, n_dimnum_points, size_pop40, max_iter200, distance_matrixdistance_matrix, alfa1.0, rho0.5) best_points, best_distance aca.run()注意这里distance_matrix是直接作为参数传入的不是从func函数里反推的这点跟 GA_TSP 不同。alfa是信息素重要程度系数值越大蚂蚁越倾向于走信息素浓的路径探索能力下降rho是信息素蒸发率0.5 表示每轮信息素衰减一半rho大则算法容易早熟收敛到局部最优rho小则收敛慢但解更分散。实际调参时我会先固定alfa1.0从rho0.3开始试看收敛曲线是否平滑下降。4. 常见问题与避坑清单我踩过的 5 个坑每个都对应一条报错或错误结果4.1 目标函数返回类型错误list 和 ndarray 的维度陷阱现象ga.run()报错ValueError: operands could not be broadcast together或者结果明显错误最优解落在边界上。原因自定义目标函数里用了return [x[0]**2 x[1]**2]返回了一个 list而 scikit-opt 内部默认目标函数返回 numpy 数组。list 参与广播运算时容易触发维度不匹配或者被当成一维数组处理。解决所有目标函数统一返回标量或 numpy 数组不要用 list。写函数时第一件事就是强制转换def demo_func(x): return np.array([x[0] ** 2 x[1] ** 2])从那以后我每次写完目标函数都先单独调一次传入一个具体值看返回类型确认是 ndarray 再丢给算法跑。4.2 边界约束不生效lb 和 ub 的长度必须严格等于 n_dim现象设置了lb[0, 0, 0]、ub[1, 1, 1]但结果变量仍然出现在 [2, 3] 这类越界值上或者报错lb and ub must be the same length as n_dim。原因n_dim3时lb必须传 3 个元素。如果只传了[0, 0]内部的边界检查会跳过最后一位或直接报错。还有一种情况是lb和ub虽然长度对但ub里有元素小于等于lb对应元素也会导致边界逻辑失效。解决写一个辅助函数做参数校验。def check_bounds(n_dim, lb, ub): assert len(lb) n_dim, flb 长度 {len(lb)} 与 n_dim {n_dim} 不一致 assert len(ub) n_dim, fub 长度 {len(ub)} 与 n_dim {n_dim} 不一致 assert all(u l for u, l in zip(ub, lb)), ub 必须严格大于 lb4.3 SA 结果和初值几乎一样温度参数没给够现象sa.run()跑完最优解跟传入的x0相差无几看起来算法完全没有搜索。原因T_max设得太小比如 5而目标函数的取值范围很大温度低时接受差解的概率极低相当于从头到尾只做局部爬山跳不出初值附近的谷底。解决T_max至少要设置成目标函数值量级的 10 倍以上。例如目标函数取值范围在 0 到 100T_max从 500 开始试T_min设 1L设 50。验证方法是打印每一轮的接受率如果接受率小于 5%说明温度太低。4.4 GPU 版本算子失效operators_gpu 只覆盖部分遗传算子现象from sko.operators_gpu import ...导入成功但跑demo_ga_gpu.py时速度反而比 CPU 慢或直接报显存相关错误。原因scikit-opt 的 GPU 加速是针对遗传算法中交叉和变异算子用 numba 的 CUDA 实现的且前提是安装 numba 且显存够大。小规模问题上种群小于 200GPU 拷贝数据到显存的开销远超计算加速收益。解决确认numba和cudatoolkit版本匹配然后用大种群测试pip install numba python examples/demo_ga_gpu.py拿 GPU 版和 CPU 版跑同一个size_pop2000, max_iter1000的问题对比时间只有 GPU 明显更快才值得用。小规模问题老老实实用 CPU 版。4.5 每次运行结果不一致随机种子没有固定现象同一份代码连续跑三次GA 和 PSO 每次给出的最优解都不一样差距还挺大。原因启发式算法本质是随机搜索种群初始化、变异交叉都是随机的。没有固定随机种子时结果天然有波动这不一定是 bug。解决在run()之前设置全局随机种子和 numpy 种子。import random import numpy as np random.seed(42) np.random.seed(42)固定种子后能保证可复现方便调参对比。实测在size_pop较小时不同种子之间结果差异能达到 20%所以做算法对比实验时必须保证所有算法用同一随机种子和同一初始种群。5. 进阶用法自定义算子、GPU 加速与 TSP 实战验证5.1 用 UDF 机制注入自定义交叉和变异逻辑scikit-opt 的 GA 支持用户自定义算子examples 里的demo_ga_udf.py把交叉和变异函数替换成了自定义版本。核心做法是把自定义函数传入run()import numpy as np from sko.GA import GA def obj_func(x): return np.sum(x ** 2) def custom_cross(p1, p2): # 自定义单点交叉取前一半来自 p1后一半来自 p2 n len(p1) cut n // 2 child1 np.concatenate([p1[:cut], p2[cut:]]) child2 np.concatenate([p2[:cut], p1[cut:]]) return child1, child2 def custom_mutation(child, mutation_prob): # 自定义高斯扰动变异 if np.random.rand() mutation_prob: child child np.random.normal(0, 0.1, sizelen(child)) return child ga GA(funcobj_func, n_dim5, size_pop50, max_iter100, lb[-5]*5, ub[5]*5) best_x, best_y ga.run(cross_overcustom_cross, mutationcustom_mutation)这个机制的价值在于当默认的二进制交叉不适合你的编码方式时不需要改源码直接注入自己的算子即可。需要注意自定义函数的签名必须与示例一致——交叉函数接收两个父代数组返回两个子代数组变异函数接收一个子代数组和变异概率返回一个数组。5.2 从 demo 走向真实项目替换欧氏距离为实际路网距离官方 demo 里的distance_matrix用的是scipy.spatial.distance.cdist算欧氏距离这在实际物流场景中不够用两点之间不能直线到达。我的做法是把距离矩阵替换成路网实际距离可以是 OSRM、百度地图 API 或者公司内部物流系统的测算结果。import numpy as np from sko.GA import GA_TSP # 假设 real_distance_matrix 是从路网 API 获取的 NxN 实际距离矩阵 real_distance_matrix np.array([ [0, 12, 35, 18], [12, 0, 28, 22], [35, 28, 0, 30], [18, 22, 30, 0] ]) def cal_real_distance(routine): num_points, routine.shape return sum([real_distance_matrix[routine[i % num_points], routine[(i1) % num_points]] for i in range(num_points)]) ga_tsp GA_TSP(funccal_real_distance, n_dim4, size_pop100, max_iter300) best_points, best_distance ga_tsp.run()替换的关键点是cal_real_distance函数本身不知道距离是怎么算的只负责查表所以换数据源不影响算法逻辑。真实项目中把real_distance_matrix的构造换成读取数据库或调用接口即可。5.3 收敛验证方法记录历史最优值并画出收敛曲线调参时最怕的是跑完一轮不知道结果靠不靠谱。我习惯把每轮的最优值记录下来画出收敛曲线来判断参数是否合理。GA 类自带run()只返回最终结果但可以用ga.generation_best_Y属性拿到每一代的最优值import matplotlib.pyplot as plt ga GA(funcobj_func, n_dim5, size_pop80, max_iter200, lb[-5]*5, ub[5]*5) best_x, best_y ga.run() history ga.generation_best_Y # 每代最优值ndarray plt.plot(history) plt.xlabel(Generation) plt.ylabel(Best Y) plt.title(GA Convergence Curve) plt.savefig(ga_convergence.png)一条正常的收敛曲线应该先快速下降然后趋于平缓。如果曲线在末尾还明显下降说明max_iter不够结果是次优解如果曲线前 20 代就完全平了说明初值或变异概率偏大种群已经失去多样性。这个验证方法同样适用于 PSOpso.gbest_y_hist和 DEde.gbest_y_hist。我每次调完参数都会强制画一遍收敛曲线看到曲线尾部变平才敢把参数用在正式任务上。这个习惯帮我省了很多返工时间希望帮到你。本文还有配套的精品资源点击获取
返回列表