ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

互信息详解:从信息熵到特征选择的实战指南

2026/9/25 18:45:40 拓冰建站 浏览量
互信息详解:从信息熵到特征选择的实战指南 1. 互信息到底是什么从“信息重叠”说起我第一次接触互信息这个概念是在做特征工程的时候。当时手里有一堆用户行为特征想从中挑出对预测“用户是否流失”最有用的那几组。常规做法是先算皮尔逊相关系数看看特征和标签之间线性相关性如何。但很快我就发现一个问题很多特征和标签之间的关系根本不是线性的相关系数算出来接近零可实际上这些特征对预测结果的影响非常明显。后来一位做NLP的朋友提了一嘴“你试试互信息啊。”我查了一圈资料总算是把这套东西搞明白了。一句话概括就是互信息衡量的是一个随机变量中包含的关于另一个随机变量的信息量。说得更直白一点当一个变量取值确定之后另一个变量的不确定性减少多少减少得越多说明这两个变量的关联越强。这个指标和相关系数最大的区别在于它不假设任何函数关系。线性、非线性、周期性、分段关系只要有信息关联互信息都能捕捉到。它来源于信息论核心思想框架就是熵、条件熵那一套。这篇内容适合谁看如果你是做机器学习特征工程的人或者做生物信息学、医疗数据分析、推荐系统、时间序列分析的都逃不开互信息这个概念。读完你能掌握三件事一是互信息在数学上是怎么定义和计算的二是离散、连续两种场景下怎么用Python实现三是实际项目里怎么避坑尤其是连续变量估计偏差和计算规模爆炸这两个高发问题。2. 从熵到互信息一张图就能懂的数学逻辑2.1 熵理解互信息的第一块基石要讲互信息必须先讲熵Entropy。熵在信息论里代表的是一个随机变量的不确定性。不要被“信息熵”这个名字吓到它本质上是说如果一个事件的结果越难预测它的熵就越大。举例来说一个公平硬币抛一次的结果是正面还是反面各有50%概率。这个系统的熵就是1比特。但如果是一枚做了手脚的硬币正面出现的概率是99%那结果基本可预测熵就小很多。熵的计算公式是$$H(X) -\sum_{i} P(x_i) \log P(x_i)$$这里的对数底通常取2单位是比特。如果取自然对数单位就是纳特nat。做机器学习的时候Scikit-learn和各类信息论库中默认的log都是以自然对数为底的所以算出来的数值会略有差异但排序关系完全一致。2.2 联合熵与条件熵信息是如何被共享的光有熵还不够。如果要衡量两个变量之间的信息共享量就需要联合熵和条件熵。联合熵Joint Entropy衡量的是两个变量一起时的不确定性$$H(X,Y) -\sum_{i}\sum_{j} P(x_i, y_j) \log P(x_i, y_j)$$条件熵Conditional Entropy衡量的是已知一个变量之后另一个变量剩余的不确定性$$H(X|Y) -\sum_{i}\sum_{j} P(x_i, y_j) \log \frac{P(x_i, y_j)}{P(y_j)}$$可以这样理解假如你主持一个趣味竞猜节目嘉宾需要猜某个人常去的餐厅。如果不知道这个人任何信息你面对的是H(餐厅)这么大的不确定性。如果告诉你这个人的职业是健身教练你猜中他常去轻食餐厅的概率就大大提高不确定性从H(餐厅)降到了H(餐厅|职业)。那么职业这个信息带来的不确定性减少量就是互信息。用公式来表达就是$$I(X;Y) H(X) - H(X|Y)$$同时它也等于 H(Y) H(X) - H(X,Y)也就是三个熵之间的代数关系。这里的逻辑可以类比成一个两圈交集问题H(X)是第一个圆H(Y)是第二个圆联合熵H(X,Y)是两个圆并起来的面积互信息就是两个圆交集的那块面积。这四者之间的关系是信息论中最基础也最重要的恒等式之一H(X,Y) H(X) H(Y|X) H(Y) H(X|Y)I(X;Y) H(X) - H(X|Y) H(Y) - H(Y|X)I(X;Y) H(X) H(Y) - H(X,Y)这个图式理解方式比死记公式强得多。我在做特征选择的时候脑子里始终装的不是公式而是这几块面积的关系到哪儿都不乱。3. 互信息的定义与它和相关系数的本质区别3.1 严谨定义用联合分布来度量独立性互信息的严格定义是$$I(X;Y) \sum_{x \in X}\sum_{y \in Y} P(x,y) \log \frac{P(x,y)}{P(x)P(y)}$$如果X和Y是连续型随机变量求和号就换成积分号。这里最关键的地方在于 $\frac{P(x,y)}{P(x)P(y)}$ 这个比值。如果X和Y独立联合分布等于边际分布的乘积分子分母相等比值恒为1取对数后全部为0互信息就是0。联合分布与“独立情况下的期望联合分布”偏离越大互信息就越大。从另一个角度理解互信息其实就是联合分布 P(x,y) 和乘积分布 P(x)P(y) 之间的KL散度$$I(X;Y) D_{KL}(P(x,y) | P(x)P(y))$$所以互信息天然具备“度量分布差异”的能力。它不仅是一个相关性的度量更是一个广义的关联度量。3.2 相关系数 vs 互信息一个只能抓直线一个全都要皮尔逊相关系数是大家最常用的相关性指标但它有个致命前提假设它只衡量线性关系。我做过一个实验模拟一个正弦关系的数据X是从-π到π的均匀分布Y sin(X)。皮尔逊相关系数算出来几乎等于0因为正弦函数在一个周期内正负抵消了。但是Y显然完全由X决定两者之间是严格确定的函数关系。如果用互信息去衡量因为Y是X的确定性函数互信息等于H(Y)是个不小的正数。这个案例能直接说明问题指标线性关系非线性单调关系非线性非单调关系如正弦随机独立皮尔逊相关系数强中等或强接近00互信息强强强0距离相关系数强强强0注意距离相关系数Distance Correlation也能捕捉非线性但距离相关不满足“信息”意义上的解释它给不了你“知道了X以后Y的不确定性减少了多少比特”这种直观信息。而互信息的单位是比特是可以叠加、比较和解释的。4. 实践中如何计算互信息离散与连续两种场景4.1 离散变量的互信息手工推导一个完整算例在离散情况下只需要统计联合概率分布就可以计算。我拿一个非常小的例子手算一遍看完就知道整个计算的步骤。假设有10个样本X取值为A或BY取值为0或1。联合频数表如下XYY0Y1XA41XB14总共10个样本。先算边际分布P(XA) 5/10 0.5P(XB) 5/10 0.5P(Y0) 5/10 0.5P(Y1) 5/10 0.5联合分布就是频数除以总数。然后代入互信息的公式$$I(X;Y) \frac{4}{10}\log\frac{4/10}{0.5 \times 0.5} \frac{1}{10}\log\frac{1/10}{0.5 \times 0.5} \frac{1}{10}\log\frac{1/10}{0.5 \times 0.5} \frac{4}{10}\log\frac{4/10}{0.5 \times 0.5}$$经过计算可得约等于0.503比特。直观来看就是本来你猜X的结果不确定性是1比特。如果知道了Y的取值不确定性能减少一半左右所以互信息约等于0.5比特数值上非常合理。4.2 用Python实现离散互信息计算实际项目中不需要每次都手写直接用现成库就行。我常用的是Scikit-learn里的mutual_info_score注意它接收的是两个离散标签序列。from sklearn.metrics import mutual_info_score # 假设 X 和 y 是两个等长的离散标签序列 X [0, 0, 1, 1, 0, 1, 0, 1, 0, 1] y [0, 0, 0, 1, 1, 1, 0, 1, 0, 0] mi mutual_info_score(X, y) print(f互信息: {mi:.4f})这里有一个非常隐蔽的坑mutual_info_score内部使用了自然对数以e为底所以出来的单位是纳特而不是比特。如果你需要以比特为单位需要对结果除以 log(2)。我在一次项目复盘时发现同一种数据在sklearn里和在函数手写版本里算出的值总差一个常数倍查了半天才发现是这个对数底的问题。如果你想要自己写一个也不难。核心就是估计联合概率表和边际概率表然后按照公式逐项累加。自己写的优势是可以顺便打印出每一个点位的贡献排查数据问题非常方便。4.3 连续变量的互信息分箱法与kNN估计连续变量的互信息在理论上定义很漂亮但实际操作起来就是另一回事了。因为连续变量的概率密度函数是连续的联合密度估计困难得多。最朴素的做法是分箱法。把连续变量切成多个区间然后把区间当作离散值来统计。这个方法简单直接但有两个致命问题分箱数量不好确定。箱太少会丢失细节箱太多会拉高互信息因为每个样本都独占一个箱子看上去相关性爆表。结果对分箱边界极其敏感。同样的数据稍微移动一下切分点互信息可能飙升或者骤降。我做实验的时候测过同一份数据不同分箱方案的互信息有的结果是0.1有的能到0.8差异大到离谱。在实际项目中我更推荐基于k近邻的估计方法。sklearn的mutual_info_regression和mutual_info_classif用的就是这类方法它通过估计每个点附近的样本密度来推算互信息对连续变量的处理更稳定。from sklearn.feature_selection import mutual_info_classif # X 是特征矩阵y 是离散标签 # 返回每个特征与标签之间的互信息估计值 mi_scores mutual_info_classif(X, y, random_state42)这段代码在特征选择中非常实用后面我会展开讲。5. 互信息的三大实战场景特征选择、深度学习、时序分析5.1 特征选择用互信息给特征排序做特征选择时我通常用互信息做一个“初筛”。不管特征是连续还是离散标签是连续还是离散都有对应的函数可以用mutual_info_classif(X, y, discrete_features)— 特征可以是混合的标签是离散的mutual_info_regression(X, y)— 特征可以是混合的标签是连续的它会返回一串浮点数每个值代表对应特征对标签的互信息贡献。然后我就按数值从大到小排序取前N个特征进入后续建模流程。实操中有个细节要特别注意互信息计算没有考虑特征之间的冗余。两个特征单独都和标签有很高的互信息但它们的信息高度重叠加在一起并不会带来双倍收益。这就是为什么互信息初筛之后还要用Lasso、随机森林重要性、甚至递归特征消除再做第二轮精选。互信息初筛最大的优势是速度快、不依赖模型。在大规模特征工程中先把几千个特征压缩到几十个再进入建模环节效率提升非常明显。我在一个流失预测项目里原始特征有一千多个先用互信息过滤掉800多个建模效果不仅没下降泛化能力反而提升了。5.2 深度学习和自监督学习里的互信息神经网络领域这几年有一个方向叫“互信息最大化”。经典的Deep InfoMaxDIM就是这个思路它让编码器把输入图像映射为一个特征表示然后最大化这个特征表示和原始图像之间的互信息这样学到的特征就保留了关于输入的核心信息。这个方法背后有一个逻辑在自监督学习里没有标签你没法做交叉熵损失只能想办法让模型自己学到有意义的表示。互信息恰好提供了一个度量“表示和输入有多像”的指标。虽然现代方法已经在用对比学习那套框架SimCLR、MoCo等但从信息论的角度看对比学习的本质在数学上仍然可以理解为互信息的一个下界估计。如果你研究的不是深度学习理论而是实际工程这一块了解即可不太会直接在业务里手动算互信息。但它能帮你建立起一个全局认知互信息之所以被频繁提起不是因为传统统计学缺少相关性度量而是因为它提供了一个不依赖标签、不依赖线性假设的信息度量视角。5.3 时间序列与生物信息学中的互信息时间序列分析里面互信息可以用来做滞后相关性分析。给定两个序列计算不同时间偏移下的互信息峰值所在的滞后就是两个序列之间最强的关联延迟。这个做法在脑电信号同步分析、金融时间序列联动分析中都能用。生物信息学中互信息常被用来做基因表达调控网络推断。ARACNE算法就利用互信息筛选基因-基因之间的直接调控关系。看起来很学术本质上和做特征选择是一样的逻辑变量之间的信息共享程度超过了某个阈值就认为有关联。我在项目中做过类似的尝试用互信息分析工业设备不同传感器信号之间的同步性。当两个原本高度同步的传感器信号互信息突然下降通常意味着设备某个部位出现了异常。这个思路在医院的生命体征监测中也有类似应用。6. 实操中必须避开的五个坑6.1 归一化互信息当数值范围不同时没法比较互信息有一个古怪的性质它的取值范围是[0, min(H(X), H(Y))]上限取决于变量本身的熵。所以如果一个变量本身熵特别大它和其他变量的互信息天然倾向于更大。这就导致直接用互信息比较不同特征时结果可能失真。解决办法是使用归一化互信息Normalized Mutual Information, NMI。常用公式是$$NMI(X;Y) \frac{I(X;Y)}{\sqrt{H(X) \cdot H(Y)}}$$或者$$NMI(X;Y) \frac{2 \cdot I(X;Y)}{H(X) H(Y)}$$两种都常见取值都落在[0,1]区间。0表示完全独立1表示完全确定。在Sklearn的normalized_mutual_info_score里可以直接调用。当你需要跨特征比较时务必使用归一化版本否则很容易选出“熵大的垃圾特征”。6.2 连续变量分箱最容易被低估的误差来源分箱法虽然实现简单但坑非常多。分箱过细每个箱子里只有一两个样本概率估计的方差爆炸互信息会被系统性高估。分箱过粗信息都被合并掉了互信息被严重低估。我踩过一次最惨的坑在一份客户数据上我用了pd.cut默认的等宽分箱互信息算出来某个特征和标签的关联是0.42当时觉得捡到宝了。后面换了分位数分箱同样的数据同样的特征互信息变成了0.11。这个差距足以彻底影响特征选择结果。正确的做法是优先用mutual_info_classif这类不停留在分箱法的实现内部用的是k近邻估算如果必须自己分箱使用分位数分箱而不是等宽分箱对分布偏斜的特征更公平可以做敏感性分析改变分箱数量比如10箱、20箱、50箱观察互信息是否稳定6.3 稀疏数据与大离散空间概率估计的偏差问题当变量取值空间特别大但样本量有限比如一个特征有几百个类别、几万个样本联合概率表会变得极其稀疏。很多组合在样本中根本没出现过估计出来的互信息会严重偏高。这其实是一个经典统计问题有限样本下最大似然估计是有偏的稀疏数据下尤其严重。解决办法是使用Miller-Madow估计或者Shrinkage估计它们会对概率表做平滑处理。不过工程上更常见的做法是做类别合并把低频类别归入“其他”类减少联合分布的空间维度。Python里有一个专门的库叫infotheory支持多种偏差修正方法import infotheory # 创建信息论工具对象启用偏差纠正 it infotheory.InfoTools() it.set_discrete([True, True]) it.set_data([X_list, y_list]) # 计算偏差修正后的互信息 mi_corrected it.mutual_information([0, 1], biasMiller-Madow)这类库在生物信息学社区里用得更多工业界遇到离散变量多的问题时也可以参考。6.4 计算复杂度爆炸大特征矩阵互信息太慢互信息的计算复杂度在特征对之间是O(n²)级别的。如果特征只有几十个还好一旦特征数量上千全算一遍互信息矩阵就会变得非常慢。我在一个风控项目里试过2000多个特征用sklearn的mutual_info_classif计算所有特征与标签的互信息总共耗时大概不到一分钟这是可以接受的因为它只算特征和标签之间不算特征和特征相互之间。但如果要算特征与特征之间的互信息矩阵做冗余分析2000特征就要算约200万个特征对每条都要重新估计联合分布直接跑崩。工程上的处理策略有两个第一轮先用快速筛选方差过滤、相关系数过滤把特征降到500以内再做互信息分析利用n_jobs-1参数开启并行计算sklearn里的mutual_info_classif支持n_jobs参数这一点很多教程没提。我第一次用的时候不知道跑了很久才跑完。加了这个参数之后速度翻倍都不止。7. 从一个实际问题出发完整互信息分析实操流程最后我以一个实际案例串一遍整个流程。假设目标是从100个候选特征中选出20个对二分类任务最有用的特征然后训练一个逻辑回归模型。第一步读取数据把所有特征和标签准备好。第二步区分离散特征和连续特征。离散特征的标准是类别数少、无大小意义连续特征直接用数值。import pandas as pd from sklearn.feature_selection import mutual_info_classif df pd.read_csv(user_features.csv) y df[label] features df.drop(columns[label]) # 标记哪些特征是离散的 discrete_flags [True if col in discrete_cols else False for col in features.columns] mi_scores mutual_info_classif( features, y, discrete_featuresdiscrete_flags, n_jobs-1, random_state42 ) mi_series pd.Series(mi_scores, indexfeatures.columns).sort_values(ascendingFalse)第三步打印前20个特征。这里有一个重要心得互信息数值分布往往是长尾的前几个特征遥遥领先后面逐步衰减。如果画一张柱状图你会看到明显的手肘elbow位置这个位置对应的就是自然的特征截断点。第四步把筛选出的特征交给建模环节。注意还要再做一次共线性检查因为互信息不能甄别特征间的冗余。我在实际项目中通常是互信息初筛 Lasso二次筛选两层配合下来特征质量非常稳定。如果你在整个过程中发现某个特征的互信息为0或者接近0不要立刻认定它无用。互信息估计是数据驱动的如果联合分布估计不够准真实有关联的特征也可能算出来很低。这时候可以用一个简单的散点图或者分组均值表验证一下看看是否存在函数关系但分布太稀疏的情况。至于代码本身的运行环境我用的是Anaconda默认的Python 3.9环境没有额外装特殊依赖只用到了numpy、pandas、sklearn。这三件套足够覆盖90%以上的互信息分析任务。如果你有更高级的需求比如大规模互信息网络推断可以考虑skfeature或者自行实现并行版本。这套流程我在多个项目中反复使用稳定可靠。互信息这个指标真正厉害的地方在于它的普适性和解释性它不像深度学习模型那样是个黑盒子又不像相关系数那样被线性假设束缚。掌握了它你等于多了一把适用于几乎所有数据关联场景的通用工具。