联合熵、条件熵与互信息
一句话概述
联合熵、条件熵与互信息是信息论中描述"变量之间关系"的三件套——联合熵衡量两个变量一起的总体不确定性,条件熵衡量已知一个变量后另一个变量还剩多少不确定性,互信息则衡量两个变量共享了多少信息、彼此之间有多"相关"。这三个概念构成了信息论中理解变量关系的核心框架,也是机器学习中特征选择、聚类评估和表示学习的信息论基础。
💡 核心要点:①联合熵 H(X,Y) 是两个变量一起的不确定性总和,它总是介于 max(H(X),H(Y)) 和 H(X)+H(Y) 之间;②条件熵 H(Y|X) 是已知 X 后 Y 剩余的不确定性,条件熵越小说明 X 对 Y 的解释力越强;③互信息 I(X;Y) = H(X) + H(Y) - H(X,Y) 衡量两个变量共享的信息量,互信息越大关系越紧密;④互信息在AI中是特征选择、聚类评估和信息瓶颈理论的核心工具。
教学与演示
一、联合熵——两个变量一起的不确定性
是什么:联合熵(Joint Entropy)是信息熵在两维随机变量上的自然推广,定义为 H(X,Y) = -Σ_x Σ_y p(x,y) log₂ p(x,y)。它衡量的是"同时观测 X 和 Y 的取值时,我们面临的总不确定性"。联合熵就像是把两个随机变量打包成一个"超级变量"来看待,然后计算这个超级变量的熵。
大白话 想象你同时抛一枚硬币和掷一个骰子。你完全不知道硬币是正是反、骰子是几点——这就是"联合不确定性"。联合熵把这个"双重不确定性"用一个数字表达出来。如果硬币和骰子独立,联合熵 = 硬币熵 + 骰子熵(1 + 2.585 = 3.585 bit);但如果它们有关系(比如你看到硬币正面就猜骰子是大点数),联合熵就会小于3.585 bit。
为什么:联合熵的数学定义直接来自熵的通用定义,只是把单变量概率 p(x) 替换为联合概率 p(x,y)。联合熵总是满足 H(X,Y) ≤ H(X) + H(Y),等号成立当且仅当 X 和 Y 相互独立。这个不等式反映了信息论中一个深刻事实:变量之间的相关性会"减少"总不确定性——因为知道一个变量就能部分推断另一个。联合熵的下界是 max(H(X), H(Y)),因为联合不确定至少不会小于任何一个单独变量的不确定性。
怎么做:
import numpy as np
# ==================== 联合熵的计算 ====================
def joint_entropy(prob_matrix):
"""
计算联合熵 H(X, Y)
参数:
prob_matrix: 联合概率矩阵,形状 (n_x, n_y)
prob_matrix[i][j] = P(X=x_i, Y=y_j)
返回:
联合熵值(bit)
"""
# 扁平化将所有联合概率展成一维
flat_probs = prob_matrix.flatten()
# 过滤零概率
flat_probs = flat_probs[flat_probs > 0]
return -np.sum(flat_probs * np.log2(flat_probs))
# ==================== 场景1:独立变量 vs 相关变量的联合熵 ====================
# --- 独立情况:硬币(X)和骰子(Y)独立 ---
# X: 硬币,p(正)=0.5, p(反)=0.5
# Y: 骰子,p(1)=...=p(6)=1/6
# 联合概率 = 边缘概率乘积(独立)
p_x = np.array([0.5, 0.5])
p_y = np.array([1/6]*6)
# 构建联合概率矩阵:p_xy[i][j] = p_x[i] * p_y[j]
p_xy_independent = np.outer(p_x, p_y) # 外积 = 独立联合分布
print("=" * 55)
print("联合熵:独立 vs 相关的对比")
print("=" * 55)
print(f"\n独立情况: 硬币(X) ∐ 骰子(Y)")
print(f" H(X) = {(-np.sum(p_x*np.log2(p_x))):.4f} bit")
print(f" H(Y) = {(-np.sum(p_y*np.log2(p_y))):.4f} bit")
print(f" H(X)+H(Y) = {(-np.sum(p_x*np.log2(p_x)) - np.sum(p_y*np.log2(p_y))):.4f} bit")
h_joint_ind = joint_entropy(p_xy_independent)
print(f" H(X,Y) = {h_joint_ind:.4f} bit")
print(f" 验证: H(X,Y) = H(X) + H(Y) = {h_joint_ind:.4f} ✓ (独立时成立)")
# --- 相关情况:硬币结果影响骰子结果 ---
# 假设:如果硬币正面,骰子倾向于大点数(4,5,6)
# 如果硬币反面,骰子倾向于小点数(1,2,3)
p_xy_correlated = np.array([
# 正面: 骰子1-6的概率
[0.05, 0.05, 0.05, 0.15, 0.15, 0.05], # 正面"偏好"4,5
# 反面: 骰子1-6的概率
[0.15, 0.15, 0.05, 0.05, 0.05, 0.05] # 反面"偏好"1,2
])
# 验证联合概率和为1
p_xy_correlated = p_xy_correlated / np.sum(p_xy_correlated)
print(f"\n相关情况: 硬币结果影响骰子点数")
# 从联合分布计算边缘分布
p_x_corr = np.sum(p_xy_correlated, axis=1) # 按行求和
p_y_corr = np.sum(p_xy_correlated, axis=0) # 按列求和
h_x_corr = -np.sum(p_x_corr[p_x_corr > 0] * np.log2(p_x_corr[p_x_corr > 0]))
h_y_corr = -np.sum(p_y_corr[p_y_corr > 0] * np.log2(p_y_corr[p_y_corr > 0]))
h_joint_corr = joint_entropy(p_xy_correlated)
print(f" H(X) = {h_x_corr:.4f} bit")
print(f" H(Y) = {h_y_corr:.4f} bit")
print(f" H(X)+H(Y) = {h_x_corr + h_y_corr:.4f} bit")
print(f" H(X,Y) = {h_joint_corr:.4f} bit")
print(f" 验证: H(X,Y) = {h_joint_corr:.4f} < H(X)+H(Y) = {h_x_corr + h_y_corr:.4f} ✓ (相关时严格小于)")
# ==================== 场景2:联合熵的上下界验证 ====================
print(f"\n联合熵的上下界性质验证:")
print(f" 下界 max(H(X),H(Y)) = {max(h_x_corr, h_y_corr):.4f}")
print(f" 实际 H(X,Y) = {h_joint_corr:.4f}")
print(f" 上界 H(X)+H(Y) = {h_x_corr + h_y_corr:.4f}")
assert max(h_x_corr, h_y_corr) <= h_joint_corr <= h_x_corr + h_y_corr + 1e-10
print(f" ✓ 满足: max(H(X),H(Y)) ≤ H(X,Y) ≤ H(X)+H(Y)")
# ==================== 场景3:从数据估计联合熵 ====================
np.random.seed(42)
n_samples = 500
# 模拟两个相关变量:年龄和收入
# 年龄: 青年(20-30), 中年(30-50), 老年(50+)
# 收入: 低, 中, 高
# 年龄和收入正相关
age = np.random.choice(['青年', '中年', '老年'], size=n_samples, p=[0.3, 0.45, 0.25])
income = np.zeros(n_samples, dtype=object)
for i in range(n_samples):
if age[i] == '青年':
income[i] = np.random.choice(['低', '中', '高'], p=[0.6, 0.3, 0.1])
elif age[i] == '中年':
income[i] = np.random.choice(['低', '中', '高'], p=[0.2, 0.5, 0.3])
else: # 老年
income[i] = np.random.choice(['低', '中', '高'], p=[0.4, 0.4, 0.2])
# 构建联合频数矩阵
age_labels = ['青年', '中年', '老年']
inc_labels = ['低', '中', '高']
joint_counts = np.zeros((3, 3))
for i in range(n_samples):
ai = age_labels.index(age[i])
ii = inc_labels.index(income[i])
joint_counts[ai, ii] += 1
joint_probs = joint_counts / n_samples
print(f"\n{'='*55}")
print("实际数据:年龄-收入的联合熵分析")
print("=" * 55)
print(f"\n年龄-收入联合概率矩阵:")
print(f"{'':>8}", end="")
for inc in inc_labels:
print(f"{inc:>8}", end="")
print(f"{'':>12}", end="")
print(f"H(年龄|收入)")
print("-" * 50)
for i, age_lbl in enumerate(age_labels):
row_sum = np.sum(joint_probs[i])
print(f"{age_lbl:>8}", end="")
for j in range(3):
print(f"{joint_probs[i][j]:>8.4f}", end="")
# 条件分布: P(年龄|收入)
cond_str = ""
for j in range(3):
col_sum = np.sum(joint_probs[:, j])
if col_sum > 0:
cond_str += f"{joint_probs[i][j]/col_sum:.2f} "
print(f" | {cond_str.strip()}")
h_joint_age_inc = joint_entropy(joint_probs)
h_age = -np.sum(np.sum(joint_probs, axis=1)[np.sum(joint_probs, axis=1) > 0] *
np.log2(np.sum(joint_probs, axis=1)[np.sum(joint_probs, axis=1) > 0]))
h_inc = -np.sum(np.sum(joint_probs, axis=0)[np.sum(joint_probs, axis=0) > 0] *
np.log2(np.sum(joint_probs, axis=0)[np.sum(joint_probs, axis=0) > 0]))
print(f"\n熵分析结果:")
print(f" H(年龄) = {h_age:.4f} bit")
print(f" H(收入) = {h_inc:.4f} bit")
print(f" H(年龄,收入) = {h_joint_age_inc:.4f} bit")
print(f" H(年龄)+H(收入) = {h_age + h_inc:.4f} bit")
print(f" 联合熵比独立之和少: {(h_age + h_inc - h_joint_age_inc):.4f} bit")
print(f" 这说明年龄和收入之间存在关联!")
什么用:联合熵在AI中有多个直接应用。在聚类评估中,联合熵 H(真实标签, 聚类标签) 与互信息共同用于计算 NMI(归一化互信息)指标;在多标签分类中,联合熵衡量了标签向量整体的不确定性,帮助设计标签纠错策略;在生成模型中,联合熵 H(真实数据, 生成数据) 可以评估生成质量。
哪些坑:联合熵的维度随变量数量指数增长——如果有10个二元变量,联合概率表有 2^10 = 1024 个条目,需要大量样本才能可靠估计;在高维情况下,大多数联合概率条目为零或接近零,导致熵估计严重偏差;联合熵不区分"哪个变量提供多少信息",需要结合条件熵和互信息来解耦。
二、条件熵——已知一个变量后另一个的不确定性
是什么:条件熵(Conditional Entropy)H(Y|X) 衡量的是在已知随机变量 X 的取值后,Y 还剩多少不确定性。它定义为给定 X 时 Y 的条件分布的熵的加权平均:H(Y|X) = Σ_x p(x) · H(Y|X=x)。条件熵越小,说明 X 提供了关于 Y 的越多信息——极端情况下 H(Y|X) = 0 意味着 X 完全决定了 Y 的取值。
大白话 如果你想知道一个人的收入(Y),但没有任何线索,不确定性是 H(Y)。如果你知道了他的学历(X),对收入的猜测就"有谱了"——剩下的不确定性就是条件熵 H(Y|X)。如果你知道了他的精确职位(X'),收入几乎能确定——条件熵 H(Y|X') 接近于零。条件熵就是"知道 X 之后,Y 还剩多少谜团"的度量。
为什么:条件熵的链式法则 H(X,Y) = H(X) + H(Y|X) 是信息论中最优美的恒等式之一。它的直观解释是:要确定 (X,Y) 的取值,你可以先确定 X(需要 H(X) bit),然后在知道 X 的基础上再确定 Y(需要 H(Y|X) bit)。这个加法关系对于任意多个变量都成立,链式法则 H(X₁,...,Xₙ) = Σᵢ H(Xᵢ|X₁,...,Xᵢ₋₁) 是信息论中最重要的递推关系。
怎么做:
import numpy as np
# ==================== 条件熵的完整计算 ====================
def conditional_entropy_from_joint(prob_matrix):
"""
从联合概率矩阵计算条件熵 H(Y|X)
参数:
prob_matrix: 联合概率矩阵,形状 (n_x, n_y)
prob_matrix[i][j] = P(X=x_i, Y=y_j)
返回:
H(Y|X) 条件熵
"""
prob_matrix = np.array(prob_matrix, dtype=float)
# 边缘分布 P(X)
p_x = np.sum(prob_matrix, axis=1) # 按行求和
# 过滤掉概率为0的X值
valid = p_x > 0
p_x = p_x[valid]
prob_matrix = prob_matrix[valid]
h_cond = 0.0
for i in range(len(p_x)):
# 条件分布: P(Y|X=x_i)
p_y_given_x = prob_matrix[i] / p_x[i]
# 过滤零概率项
p_y_given_x = p_y_given_x[p_y_given_x > 0]
# 条件熵: H(Y|X=x_i)
h_y_given_x = -np.sum(p_y_given_x * np.log2(p_y_given_x))
# 加权: p(x_i) * H(Y|X=x_i)
h_cond += p_x[i] * h_y_given_x
return h_cond
def conditional_entropy_from_data(labels, condition):
"""
从数据直接计算条件熵 H(labels|condition)
参数:
labels: 目标变量(如收入)
condition: 条件变量(如年龄)
返回:
H(labels|condition)
"""
unique_cond = np.unique(condition)
n_total = len(labels)
h_cond = 0.0
for val in unique_cond:
mask = condition == val
n_group = np.sum(mask)
weight = n_group / n_total # P(X=x)
# 该条件下的标签分布
group_labels = labels[mask]
_, counts = np.unique(group_labels, return_counts=True)
probs = counts / n_group
probs = probs[probs > 0]
h_group = -np.sum(probs * np.log2(probs)) # H(Y|X=x)
h_cond += weight * h_group
return h_cond
# ==================== 实战:分析学历对收入的条件熵 ====================
np.random.seed(42)
# 模拟数据:学历和收入
n_people = 1000
education = np.random.choice(
['高中', '本科', '硕士', '博士'],
size=n_people,
p=[0.30, 0.45, 0.18, 0.07]
)
# 收入分为:低、中、高、超高
income_labels = ['低', '中', '高', '超高']
income = np.zeros(n_people, dtype=object)
# 不同学历的收入分布不同
income_dist = {
'高中': [0.55, 0.35, 0.08, 0.02],
'本科': [0.15, 0.45, 0.30, 0.10],
'硕士': [0.05, 0.25, 0.45, 0.25],
'博士': [0.02, 0.13, 0.35, 0.50]
}
for i in range(n_people):
income[i] = np.random.choice(income_labels, p=income_dist[education[i]])
print("=" * 60)
print("条件熵分析:学历对收入的解释力")
print("=" * 60)
# 1. 计算无条件熵 H(收入)
_, counts = np.unique(income, return_counts=True)
p_income = counts / n_people
p_income = p_income[p_income > 0]
h_income = -np.sum(p_income * np.log2(p_income))
print(f"\n收入的边缘熵 H(收入) = {h_income:.4f} bit")
print(f"(完全不知道任何信息时,对收入的不确定性)")
# 2. 计算条件熵 H(收入|学历)
h_income_given_edu = conditional_entropy_from_data(income, education)
print(f"\n条件熵 H(收入|学历) = {h_income_given_edu:.4f} bit")
print(f"(知道学历后,对收入剩余的不确定性)")
# 3. 信息量 = 不确定性的减少
info = h_income - h_income_given_edu
print(f"\n学历提供的关于收入的信息量 = {info:.4f} bit")
print(f"信息量占比: {info/h_income*100:.1f}%")
# 4. 各学历层次的条件熵分解
print(f"\n各学历层次的条件熵 H(收入|学历=x):")
print(f"{'学历':<8} | {'样本数':>6} | {'H(收入|学历)':>14} | 解读")
print("-" * 55)
for edu in ['高中', '本科', '硕士', '博士']:
mask = education == edu
n = np.sum(mask)
sub_income = income[mask]
_, cnts = np.unique(sub_income, return_counts=True)
probs = cnts / n
probs = probs[probs > 0]
h = -np.sum(probs * np.log2(probs))
if h < 0.8:
note = "收入分化明显,高确定性"
elif h < 1.5:
note = "收入有一定分化"
else:
note = "收入分布较均匀,高不确定性"
print(f"{edu:<8} | {n:>6} | {h:>14.4f} | {note}")
# 5. 对比:如果知道"精确职位"(条件熵 ≈ 0)
print(f"\n极端情况:如果知道精确职位(几乎确定收入)")
print(f" H(收入|精确职位) ≈ 0 bit(几乎完全确定)")
print(f" H(收入|学历) = {h_income_given_edu:.4f} bit(仍有一定不确定性)")
print(f" 结论:学历解释了部分但非全部收入差异")
# 6. 验证链式法则
# 构建联合概率矩阵
edu_labels = ['高中', '本科', '硕士', '博士']
joint_ct = np.zeros((4, 4))
for i in range(n_people):
ei = edu_labels.index(education[i])
ii = income_labels.index(income[i])
joint_ct[ei, ii] += 1
joint_p = joint_ct / n_people
h_edu = -np.sum(np.sum(joint_p, axis=1)[np.sum(joint_p, axis=1) > 0] *
np.log2(np.sum(joint_p, axis=1)[np.sum(joint_p, axis=1) > 0]))
h_joint = -np.sum(joint_p[joint_p > 0] * np.log2(joint_p[joint_p > 0]))
h_cond_from_joint = conditional_entropy_from_joint(joint_p)
print(f"\n验证链式法则: H(学历,收入) = H(学历) + H(收入|学历)")
print(f" H(学历) = {h_edu:.4f} bit")
print(f" H(收入|学历) = {h_cond_from_joint:.4f} bit")
print(f" H(学历)+H(收入|学历) = {h_edu + h_cond_from_joint:.4f} bit")
print(f" H(学历,收入) = {h_joint:.4f} bit")
print(f" 是否相等: {np.isclose(h_edu + h_cond_from_joint, h_joint, atol=1e-4)}")
什么用:条件熵是特征选择的利器——在分类问题中,H(Y|X) 越小的特征越有区分度。在决策树中,条件熵直接用于计算信息增益。在自然语言处理中,语言模型的核心就是条件熵 H(wₙ|w₁,...,wₙ₋₁),衡量了给定上文后下一个词的不确定性——困惑度(perplexity) = 2^(条件熵)。在推荐系统中,H(评分|用户画像) 衡量了用户特征对偏好的解释力。
哪些坑:条件熵对条件变量的取值细化很敏感——如果 X 取值太多(如"身份证号"),每个条件下样本很少,条件熵会被人为拉低;条件熵非对称——H(Y|X) ≠ H(X|Y),前者是"已知X预测Y",后者是"已知Y推断X",两者含义完全不同;在连续变量情况下,条件微分熵可能为负,需要谨慎解释。
三、互信息——两个变量共享的信息
是什么:互信息(Mutual Information,MI)是衡量两个随机变量之间共享信息量的核心度量。它定义为 I(X;Y) = H(X) + H(Y) - H(X,Y),即两个变量各自熵之和减去它们的联合熵。直观上,互信息就是"知道 X 之后,对 Y 的不确定性减少了多少"——也就是 I(X;Y) = H(Y) - H(Y|X) = H(X) - H(X|Y)。
大白话 互信息回答了一个问题:X 和 Y 到底有多"相关"?但不是简单的线性相关(皮尔逊相关系数),而是任何形式的统计依赖——包括非线性的、非单调的关联。比如"身高"和"体重"的互信息比较大,"身高"和"银行卡密码"的互信息接近零。互信息让你不必假设关系的形式,只需要问"知道一个能让我对另一个猜得多准?"
为什么:互信息具有对称性 I(X;Y) = I(Y;X),这反映了"X 透露关于 Y 的信息"和"Y 透露关于 X 的信息"是等量的。互信息总是非负的,且 I(X;Y) = 0 当且仅当 X 和 Y 独立。互信息的上界是 min(H(X), H(Y))——共享的信息不可能超过各自拥有的信息。互信息与 KL 散度有深刻联系:I(X;Y) = KL(p(x,y) || p(x)p(y)),即联合分布与独立乘积分布之间的 KL 散度。
怎么做:
import numpy as np
# ==================== 互信息的三种计算方式 ====================
def mutual_info_from_joint(prob_matrix):
"""
从联合概率矩阵计算互信息
方法1: I(X;Y) = H(X) + H(Y) - H(X,Y)
"""
prob_matrix = np.array(prob_matrix, dtype=float)
# 边缘分布
p_x = np.sum(prob_matrix, axis=1)
p_y = np.sum(prob_matrix, axis=0)
# 边缘熵
valid_x = p_x[p_x > 0]
h_x = -np.sum(valid_x * np.log2(valid_x))
valid_y = p_y[p_y > 0]
h_y = -np.sum(valid_y * np.log2(valid_y))
# 联合熵
flat = prob_matrix.flatten()
flat = flat[flat > 0]
h_xy = -np.sum(flat * np.log2(flat))
# 互信息
mi = h_x + h_y - h_xy
return max(mi, 0.0) # 防止浮点误差导致负数
def mutual_info_from_kl(prob_matrix):
"""
方法2: I(X;Y) = KL(p(x,y) || p(x)p(y))
"""
prob_matrix = np.array(prob_matrix, dtype=float)
p_x = np.sum(prob_matrix, axis=1, keepdims=True)
p_y = np.sum(prob_matrix, axis=0, keepdims=True)
# 独立乘积分布
p_independent = p_x @ p_y # 矩阵乘法,形状 (n_x, n_y)
# KL散度计算
mi = 0.0
for i in range(prob_matrix.shape[0]):
for j in range(prob_matrix.shape[1]):
if prob_matrix[i, j] > 0 and p_independent[i, j] > 0:
mi += prob_matrix[i, j] * np.log2(prob_matrix[i, j] / p_independent[i, j])
return max(mi, 0.0)
def mutual_info_empirical(x_data, y_data):
"""
方法3: 从数据直接估计互信息
使用频率估计联合概率和边缘概率
"""
n = len(x_data)
# 获取所有唯一值
x_vals = np.unique(x_data)
y_vals = np.unique(y_data)
# 构建联合计数矩阵
n_x, n_y = len(x_vals), len(y_vals)
joint_counts = np.zeros((n_x, n_y))
x_to_idx = {v: i for i, v in enumerate(x_vals)}
y_to_idx = {v: i for i, v in enumerate(y_vals)}
for i in range(n):
xi = x_to_idx[x_data[i]]
yi = y_to_idx[y_data[i]]
joint_counts[xi, yi] += 1
joint_probs = joint_counts / n
return mutual_info_from_joint(joint_probs)
# ==================== 场景1:不同程度的关联——互信息对比 ====================
np.random.seed(42)
# 场景A: 完全独立(互信息=0)
x_a = np.random.choice(['猫', '狗'], size=1000, p=[0.5, 0.5])
y_a = np.random.choice(['红', '蓝'], size=1000, p=[0.5, 0.5])
mi_a = mutual_info_empirical(x_a, y_a)
# 场景B: 弱关联
x_b = np.random.choice(['猫', '狗'], size=1000, p=[0.5, 0.5])
y_b = np.array(['红' if (np.random.random() < 0.65 if xb == '猫' else 0.35) else '蓝'
for xb in x_b])
mi_b = mutual_info_empirical(x_b, y_b)
# 场景C: 强关联
x_c = np.random.choice(['猫', '狗'], size=1000, p=[0.5, 0.5])
y_c = np.array(['红' if (np.random.random() < 0.95 if xc == '猫' else 0.05) else '蓝'
for xc in x_c])
mi_c = mutual_info_empirical(x_c, y_c)
# 场景D: 确定性关联(互信息=H(X)=H(Y)=1 bit)
x_d = np.random.choice(['猫', '狗'], size=1000, p=[0.5, 0.5])
y_d = np.where(x_d == '猫', '红', '蓝') # 完全确定
mi_d = mutual_info_empirical(x_d, y_d)
print("=" * 55)
print("互信息:不同关联程度的对比")
print("=" * 55)
print(f"\n{'场景':<20} | {'关联程度':<12} | {'互信息':>10} | {'占最大可能':>12}")
print("-" * 55)
scenarios = [
("完全独立", "独立", mi_a),
("弱关联", "弱", mi_b),
("强关联", "强", mi_c),
("确定性关联", "确定", mi_d),
]
for name, level, mi in scenarios:
max_mi = 1.0 # 二元变量的最大互信息 = log2(2) = 1
pct = mi / max_mi * 100 if max_mi > 0 else 0
print(f"{name:<20} | {level:<12} | {mi:>10.4f} | {pct:>10.1f}%")
# ==================== 场景2:互信息 vs 相关系数 ====================
print(f"\n{'='*55}")
print("互信息 vs 皮尔逊相关系数:非线性关系的优势")
print("=" * 55)
# 构造一个非线性关系:X~U(-1,1), Y = X^2
x_nonlin = np.random.uniform(-1, 1, 1000)
y_nonlin = x_nonlin ** 2
# 皮尔逊相关系数(只衡量线性关系)
corr = np.corrcoef(x_nonlin, y_nonlin)[0, 1]
# 互信息(需要离散化)
# 分箱:将连续值离散化
x_binned = np.digitize(x_nonlin, bins=np.linspace(-1, 1, 11))
y_binned = np.digitize(y_nonlin, bins=np.linspace(0, 1, 11))
mi_nonlin = mutual_info_empirical(x_binned, y_binned)
print(f" X ~ U(-1,1), Y = X²(非线性关系)")
print(f" 皮尔逊相关系数 = {corr:.4f}(接近0,检测不到关系)")
print(f" 互信息 = {mi_nonlin:.4f} bit(远大于0,检测到了关系)")
print(f" 结论:互信息能捕获非线性依赖,相关系数不能!")
# ==================== 场景3:互信息在特征选择中的应用 ====================
print(f"\n{'='*55}")
print("互信息在特征选择中的应用")
print("=" * 55)
# 模拟分类问题:3个特征,目标标签
n_samples = 500
# 特征1: 与标签强相关
f1 = np.random.choice(['A', 'B', 'C'], size=n_samples, p=[0.3, 0.4, 0.3])
# 特征2: 与标签弱相关
f2 = np.random.choice(['X', 'Y'], size=n_samples, p=[0.5, 0.5])
# 特征3: 与标签无关(噪声)
f3 = np.random.choice(['P', 'Q', 'R'], size=n_samples, p=[0.33, 0.33, 0.34])
# 标签由特征1决定
labels = np.array([
np.random.choice(['正', '负'], p=[0.9, 0.1] if f == 'A' else
([0.3, 0.7] if f == 'B' else [0.1, 0.9]))
for f in f1
])
# 计算每个特征与标签的互信息
mi_f1 = mutual_info_empirical(f1, labels)
mi_f2 = mutual_info_empirical(f2, labels)
mi_f3 = mutual_info_empirical(f3, labels)
print(f"\n{'特征':<10} | {'与标签的互信息':>16} | 选择建议")
print("-" * 45)
features_mi = [
("特征1", mi_f1, "强相关,保留"),
("特征2", mi_f2, "弱相关,视情况保留"),
("特征3", mi_f3, "几乎无关,可剔除"),
]
for name, mi, advice in features_mi:
print(f"{name:<10} | {mi:>16.4f} | {advice}")
# 按互信息排序特征
best_feature = max([("特征1", mi_f1), ("特征2", mi_f2), ("特征3", mi_f3)],
key=lambda x: x[1])
print(f"\n最佳特征: {best_feature[0]}(互信息最大 = {best_feature[1]:.4f})")
什么用:互信息是AI中使用最广泛的信息论概念之一。在特征选择中,mRMR(最大相关最小冗余)算法用互信息选择既与标签相关又彼此不冗余的特征;在图像配准中,互信息作为相似度度量比相关系数更鲁棒;在表示学习中,InfoNCE(对比学习)和MINE(互信息神经估计)用互信息最大化来学习好的表示;在因果推断中,条件互信息用于检测条件独立性。
哪些坑:互信息从有限样本估计非常困难——高维连续变量的互信息估计至今仍是开放问题(MINE/InfoNCE等方法试图解决);互信息对离散化策略敏感——不同的分箱方式得到不同的值;互信息只能衡量"有多少信息"而不能衡量"信息的性质"——两个完全不同的关系可以有相同的互信息值;互信息的上界随变量取值数量增长,不能直接比较不同取值数变量的互信息(需要归一化,如 NMI)。
四、计算示例
是什么:本节通过一个完整的综合案例,将联合熵、条件熵和互信息的概念串联起来,演示它们在实际数据分析中的完整流程。我们将构建一个"天气-出行方式"的数据集,逐步计算所有熵度量,验证链式法则和互信息的对称性,并展示互信息如何揭示数据中隐藏的关联。
大白话 前面我们分别学了联合熵、条件熵和互信息,现在把它们"串起来"用在一个真实场景中:分析天气如何影响人们的出行方式。你会发现这三个概念就像一套"信息分析三件套"——联合熵告诉你"天气+出行"一起有多不确定,条件熵告诉你"知道天气后出行还剩多少不确定",互信息告诉你"天气对出行方式有多少解释力"。
怎么做:
import numpy as np
# ==================== 综合案例:天气与出行方式 ====================
np.random.seed(42)
# 生成模拟数据:天气和出行方式
n_days = 2000
weather = np.random.choice(['晴', '阴', '雨'], size=n_days, p=[0.5, 0.3, 0.2])
# 出行方式:步行、骑车、公交、开车
# 不同天气下的出行偏好不同
transport = np.zeros(n_days, dtype=object)
for i in range(n_days):
w = weather[i]
if w == '晴':
transport[i] = np.random.choice(['步行', '骑车', '公交', '开车'],
p=[0.30, 0.35, 0.20, 0.15])
elif w == '阴':
transport[i] = np.random.choice(['步行', '骑车', '公交', '开车'],
p=[0.20, 0.25, 0.30, 0.25])
else: # 雨
transport[i] = np.random.choice(['步行', '骑车', '公交', '开车'],
p=[0.05, 0.02, 0.43, 0.50])
# ==================== 构建联合概率矩阵 ====================
weather_labels = ['晴', '阴', '雨']
trans_labels = ['步行', '骑车', '公交', '开车']
n_w, n_t = len(weather_labels), len(trans_labels)
joint_counts = np.zeros((n_w, n_t))
for i in range(n_days):
wi = weather_labels.index(weather[i])
ti = trans_labels.index(transport[i])
joint_counts[wi, ti] += 1
joint_probs = joint_counts / n_days
print("=" * 60)
print("综合案例:天气-出行方式的信息论分析")
print("=" * 60)
# 打印联合概率矩阵
print(f"\n联合概率矩阵 P(天气, 出行):")
print(f"{'':>6}", end="")
for t in trans_labels:
print(f"{t:>8}", end="")
print(f"{' P(天气)':>10}")
print("-" * 55)
for i, w in enumerate(weather_labels):
row_sum = np.sum(joint_probs[i])
print(f"{w:>6}", end="")
for j in range(n_t):
print(f"{joint_probs[i][j]:>8.4f}", end="")
print(f"{row_sum:>10.4f}")
# 边缘概率
print(f"{'P(出行)':>6}", end="")
col_sums = np.sum(joint_probs, axis=0)
for j in range(n_t):
print(f"{col_sums[j]:>8.4f}", end="")
print()
# ==================== 计算所有熵度量 ====================
# 边缘熵
p_w = np.sum(joint_probs, axis=1)
p_w = p_w[p_w > 0]
h_w = -np.sum(p_w * np.log2(p_w))
p_t = np.sum(joint_probs, axis=0)
p_t = p_t[p_t > 0]
h_t = -np.sum(p_t * np.log2(p_t))
# 联合熵
flat = joint_probs.flatten()
flat = flat[flat > 0]
h_joint = -np.sum(flat * np.log2(flat))
# 条件熵 H(出行|天气)
h_t_given_w = 0.0
for i in range(n_w):
row_p = np.sum(joint_probs[i])
if row_p > 0:
cond_p = joint_probs[i] / row_p
cond_p = cond_p[cond_p > 0]
h_t_given_w += row_p * (-np.sum(cond_p * np.log2(cond_p)))
# 条件熵 H(天气|出行)
h_w_given_t = 0.0
for j in range(n_t):
col_p = np.sum(joint_probs[:, j])
if col_p > 0:
cond_p = joint_probs[:, j] / col_p
cond_p = cond_p[cond_p > 0]
h_w_given_t += col_p * (-np.sum(cond_p * np.log2(cond_p)))
# 互信息
mi = h_t - h_t_given_w # I(天气;出行) = H(出行) - H(出行|天气)
mi_check = h_w + h_t - h_joint # 验证公式
# ==================== 结果汇总 ====================
print(f"\n{'='*60}")
print("信息论度量汇总")
print("=" * 60)
print(f"\n边缘熵:")
print(f" H(天气) = {h_w:.4f} bit")
print(f" H(出行) = {h_t:.4f} bit")
print(f" H(天气) + H(出行) = {h_w + h_t:.4f} bit")
print(f"\n联合熵:")
print(f" H(天气, 出行) = {h_joint:.4f} bit")
print(f"\n条件熵:")
print(f" H(出行|天气) = {h_t_given_w:.4f} bit (知道天气后出行剩余的不确定性)")
print(f" H(天气|出行) = {h_w_given_t:.4f} bit (知道出行后天气剩余的不确定性)")
print(f"\n互信息:")
print(f" I(天气;出行) = {mi:.4f} bit")
print(f" 验证: H(出行)-H(出行|天气) = {h_t - h_t_given_w:.4f} ✓")
print(f" 验证: H(天气)+H(出行)-H(天气,出行) = {mi_check:.4f} ✓")
print(f" 验证: H(天气)-H(天气|出行) = {h_w - h_w_given_t:.4f} ✓")
# ==================== 验证链式法则 ====================
print(f"\n链式法则验证:")
print(f" H(天气) + H(出行|天气) = {h_w + h_t_given_w:.4f}")
print(f" H(出行) + H(天气|出行) = {h_t + h_w_given_t:.4f}")
print(f" H(天气, 出行) = {h_joint:.4f}")
print(f" 链式法则成立 ✓")
# ==================== 归一化互信息 (NMI) ====================
# NMI = I(X;Y) / sqrt(H(X)*H(Y)) 或 / max(H(X),H(Y))
nmi_sqrt = mi / np.sqrt(h_w * h_t) if h_w * h_t > 0 else 0
nmi_max = mi / max(h_w, h_t) if max(h_w, h_t) > 0 else 0
nmi_min = mi / min(h_w, h_t) if min(h_w, h_t) > 0 else 0
print(f"\n归一化互信息 (NMI):")
print(f" NMI (sqrt) = I/√(H(X)H(Y)) = {nmi_sqrt:.4f}")
print(f" NMI (max) = I/max(H(X),H(Y)) = {nmi_max:.4f}")
print(f" NMI (min) = I/min(H(X),H(Y)) = {nmi_min:.4f}")
print(f" NMI 越接近 1,变量关联越紧密")
# ==================== 业务解读 ====================
print(f"\n{'='*60}")
print("业务解读")
print("=" * 60)
print(f" 天气对出行方式的解释力 = {mi/h_t*100:.1f}%")
print(f" 即知道天气后,对出行方式的不确定性减少了 {mi/h_t*100:.1f}%")
print(f" 不同天气下的出行偏好差异明显——雨天明显减少步行/骑车,")
print(f" 增加公交/开车比例")
什么用:这种综合分析在实际项目中非常实用。当你需要评估某个特征对目标变量的预测能力时,这种全流程分析能给出定量答案;在数据探索阶段,计算所有特征与目标的互信息并排序,可以快速筛选出最有价值的特征;在聚类分析中,NMI 是衡量聚类质量的首选指标之一。
哪些坑:所有计算都基于从有限样本估计的概率——当样本量不足时,联合概率矩阵中很多格子的值很不稳定,导致所有衍生度量(熵、互信息)都不可靠;数据离散化策略(分箱宽度和数量)会显著影响互信息估计值,需要交叉验证来选择最佳分箱;互信息的值域依赖于变量取值数量,需始终使用归一化版本(NMI)进行比较。
五、AI中的互信息——特征选择、表示学习
是什么:在AI中,互信息作为"不依赖模型假设"的依赖度量,在两个方向上发挥着不可替代的作用。在特征选择中,mRMR(最大相关最小冗余)算法用互信息同时优化"特征与标签的相关性"和"特征之间的冗余性";在表示学习中,InfoNCE 和 MINE 用互信息最大化来训练自监督学习模型,学习到的表示能最大程度地保留原始数据的信息。
大白话 计算机视觉中的自监督学习(如SimCLR)本质上是"让同一张图片的两个不同裁剪版本在表示空间中靠近"——这在信息论中就是"最大化两个视图之间的互信息"。而特征选择中的mRMR就像是"选一个精英团队"——既要有能力(与标签互信息大),又不能内部重复(彼此互信息小)。互信息就是那个"不依赖任何模型假设"的万能评判标准。
为什么:互信息在AI中如此重要的根本原因是它的"非参数"特性——互信息不假设变量之间有任何特定形式的函数关系(线性、多项式等),它衡量的是最一般的统计依赖。在信息瓶颈理论中,Tishby 等人提出:深度学习的本质是网络在"压缩输入"和"保留标签信息"之间寻找最优平衡点——这恰好对应了最小化 I(X;T) 同时最大化 I(T;Y),其中 T 是隐藏层表示。这个理论为理解深度学习的泛化能力提供了全新的信息论视角。
怎么做:
import numpy as np
# ==================== 1. mRMR 特征选择算法 ====================
def mutual_info_empirical(x_data, y_data):
"""从数据估计互信息"""
n = len(x_data)
x_vals = np.unique(x_data)
y_vals = np.unique(y_data)
n_x, n_y = len(x_vals), len(y_vals)
joint_counts = np.zeros((n_x, n_y))
x_map = {v: i for i, v in enumerate(x_vals)}
y_map = {v: i for i, v in enumerate(y_vals)}
for i in range(n):
joint_counts[x_map[x_data[i]], y_map[y_data[i]]] += 1
joint_p = joint_counts / n
p_x = np.sum(joint_p, axis=1)
p_y = np.sum(joint_p, axis=0)
h_x = -np.sum(p_x[p_x > 0] * np.log2(p_x[p_x > 0]))
h_y = -np.sum(p_y[p_y > 0] * np.log2(p_y[p_y > 0]))
flat = joint_p.flatten()
flat = flat[flat > 0]
h_xy = -np.sum(flat * np.log2(flat))
return max(h_x + h_y - h_xy, 0.0)
def mrmr_feature_selection(features, labels, k=3):
"""
mRMR 特征选择:最大相关、最小冗余
参数:
features: 特征列表,每个特征是一个数组
labels: 标签数组
k: 要选择的特征数量
返回:
选中的特征索引列表
"""
n_features = len(features)
# 第一步:计算每个特征与标签的互信息(相关性)
relevance = np.zeros(n_features)
for i in range(n_features):
relevance[i] = mutual_info_empirical(features[i], labels)
# 第二步:选择第一个特征(与标签互信息最大)
selected = [np.argmax(relevance)]
# 第三步:迭代选择剩余特征
for _ in range(1, k):
best_score = -float('inf')
best_idx = -1
for i in range(n_features):
if i in selected:
continue
# 相关性 = I(feature_i; labels)
rel = relevance[i]
# 冗余性 = 与已选特征的平均互信息
red = 0.0
for j in selected:
red += mutual_info_empirical(features[i], features[j])
red /= len(selected)
# mRMR 分数 = 相关性 - 冗余性
score = rel - red
if score > best_score:
best_score = score
best_idx = i
selected.append(best_idx)
return selected
# ==================== 模拟数据:10个特征,5个候选 ====================
np.random.seed(42)
n_samples = 500
# 创建5个候选特征
# 特征1: 与标签强相关
f1 = np.random.choice(['A', 'B', 'C'], size=n_samples, p=[0.3, 0.4, 0.3])
# 特征2: 与标签弱相关,但与特征1高度相关(冗余)
f2 = np.where(f1 == 'A',
np.random.choice(['X', 'Y'], size=n_samples, p=[0.9, 0.1]),
np.where(f1 == 'B',
np.random.choice(['X', 'Y'], size=n_samples, p=[0.3, 0.7]),
np.random.choice(['X', 'Y'], size=n_samples, p=[0.1, 0.9])))
# 特征3: 与标签中度相关,与特征1不相关
f3 = np.random.choice(['M', 'N'], size=n_samples, p=[0.5, 0.5])
# 特征4: 噪声特征
f4 = np.random.choice(['P', 'Q', 'R'], size=n_samples, p=[0.33, 0.33, 0.34])
# 特征5: 与标签强相关,但与特征1不相关
f5 = np.random.choice(['H', 'L'], size=n_samples, p=[0.5, 0.5])
# 标签由特征1和特征5共同决定
labels = np.array([
np.random.choice(['正', '负'],
p=[(0.8 if f1_i == 'A' else 0.5 if f1_i == 'B' else 0.2) * 0.7 +
(0.7 if f5_i == 'H' else 0.3) * 0.3,
1 - ((0.8 if f1_i == 'A' else 0.5 if f1_i == 'B' else 0.2) * 0.7 +
(0.7 if f5_i == 'H' else 0.3) * 0.3)])
for f1_i, f5_i in zip(f1, f5)
])
all_features = [f1, f2, f3, f4, f5]
feature_names = ['特征1(强相关)', '特征2(冗余)', '特征3(中等)', '特征4(噪声)', '特征5(强相关2)']
print("=" * 60)
print("mRMR 特征选择算法演示")
print("=" * 60)
# 计算每个特征与标签的互信息
print(f"\n各特征与标签的互信息(相关性):")
mi_scores = []
for i, (f, name) in enumerate(zip(all_features, feature_names)):
mi = mutual_info_empirical(f, labels)
mi_scores.append(mi)
print(f" {name}: I = {mi:.4f} bit")
# 特征间互信息矩阵(冗余性)
print(f"\n特征间互信息矩阵(冗余性):")
print(f"{'':>14}", end="")
for name in feature_names:
print(f"{name[:6]:>8}", end="")
print()
for i, name_i in enumerate(feature_names):
print(f"{name_i:>14}", end="")
for j in range(len(feature_names)):
mi = mutual_info_empirical(all_features[i], all_features[j])
print(f"{mi:>8.4f}", end="")
print()
# 执行mRMR选择
selected_indices = mrmr_feature_selection(all_features, labels, k=3)
print(f"\nmRMR 选择的 Top-3 特征:")
for rank, idx in enumerate(selected_indices):
mi_label = mutual_info_empirical(all_features[idx], labels)
print(f" {rank+1}. {feature_names[idx]} (与标签互信息: {mi_label:.4f})")
# ==================== 2. 互信息最大化在表示学习中的简化演示 ====================
print(f"\n{'='*60}")
print("互信息在表示学习中的应用(简化演示)")
print("=" * 60)
# 模拟:原始数据X和两个不同的表示Z1、Z2
# 好的表示Z1应该与X有高互信息,与噪声有低互信息
np.random.seed(123)
n = 1000
# 原始数据X(有信息的信号+噪声)
signal = np.random.choice(['A', 'B', 'C', 'D'], size=n, p=[0.25]*4)
noise = np.random.choice(['x', 'y'], size=n, p=[0.5, 0.5])
# 好的表示Z1:保留了信号的大部分信息
z1 = np.where(signal == 'A', np.random.choice(['a1', 'a2'], size=n, p=[0.95, 0.05]),
np.where(signal == 'B', np.random.choice(['b1', 'b2'], size=n, p=[0.95, 0.05]),
np.where(signal == 'C', np.random.choice(['c1', 'c2'], size=n, p=[0.95, 0.05]),
np.random.choice(['d1', 'd2'], size=n, p=[0.95, 0.05]))))
# 差的表示Z2:几乎丢失了信号的信息
z2 = np.random.choice(['α', 'β', 'γ'], size=n, p=[0.33, 0.33, 0.34])
mi_x_z1 = mutual_info_empirical(signal, z1)
mi_x_z2 = mutual_info_empirical(signal, z2)
mi_noise_z1 = mutual_info_empirical(noise, z1)
mi_noise_z2 = mutual_info_empirical(noise, z2)
print(f"\n I(原始数据; 好表示Z1) = {mi_x_z1:.4f} bit ← 高,保留了信息")
print(f" I(原始数据; 差表示Z2) = {mi_x_z2:.4f} bit ← 低,丢失了信息")
print(f" I(噪声; 好表示Z1) = {mi_noise_z1:.4f} bit ← 低,滤除了噪声")
print(f" I(噪声; 差表示Z2) = {mi_noise_z2:.4f} bit ← 低(但也没保留信号)")
print(f"\n 好的表示学习目标:最大化 I(X;Z)(保留信息),最小化 I(噪声;Z)(滤除噪声)")
print(f" 这就是信息瓶颈理论的核心思想!")
什么用:互信息在AI中的应用已经渗透到方方面面。在对比学习(SimCLR、MoCo、CLIP)中,InfoNCE 损失函数本质上是互信息的下界估计;在生成模型(InfoGAN)中,互信息最大化被用于学习解耦的表示;在域适应中,条件互信息用于检测域不变特征;在因果发现中,条件互信息检验用于构建因果图;在联邦学习中,互信息度量了模型参数泄露的隐私风险。
哪些坑:高维连续变量的互信息估计是仍未完全解决的问题——MINE 和 InfoNCE 等方法虽然有效,但对超参数敏感且训练不稳定;互信息最大化可能导致"表示坍缩"——所有输入映射到同一个表示(InfoNCE 通过负样本缓解了这个问题);mRMR 的计算复杂度是 O(n_features²),在特征数为十万级时不可行;互信息作为无模型度量,可能忽略了数据中的因果结构——"相关不等于因果"在互信息中同样适用。
概念关系图谱
| 概念 | 核心含义 | 与AI的关系 | 关联概念 |
|---|---|---|---|
| 联合熵 H(X,Y) | 两个变量一起的不确定性总和 | 聚类评估、多标签分类的不确定性度量 | 条件熵、互信息、边缘熵 |
| 条件熵 H(Y|X) | 已知X后Y剩余的不确定性 | 特征有效性评估、语言模型困惑度 | 互信息、信息增益、链式法则 |
| 互信息 I(X;Y) | 两个变量共享的信息量 | 特征选择(mRMR)、表示学习(InfoNCE)、信息瓶颈 | 条件熵、KL散度、联合熵 |
| 链式法则 | 联合熵 = 条件熵之和 | 自回归模型损失分解、序列建模 | 联合熵、条件熵 |
| NMI | 归一化互信息,消除尺度影响 | 聚类评估标准指标 | 互信息、信息熵 |
| mRMR | 最大相关最小冗余特征选择 | 高维数据的特征筛选 | 互信息、特征选择 |
| 信息瓶颈 | 压缩与保留的平衡 | 深度学习泛化理论 | 互信息、KL散度、表示学习 |
重点答疑
Q1: 条件熵 H(Y|X) 和互信息 I(X;Y) 有什么区别?它们之间有什么关系?
条件熵和互信息从不同角度描述了 X 和 Y 的关系。条件熵 H(Y|X) 是"知道 X 之后 Y 还剩多少不确定性"——它是一个绝对值,范围从 0 到 H(Y)。互信息 I(X;Y) = H(Y) - H(Y|X) 是"知道 X 后 Y 的不确定性减少了多少"——它是减少量,范围从 0 到 min(H(X), H(Y))。如果你把 H(Y) 想象成"谜团的初始大小",条件熵是"谜团剩余的尺寸",互信息是"已解开的尺寸"。三者的关系是 I(X;Y) + H(Y|X) = H(Y)。在AI中,评估特征 X 的好坏时,既可以用条件熵(越小越好),也可以用互信息(越大越好),两者等价。
Q2: 互信息和皮尔逊相关系数有什么本质区别?什么时候应该用互信息?
皮尔逊相关系数只衡量线性关系——如果 X 和 Y 的关系是 Y = X²,相关系数可能接近 0(检测不到),但互信息会很大。这是因为互信息基于概率分布而非协方差,它不假设任何函数形式。此外,互信息对变量可逆变换不变——如果把 X 替换为 e^X 或 log(X),互信息不变,但相关系数会变。什么时候用互信息?(1) 怀疑变量间存在非线性关系时;(2) 变量是离散的(相关系数不适合离散变量);(3) 需要比较不同类型变量的关联强度时(如一个离散、一个连续)。什么时候用相关系数?当已知变量间是线性关系且需要正负方向信息时(互信息不区分正负相关)。
Q3: 为什么说"高维连续变量的互信息估计"是个难题?有什么解决方法?
核心困难在于"维度灾难"——要估计互信息,需要估计联合概率密度 p(x,y),而高维空间中样本极度稀疏,直方图或核密度估计都不可靠。传统的 kNN 方法(KSG估计器)虽然有理论保证,但在实践中对 k 值敏感且计算量大。深度学习方法(MINE、InfoNCE)通过神经网络来估计互信息的下界,绕过了显式密度估计,但引入了新的问题:MINE 需要训练一个判别网络,对超参数敏感;InfoNCE 估计的精度依赖于负样本数量,且只给出互信息的下界而非精确值。实践中,对于中等维度(<20),可以用 KSG 或分箱+kNN;对于高维数据(如图像表示),InfoNCE 是目前最实用的选择。
Q4: 链式法则 H(X,Y) = H(X) + H(Y|X) 在机器学习中有什么实际用途?
最直接的应用在于自回归模型(如语言模型 GPT)。在自回归模型中,联合概率被分解为 p(x₁,...,xₙ) = Πᵢ p(xᵢ|x₁,...,xᵢ₋₁),对应的负对数似然损失(交叉熵)的期望就是链式法则中的条件熵之和。语言模型的困惑度 = 2^{H(wₙ|w₁,...,wₙ₋₁)},直接来自条件熵。另一个应用是变分自编码器(VAE)——ELBO 的推导中,log p(x) 的下界分解涉及条件熵和 KL 散度,链式法则帮助理解隐变量模型的证据下界。在贝叶斯深度学习中,链式法则帮助分解复杂的联合后验分布,使变分推断成为可能。
Q5: 在实际项目中,如何使用互信息做特征选择?和常用的基于模型的特征重要性(如随机森林的feature_importance)有什么区别?
使用互信息做特征选择的流程:(1) 如果是连续特征,先做离散化(分箱或使用KSG估计器);(2) 计算每个特征与标签的互信息;(3) 用 mRMR 或 JMI(联合互信息)等准则选择特征子集。与基于模型的特征重要性相比:互信息方法是无模型的——不依赖任何分类器,因此不会引入模型偏差(如树模型偏向高基数特征);互信息能捕获非线性关系,而线性模型的特征重要性只能捕获线性关系;但互信息方法计算量大(O(n_features²)),且无法给出特征重要性的方向(正相关还是负相关)。实践中,对于特征数量适中(<1000)且需要无偏评估的场景,互信息是首选;对于特征数量极大且需要快速筛选的场景,基于模型的方法更实用。
章节单词汇总
| 英文 | 音标 | 术语/释义 |
|---|---|---|
| joint entropy | /dʒɔɪnt ˈentrəpi/ | 联合熵,两个变量一起的不确定性 |
| conditional entropy | /kənˈdɪʃənl ˈentrəpi/ | 条件熵,已知一个变量后另一个的不确定性 |
| mutual information | /ˈmjuːtʃuəl ˌɪnfərˈmeɪʃn/ | 互信息,两个变量共享的信息量 |
| chain rule | /tʃeɪn ruːl/ | 链式法则,联合熵的条件熵分解 |
| normalized mutual information | /ˈnɔːrməlaɪzd/ | 归一化互信息 |
| mRMR | /ɛm ɑːr ɛm ɑːr/ | 最大相关最小冗余特征选择 |
| redundancy | /rɪˈdʌndənsi/ | 冗余性,特征之间重复的信息 |
| relevance | /ˈreləvəns/ | 相关性,特征与标签的关系 |
| information bottleneck | /ˌɪnfərˈmeɪʃn ˈbɑːtlnek/ | 信息瓶颈,压缩与保留的平衡理论 |
| perplexity | /pərˈpleksəti/ | 困惑度,语言模型评估指标 = 2^H |
面试练习
Q1 [单选] 若 X 和 Y 相互独立,则以下哪个等式成立?
- A. H(X,Y) = H(X) + H(Y|X)
- B. H(X,Y) = H(X) + H(Y)
- C. I(X;Y) = 0
- D. H(Y|X) = H(Y)
解答:独立时,C正确(互信息为0),D也正确(H(Y|X)=H(Y),因为知道X对Y毫无帮助)。但D是C的推论:I(X;Y)=H(Y)-H(Y|X)=0 ⇒ H(Y|X)=H(Y)。B在独立时也成立,因为H(X,Y)=H(X)+H(Y|X)=H(X)+H(Y)。本题关键是要选独立时必然成立的等式,C和D和B都对,但最直接反映独立性的定义是C。
Q2 [单选] 已知 H(X)=2 bit, H(Y)=3 bit, H(X,Y)=4 bit,则 H(Y|X) 等于多少?
- A. 1 bit
- B. 2 bit
- C. 3 bit
- D. 5 bit
解答:H(Y|X) = H(X,Y) - H(X) = 4 - 2 = 2 bit。同时 I(X;Y) = H(X)+H(Y)-H(X,Y) = 2+3-4 = 1 bit,且 H(X|Y) = H(X,Y)-H(Y) = 4-3 = 1 bit。
Q3 [单选] 互信息 I(X;Y) 的取值范围是?
- A. [0, 1]
- B. [0, H(X)]
- C. [0, min(H(X), H(Y))]
- D. [0, ∞)
解答:互信息不能超过任何一个变量的熵——因为共享的信息不可能超过各自拥有的信息。因此 I(X;Y) ≤ min(H(X), H(Y))。同时 I(X;Y) ≥ 0(非负性)。所以取值范围是 [0, min(H(X), H(Y))]。
Q4 [单选] 以下哪个关于条件熵的说法是正确的?
- A. H(Y|X) 总是大于 H(Y)
- B. H(Y|X) 总是等于 H(X|Y)
- C. H(Y|X) ≤ H(Y),等号成立当且仅当 X 和 Y 独立
- D. H(Y|X) 可以为负数
解答:知道更多信息不会增加不确定性,所以 H(Y|X) ≤ H(Y) 恒成立。当 X 和 Y 独立时,知道 X 对 Y 毫无帮助,条件熵等于边缘熵。条件熵非对称:H(Y|X) ≠ H(X|Y)(除非 H(X)=H(Y) 且满足特殊条件)。离散条件熵绝不可能是负数。
Q5 [多选] 以下关于互信息的说法,哪些是正确的?
- A. I(X;Y) = I(Y;X),互信息是对称的
- B. I(X;Y) = 0 当且仅当 X 和 Y 独立
- C. I(X;Y) = H(X) - H(X|Y)
- D. I(X;Y) 永远大于等于 H(X)
解答:A、B、C均正确。D错误,互信息 ≤ min(H(X), H(Y)),不可能大于 H(X)。
Q6 [单选] 在特征选择中,mRMR 算法同时考虑了什么?
- A. 特征的计算速度和存储开销
- B. 特征与标签的相关性(Relevance)和特征之间的冗余性(Redundancy)
- C. 特征的均值和方差
- D. 特征的缺失比例和异常值数量
解答:mRMR = Max-Relevance Min-Redundancy,即最大化特征与标签的互信息(相关性),同时最小化特征之间的互信息(冗余性)。这避免了选择多个高度相关(冗余)的特征,使特征子集既有效又精简。
Q7 [多选] 以下哪些是互信息在AI中的实际应用?
- A. 对比学习(SimCLR)中的 InfoNCE 损失函数
- B. 图像配准中的相似度度量
- C. 因果发现中的条件独立性检验
- D. 聚类评估中的 NMI 指标
解答:四个选项全部正确。A:InfoNCE 是互信息的下界估计。B:互信息比相关系数对图像变换更鲁棒。C:条件互信息 I(X;Y|Z)=0 意味着 X 和 Y 在给定 Z 下条件独立,是因果发现的核心工具。D:NMI 基于互信息,是聚类评估的金标准。
Q8 [单选] 信息瓶颈理论中,优化目标是什么?
- A. 最大化 I(X;T) 同时最大化 I(T;Y)
- B. 最大化 I(X;T) 同时最小化 I(T;Y)
- C. 最小化 I(X;T) 同时最大化 I(T;Y)
- D. 最小化 I(X;T) 同时最小化 I(T;Y)
解答:信息瓶颈的目标是 min[I(X;T) - β·I(T;Y)]。这意味着压缩表示 T(减小 I(X;T),即 T 携带的关于输入的信息越少越好),同时保留对标签 Y 的预测能力(增大 I(T;Y))。这体现了"用最精简的表示完成预测任务"的理念。
Q9 [单选] 对于两个离散随机变量,如果它们完全确定性地关联(即Y是X的确定性函数),那么:
- A. I(X;Y) = 0
- B. I(X;Y) = H(X) + H(Y)
- C. I(X;Y) = H(Y) = H(X,Y) - H(X|Y) 且 H(Y|X) = 0
- D. I(X;Y) > H(X)
解答:如果 Y 是 X 的确定性函数,则 H(Y|X) = 0(知道X后Y完全确定),因此 I(X;Y) = H(Y) - H(Y|X) = H(Y),且 H(X,Y) = H(X) + H(Y|X) = H(X)。互信息等于 H(Y) 但不超过 H(X)(因为 I(X;Y) ≤ min(H(X), H(Y)))。
Q10 [多选] 关于归一化互信息(NMI),以下哪些说法是正确的?
- A. NMI 将互信息映射到 [0, 1] 区间
- B. NMI = 1 总是意味着两个变量完全相同
- C. NMI 常用于聚类评估,比较聚类结果与真实标签
- D. NMI = 0 意味着两个变量完全独立
解答:A正确,NMI 通过除以熵的几何平均或算术平均将互信息归一化。B错误,NMI=1意味着存在确定性关系但未必是"相同"——例如 Y=2X 是确定性函数关系但不是"相同"。C正确,NMI 是聚类评估的标准指标。D正确,NMI=0 ⇔ I=0 ⇔ 独立。