☰
动态加权条件互信息特征选择算法WMRI:原理与Python实现
2026/10/3 7:53:06 网站建设 项目流程

简介:针对高维数据中无关与冗余特征带来的建模难题,这份文档系统阐述了一种新的过滤式特征选择算法——动态加权条件互信息的特征选择算法(WMRI)。内容从信息论框架入手,梳理了传统过滤式方法(如MRMR、JMI等)的局限,重点推导WMRI如何利用均值和标准差动态调节“新分类信息”与“保留类别信息”之间的权重,从而避免固定参数对无关特征和冗余特征的忽略或误判。全文结构清晰,适合机器学习、数据挖掘方向的研究者、学生以及从事高维数据分析的工程师用于算法理解与复现参考。资源为单个Word文档,共1个docx文件,压缩包大小454KB,携带方便、便于批注阅读。文档包含算法定义、候选特征评估公式、完整伪代码,以及对10个基准数据集的实验对比结论,可帮助读者掌握条件互信息、特征冗余度量等核心知识点,并借鉴其实验设计与结果分析方法。该资源已有80人浏览学习,对关注特征选择前沿方法的人来说是一份紧凑且实用的参考资料。

1. 动态加权条件互信息:为什么固定权重的特征选择会翻车

特征选择做到后期,很多人会卡在同一个问题上:MRMR、JMI、CMIM 这些经典算法表现都还行,但 β、λ 一旦设死,换数据集就翻车。动态加权条件互信息的特征选择算法 WMRI 把固定参数改成用均值和标准差动态计算,让"新分类信息"和"保留类别信息"的重要程度随数据分布自动调整,这是它和同系列算法最本质的区别。

这份资源是一篇完整论文,包含 WMRI 的推导、可照抄的伪代码、10 个基准数据集的实验设置,以及与 IG-RFE、CFR、JMIM、DCSF、MRI 五个基线的 fmi 对比。适合正在做高维特征选择、想在不引入额外分类器的情况下压缩特征空间的工程师和研究生。

下文先拆 Brown 框架和 MRI 的局限,再给出可运行的 Python 实现,最后列五个复现时踩过的坑。

2. Brown 框架到 WMRI:两个动态权重是怎么算出来的

2.1 Brown 统一框架:MRMR、JMI、CMIM 只是参数不同

Brown 等人提出的信息论特征选择框架,把一大类过滤式算法统一成了一个式子:

J(fk) = I(fk; C) − β · Σ I(fk; fsel) + λ · Σ I(fk; fsel | C)

这里的 S 是已选特征集合,fk 是候选特征,C 是类标签。第一项 I(fk; C) 衡量候选特征和标签的直接相关性;第二项减去的是候选特征和已选特征之间的冗余;第三项加上的是"在已选特征条件下"候选特征和标签的条件互信息,也就是已选特征没能解释、fk 还能补充的那部分分类信息。

之所以说"选不同参数就是选不同算法",是因为 β 和 λ 取不同值会退化成不同的经典方法。β=0 且 λ=0 时是最朴素的 max-Relevance 贪心;MRMR 是设定 β 为 1/|S| 量级、λ 为 0;JMI 和 CMIM 则把 λ 调成和 β 同一个量级,让条件互信息项参与竞争。具体数值因实现略有差异,但它们在统一框架里的参数位置是固定的。实现层面,这三个算法共用同一个计算骨架,区别只在 β、λ 的取值,所以调参的玄学就在这两个系数上。

2.2 MRI 的局限:两个信息项被当成同等重要

MRI 算法在 Brown 框架上做了一步改写,把目标函数整理成:

J_MRI(fk) = I(C; fk) + Σ [ I(C; fk | fsel) + I(C; fsel | fk) ] ∝ I(fk; C) − (2 / (|S| + 1)) · Σ [ I(fk; fsel) − I(fk; fsel | C) ]

