开始前,会这些就够了
- 知道二进制的 0 和 1
- 能把四个等可能情况分别记为 1
/4 - 先读 只有开和关,怎样算出与、或、非?:先熟悉 0
/1输入与真值表,再读四种异或情况。 - 先读 同样预测全对,为什么重要特征排名相反?:了解特征的重要性必须说明衡量对象;本篇衡量的是分布中的信息。
有两列 0/1 特征 X₁、X₂,各自像公平硬币,而且相互独立。标签规则是:两列不同就记 1,相同就记 0。四种输入各占四分之一。
如果一次只给一列打分,两列对标签的互信息都等于 0。但同时给出两列,标签完全确定。这不是计算矛盾,而是“单独知道一列”和“同时知道两列”回答了不同的问题。
把例子带到自己的电脑上
下载完整练习包
需要 Python 3.10 或更新版本,无第三方依赖;实测 Windows / Python 3.14.0,Python 3.10 仅做语法兼容检查。使用标准库,不需要安装第三方包。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。
下载 ZIP · 5 个文件包里有什么
- demo.py:完整可运行源码
- README.md:运行步骤与练习
- observed.stdout.txt:本次实际输出
- verification.json:源码和运行验证记录
- SHA256SUMS.txt:文件摘要
在这个文件夹打开 PowerShell,逐行运行
python --version
python -B demo.py解压后,在含 demo.py 的目录打开终端。命令分别回车执行。代码枚举八个“输入对与最终标签”的可能结果,按概率聚合熵,并用独立的二元熵公式核对 51 组翻转概率;无随机样本、没有拟合模型,也没有运行 scikit-learn 估计器。
找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。
每列单独得零分,两列合起来能确定标签吗?
正在准备实验控件…
X₁、X₂ 是相互独立的公平二进制变量;先做异或,再独立以 ε 概率翻转标签。直接枚举完整概率表,互信息用 log₂,单位 bit。没有抽样、训练模型或调用 scikit-learn;其 API 输出单位为 nat。
先遮住一列,再把两列一起展开
无噪声的四种情况是 (0,0)→0、(0,1)→1、(1,0)→1、(1,1)→0,每种概率都是 1/4。这条规则就是异或 XOR。
只告诉你 X₁=0 时,标签仍可能为 0 或 1,而且各占一半。X₁=1 时也一样。所以知道 X₁ 后,标签分布完全没变。对称地,只知道 X₂ 也没有帮助。
同时知道 X₁、X₂ 就不同了:四个输入对各对应唯一标签。此时不仅可以预测,而且在这张完整概率表里没有剩余不确定性。这里还没训练任何模型。
把“少了多少不确定性”写成一个数
用二为底的熵 H(Y)=−Σp(y)log₂p(y) 来记不确定性,约定 0log₂0=0。公平的二元标签,熵为 1 bit;已确定的标签,熵为 0。
互信息 I(X;Y)=H(Y)−H(Y|X),表示知道 X 之后平均减少多少标签不确定性。只看第一列时 H(Y|X₁)=1,所以 I(X₁;Y)=0;两列一起看时 H(Y|X₁,X₂)=0,所以 I((X₁,X₂);Y)=1。
不能直接把 I(X₁;Y) 与 I(X₂;Y) 相加来替代联合信息。分步计算应是:先获得第一列的信息,再加上“已经知道第一列后,第二列额外提供的信息”。在本例里,后者为 1 bit。
加一点独立翻转,仍能区分单列与联合
现在先算异或,再独立以 ε 的概率把标签翻转。限制 0≤ε≤0.5。两个输入变量仍相互独立、各自公平,翻转也与输入独立。标签的总体分布依旧一半为 0、一半为 1。
单列仍与标签独立,互信息保持 0。知道两列后,剩余不确定性只来自这次翻转,等于二元熵 h₂(ε)=−εlog₂ε−(1−ε)log₂(1−ε)。所以联合互信息是 1−h₂(ε)。
ε=0 时得到 1 bit;ε=0.25 时约为 0.188722 bit;ε=0.5 时为 0。互动表里显示的是精确给定分布的概率,不是反复抽样得到的频数。
运行完整小例子
代码枚举八个“输入对与最终标签”的可能结果,按概率聚合熵,并用独立的二元熵公式核对 51 组翻转概率;无随机样本、没有拟合模型,也没有运行 scikit-learn 估计器。
展开收起代码 · 小实验
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
"""Enumerate a finite probability distribution; no sampling or estimator."""
from collections import defaultdict
from math import isclose, isfinite, log2
def distribution(flip):
if (type(flip) not in (int, float) or not isfinite(flip)
or not 0 <= flip <= 0.5):
raise ValueError("flip must be a number between 0 and 0.5")
return [(x1, x2, y, 0.25 * (1 - flip if y == (x1 ^ x2) else flip))
for x1 in (0, 1) for x2 in (0, 1) for y in (0, 1)]
def entropy(masses):
return -sum(p * log2(p) for p in masses if p > 0)
def mutual_information(rows, feature):
px, py, pxy = defaultdict(float), defaultdict(float), defaultdict(float)
for x1, x2, y, p in rows:
x = feature(x1, x2)
px[x] += p
py[y] += p
pxy[x, y] += p
result = entropy(px.values()) + entropy(py.values()) - entropy(pxy.values())
return 0.0 if abs(result) < 1e-12 else result
def binary_entropy(p):
return 0.0 if p in (0, 1) else -p * log2(p) - (1 - p) * log2(1 - p)
def main():
print("X1 and X2: independent fair bits; information unit: bit")
for flip in (0.0, 0.25, 0.5):
rows = distribution(flip)
one = mutual_information(rows, lambda a, b: a)
two = mutual_information(rows, lambda a, b: b)
joint = mutual_information(rows, lambda a, b: (a, b))
print(f"flip={flip:.2f} I(X1;Y)={one:.6f} I(X2;Y)={two:.6f} I(pair;Y)={joint:.6f}")
# Independent formula for a fair XOR bit through a binary flip channel.
for k in range(51):
p = k / 100
rows = distribution(p)
assert isclose(sum(row[3] for row in rows), 1.0, abs_tol=1e-12)
assert mutual_information(rows, lambda a, b: a) == 0.0
assert mutual_information(rows, lambda a, b: b) == 0.0
assert isclose(mutual_information(rows, lambda a, b: (a, b)),
1 - binary_entropy(p), abs_tol=1e-12)
print("checks: 51 probability tables agree with 1 - binary_entropy(flip)")
if __name__ == "__main__":
main()
怎样运行:在独立练习文件夹中,依次执行下载区的命令。2026-10-26 使用 Python 3.10 或更新版本,无第三方依赖;实测 Windows / Python 3.14.0,Python 3.10 仅做语法兼容检查 复跑这份源码并核对完整输出。代码枚举八个“输入对与最终标签”的可能结果,按概率聚合熵,并用独立的二元熵公式核对 51 组翻转概率;无随机样本、没有拟合模型,也没有运行 scikit-learn 估计器。
运行后对照结果
X1 and X2: independent fair bits; information unit: bit
flip=0.00 I(X1;Y)=0.000000 I(X2;Y)=0.000000 I(pair;Y)=1.000000
flip=0.25 I(X1;Y)=0.000000 I(X2;Y)=0.000000 I(pair;Y)=0.188722
flip=0.50 I(X1;Y)=0.000000 I(X2;Y)=0.000000 I(pair;Y)=0.000000
checks: 51 probability tables agree with 1 - binary_entropy(flip)用单列分数删特征时,究竟删掉了什么?
scikit-learn 的单变量特征选择按每列与目标的分数筛选;mutual_info_classif 返回的也是每个特征各自的估计值。互信息能表达单个特征与目标之间的非线性依赖,但这不意味着逐列调用就自动检查了所有特征组合。
在本例里,如果用“单列互信息为零就全部删除”的规则,会把组成异或答案的两列一起删除。增加一个明确的组合特征,例如这两列的异或,能够表达这里的交互;在真实任务里是否值得加入,仍需要按训练与验证边界评估。
注意单位与估计对象:本页用 log₂,单位是 bit;文档中 mutual_info_classif 的输出单位是 nat,转成 bit 要除以 ln(2)。二元离散特征使用该 API 时应明确离散设置,不能因为数据是整数就假定 dense 输入的 auto 一定按离散变量处理。
精确分布中的零与有限样本估计出的零也不同。后者可能受样本、估计方式和截断影响。本文没有证明你的实际数据是 XOR,也没有证明所有低分特征都该保留;它只是给出一个不能单凭单列分数排除交互作用的反例。
记住这三点
- 单列互信息为零,描述的是这一列单独与目标的独立性,不是它与其他列组合后一定无用。
- 联合信息不是单列信息简单相加;第二列的价值会随已知条件而改变。
- 概率表的精确值、样本估计值、不同对数单位,需要分别说明。
别把例子用过头
- 认为互信息能处理非线性,就等于逐列筛选能自动发现任意高阶交互。
- 忘记独立、公平与独立翻转这些前提,仍把本例的零分结论套到任意数据。
- 把 bit 与 nat 直接比较,或把有限样本的估计零当成严格独立的证明。
自己试一小步
换一条标签规则,看看零分来自规则还是来自变量名称。
- 把 Y 改成 X₁,先手算两列各自和联合的互信息。
- 恢复无噪声 XOR,将第一列固定为已知值,再比较第二列的作用。
- 把一枚公平硬币改成偏置硬币,重新写出联合概率表,不要只改结论。
做到这些,就算完成
- 每张表的概率非负且合计为 1。
- 分别说明完整分布、单列条件分布和两列条件分布。
- 写明使用的对数底数,并解释自己的验证是否涉及抽样或模型训练。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- Information Theory for Decision Trees — CSC311 (新标签页)↗
熵、条件熵、互信息的定义与独立时互信息为零;本文的 XOR 概率表和数值为据此自行推导。
- Univariate feature selection — scikit-learn 1.9 (新标签页)↗
单变量特征筛选按单个特征统计分数工作;互信息方法可捕捉非线性依赖。
- mutual_info_classif — scikit-learn 1.9 (新标签页)↗
每列返回一个互信息估计;输出单位 nat;
discrete_features对 dense 输入的 auto 规则。