ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

改进二元蚁群优化算法做特征选择:Python 实现与源码解析

改进二元蚁群优化算法做特征选择:Python 实现与源码解析 简介基于改进二元蚁群优化MBACO算法的特征选择Python实现面向机器学习与数据挖掘学习者、算法研究以及需要处理高维特征集的开发者。该方案将每个特征视作节点通过模拟蚂蚁寻路、信息素沉积与蒸发机制在离散空间中搜索最优特征子集以提升模型性能与可解释性。压缩包共6个文件以5个Python脚本为主体另含1个Markdown说明文档整体约8KB。脚本分别对应MBACO主程序、适应度函数、启发式规则、遗传算法对比实现及结果绘图分析目录结构清晰便于直接阅读和二次开发。目前已有193人浏览学习适合作为生物启发式算法与特征选择结合的学习范例。读者可从中获取完整的算法流程与参数配置思路了解路径构造、信息素正向更新与蒸发、全局最优解迭代搜索等关键环节配合fitness功能模块与评价脚本可在公开数据集上快速复现实验并通过与GA等方法的对比直观评估MBACO的寻优效果。1. 改进二元蚁群优化算法的特征选择实现这份 Python 代码能直接改着用改进二元蚁群优化算法MBACO在特征选择任务里的应用一直是特征工程环节关注度很高的方向。这份 modifiedACO 源码包把完整流程拆成了五个可独立修改的模块从蚂蚁路径构造、启发式计算到适应度评估和收敛可视化都覆盖到了。适合想拿元启发式算法做特征选择的入门者也适合需要快速对比 ACO、GA 两种选择策略效果的从业者。它解决的核心问题是当特征维度一高穷举和过滤式方法都开始吃力时用一群蚂蚁的协同搜索帮你逼近最优特征子集。下面直接拆源码。2. 从 ACO 到 MBACO二元蚁群优化的原理与源码结构2.1 二元编码如何把特征选择变成蚂蚁寻路标准蚁群算法是为连续或图路径问题设计的比如 TSP 里蚂蚁在城市之间移动。特征选择却是典型的离散组合优化每个特征只有“选”和“不选”两个状态。把每个特征看成一个节点n 维特征向量就变成 n 个节点的集合蚂蚁经过的路径代表它选中的特征子集。这里的路径不是一个序列而是一个二进制掩码向量长度等于特征数。MBACO 相对于基本 ACO 的改进常见做法集中在三处。第一是信息素更新规则传统 ACO 只有全局最优路径更新信息素MBACO 通常会在每轮迭代后对当前最优子集做正向增强同时对所有路径做统一蒸发避免信息素无限堆积。第二是启发式信息的引入方式每个特征被选中的概率由信息素浓度和启发式值共同决定这份代码单独拆出 heuristics.py说明设计者把启发式计算和蚂蚁选择逻辑解耦了方便替换成不同的特征评估指标。第三是收敛速度控制二元蚁群很容易在迭代后期信息素两极分化改进版普遍会加信息素上下限约束或者修改转移概率公式避免过早停止探索。提示如果你之前只做过过滤式特征选择比如方差过滤、卡方检验ACO 这类包裹式的本质区别是它把特征子集的选择和模型性能直接绑定评价的是“这个组合好不好”而不是“单个特征好不好”。2.2 文件清单与调用关系五个 py 文件各管哪一段压缩包解压后是 modifiedACO-master 目录里面除 README.md 外有 5 个 Python 文件。先说清每个文件的职责边界这是后续改代码的基础。文件职责mbaco.py主算法入口蚂蚁数量、迭代次数、信息素更新、全局最优搜索都在这heuristics.py启发式信息计算常见做法是互信息、信息增益或卡方统计量fitness_function.py适应度函数评估特征子集对应的模型表现feature_selection_ga.py遗传算法版特征选择用于和 MBACO 做基线对比plotaco.py可视化收敛曲线、信息素变化、特征选择结果展示调用关系上mbaco.py 是总指挥。它先调用 heuristics.py 算出每个特征的初始启发式值再利用 fitness_function.py 评估蚂蚁每次构造出的特征子集迭代结束后可以调用 plotaco.py 画收敛趋势。feature_selection_ga.py 是独立的对比程序它和 mbaco.py 共用同一套 fitness_function.py这样才能保证对比公平否则两边评估口径不同结论站不住。README.md 的开头通常是运行说明和环境依赖。实际操作上这个项目依赖 numpy、pandas 和 scikit-learn。sklearn 负责加载数据集和评估模型numpy 做矩阵运算pandas 处理数据切片。建议直接用 conda 建一个独立环境避免系统 Python 环境被改乱这一点我在第 5 章会展开讲。2.3 一段最简运行流程先跑通先把流程跑通再研究原理是最省时间的学习路径。首次运行建议用 sklearn 自带的乳腺癌数据集维度适中30 个特征特征之间有一定相关性非常适合观察蚁群搜索的效果。如果代码里没有写数据加载部分常见做法是直接在脚本入口加一段加载逻辑。# 先验证算法主流程能跑用 sklearn 内置数据 import numpy as np from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split data load_breast_cancer() X, y data.data, data.target X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) print(f训练集 {X_train.shape}测试集 {X_test.shape})这里用 stratifyy 保证训练测试集里正负样本比例一致避免在样本不均衡时评估结果失真。random_state 固定为 42是为了后续和 GA 对比时确保数据切分一致否则两边用的数据点不同对比就没有意义了。跑通这段后再到 mbaco.py 里找主函数入口把训练测试数据传进去。3. 核心参数与信息素更新逻辑把 mbaco.py 的每个旋钮调到能用3.1 参数表蚂蚁数、迭代次数、蒸发率、启发式权重怎么取舍mbaco.py 里最影响结果的一组参数核心就那么几个。做一个对照表你再对着自己的数据集去调。参数常见取值范围作用调参倾向n_ants蚂蚁数量10~50每轮搜索的特征子集个数特征多就加大但太大拖慢收敛n_iter迭代次数20~100总搜索轮数看收敛曲线是否走平rho信息素蒸发率0.05~0.3控制旧信息素遗忘速度太小容易早熟太大容易震荡alpha信息素权重0.5~2.0蚂蚁对信息素的依赖程度前期小一点后期可以大beta启发式权重0.5~2.0蚂蚁对启发式信息的依赖与 alpha 保持平衡一方独大必翻车这组参数的物理含义要抓住alpha 和 beta 共同决定蚂蚁选特征是“跟经验走”还是“跟数据走”。信息素代表历史搜索的经验积累启发式代表特征本身的统计价值。alpha 太大蚂蚁反复选同一批特征搜索范围快速收缩beta 太大等价于退化成贪心选择每步都挑启发式最大的特征蚁群的协同优势就没了。蒸发率 rho 是最容易被忽略但后果最严重的参数。它决定每轮迭代后旧信息素衰减多少。rho0.1 表示每轮保留 90% 的旧信息素历史记忆很长rho0.3 表示每轮只留 70%记忆快速淡化。特征维度高、数据噪声大时我一般会把 rho 调到 0.2 左右既不让早期错误路径长期占优也保留足够的路径历史。3.2 主循环代码注释信息素更新到底改了什么下面这段是 mbaco.py 里最常见的 ACO 主循环骨架。不同版本的代码写法差异很大但核心逻辑离不开这几步。我按这份代码最可能的结构来注释重点让你看得懂每步在干什么。# mbaco.py 主循环骨架关键逻辑注释 import numpy as np def aco_feature_selection(X, y, n_ants20, n_iter50, rho0.2, alpha1.0, beta1.5): n_features X.shape[1] # 每个特征一个信息素值一维数组就够不需要全连接矩阵 pheromone np.ones(n_features) # 启发式信息由 heuristics.py 单独计算这里先占位 heuristics np.ones(n_features) best_mask None best_fitness -np.inf for it in range(n_iter): all_masks [] all_fitness [] for ant in range(n_ants): # 二元状态下蚂蚁逐个特征决定选(1)还是不选(0) mask np.random.rand(n_features) 0.3 # 更完整的做法是按转移概率公式逐个特征采样此处略写 fitness fitness_function(X, y, mask) all_masks.append(mask) all_fitness.append(fitness) # 信息素蒸发所有路径统一的负向更新 pheromone * (1.0 - rho) # 正向更新当前最优蚂蚁走过的特征加信息素 best_idx np.argmax(all_fitness) if all_fitness[best_idx] best_fitness: best_fitness all_fitness[best_idx] best_mask all_masks[best_idx] pheromone[best_mask] 0.1 return best_mask, best_fitness关键点在最后三行。pheromone * (1.0 - rho)是全局蒸发保证没有特征的信息素会无限累积pheromone[best_mask] 0.1是增量更新只增强当前全局最优子集对应的特征。这个“全局蒸发生效、集中增强最优”的组合就是 ACO 区别于纯随机搜索的核心机制。这个简化版里我用了np.random.rand直接按 0.3 概率选特征实际 mbaco.py 会用转移概率公式把信息素的 alpha 次方乘启发式的 beta 次方再归一化成选择概率。你如果看到代码里有tau ** alpha * eta ** beta这种表达式就是这个公式没错。想调整探索力度就看这个公式里除了 alpha、beta 之外有没有加随机扰动项加了扰动项的一般就是改进版的防早熟设计。3.3 用命令行参数控制实验避免每次改源码改源码跑实验是新手最容易养成的坏习惯。每改一个参数就动一次代码跑完想回溯就发现不知道哪个版本对应哪个结果。这份代码没有自带配置文件我一般会建议你包一层 argparse把关键参数全部暴露成命令行参数。# run_experiment.py 自行封装不动原始 mbaco.py import argparse from mbaco import aco_feature_selection parser argparse.ArgumentParser() parser.add_argument(--ants, typeint, default20) parser.add_argument(--iter, typeint, default50) parser.add_argument(--rho, typefloat, default0.2) parser.add_argument(--alpha, typefloat, default1.0) parser.add_argument(--beta, typefloat, default1.5) args parser.parse_args() best_mask, best_fitness aco_feature_selection( X, y, n_antsargs.ants, n_iterargs.iter, rhoargs.rho, alphaargs.alpha, betaargs.beta ) print(最佳子集特征数:, int(best_mask.sum())) print(最佳适应度:, best_fitness)这里假设 mbaco.py 的入口函数叫 aco_feature_selection你解压出来后先看一眼文件末尾有没有if __name__ __main__再按实际函数名替换。参数化之后跑一个网格搜索就很干净外层用 bash 循环或者 Python 的 subprocess 批量执行结果落成 CSV 再画热力图找最优参数区间。这是任何元启发式算法落地时的基本功源码里没写但实践里躲不开。4. 适应度函数与启发式信息决定特征子集好坏的两块基石4.1 fitness_function.py准确率、F1 值还是模型稳定性适应度函数是整个 MBACO 的指挥棒蚂蚁往哪走完全取决于这个函数怎么定义。fitness_function.py 里最常见的设计是给定一个特征子集掩码先用它切出 X 的子集再训练一个分类器做交叉验证把平均准确率或 F1 值作为适应度返回。特征是布尔数组可以直接作为列索引。分类器的选择有讲究。用逻辑回归速度快但拟合能力有限用随机森林或 KNN效果好但每个子集都要重新训练耗时成倍增加。特征维度在 30 以下时我推荐用逻辑回归加 5 折交叉验证兼顾速度和区分度。维度上百的话可以把交叉验证折数降到 3 折或者改用更快的朴素贝叶斯。# fitness_function.py 核心结构子集掩码 - 交叉验证评估 import numpy as np from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score def fitness_function(X, y, mask): # 布尔掩码选列保留被选中特征 selected X[:, mask] # 防止选中 0 个或全部特征时程序崩掉 if selected.shape[1] 0 or selected.shape[1] X.shape[1]: return 0.0 model LogisticRegression(max_iter1000) scores cross_val_score(model, selected, y, cv5, scoringf1_macro) return scores.mean()这里返回的是 f1_macro 的平均值适合类别不平衡的数据。如果数据是平衡的换成scoringaccuracy更直观。关键判断是这个返回值必须是“越大越好”的方向。MBACO 主循环里用np.argmax找全局最优方向反了会把最优子集当成最差子集丢弃。还有一个常用优化是在适应度里加特征数惩罚项比如fitness f1 - 0.01 * selected_features_count。这样蚂蚁会倾向用更少的特征获得相近的精度符合特征选择“降维且保性能”的目标。注意惩罚系数不能太大否则适应度会被特征数主导精度差异完全体现不出来。4.2 heuristics.py互信息、信息增益与卡方怎么参与路径选择启发式告诉蚂蚁“每个特征单独看值不值得选”。这份代码把 heuristics.py 单独拆出来说明设计者希望启发式是可插拔的。最常见的实现是互信息因为 sklearn 的mutual_info_classif可以直接算对线性和非线性关系都敏感且不限定分布假设。选择启发式指标的判断标准是它跟适应度函数的评价口径是否一致。适应度用分类准确率启发式用互信息两者恰好都反映“特征对类别的区分能力”配合最自然。如果启发式用了方差这种无监督指标蚂蚁在第一步就会忽略掉那些方差小但有判别力的特征。# heuristics.py 建议结构返回每个特征的启发式值数组 from sklearn.feature_selection import mutual_info_classif def compute_heuristics(X, y): # 返回一维数组长度等于特征数 heu mutual_info_classif(X, y, random_state42) # 归一化到 [0,1]避免和信息素量纲差异过大 if heu.max() 0: heu heu / heu.max() return heu归一化这步很容易被省略但省略后会出问题。信息素的更新量通常在 0 到 1 的量级如果启发式的原始值从 0 到几十那 beta 权重乘出来的选择概率就被启发式完全主导信息素形同虚设。把启发式压到与信息素同量级alpha 和 beta 才有真实的竞争关系。这一步具体踩坑记录在第 5 章。4.3 两块的配合启发式偏“单点”适应度偏“组合”新手最常犯的概念错误是把启发式值和适应度函数当成一回事。它们的分工完全不同。启发式只看单个特征与目标的关系好处是计算快坏处是看不到特征与特征之间的互补和冗余。适应度函数评估的是整个子集的组合效果能捕捉两个单独都很弱、合在一起很强的情况代价是每次评估都要训练模型。MBACO 的聪明之处在于用信息素把这两层信息缝合起来。蚂蚁对第 k 个特征的选择概率分子是信息素 alpha 次方乘以启发式 beta 次方分母是全部未选特征的同款累加。信息素层面积累的是“组合幸存者”的经验启发式层面积累的是“单点竞争力”的判断。调参时如果发现选出来的子集精度不高先看是 beta 太大导致启发式主导还是 alpha 太大导致过早锁定同一批特征。5. 避坑指南MBACO 特征选择最常见的五个翻车现场MBACO 这类元启发式算法跑通容易跑出可信结果难。下面五条是复现过程中最容易踩的坑按“现象-原因-解决”的顺序记录每条都有具体参数和代码层面的应对。5.1 蒸发率设置过大收敛曲线剧烈震荡现象信息素更新后最优特征在下一轮被选中的概率反而下降收敛曲线一会儿高一会儿低50 轮迭代结束还在震荡。原因rho 过大时每轮蒸发掉超过三分之一的信息素旧经验几乎被清空蚂蚁每轮都接近重新开始搜索。信息素的正向更新速度跟不上蒸发速度搜索过程就失去了累积性。解决把 rho 降到 0.1 到 0.2 之间。如果用 50 轮迭代rho0.2 已经足够让 5 轮之前的信息素基本衰减干净没必要再激进。观察收敛曲线后如果曲线在 30 轮以后还在持续上升但没有明显走平说明 rho 偏小可以适当加大到 0.25。5.2 转移概率里启发式没归一化信息素形同虚设现象不管怎么调 alpha蚂蚁选出来的特征都高度集中在几个特征上其他特征几乎不被选中而且这个结果跟 ACO 的历史搜索过程没什么关系。原因heuristics.py 返回的原始值量级太大比如信息增益在 0 到 3 之间乘上 beta 权重后直接压过了信息素。选择概率被启发式单方面统治ACO 退化成了贪心过滤式选择。解决把启发式数组统一归一化到 0 到 1 区间再进入 mbaco.py同时把 beta 初始值设得比 alpha 小一些。我在 4.2 节写的归一化代码不是装饰是这类算法能正常工作的底线。5.3 适应度函数的优化方向搞反全局最优变成最差现象跑完 50 轮输出的 best_fitness 是负数或者选出来的特征子集精度比随机选择还差。原因fitness_function 返回的值方向与主循环里的比较逻辑不一致。mbaco.py 如果用的是np.argmax找最大值而适应度函数返回的是错误率或距离值越小越好两者方向天然相冲。解决先跑一次单次评估打印 fitness_function 的返回值和范围确认“好的子集”对应的值是大还是小。然后在 mbaco.py 主循环的best np.argmax(...)附近加一行注释标明这个方向对应的物理含义是“越大越好”。如果项目里默认用的是错误率就把返回值改成1 - error_rate。5.4 没固定随机种子结果不可复现现象同一组参数连续跑三次选出来的特征子集每一次都不一样最优适应度也在波动。写进文档里的结果别人复现不出来。原因蚂蚁的初始路径、转移概率里的随机采样、交叉验证的数据划分都有随机性。如果代码里没有设置全局种子或者 sklearn 内部各自的 random_state 没固定实验本身就不具备可重复性。解决在 mbaco.py 主入口统一设置种子。import numpy as np np.random.seed(42) from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42)把random_state42同时下到数据切分和启发式计算里。这样做以后同一环境、同一参数跑出来的结果是逐位一致的。如果做 GA 和 ACO 的对比实验两边更要统一种子的设置逻辑。5.5 numpy 版本差异引发的隐形错误现象在旧环境里正常运行的 mbaco.py换到 numpy 1.24 以上版本后报错或者运行结果和原来不一样。原因新版本 numpy 对部分旧 API 做了兼容性调整和废弃处理比如np.float、np.int这类别名已被移除。老代码里偶尔会出现np.float(...)这种写法在新版本直接跑不起来。解决安装时锁定 numpy 版本范围推荐numpy1.21,1.24这个区间既能兼容 sklearn 的依赖又避开了新版本对旧 API 的破坏。如果你的环境已经装了大版本用pip install numpy1.24降回去或者按报错信息把np.float改成float。看 README.md 里的依赖列表里面通常会写推荐版本没有写的话就以能跑通为准。提示以上五个坑是我复现这类蚁群特征选择代码时踩过的真实组合。其中“启发式未归一化”和“随机种子未固定”是最隐蔽的两个因为它们不报错只会让结果看起来“好像还行”但一对比就会发现问题。6. 进阶玩法用对比实验与可视化验证算法改进效果6.1 用 feature_selection_ga.py 搭一个公平的基线对比feature_selection_ga.py 是这份代码里最有价值的存在。它实现了遗传算法版的特征选择和 MBACO 共用 fitness_function.py。这意味着两者评估同一个特征子集时用的是完全一样的模型和打分方式对比的只是搜索策略本身而不是评估口径。对比实验要守住三个一致数据切分一致、适应度函数一致、评价指标一致。第一个种子统一在 train_test_split 处固定第二个直接共用 fitness_function.py 不改动第三个在交叉验证的参数上保持 cv 和 scoring 字面量完全一样。满足这三个一致后跑十组随机种子比较最优适应度的均值、方差和选出的特征数。我一般习惯记录三个数字MBACO 与 GA 各自的最优适应度、两者找到最优解时的迭代轮数、选出的特征子集大小。第一个反映搜索精度第二个反映收敛速度第三个反映可解释性。如果 MBACO 在精度相近的前提下选出的特征更少这个“改进”才真的站得住。6.2 plotaco.py 的价值别只看最终结果要看收敛过程plotaco.py 把每轮最优适应度画成折线观察这条曲线比只看最终数字更能判断算法健康状况。健康的收敛曲线是前 20 轮快速上升之后缓慢走平最终稳定在一个高值。如果曲线是一条缓慢上升的直线说明搜索效率低加大蚂蚁数比加大迭代轮数更有效。如果曲线很快走平且平坦期很长说明提前收敛可以降低 rho 或加大探索性。顺着 plotaco.py 的思路我还会额外记录每一轮信息素分布的标准差。刚开始迭代时各特征信息素接近标准差小后期最优特征的信息素被反复增强标准差变大。如果标准差增长得过快说明蚂蚁过早认定了一批特征如果到结束标准差都不大说明算法根本没学会区分好坏特征。在 plotaco.py 里加一个信息素标准差的 y 轴判断会直观很多。从那以后我每次跑完 MBACO 都要强制看一遍收敛曲线和特征掩码的可视化再确认随机种子已经固定然后才敢把结果写进分析里。这种“先看过程再信结果”的习惯帮我挡掉了不下五个假结论。希望帮到你。本文还有配套的精品资源点击获取
返回列表