后者的等价形式意味着 β = λ = 2/(|S|+1),也就是每个已选特征的权重随 |S| 增长而递减,但两个方向的条件互信息始终等权。问题就在这:I(C; fk | fsel) 表示"已选特征已经告诉我的情况下,fk 还能新增多少分类信息",I(C; fsel | fk) 表示"已知 fk 后,已选特征里有多少类别信息值得保留"。这两者在实际数据里几乎不可能相等——fk 如果是强特征,前者会远大于后者;如果 fk 是弱特征但和已选特征高度耦合,后者反而占优。MRI 默认它们五五开,换到噪声多的数据集上,选出来的特征子集经常偏向冗余侧。

2.3 WMRI 的核心改动:用标准差当权重

WMRI 的评估标准是:

J_WMRI(fk) = I(C; fk) + (1 − α) · Σ I(C; fk | fsel) + (1 − β) · Σ I(C; fsel | fk)

其中 α 是 I(C; fk | fsel) 这一组值在全部已选特征上的标准差,β 是 I(C; fsel | fk) 这一组值的标准差。标准差大说明这个信息项在不同已选特征之间波动剧烈,也就是它和已选特征的交互不稳定,应该降权;标准差小说明该项在不同已选特征上表现一致,是稳定贡献,应该加回去。均值在这里有两个作用:一是作为归一化的参照,让标准差相对均值有意义;二是如果两组信息项的量纲差异大,均值可以帮助识别哪一组整体偏低、需要压缩。

(1−α) 这个形式要留意:当标准差 α 大于 1 时权重会变成负数。论文实验里的数据特征经过离散化和分箱,互信息值大多落在 0 到 1 之间,所以 α 超 1 的情况不常出现;但自己复现时如果遇到负权重,我会先把互信息做 min-max 归一化,而不是直接改公式。

2.4 伪代码逐行拆解:前向贪心选择的三阶段

论文表 1 的伪代码可以分成三个阶段。第一阶段是初始化:S 置空,计算每个特征和标签的互信息 I(C; fk),把最大值对应的特征作为第一个入选特征,从 F 移到 S。第二阶段是循环迭代:对每个候选 fk,分别计算它和每一个已选特征的 I(C; fk | fsel) 与 I(C; fsel | fk),得到两组长度等于 |S| 的数组,再按式(4)-(7) 求各自的均值 μ 和标准差 α/β。第三阶段用式(3) 更新评估值,选出最大 J 对应的特征加入 S,直到达到阈值 K。

用 Python 写这个循环的骨架时,我一般这样组织:

S = [] F = list(range(X.shape[1])) mi = mutual_info_classif(X, y, random_state=42) first = int(np.argmax(mi)) S.append(first); F.remove(first) while len(S) < K: best_j, best_f = -np.inf, None for fk in F: cmi_new, cmi_keep = [], [] for fsel in S: cmi_new.append(conditional_mutual_info(X[:, fk], y, X[:, fsel])) cmi_keep.append(conditional_mutual_info(X[:, fsel], y, X[:, fk])) alpha = np.std(cmi_new); beta = np.std(cmi_keep) j = mi[fk] + (1 - alpha) * np.sum(cmi_new) + (1 - beta) * np.sum(cmi_keep) if j > best_j: best_j, best_f = j, fk S.append(best_f); F.remove(best_f)

这段代码里 mi 数组提前算好,每次循环直接取 mi[fk],避免重复调用互信息估计器。cmi_new 是「已知已选特征后,fk 新增的分类信息」,cmi_keep 是「已知 fk 后,已选特征保留的分类信息」,两组分别求标准差后各自加权,这和式(3) 完全对应。论文说时间复杂度是 O(Kmn),和 MRI、CFR 是一个量级,但多了均值和标准差的计算,实际跑下来在特征数 600+ 的数据上会明显慢一截,优化手段放在第 5 章讲。

3. 复现 WMRI:Python 实现、参数设置与 sklearn 管线接入

3.1 互信息和条件互信息的估计:先决定离散化还是连续估计

复现 WMRI 第一件要决定的事,是怎么算 I(C; fk) 和条件互信息。论文实验数据来自 UCI 和 ASU,混合了连续特征和离散特征,但论文没有详细说明预处理阶段是否做了分箱。常见的做法有两种:一是把连续特征等宽分箱后,用联立直方图估计互信息;二是直接用 sklearn 的 mutual_info_classif,它内部用 k 近邻估计连续变量的互信息,对连续特征更友好。

我一般建议主流程用 mutual_info_classif 算基础互信息,因为它在 sklearn 1.x 里已经调得比较稳,不需要手动分箱;条件互信息由于 sklearn 没现成接口,只能自己实现。这里给一个最小实现,用等宽分箱 + 条件概率加权:

import numpy as np from sklearn.metrics import mutual_info_score def _digitize(x, bins=10): edges = np.histogram_bin_edges(x, bins=bins) return np.clip(np.digitize(x, edges[:-1]), 0, bins - 1) def conditional_mutual_info(x, y, z, bins=10): if np.ptp(x) == 0 or np.ptp(y) == 0 or np.ptp(z) == 0: return 0.0 xd = _digitize(x, bins) yd = _digitize(y, bins) zd = _digitize(z, bins) cmi = 0.0 for z_val in np.unique(zd): mask = zd == z_val pz = mask.mean() if pz == 0: continue cmi += pz * mutual_info_score(xd[mask], yd[mask]) return max(cmi, 0.0)

逻辑说明:I(X; Y | Z) 的定义就是按 Z 取值的条件加权求和,所以这里对每个 z_val 取出对应样本子集,在子集上算 X 和 Y 的互信息,再乘上 p(Z=z_val) 累加。开头对零方差特征的判断是防御性写法,常量特征算不出分箱边界,直接返回 0 比报错更符合特征选择场景。bins 控制离散化粒度,论文里特征维度从几十到上千都有,bin 数取 10 到 20 之间比较安全;bins 太小会把连续特征压成几个粗糙档位丢失信息,bins 太大则每个格子里样本太少,估计方差变大。末尾的 max(cmi, 0.0) 是我个人加的约束,直方图估计在小样本上很容易出现轻微的负偏差,论文公式不允许负权重参与后续计算。

3.2 WMRI 的完整实现:类封装与前向搜索

把 2.4 的骨架补全,封装成一个 sklearn 风格的转换器:

import numpy as np from sklearn.feature_selection import mutual_info_classif class WMRI: def __init__(self, n_features=30, bins=10, epsilon=1e-12): self.n_features = n_features self.bins = bins self.epsilon = epsilon self.selected_ = [] self.mi_ = None def fit(self, X, y): n, d = X.shape self.mi_ = mutual_info_classif(X, y, random_state=42) first = int(np.argmax(self.mi_)) self.selected_ = [first] remaining = [i for i in range(d) if i != first] while len(self.selected_) < min(self.n_features, d): best_j, best_f = -np.inf, None for fk in remaining: cmi_new, cmi_keep = [], [] for fsel in self.selected_: cmi_new.append(conditional_mutual_info( X[:, fk], y, X[:, fsel], self.bins)) cmi_keep.append(conditional_mutual_info( X[:, fsel], y, X[:, fk], self.bins)) cmi_new = np.asarray(cmi_new) + self.epsilon cmi_keep = np.asarray(cmi_keep) + self.epsilon alpha = np.std(cmi_new) beta = np.std(cmi_keep) j = (self.mi_[fk] + (1 - alpha) * cmi_new.sum() + (1 - beta) * cmi_keep.sum()) if j > best_j: best_j, best_f = j, fk self.selected_.append(best_f) remaining.remove(best_f) return self def transform(self, X): return X[:, self.selected_]

说明几个设计点。epsilon 加在两组条件互信息数组上,既避免标准差为 0 时权重失效,也避免 alpha 恰好等于 1 时该项被完全抹掉,这是一个可以调的稳定性参数。mi_ 在 fit 里一次性算完,循环里直接索引,能省掉大量重复计算。mutual_info_classif 对每个特征返回标量互信息,所以 mi_[fk] 就是式(3) 里的 I(C; fk)。n_features 是停止条件,对应论文里预先指定的特征子集规模 K;论文实验取 30,但对小数据集我建议设成 min(30, d),否则 while 循环会在特征选完前越界。

3.3 阈值 K 怎么定:从论文的 30 到实际项目的经验值

论文统一把特征子集规模设成 30,这是为了在 10 个数据集上做横向对比,保证可比性。实际项目中我一般先用 30 跑一遍,记录 fmi 随特征数的变化曲线,看拐点在哪。特征数在 100 以下的低维数据,K 取总特征数的 20%~30% 起步;特征数 1000 以上的高维数据,K 取 30~100 观察曲线。如果分类器是随机森林这种对冗余不敏感的方法,K 可以偏小,因为 RF 自带特征重采样;如果是 KNN 或线性模型,冗余特征对距离度量的干扰更大,K 要偏保守。

3.4 接进 sklearn 管线:注意 fit 和 transform 的两次调用

WMRI 和 sklearn 的 SelectKBest 用法一致,可以包进 Pipeline:

from sklearn.pipeline import Pipeline from sklearn.neighbors import KNeighborsClassifier pipe = Pipeline([ ('wmri', WMRI(n_features=30, bins=15)), ('clf', KNeighborsClassifier(n_neighbors=5)), ]) scores = cross_val_score(pipe, X, y, cv=6, scoring='f1_macro')

Pipeline 会自动在每一折的训练数据上调 WMRI.fit,再对验证数据调 transform。这里要强调一个容易翻车的点:特征选择必须在交叉验证循环内部执行,不能在全集上先筛完特征再交叉验证,否则会数据泄漏。WMRI 的 fit 只用训练折的统计量,transform 只做列切片,不引入测试折信息,这是它能安全放进 Pipeline 的前提。

4. 实验复现:10 个数据集上的评价指标与结果解读

4.1 数据集清单与预处理

论文用了 10 个公开数据集,覆盖手写数字、文本、语音、图像和生物数据。下表是我整理的复现清单:

数据集特征数样本数类别数来源
musk21664762UCI
madelon50026002ASU
ALLAML7129722ASU
CNAE-985710809UCI
mfeat-kar64200010UCI
USPS256929810ASU
semeion256159310ASU
COIL201024144020ASU
wpbc341982UCI
Isolet617156026ASU

预处理环节论文只说了 6 折交叉验证,没有细说标准化。我的做法是:连续特征做 z-score 标准化再分箱;类别特征直接编码成 0 到 C−1 的整数。要注意 ALLAML 只有 72 个样本、7129 个特征,属于典型的 p>>n 数据,6 折交叉验证时每折训练集只有 60 个样本,条件互信息的直方图估计会非常稀疏,这种数据上最好把 bins 降到 8 以下,或者先用方差过滤砍掉大部分零方差特征再跑 WMRI。

4.2 评价指标:sen、prc 和 fmi 的计算

论文用宏平均的 sen、prc 和 fmi 三个指标,其中 fmi 是 sen 和 prc 的调和平均,对应机器学习里常见的 macro-F1:

sen = (1/|C|) · Σ TP_i / (TP_i + FN_i) prc = (1/|C|) · Σ TP_i / (TP_i + FP_i) fmi = 2 · prc · sen / (prc + sen)

复现时直接用 sklearn 的 f1_score 就能拿到宏平均 F1,不需要手写混淆矩阵。注意 sklearn 的 macro-F1 默认对每个类分别算 F1 再平均,和论文的"先宏平均 sen/prc 再算调和平均"在类别不均衡时有细微差异;为了对齐论文结果,建议用 average=None 取出每类的 precision/recall 手动算。

from sklearn.metrics import precision_recall_fscore_support def paper_fmi(y_true, y_pred): prec, rec, _, _ = precision_recall_fscore_support( y_true, y_pred, average=None) prec_macro = prec.mean() rec_macro = rec.mean() return 2 * prec_macro * rec_macro / (prec_macro + rec_macro + 1e-12)

这个函数在类别均衡的数据集上结果和 macro-F1 基本一致,但在 wpbc(34 特征、198 样本、2 类但不均衡)这种数据上会略有差别。复现对比实验时统一用这个函数,避免"算法没实现对齐,先输在评估口径上"。

4.3 W/T/L 统计:论文结果里那些加减号怎么读

论文表 3-5 的每一行里,WMRI 和其他算法的差值后面标了 +、=、−,分别表示 WMRI 高于、等于、低于该基线。W/T/L 行是汇总胜平负次数。我自己复现时会写一小段统计代码:

def wtl(wmri_scores, base_scores, tol=1e-6): datasets = wmri_scores.keys() w = sum(1 for d in datasets if wmri_scores[d] - base_scores[d] > tol) t = sum(1 for d in datasets if abs(wmri_scores[d] - base_scores[d]) <= tol) l = len(datasets) - w - t return w, t, l

tol 取 1e-6 是为了避免浮点比较的误判。论文里的"等于"比较少见,只在 CNAE-9 和 mfeat-kar 这类数据集上出现,因为这两个数据集上某些算法收敛到了相同的特征子集,差值在数值精度内为 0。

4.4 高维数据集上的性能曲线:为什么选 3 个特定规模点画图

论文的图 1-3 展示了部分高维数据集在不同特征子集规模下的 fmi 变化,用来证明动态加权在高维时更有效。复现时不需要画 10 条曲线,选特征数最多的 3 个数据集(madelon、COIL20、Isolet)就够。每个数据集上取 K = 5, 10, 15, 20, 25, 30 六个节点,跑完画折线图。你会发现 WMRI 和 MRI 的差距在 K 增大后逐渐拉开,这正是因为 |S| 增大后标准差 α/β 有了足够的样本量去估计,动态权重的优势才体现出来。

5. 避坑指南:条件互信息估计、负值与复杂度五个坑

5.1 连续特征直接算互信息,结果全是零

现象:拿原始 CSV 跑 WMRI,所有特征的基础互信息都接近 0,第一个特征选完基本等于随机。

原因:条件互信息实现里用 np.histogram_bin_edges 分箱,但连续特征如果没有先做标准化,量纲差异大(比如一个特征范围 0-1,另一个 0-10000),同一组 bin 数量下有的特征被压成 1-2 个档位,信息全部丢失。

解决:进 WMRI 前先做 min-max 标准化或 z-score 标准化;如果特征本身是稀疏的,先跑一遍 VarianceThreshold 把零方差特征清掉。我在 3.1 代码里加的那行零方差防御,就是为这个坑兜底。

5.2 条件互信息算出负值

现象:cmi_new 或 cmi_keep 里出现负数,导致 (1−α) 权重被撑大或压小,选出的特征和直觉不符。

原因:直方图估计在样本量不足时是有偏的,个别格子样本太少,联立概率估计出现负偏差;连续特征分箱后还会引入量化误差。

解决:在 3.1 的实现末尾加 max(cmi, 0.0) 截断;同时把 bins 从 10 调小到 8 或 6 看结果是否稳定。如果截断后仍然明显异常,怀疑是特征本身和已选特征完全独立,条件互信息理论值确实接近 0,此时截断不影响排序。

5.3 标准差为零:选第一个特征后的权重失效

现象:|S|=1 时,cmi_new 和 cmi_keep 各只有一个元素,标准差恒为 0,α=β=0,式(3) 退化成 I(C; fk) + 两组条件互信息之和,动态加权失去意义。

原因:这是公式本身的边界情况——单个样本点上的标准差没有统计意义。

解决:我一般在 |S| < 3 时让 α 和 β 退化为等权,也就是直接用 MRI 的公式走前两步,等 S 里积累了 3 个以上特征再启用标准差加权。这是一个工程上的 hack,论文没提这个边界,复现时必须有。

5.4 计算复杂度 O(Kmn):Isolet 上跑到怀疑人生

现象:特征数 617 的 Isolet,K=30,每个候选特征要对每个已选特征算两次条件互信息,单次拟合跑了 20 多分钟。

原因:条件互信息内部要遍历 z 的所有取值做子集切片,每切一次都要重新算 mutual_info_score;外层循环是 K×n,整体复杂度是 O(Kmn) 且常数很大。

解决:三个手段叠加。第一,先算 mi 排序,取前 200 个特征做候选集,其余直接丢弃;第二,conditional_mutual_info 里用 np.unique(zd) 的循环改成按组索引预切好,避免在 Python 里重复建 mask;第三,对 K 从 10 开始调,确认曲线趋势后再拉高到 30。高维数据上这个预处理和循环优化,通常能把时间压到原来的 1/4。

5.5 交叉验证里的数据泄漏

现象:在全部数据上先跑 WMRI 选出 30 个特征,再做 6 折交叉验证,fmi 虚高 5-8 个百分点;换成真实部署数据,效果立刻崩。

原因:特征选择在全集上看到了测试折的分布信息,等价于把测试集的信息泄漏进了训练流程。

解决:把 WMRI 包进 Pipeline,让每一折的交叉验证内部重新 fit。这条是老生常谈,但在复现论文算法时特别容易犯——论文里给的是"先在全集上选特征,再评估子集"的简写流程,工程上必须把特征选择当作模型训练的一部分,而不是数据预处理的一部分。

6. 验证 WMRI:从合成数据到 MRMR 对比的检查清单

6.1 合成数据上的冒烟测试

动手之前,先把文档里表 2 的数据集清单和表 1 的伪代码放在手边,下面所有代码都按这个口径实现。复现论文算法第一步,我建议先不碰真实数据集,用 make_classification 造一批特征,验证 WMRI 的排序是否符合预期:

from sklearn.datasets import make_classification X, y = make_classification( n_samples=500, n_features=30, n_informative=8, n_redundant=5, n_repeated=2, n_classes=3, random_state=0) wmri = WMRI(n_features=15) wmri.fit(X, y) print(wmri.selected_)

informative 特征在 make_classification 里固定排在最前面,也就是索引 0-7。如果 WMRI 的前 15 个选中特征里包含了 0-7 的大部分,说明动态加权没有把核心相关特征丢掉;如果前几个偏偏都是 10-14 的冗余特征(n_redundant 部分),说明条件互信息估计有问题,要回头查分箱参数。

6.2 和 MRMR 的定向对比

MRMR 是我最常拿来和 WMRI 对比的基线:语义接近、实现简单、参数只有一个 K。手写一个只做等权去冗余的 MRMR,和 WMRI 对比同样数据下的 Top-K 差异:

def mrmr_select(X, y, K): mi = mutual_info_classif(X, y, random_state=42) sel = [int(np.argmax(mi))] while len(sel) < K: best = None; best_v = -np.inf for f in range(X.shape[1]): if f in sel: continue red = np.mean([conditional_mutual_info(X[:, f], X[:, s], y) for s in sel]) v = mi[f] - red if v > best_v: best_v, best = v, f sel.append(best) return sel

这个函数里 conditional_mutual_info(x, y, z) 第三个参数传的是 y,算的是"在标签条件下两个特征的冗余",这正好对应 Brown 框架里的 I(fk; fsel | C) 项。和 WMRI 对比时重点看两类特征:和标签强相关但彼此也强冗余的特征对,MRMR 会尽量只留一个,WMRI 则通过保留类别信息项的加权,保留"对已选特征有补充价值"的特征。两类算法的分歧点往往就是动态权重发挥价值的地方。

6.3 上线前的检查清单

在真实数据集上跑之前,我一般会过一遍这张表:

检查项通过标准
连续特征标准化所有特征量纲一致,稀疏特征已过滤
bins 取值8~15,验证相邻取值结果稳定
小样本数据集ALLAML 这类 p>>n 的数据 bins 降到 8 以下
S
条件互信息负值已做 max(0, ·) 截断
K 值曲线跑 3 个 K 值确认 fmi 趋势单调
交叉验证嵌套WMRI 在 Pipeline 内,无泄漏
评估口径用 paper_fmi 而非默认 macro-F1
时间预算每折 WMRI 拟合时间可控
和 MRMR 差异合成数据上先确认排序合理

这份资源里的表 2-5 可以直接当实验设计模板用,伪代码对应上面的 WMRI 类,数据集清单对应 4.1 的表,拿到手后建议先按第 2 章的公式把式(3) 到式(7) 在草稿上演算一遍再跑代码。这张表也是我复现任何信息论特征选择算法的通用流程。最后说一个我的习惯:从那以后我每次拿到新的特征选择论文,都会先造一组 synthetic 数据把算法的核心方程跑通,再上真实数据集,而不是直接拿 UCI 数据一跑了之——因为特征选择算法的问题往往不在数学推导,而在估计器和边界条件的处理上。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询