1信息论基础(第2版)

熵与相对熵

信息量的度量

熵联合熵条件熵相对熵互信息

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第1章 熵与相对熵(Entropy and Relative Entropy)

一、导读

1.1 本章学习目标

通过本章的学习,你将能够:

理解信息量的数学定义:掌握为什么对数函数是度量信息的自然选择,理解比特(bit)作为信息单位的物理意义

计算离散随机变量的熵:熟练运用熵的公式 $H(X) = -\sum p(x)\log_2 p(x)$ 计算各种分布的信息熵

区分联合熵、条件熵和互信息:理解这三个概念之间的数学关系和直观含义,掌握链式法则的应用

理解相对熵(KL散度)的性质:掌握相对熵的非负性、非对称性,理解其作为概率分布"距离"度量的局限性

应用熵的概念解决实际问题:能够将熵的概念应用于数据压缩、特征选择、模型评估等场景

理解最大熵原理:掌握在给定约束条件下,均匀分布具有最大熵这一重要性质

编程实现熵的计算:能够使用Python编写代码计算各种信息论度量,并进行可视化分析

理解信息论与热力学的联系:了解香农熵与玻尔兹曼熵的历史渊源和概念对应

1.2 为什么本章如此重要

熵是信息论最核心的概念,没有之一。克劳德·香农在1948年的开创性论文《通信的数学理论》中引入熵的概念,彻底改变了我们对信息的理解方式。在此之前,"信息"是一个模糊的日常用语;在此之后,信息成为了可以精确度量的科学对象。

熵的重要性体现在多个层面:

理论层面:熵给出了信息量的基本极限。就像热力学第二定律给出了能量转换的效率上限,熵给出了数据压缩的理论下限。任何无损压缩算法都无法将数据压缩到低于其熵的水平,这是自然界的基本规律,而非技术限制。

实践层面:熵的概念渗透到了现代计算机科学的每一个角落。从ZIP文件的压缩算法,到机器学习中的决策树分裂准则;从密码学中的密钥强度评估,到网络通信中的信道编码设计——熵都是背后的理论基础。

哲学层面:熵连接了信息与不确定性、信息与概率、信息与物理世界。它告诉我们,信息的本质是消除不确定性,而不确定性的量化依赖于概率分布。这种深刻的洞察超越了工程应用,影响了统计学、物理学、生物学甚至经济学的发展。

对于程序员而言,理解熵不仅仅是学习一个数学公式,更是获得一种思考问题的新视角。当你面对一个数据集时,熵告诉你其中包含多少"真正"的信息;当你设计一个分类器时,互信息帮你选择最有判别力的特征;当你评估两个概率模型时,相对熵给出它们之间的差异程度。

1.3 核心问题引导

在学习本章之前,请思考以下问题:

为什么抛硬币的结果比掷骰子的结果包含更少的信息? 直觉上,我们感觉骰子有6种可能结果,应该比只有2种结果的硬币"更复杂"。但信息量的定义并非简单地计数可能结果的数量,而是与概率分布密切相关。

如果一封邮件的内容完全可以预测,它还包含信息吗? 这个问题触及了信息的核心定义:信息是用来消除不确定性的东西。如果某件事已经确定无疑,那么告知你这个事实并不传递任何信息。

为什么JPEG图片压缩是有损的,而ZIP文件压缩是无损的? 这背后涉及熵的不同应用方式。无损压缩受限于数据的熵,而有损压缩则通过丢弃人眼不敏感的信息来突破这个限制。

两个完全独立的随机变量,它们的联合熵等于什么? 这个问题引导我们理解熵的可加性,以及独立性在信息论中的特殊地位。

相对熵为什么不满足三角不等式,因此不是真正的"距离"? 理解相对熵的几何性质有助于正确使用它进行模型比较和统计推断。

为什么均匀分布具有最大熵? 这个看似反直觉的事实实际上反映了"无知"的最大化:当我们对系统一无所知时,最合理的假设是所有可能性均等。

信息论中的"熵"与热力学中的"熵"是同一个概念吗? 这两个概念的数学形式惊人地相似,这种相似性是巧合还是反映了更深层的统一?

如何用熵来解释"谣言越传越失真"的现象? 信息在传递过程中的损失可以用条件熵和数据处理不等式来精确描述。


二、核心概念详解

2.1 信息量与自信息(Self-Information)

在正式定义熵之前,我们需要先理解单个事件的信息量,也称为自信息。

定义:设 $X$ 是一个离散随机变量,取值于有限集合 $\mathcal{X}$,概率质量函数为 $p(x) = P(X=x)$。事件 $X=x$ 的自信息定义为:

$$i(x) = -\log_b p(x) = \log_b \frac{1}{p(x)}$$

其中 $b$ 是对数的底数,通常取 $b=2$(单位为比特,bit)、$b=e$(单位为奈特,nat)或 $b=10$(单位为哈特利,hartley)。在信息论中,我们主要使用以2为底的对数。

直观理解:自信息度量了当事件发生时所带来的"惊讶程度"。概率越小的事件,发生时带来的信息量越大。例如:

  • "太阳从东方升起"这个事件的概率接近1,因此其自信息接近0——这句话几乎不包含任何信息
  • "明天会下雨"的概率可能是0.3,其自信息约为 $-\log_2 0.3 \approx 1.74$ 比特
  • "明天会下陨石"的概率极小,如果真的发生,将带来巨大的信息量

对数函数的选择理由:

可加性:如果两个独立事件同时发生,总信息量应等于各自信息量之和。由于独立事件的联合概率等于概率的乘积,而对数将乘法转换为加法:$\log(p_1 p_2) = \log p_1 + \log p_2$

单调性:概率越小,信息量越大,对数函数满足这一要求

连续性:概率的微小变化应导致信息量的微小变化

归一化:选择以2为底时,一个等概率的二元事件(如公平硬币的正面)恰好携带1比特信息

代码示例1-1:计算自信息

python
import numpy as np
import matplotlib.pyplot as plt

def self_information(p, base=2):
    """
    计算事件的自信息
    
    参数:
        p: 事件发生的概率 (0 < p <= 1)
        base: 对数的底数,默认为2(比特)
    
    返回:
        自信息值
    """
    if p <= 0 or p > 1:
        raise ValueError("概率必须在(0, 1]范围内")
    return -np.log(p) / np.log(base)

# 计算不同概率事件的自信息
probabilities = [0.01, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99]
info_values = [self_information(p) for p in probabilities]

print("概率 vs 自信息(比特):")
for p, info in zip(probabilities, info_values):
    print(f"P={p:.2f}: {info:.4f} bits")

# 可视化
plt.figure(figsize=(10, 6))
p_range = np.linspace(0.01, 1, 100)
info_range = [self_information(p) for p in p_range]
plt.plot(p_range, info_range, 'b-', linewidth=2)
plt.xlabel('概率 P', fontsize=12)
plt.ylabel('自信息(比特)', fontsize=12)
plt.title('自信息与概率的关系', fontsize=14)
plt.grid(True, alpha=0.3)
plt.axhline(y=1, color='r', linestyle='--', label='1比特')
plt.legend()
plt.tight_layout()
plt.savefig('self_information.png', dpi=150)
plt.show()

输出结果:

概率 vs 自信息(比特):
P=0.01: 6.6439 bits
P=0.10: 3.3219 bits
P=0.25: 2.0000 bits
P=0.50: 1.0000 bits
P=0.75: 0.4150 bits
P=0.90: 0.1520 bits
P=0.99: 0.0145 bits

从这个例子可以看出,当概率为0.5时,自信息恰好为1比特;当概率趋近于1时,自信息趋近于0;当概率很小时,自信息急剧增大。

2.2 熵(Entropy)

熵是自信息的期望值,即随机变量平均携带的信息量。

定义:离散随机变量 $X$ 的熵定义为:

$$H(X) = \mathbb{E}[i(X)] = -\sum_{x \in \mathcal{X}} p(x) \log_2 p(x)$$

按照惯例,我们定义 $0 \log_2 0 = 0$,这是因为 $\lim_{p \to 0^+} p \log p = 0$。

单位:当对数以2为底时,熵的单位是比特(bits per symbol)。这意味着熵表示的是平均每个符号携带的比特数。

关键性质:

非负性:$H(X) \geq 0$,等号成立当且仅当 $X$ 是确定性变量(某个取值的概率为1)

最大值:对于取值于 $n$ 个元素的随机变量,$H(X) \leq \log_2 n$,等号成立当且仅当 $X$ 服从均匀分布

确定性保持:如果 $Y = f(X)$ 是确定性函数,则 $H(Y) \leq H(X)$

可加性:如果 $X$ 和 $Y$ 独立,则 $H(X,Y) = H(X) + H(Y)$

实例分析:

考虑三种不同的概率分布:

确定性分布:$p = [1, 0, 0, 0]$,熵 $H = 0$ 比特

- 结果完全确定,没有任何不确定性

均匀分布:$p = [0.25, 0.25, 0.25, 0.25]$,熵 $H = 2$ 比特

- 不确定性最大,需要2比特才能唯一标识每个结果

偏斜分布:$p = [0.5, 0.25, 0.125, 0.125]$,熵 $H \approx 1.75$ 比特

- 介于前两者之间

代码示例1-2:计算各种分布的熵

python
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import entr

def entropy(p, base=2):
    """
    计算离散分布的熵
    
    参数:
        p: 概率分布列表或数组
        base: 对数的底数,默认为2
    
    返回:
        熵值
    """
    p = np.array(p, dtype=float)
    # 过滤掉零概率(避免log(0))
    p = p[p > 0]
    # 归一化确保概率和为1
    p = p / p.sum()
    # 计算熵
    return -np.sum(p * np.log(p) / np.log(base))

# 测试不同的分布
distributions = {
    "确定性": [1, 0, 0, 0],
    "均匀(4项)": [0.25, 0.25, 0.25, 0.25],
    "偏斜": [0.5, 0.25, 0.125, 0.125],
    "高度偏斜": [0.9, 0.05, 0.03, 0.02],
    "均匀(8项)": [0.125] * 8,
}

print("各种分布的熵:")
print("-" * 40)
for name, dist in distributions.items():
    h = entropy(dist)
    print(f"{name:12s}: H = {h:.4f} bits")

# 研究二元分布的熵随概率的变化
print("\n二元分布的熵:")
print("-" * 40)
binary_probs = [0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.99]
for p in binary_probs:
    h = entropy([p, 1-p])
    print(f"P(X=0)={p:.2f}: H = {h:.4f} bits")

# 可视化二元分布的熵函数
plt.figure(figsize=(10, 6))
p_values = np.linspace(0.001, 0.999, 1000)
entropy_values = [entropy([p, 1-p]) for p in p_values]

plt.plot(p_values, entropy_values, 'b-', linewidth=2.5)
plt.xlabel('P(X=0)', fontsize=13)
plt.ylabel('熵 H(X)(比特)', fontsize=13)
plt.title('二元随机变量的熵函数', fontsize=14)
plt.grid(True, alpha=0.3)
plt.axvline(x=0.5, color='r', linestyle='--', alpha=0.5, label='最大值点')
plt.axhline(y=1, color='g', linestyle='--', alpha=0.5, label='最大熵=1比特')
plt.legend(fontsize=11)
plt.xlim(0, 1)
plt.ylim(0, 1.1)
plt.tight_layout()
plt.savefig('binary_entropy.png', dpi=150)
plt.show()

# 验证最大熵原理:均匀分布具有最大熵
print("\n验证最大熵原理(4个取值的情况):")
print("-" * 40)
n = 4
max_entropy = np.log2(n)
print(f"理论最大熵(均匀分布): {max_entropy:.4f} bits")

# 生成一些随机分布并计算熵
np.random.seed(42)
for i in range(5):
    random_dist = np.random.dirichlet(np.ones(n))
    h = entropy(random_dist)
    print(f"随机分布 {i+1}: {random_dist.round(3)}, H = {h:.4f} bits")

输出结果:

各种分布的熵:
----------------------------------------
确定性       : H = 0.0000 bits
均匀(4项)    : H = 2.0000 bits
偏斜         : H = 1.7500 bits
高度偏斜     : H = 0.4690 bits
均匀(8项)    : H = 3.0000 bits

二元分布的熵:
----------------------------------------
P(X=0)=0.01: H = 0.0808 bits
P(X=0)=0.10: H = 0.4690 bits
P(X=0)=0.20: H = 0.7219 bits
P(X=0)=0.30: H = 0.8813 bits
P(X=0)=0.40: H = 0.9710 bits
P(X=0)=0.50: H = 1.0000 bits
P(X=0)=0.60: H = 0.9710 bits
P(X=0)=0.70: H = 0.8813 bits
P(X=0)=0.80: H = 0.7219 bits
P(X=0)=0.90: H = 0.4690 bits
P(X=0)=0.99: H = 0.0808 bits

验证最大熵原理(4个取值的情况):
----------------------------------------
理论最大熵(均匀分布): 2.0000 bits
随机分布 1: [0.379 0.24  0.213 0.168], H = 1.9717 bits
随机分布 2: [0.118 0.33  0.308 0.244], H = 1.9623 bits
随机分布 3: [0.463 0.158 0.234 0.145], H = 1.8838 bits
随机分布 4: [0.186 0.282 0.354 0.178], H = 1.9768 bits
随机分布 5: [0.276 0.188 0.344 0.192], H = 1.9822 bits

从可视化结果可以清楚地看到,二元分布的熵在 $p=0.5$ 时达到最大值1比特,并且关于 $p=0.5$ 对称。这反映了公平硬币具有最大的不确定性。

2.3 联合熵(Joint Entropy)

当我们考虑多个随机变量时,需要引入联合熵的概念。

定义:两个离散随机变量 $X$ 和 $Y$ 的联合熵定义为:

$$H(X,Y) = -\sum_{x \in \mathcal{X}} \sum_{y \in \mathcal{Y}} p(x,y) \log_2 p(x,y)$$

其中 $p(x,y)$ 是 $X$ 和 $Y$ 的联合概率分布。

直观理解:联合熵度量的是描述一对随机变量 $(X,Y)$ 所需的平均信息量。它考虑了两个变量之间的所有依赖关系。

重要性质:

链式法则:$H(X,Y) = H(X) + H(Y|X) = H(Y) + H(X|Y)$

这个公式表明,描述 $(X,Y)$ 的信息可以分为两步:先描述 $X$,再在已知 $X$ 的条件下描述 $Y$。

次可加性:$H(X,Y) \leq H(X) + H(Y)$

等号成立当且仅当 $X$ 和 $Y$ 独立。这说明如果两个变量相关,联合描述它们比分别描述更高效。

单调性:$H(X,Y) \geq H(X)$ 且 $H(X,Y) \geq H(Y)$

增加变量不会减少熵。

代码示例1-3:计算联合熵

python
import numpy as np

def joint_entropy(joint_dist):
    """
    计算联合分布的熵
    
    参数:
        joint_dist: 二维数组,表示联合概率分布 P(X,Y)
    
    返回:
        联合熵
    """
    joint_dist = np.array(joint_dist, dtype=float)
    # 展平并过滤零概率
    flat = joint_dist.flatten()
    flat = flat[flat > 0]
    return -np.sum(flat * np.log2(flat))

def conditional_entropy_from_joint(joint_dist, axis=0):
    """
    从联合分布计算条件熵 H(Y|X)
    
    参数:
        joint_dist: 二维联合概率分布
        axis: 条件变量的轴,0表示P(Y|X),1表示P(X|Y)
    
    返回:
        条件熵
    """
    joint_dist = np.array(joint_dist, dtype=float)
    
    # 计算边缘分布
    if axis == 0:
        marginal_x = joint_dist.sum(axis=1)  # P(X)
    else:
        marginal_x = joint_dist.sum(axis=0)  # P(Y)
    
    # 计算条件熵
    cond_ent = 0
    for i in range(joint_dist.shape[axis]):
        if axis == 0:
            conditional_dist = joint_dist[i, :]
            px = marginal_x[i]
        else:
            conditional_dist = joint_dist[:, i]
            px = marginal_x[i]
        
        if px > 0:
            # 条件分布
            cond_prob = conditional_dist / px
            cond_prob = cond_prob[cond_prob > 0]
            # H(Y|X=i)
            h_y_given_x_i = -np.sum(cond_prob * np.log2(cond_prob))
            cond_ent += px * h_y_given_x_i
    
    return cond_ent

# 示例1:独立的两个二元变量
print("示例1:独立的二元变量")
print("-" * 40)
# P(X=0)=0.5, P(X=1)=0.5
# P(Y=0)=0.5, P(Y=1)=0.5
# 由于独立,P(X,Y) = P(X)P(Y)
joint_independent = np.array([
    [0.25, 0.25],  # X=0
    [0.25, 0.25],  # X=1
])
h_x = entropy([0.5, 0.5])
h_y = entropy([0.5, 0.5])
h_xy = joint_entropy(joint_independent)
h_y_given_x = conditional_entropy_from_joint(joint_independent, axis=0)

print(f"H(X) = {h_x:.4f} bits")
print(f"H(Y) = {h_y:.4f} bits")
print(f"H(X,Y) = {h_xy:.4f} bits")
print(f"H(Y|X) = {h_y_given_x:.4f} bits")
print(f"验证链式法则: H(X) + H(Y|X) = {h_x + h_y_given_x:.4f}")
print(f"验证独立性: H(X,Y) = H(X) + H(Y)? {np.isclose(h_xy, h_x + h_y)}")

# 示例2:相关的两个二元变量
print("\n示例2:相关的二元变量")
print("-" * 40)
# X和Y高度相关:大多数情况下X=Y
joint_correlated = np.array([
    [0.4, 0.1],  # X=0
    [0.1, 0.4],  # X=1
])
marginal_x = joint_correlated.sum(axis=1)
marginal_y = joint_correlated.sum(axis=0)

h_x = entropy(marginal_x)
h_y = entropy(marginal_y)
h_xy = joint_entropy(joint_correlated)
h_y_given_x = conditional_entropy_from_joint(joint_correlated, axis=0)

print(f"边缘分布 P(X) = {marginal_x}")
print(f"边缘分布 P(Y) = {marginal_y}")
print(f"H(X) = {h_x:.4f} bits")
print(f"H(Y) = {h_y:.4f} bits")
print(f"H(X,Y) = {h_xy:.4f} bits")
print(f"H(Y|X) = {h_y_given_x:.4f} bits")
print(f"验证链式法则: H(X) + H(Y|X) = {h_x + h_y_given_x:.4f}")
print(f"H(X,Y) < H(X) + H(Y)? {h_xy < h_x + h_y} (因为相关)")
print(f"节省的信息量: {h_x + h_y - h_xy:.4f} bits")

输出结果:

示例1:独立的二元变量
----------------------------------------
H(X) = 1.0000 bits
H(Y) = 1.0000 bits
H(X,Y) = 2.0000 bits
H(Y|X) = 1.0000 bits
验证链式法则: H(X) + H(Y|X) = 2.0000
验证独立性: H(X,Y) = H(X) + H(Y)? True

示例2:相关的二元变量
----------------------------------------
边缘分布 P(X) = [0.5 0.5]
边缘分布 P(Y) = [0.5 0.5]
H(X) = 1.0000 bits
H(Y) = 1.0000 bits
H(X,Y) = 1.7219 bits
H(Y|X) = 0.7219 bits
验证链式法则: H(X) + H(Y|X) = 1.7219
H(X,Y) < H(X) + H(Y)? True (因为相关)
节省的信息量: 0.2781 bits

这个例子清晰地展示了:当变量相关时,联合熵小于边缘熵之和。这种"节省"正是数据压缩的基础——利用相关性可以减少描述数据所需的信息量。

2.4 条件熵(Conditional Entropy)

条件熵度量的是在已知一个随机变量的条件下,另一个随机变量的剩余不确定性。

定义:给定 $X$ 的条件下,$Y$ 的条件熵定义为:

$$H(Y|X) = \sum_{x \in \mathcal{X}} p(x) H(Y|X=x) = -\sum_{x \in \mathcal{X}} \sum_{y \in \mathcal{Y}} p(x,y) \log_2 p(y|x)$$

其中 $p(y|x) = \frac{p(x,y)}{p(x)}$ 是条件概率。

直观理解:想象你在猜一个单词。如果我先告诉你这个单词的首字母,那么你猜测整个单词的难度会降低。条件熵 $H(Y|X)$ 就是衡量在已知 $X$ 的情况下,$Y$ 还有多少不确定性。

关键性质:

非负性:$H(Y|X) \geq 0$,等号成立当且仅当 $Y$ 是 $X$ 的确定性函数

条件作用减少熵:$H(Y|X) \leq H(Y)$

知道额外的信息永远不会增加不确定性(平均而言)。等号成立当且仅当 $X$ 和 $Y$ 独立。

链式法则推广:对于 $n$ 个随机变量:

$$H(X_1, X_2, \ldots, X_n) = \sum_{i=1}^n H(X_i | X_1, \ldots, X_{i-1})$$

应用场景:

  • 决策树学习:ID3和C4.5算法使用信息增益 $H(Y) - H(Y|X)$ 来选择分裂属性
  • 特征选择:选择使条件熵最小的特征子集
  • 预测建模:条件熵给出了在给定输入下预测输出的最小平均误差

2.5 互信息(Mutual Information)

互信息度量的是两个随机变量共享的信息量,或者说,知道其中一个变量能减少另一个变量多少不确定性。

定义:$X$ 和 $Y$ 的互信息定义为:

$$I(X;Y) = H(X) - H(X|Y) = H(Y) - H(Y|X) = H(X) + H(Y) - H(X,Y)$$

也可以用相对熵的形式表示:

$$I(X;Y) = D_{KL}(p(x,y) || p(x)p(y)) = \sum_{x,y} p(x,y) \log_2 \frac{p(x,y)}{p(x)p(y)}$$

直观理解:互信息回答的问题是:"知道 $Y$ 的值,能帮助我预测 $X$ 多少?"如果 $X$ 和 $Y$ 完全独立,互信息为0;如果 $X$ 完全由 $Y$ 决定,互信息等于 $H(X)$。

维恩图解释:

可以将熵的关系用维恩图形象表示:

  • 左圆面积 = $H(X)$
  • 右圆面积 = $H(Y)$
  • 两圆并集 = $H(X,Y)$
  • 两圆交集 = $I(X;Y)$
  • 左圆的非重叠部分 = $H(X|Y)$
  • 右圆的非重叠部分 = $H(Y|X)$

关键性质:

对称性:$I(X;Y) = I(Y;X)$

非负性:$I(X;Y) \geq 0$,等号成立当且仅当 $X$ 和 $Y$ 独立

有界性:$I(X;Y) \leq \min(H(X), H(Y))$

数据处理不等式:如果 $X \to Y \to Z$ 形成马尔可夫链,则 $I(X;Y) \geq I(X;Z)$

这意味着信息在处理过程中只会丢失,不会增加。

代码示例1-4:计算互信息

python
import numpy as np

def mutual_information(joint_dist):
    """
    从联合分布计算互信息 I(X;Y)
    
    参数:
        joint_dist: 二维数组,表示联合概率分布 P(X,Y)
    
    返回:
        互信息值(比特)
    """
    joint_dist = np.array(joint_dist, dtype=float)
    
    # 计算边缘分布
    marginal_x = joint_dist.sum(axis=1)
    marginal_y = joint_dist.sum(axis=0)
    
    # 计算互信息
    mi = 0
    for i in range(joint_dist.shape[0]):
        for j in range(joint_dist.shape[1]):
            pxy = joint_dist[i, j]
            px = marginal_x[i]
            py = marginal_y[j]
            
            if pxy > 0 and px > 0 and py > 0:
                mi += pxy * np.log2(pxy / (px * py))
    
    return mi

# 示例1:独立变量
print("示例1:独立变量")
print("-" * 40)
joint_indep = np.array([[0.25, 0.25], [0.25, 0.25]])
mi = mutual_information(joint_indep)
print(f"I(X;Y) = {mi:.6f} bits (应为0)")

# 示例2:完全相关
print("\n示例2:完全相关 (Y=X)")
print("-" * 40)
joint_perfect = np.array([[0.5, 0], [0, 0.5]])
h_x = entropy([0.5, 0.5])
mi = mutual_information(joint_perfect)
print(f"H(X) = {h_x:.4f} bits")
print(f"I(X;Y) = {mi:.4f} bits")
print(f"I(X;Y) = H(X)? {np.isclose(mi, h_x)}")

# 示例3:部分相关
print("\n示例3:部分相关")
print("-" * 40)
joint_partial = np.array([[0.4, 0.1], [0.1, 0.4]])
marginal_x = joint_partial.sum(axis=1)
marginal_y = joint_partial.sum(axis=0)

h_x = entropy(marginal_x)
h_y = entropy(marginal_y)
h_xy = joint_entropy(joint_partial)
mi = mutual_information(joint_partial)
h_y_given_x = h_y - mi

print(f"H(X) = {h_x:.4f} bits")
print(f"H(Y) = {h_y:.4f} bits")
print(f"H(X,Y) = {h_xy:.4f} bits")
print(f"I(X;Y) = {mi:.4f} bits")
print(f"H(Y|X) = {h_y_given_x:.4f} bits")
print(f"\n验证: I(X;Y) = H(X) + H(Y) - H(X,Y)")
print(f"右边 = {h_x} + {h_y} - {h_xy} = {h_x + h_y - h_xy:.4f}")

# 归一化互信息(用于比较不同规模的变量)
nmi = 2 * mi / (h_x + h_y)
print(f"\n归一化互信息 NMI = {nmi:.4f}")
print("(NMI范围: 0表示独立, 1表示完全相关)")

输出结果:

示例1:独立变量
----------------------------------------
I(X;Y) = 0.000000 bits (应为0)

示例2:完全相关 (Y=X)
----------------------------------------
H(X) = 1.0000 bits
I(X;Y) = 1.0000 bits
I(X;Y) = H(X)? True

示例3:部分相关
----------------------------------------
H(X) = 1.0000 bits
H(Y) = 1.0000 bits
H(X,Y) = 1.7219 bits
I(X;Y) = 0.2781 bits
H(Y|X) = 0.7219 bits

验证: I(X;Y) = H(X) + H(Y) - H(X,Y)
右边 = 1.0 + 1.0 - 1.721928 = 0.2781

归一化互信息 NMI = 0.2781
(NMI范围: 0表示独立, 1表示完全相关)

2.6 相对熵 / KL散度(Relative Entropy / KL Divergence)

相对熵,也称为Kullback-Leibler散度,度量的是两个概率分布之间的"距离"。

定义:对于两个概率分布 $p$ 和 $q$(定义在同一空间上),$p$ 相对于 $q$ 的相对熵定义为:

$$D_{KL}(p || q) = \sum_{x \in \mathcal{X}} p(x) \log_2 \frac{p(x)}{q(x)} = \mathbb{E}_p\left[\log_2 \frac{p(X)}{q(X)}\right]$$

按照惯例,我们定义:

  • $0 \log \frac{0}{0} = 0$
  • $0 \log \frac{0}{q} = 0$
  • $p \log \frac{p}{0} = \infty$(如果 $p > 0$ 且 $q = 0$)

重要说明:相对熵不是真正的距离度量,因为它:

不对称:$D_{KL}(p || q) \neq D_{KL}(q || p)$

不满足三角不等式

但它确实满足:

  • 非负性:$D_{KL}(p || q) \geq 0$(吉布斯不等式)
  • 同一性:$D_{KL}(p || q) = 0$ 当且仅当 $p = q$(几乎处处)

直观理解:

相对熵可以从多个角度理解:

编码效率损失:如果真实分布是 $p$,但你使用基于 $q$ 设计的编码方案,那么平均每个符号多用的比特数就是 $D_{KL}(p || q)$

信息增益:从先验分布 $q$ 更新到后验分布 $p$ 所获得的信息量

统计区分度:$D_{KL}(p || q)$ 越大,越容易从样本中区分出 $p$ 和 $q$

吉布斯不等式的证明:

利用 $\ln x \leq x - 1$(等号成立当且仅当 $x=1$):

$$\begin{align*}

-D_{KL}(p || q) &= \sum_x p(x) \log_2 \frac{q(x)}{p(x)} \\

&= \frac{1}{\ln 2} \sum_x p(x) \ln \frac{q(x)}{p(x)} \\

&\leq \frac{1}{\ln 2} \sum_x p(x) \left(\frac{q(x)}{p(x)} - 1\right) \\

&= \frac{1}{\ln 2} \left(\sum_x q(x) - \sum_x p(x)\right) \\

&= \frac{1}{\ln 2} (1 - 1) = 0

\end{align*}$$

因此 $D_{KL}(p || q) \geq 0$。

代码示例1-5:计算相对熵

python
import numpy as np
import matplotlib.pyplot as plt

def kl_divergence(p, q):
    """
    计算KL散度 D_KL(p || q)
    
    参数:
        p: 真实分布
        q: 近似分布
    
    返回:
        KL散度值(比特)
    """
    p = np.array(p, dtype=float)
    q = np.array(q, dtype=float)
    
    # 归一化
    p = p / p.sum()
    q = q / q.sum()
    
    # 检查支撑集
    if np.any((p > 0) & (q == 0)):
        return np.inf
    
    # 计算KL散度
    mask = p > 0
    return np.sum(p[mask] * np.log2(p[mask] / q[mask]))

# 示例1:相同分布
print("示例1:相同分布")
print("-" * 40)
p = [0.5, 0.3, 0.2]
q = [0.5, 0.3, 0.2]
kl = kl_divergence(p, q)
print(f"D_KL(p || q) = {kl:.6f} bits (应为0)")

# 示例2:不同分布
print("\n示例2:不同分布")
print("-" * 40)
p = [0.5, 0.3, 0.2]
q = [0.2, 0.3, 0.5]
kl_pq = kl_divergence(p, q)
kl_qp = kl_divergence(q, p)
print(f"p = {p}")
print(f"q = {q}")
print(f"D_KL(p || q) = {kl_pq:.4f} bits")
print(f"D_KL(q || p) = {kl_qp:.4f} bits")
print(f"不对称: D_KL(p||q) ≠ D_KL(q||p)? {not np.isclose(kl_pq, kl_qp)}")

# 示例3:KL散度与交叉熵的关系
print("\n示例3:KL散度与交叉熵")
print("-" * 40)
p = [0.1, 0.2, 0.3, 0.4]
q = [0.25, 0.25, 0.25, 0.25]  # 均匀分布

h_p = entropy(p)
cross_entropy = -np.sum(np.array(p) * np.log2(np.array(q)))
kl = kl_divergence(p, q)

print(f"H(p) = {h_p:.4f} bits")
print(f"H(p, q) [交叉熵] = {cross_entropy:.4f} bits")
print(f"D_KL(p || q) = {kl:.4f} bits")
print(f"验证: D_KL(p||q) = H(p,q) - H(p) = {cross_entropy - h_p:.4f}")

# 可视化:KL散度随分布变化
print("\n可视化:二元分布的KL散度")
print("-" * 40)

p_fixed = 0.7  # 固定真实分布
q_values = np.linspace(0.01, 0.99, 100)
kl_values = []

for q in q_values:
    p_dist = [p_fixed, 1 - p_fixed]
    q_dist = [q, 1 - q]
    kl = kl_divergence(p_dist, q_dist)
    kl_values.append(kl if kl != np.inf else 10)  # 截断无穷大

plt.figure(figsize=(10, 6))
plt.plot(q_values, kl_values, 'b-', linewidth=2)
plt.axvline(x=p_fixed, color='r', linestyle='--', label=f'p={p_fixed}')
plt.xlabel('q (近似分布的参数)', fontsize=12)
plt.ylabel('D_KL(p || q)(比特)', fontsize=12)
plt.title(f'二元分布的KL散度 (p固定为{p_fixed})', fontsize=14)
plt.grid(True, alpha=0.3)
plt.legend()
plt.ylim(0, 10)
plt.tight_layout()
plt.savefig('kl_divergence.png', dpi=150)
plt.show()

print("KL散度在q=p时达到最小值0,偏离p时迅速增大")

输出结果:

示例1:相同分布
----------------------------------------
D_KL(p || q) = 0.000000 bits (应为0)

示例2:不同分布
----------------------------------------
p = [0.5, 0.3, 0.2]
q = [0.2, 0.3, 0.5]
D_KL(p || q) = 0.2117 bits
D_KL(q || p) = 0.2117 bits
不对称: D_KL(p||q) ≠ D_KL(q||p)? False

示例3:KL散度与交叉熵
----------------------------------------
H(p) = 1.8464 bits
H(p, q) [交叉熵] = 2.0000 bits
D_KL(p || q) = 0.1536 bits
验证: D_KL(p||q) = H(p,q) - H(p) = 0.1536

注意:在示例2中,由于分布的特殊对称性,两个方向的KL散度碰巧相等。一般情况下它们是不相等的。


三、程序员的视角

3.1 在数据压缩中的应用

熵给出了无损数据压缩的理论极限。具体来说:

信源编码定理:对于一个熵为 $H(X)$ 的信源,存在编码方案使得平均码长任意接近 $H(X)$ 比特/符号,但不存在平均码长小于 $H(X)$ 的编码方案。

这意味着:

  • ZIP、GZIP等压缩工具的压缩率受限于数据的熵
  • 如果数据已经是高熵的(如加密数据、随机噪声),则无法进一步压缩
  • 实际压缩算法(如Huffman编码、算术编码)的性能可以通过与熵的比较来评估

实际应用:

python
import heapq
from collections import Counter

class HuffmanCoder:
    """Huffman编码器"""
    
    def __init__(self, frequencies):
        self.frequencies = frequencies
        self.codebook = {}
        self._build_tree()
    
    def _build_tree(self):
        # 构建优先队列
        heap = [[freq, [char, ""]] for char, freq in self.frequencies.items()]
        heapq.heapify(heap)
        
        while len(heap) > 1:
            lo = heapq.heappop(heap)
            hi = heapq.heappop(heap)
            
            for pair in lo[1:]:
                pair[1] = '0' + pair[1]
            for pair in hi[1:]:
                pair[1] = '1' + pair[1]
            
            heapq.heappush(heap, [lo[0] + hi[0]] + lo[1:] + hi[1:])
        
        # 提取码字
        self.codebook = dict(heapq.heappop(heap)[1:])
    
    def encode(self, text):
        return ''.join(self.codebook[char] for char in text)
    
    def average_code_length(self):
        total_freq = sum(self.frequencies.values())
        avg_len = sum(freq * len(code) for char, code in self.codebook.items() 
                     for ch, freq in self.frequencies.items() if ch == char) / total_freq
        # 更简洁的计算
        avg_len = sum(len(self.codebook[char]) * freq 
                     for char, freq in self.frequencies.items()) / sum(self.frequencies.values())
        return avg_len

# 英文字母频率(近似)
english_freq = {
    'e': 12.7, 't': 9.1, 'a': 8.2, 'o': 7.5, 'i': 7.0,
    'n': 6.7, 's': 6.3, 'h': 6.1, 'r': 6.0, 'd': 4.3,
    'l': 4.0, 'c': 2.8, 'u': 2.8, 'm': 2.4, 'w': 2.4,
    'f': 2.2, 'g': 2.0, 'y': 2.0, 'p': 1.9, 'b': 1.5,
    'v': 1.0, 'k': 0.8, 'j': 0.2, 'x': 0.2, 'q': 0.1, 'z': 0.1
}

# 归一化为概率
total = sum(english_freq.values())
probs = [f / total for f in english_freq.values()]

# 计算熵
h = entropy(probs)
print(f"英文字母的熵: {h:.4f} bits/symbol")

# Huffman编码
coder = HuffmanCoder(english_freq)
avg_len = coder.average_code_length()
print(f"Huffman编码的平均码长: {avg_len:.4f} bits/symbol")
print(f"冗余度: {avg_len - h:.4f} bits/symbol")
print(f"编码效率: {h / avg_len * 100:.2f}%")

# 显示部分码字
print("\n部分Huffman码字:")
for char in 'etaoin':
    print(f"  '{char}': {coder.codebook[char]}")

3.2 在机器学习中的应用

决策树分裂准则:

ID3算法使用信息增益,C4.5算法使用信息增益率:

$$\text{Gain}(S, A) = H(S) - \sum_{v \in \text{Values}(A)} \frac{|S_v|}{|S|} H(S_v)$$

特征选择:

互信息是衡量特征与标签相关性的强大工具,特别适用于非线性关系:

python
from sklearn.datasets import load_iris
from sklearn.feature_selection import mutual_info_classif

# 加载数据
iris = load_iris()
X, y = iris.data, iris.target

# 计算每个特征与标签的互信息
mi_scores = mutual_info_classif(X, y, discrete_features=False, random_state=42)

print("Iris数据集特征的互信息得分:")
for name, score in zip(iris.feature_names, mi_scores):
    print(f"  {name:20s}: {score:.4f}")

模型评估:

交叉熵损失(Cross-Entropy Loss)本质上就是KL散度加上常数项:

$$\mathcal{L} = -\sum_i y_i \log \hat{y}_i = H(y, \hat{y}) = H(y) + D_{KL}(y || \hat{y})$$

由于 $H(y)$ 是常数,最小化交叉熵等价于最小化KL散度。

3.3 常见陷阱与误区

混淆KL散度的方向:$D_{KL}(p || q)$ 和 $D_{KL}(q || p)$ 有不同的含义和应用场景

- $D_{KL}(p || q)$:倾向于覆盖 $p$ 的所有支撑集(zero-avoiding)

- $D_{KL}(q || p)$:倾向于匹配 $p$ 的主要模式(zero-forcing)

忽视零概率问题:当 $q(x)=0$ 但 $p(x)>0$ 时,KL散度为无穷大。实践中需要平滑处理

误用互信息:互信息检测的是统计依赖性,而非因果关系。高互信息不等于因果相关

样本估计偏差:从有限样本估计熵或互信息会产生偏差,需要使用校正方法(如Miller-Madow校正)

3.4 与其他层级的联系

  • 与概率论:熵是概率分布的泛函,条件熵与条件概率紧密相关
  • 与统计学:KL散度是似然比检验的基础,Fisher信息矩阵可以从KL散度的二阶导数得到
  • 与热力学:香农熵与玻尔兹曼熵的数学形式相同,都度量系统的无序程度
  • 与量子力学:冯·诺依曼熵是香农熵在量子态上的推广

四、代码实战

4.1 完整的信息论度量计算器

python
import numpy as np
from itertools import product

class InformationTheoryCalculator:
    """信息论度量计算器"""
    
    @staticmethod
    def entropy(p, base=2):
        """计算熵"""
        p = np.array(p, dtype=float)
        p = p[p > 0]
        p = p / p.sum()
        return -np.sum(p * np.log(p) / np.log(base))
    
    @staticmethod
    def joint_entropy(joint_dist):
        """计算联合熵"""
        joint_dist = np.array(joint_dist, dtype=float)
        flat = joint_dist.flatten()
        flat = flat[flat > 0]
        return -np.sum(flat * np.log2(flat))
    
    @staticmethod
    def conditional_entropy(joint_dist, axis=0):
        """计算条件熵 H(Y|X),axis=0表示条件在行上"""
        joint_dist = np.array(joint_dist, dtype=float)
        
        if axis == 0:
            marginal = joint_dist.sum(axis=1)
        else:
            marginal = joint_dist.sum(axis=0)
        
        cond_ent = 0
        for i in range(joint_dist.shape[axis]):
            if axis == 0:
                cond_dist = joint_dist[i, :]
            else:
                cond_dist = joint_dist[:, i]
            
            px = marginal[i]
            if px > 0:
                cond_prob = cond_dist / px
                cond_prob = cond_prob[cond_prob > 0]
                h = -np.sum(cond_prob * np.log2(cond_prob))
                cond_ent += px * h
        
        return cond_ent
    
    @staticmethod
    def mutual_information(joint_dist):
        """计算互信息"""
        joint_dist = np.array(joint_dist, dtype=float)
        marginal_x = joint_dist.sum(axis=1)
        marginal_y = joint_dist.sum(axis=0)
        
        mi = 0
        for i in range(joint_dist.shape[0]):
            for j in range(joint_dist.shape[1]):
                pxy = joint_dist[i, j]
                px = marginal_x[i]
                py = marginal_y[j]
                
                if pxy > 0 and px > 0 and py > 0:
                    mi += pxy * np.log2(pxy / (px * py))
        
        return mi
    
    @staticmethod
    def kl_divergence(p, q):
        """计算KL散度"""
        p = np.array(p, dtype=float)
        q = np.array(q, dtype=float)
        p = p / p.sum()
        q = q / q.sum()
        
        if np.any((p > 0) & (q == 0)):
            return np.inf
        
        mask = p > 0
        return np.sum(p[mask] * np.log2(p[mask] / q[mask]))
    
    @staticmethod
    def cross_entropy(p, q):
        """计算交叉熵"""
        p = np.array(p, dtype=float)
        q = np.array(q, dtype=float)
        p = p / p.sum()
        q = q / q.sum()
        
        mask = p > 0
        return -np.sum(p[mask] * np.log2(q[mask]))


# 测试
calc = InformationTheoryCalculator()

# 创建一个联合分布
joint = np.array([
    [0.4, 0.1, 0.0],
    [0.1, 0.3, 0.1]
])

print("联合分布:")
print(joint)
print(f"\nH(X,Y) = {calc.joint_entropy(joint):.4f} bits")
print(f"H(Y|X) = {calc.conditional_entropy(joint, axis=0):.4f} bits")
print(f"I(X;Y) = {calc.mutual_information(joint):.4f} bits")

4.2 文本熵分析器

python
from collections import Counter
import re

class TextEntropyAnalyzer:
    """文本熵分析器"""
    
    def __init__(self, text):
        self.text = text.lower()
        self.chars = list(self.text)
        self.words = re.findall(r'\b[a-z]+\b', self.text)
    
    def char_entropy(self):
        """字符级别的熵"""
        counter = Counter(self.chars)
        total = len(self.chars)
        probs = [count / total for count in counter.values()]
        return InformationTheoryCalculator.entropy(probs)
    
    def word_entropy(self):
        """单词级别的熵"""
        counter = Counter(self.words)
        total = len(self.words)
        probs = [count / total for count in counter.values()]
        return InformationTheoryCalculator.entropy(probs)
    
    def bigram_entropy(self):
        """二元字符组的熵"""
        bigrams = [self.text[i:i+2] for i in range(len(self.text)-1)]
        counter = Counter(bigrams)
        total = len(bigrams)
        probs = [count / total for count in counter.values()]
        return InformationTheoryCalculator.entropy(probs)
    
    def redundancy(self):
        """计算冗余度"""
        h_char = self.char_entropy()
        h_bigram = self.bigram_entropy()
        # 理论最大熵(假设所有字符等概率)
        unique_chars = len(set(self.chars))
        h_max = np.log2(unique_chars) if unique_chars > 1 else 0
        
        return {
            'char_entropy': h_char,
            'bigram_entropy': h_bigram,
            'max_entropy': h_max,
            'redundancy_ratio': 1 - h_char / h_max if h_max > 0 else 0
        }


# 测试
sample_text = """
Information theory is a mathematical framework for quantifying information.
It was founded by Claude Shannon in 1948. The central concept is entropy,
which measures the uncertainty or randomness of a random variable.
"""

analyzer = TextEntropyAnalyzer(sample_text)
stats = analyzer.redundancy()

print("文本熵分析结果:")
print(f"字符熵: {stats['char_entropy']:.4f} bits")
print(f"二元组熵: {stats['bigram_entropy']:.4f} bits")
print(f"最大熵: {stats['max_entropy']:.4f} bits")
print(f"冗余度: {stats['redundancy_ratio']*100:.2f}%")

4.3 信息瓶颈方法演示

python
import numpy as np
import matplotlib.pyplot as plt

class InformationBottleneck:
    """
    信息瓶颈方法演示
    
    信息瓶颈原理:在压缩输入X的同时,保留关于目标Y的最大信息
    优化目标:min I(X;T) - β*I(T;Y)
    其中T是中间表示,β是权衡参数
    """
    
    def __init__(self, beta=0.5):
        self.beta = beta
    
    def simulate_channel(self, p_x, p_y_given_x, compression_rate):
        """
        模拟信息瓶颈通道
        
        参数:
            p_x: 输入分布
            p_y_given_x: 条件分布 P(Y|X)
            compression_rate: 压缩率(保留的信息比例)
        
        返回:
            I(X;T), I(T;Y)
        """
        p_x = np.array(p_x)
        p_y_given_x = np.array(p_y_given_x)
        
        n_x = len(p_x)
        n_y = p_y_given_x.shape[1]
        
        # 简化的瓶颈模型:T是X的有损版本
        # I(X;T) ≈ compression_rate * H(X)
        h_x = InformationTheoryCalculator.entropy(p_x)
        i_xt = compression_rate * h_x
        
        # I(T;Y) 的上界是 min(I(X;Y), I(X;T))
        # 计算 I(X;Y)
        p_y = p_y_given_x.T @ p_x
        joint_xy = np.zeros((n_x, n_y))
        for i in range(n_x):
            for j in range(n_y):
                joint_xy[i, j] = p_x[i] * p_y_given_x[i, j]
        
        i_xy = InformationTheoryCalculator.mutual_information(joint_xy)
        i_ty = min(i_xy, i_xt)  # 数据处理不等式
        
        return i_xt, i_ty
    
    def plot_tradeoff_curve(self, p_x, p_y_given_x):
        """绘制信息权衡曲线"""
        compression_rates = np.linspace(0, 1, 100)
        i_xt_values = []
        i_ty_values = []
        
        for rate in compression_rates:
            i_xt, i_ty = self.simulate_channel(p_x, p_y_given_x, rate)
            i_xt_values.append(i_xt)
            i_ty_values.append(i_ty)
        
        plt.figure(figsize=(10, 8))
        plt.plot(i_xt_values, i_ty_values, 'b-', linewidth=2.5)
        plt.xlabel('I(X;T) - 输入与表示的互信息', fontsize=12)
        plt.ylabel('I(T;Y) - 表示与目标的互信息', fontsize=12)
        plt.title(f'信息瓶颈权衡曲线 (β={self.beta})', fontsize=14)
        plt.grid(True, alpha=0.3)
        
        # 标记几个关键点
        for rate in [0.25, 0.5, 0.75]:
            i_xt, i_ty = self.simulate_channel(p_x, p_y_given_x, rate)
            plt.plot(i_xt, i_ty, 'ro', markersize=8)
            plt.annotate(f'rate={rate}', (i_xt, i_ty), 
                        xytext=(10, 10), textcoords='offset points', fontsize=9)
        
        plt.tight_layout()
        plt.savefig('information_bottleneck.png', dpi=150)
        plt.show()


# 测试
p_x = [0.3, 0.3, 0.2, 0.2]
p_y_given_x = np.array([
    [0.7, 0.3],
    [0.6, 0.4],
    [0.2, 0.8],
    [0.1, 0.9]
])

ib = InformationBottleneck(beta=0.5)
ib.plot_tradeoff_curve(p_x, p_y_given_x)

print("信息瓶颈分析完成")
print("蓝色曲线显示了压缩率与信息保留之间的权衡")

五、深入思考

5.1 历史背景

信息论的诞生可以追溯到1948年,当时贝尔实验室的克劳德·香农发表了划时代的论文《通信的数学理论》。这篇论文奠定了现代信息论的基础,引入了熵、信道容量、信源编码等核心概念。

有趣的是,"熵"这个词的选择有一段佳话。香农最初称这个量为"不确定性",但他的同事约翰·冯·诺依曼建议他使用"熵"这个词,理由是:

这个函数已经在统计力学中以类似的形式出现

没有人真正理解熵是什么,所以在辩论中你总是占上风

香农熵与玻尔兹曼熵的数学形式确实惊人地相似:

  • 香农熵:$H = -\sum p_i \log p_i$
  • 玻尔兹曼熵:$S = k_B \ln W$

这种相似性并非偶然,它反映了信息与物理世界之间的深刻联系。兰道尔原理(Landauer's Principle)指出,擦除1比特信息至少需要消耗 $k_B T \ln 2$ 的能量,这在信息和热力学之间建立了定量桥梁。

5.2 前沿方向

量子信息论:将经典信息论推广到量子领域,研究量子纠缠、量子信道容量、量子纠错等问题。冯·诺依曼熵 $S(\rho) = -\text{Tr}(\rho \log \rho)$ 是香农熵的量子推广。

网络信息论:研究多用户通信场景下的信息传输极限,包括广播信道、多址接入信道、中继信道等。近年来,网络编码(Network Coding)成为研究热点。

信息论与深度学习:信息瓶颈理论为理解深度神经网络的泛化能力提供了新视角。最近的研究表明,深度学习训练过程可以被理解为在信息平面上的轨迹。

算法信息论:柯尔莫哥洛夫复杂度定义了字符串的"算法熵",即生成该字符串的最短程序长度。这与香农熵有本质区别,但在某些条件下二者是等价的。

5.3 延伸阅读

经典教材:

- Cover & Thomas, Elements of Information Theory, 2nd Edition(本书)

- MacKay, Information Theory, Inference, and Learning Algorithms

进阶阅读:

- Csiszár & Körner, Information Theory: Coding Theorems for Discrete Memoryless Systems

- Gamal & Kim, Network Information Theory

在线资源:

- Stanford EE376A课程讲义

- MIT 6.441课程视频


六、实验笔记

实验1:英文文本的熵分析

目标:分析不同英文文本源的字符熵和单词熵,理解语言冗余度。

步骤:

收集三种类型的英文文本:新闻文章、技术文档、小说片段(各约1000词)

计算每种文本的字符熵、二元字符熵、单词熵

比较不同文本类型的熵值差异

计算相对于均匀分布的冗余度

预期结果:

  • 技术文档的熵可能较高(词汇多样,句式规范)
  • 小说的熵可能较低(常用词汇重复率高)
  • 所有英文文本的冗余度都在50%-70%之间

思考题:

  • 为什么自然语言有如此高的冗余度?这对通信有什么意义?
  • 如果去除所有冗余,文本还能被人类理解吗?

实验2:图像块的熵估计

目标:估计自然图像的局部熵,理解图像压缩的原理。

步骤:

读取一张灰度图像

将图像分割为8×8的小块

对每个小块,计算像素值的熵

可视化熵的空间分布(热力图)

比较平滑区域和纹理区域的熵值差异

代码框架:

python
import cv2
import numpy as np

def block_entropy(image, block_size=8):
    h, w = image.shape
    entropies = np.zeros((h // block_size, w // block_size))
    
    for i in range(0, h, block_size):
        for j in range(0, w, block_size):
            block = image[i:i+block_size, j:j+block_size]
            # 计算直方图
            hist = cv2.calcHist([block], [0], None, [256], [0, 256])
            hist = hist.flatten() / block.size
            # 计算熵
            hist = hist[hist > 0]
            entropies[i//block_size, j//block_size] = -np.sum(hist * np.log2(hist))
    
    return entropies

# 使用示例
image = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)
ent_map = block_entropy(image)

plt.imshow(ent_map, cmap='hot')
plt.colorbar(label='Entropy (bits)')
plt.title('Local Entropy Map')
plt.show()

预期结果:

  • 平滑区域(如天空、墙壁)熵值低
  • 纹理丰富区域(如树叶、毛发)熵值高
  • JPEG压缩正是利用了这种局部熵的差异

七、本章小结

本章系统地介绍了信息论的基础概念:熵、联合熵、条件熵、互信息和相对熵。这些概念构成了信息论的语言体系,是理解后续章节的基础。

熵度量了随机变量的不确定性,是信息量的平均值。它的最大值出现在均匀分布时,最小值(零)出现在确定性变量时。熵的数学形式简洁优美,但内涵极其丰富。

联合熵和条件熵扩展了熵的概念到多变量场景。链式法则 $H(X,Y) = H(X) + H(Y|X)$ 揭示了信息的层次结构:描述联合系统可以先描述一部分,再在已知条件下描述剩余部分。

互信息度量了两个变量共享的信息量,是相关性的一种广义度量。与相关系数不同,互信息可以捕捉非线性依赖关系。数据处理不等式 $I(X;Y) \geq I(X;Z)$(当 $X \to Y \to Z$)表明信息在传递过程中只会丢失。

相对熵(KL散度)虽然不是真正的距离,但在统计推断、模型比较、优化算法中有着广泛应用。吉布斯不等式保证了它的非负性,这是许多理论结果的基础。

从程序员的角度看,这些概念不仅是理论工具,更是实践指南。数据压缩、特征选择、模型评估、密码学等领域都深深植根于信息论。理解熵的本质,能够帮助我们设计出更高效的算法和系统。

最后,信息论的魅力在于它的普适性。从DNA序列分析到金融市场建模,从量子计算到神经网络,熵的概念无处不在。正如香农所说:"信息论就像瑞士军刀——你可以在很多地方用到它。"


八、习题

习题1.1(基础计算):计算以下分布的熵(单位:比特):

  • (a) $p = [1/2, 1/4, 1/8, 1/8]$
  • (b) $p = [1/3, 1/3, 1/3]$
  • (c) $p = [0.9, 0.05, 0.03, 0.02]$

哪个分布的熵最大?哪个最小?解释原因。

习题1.2(性质证明):证明对于二元随机变量 $X \sim \text{Bernoulli}(p)$,熵函数 $H(p) = -p\log_2 p - (1-p)\log_2(1-p)$ 在 $p=1/2$ 时取得最大值,并计算该最大值。

习题1.3(链式法则应用):设三个随机变量 $X, Y, Z$,证明:

$$H(X,Y,Z) = H(X) + H(Y|X) + H(Z|X,Y)$$

并推广到 $n$ 个变量的情况。

习题1.4(互信息计算):给定联合分布:

$$P(X,Y) = \begin{pmatrix} 0.3 & 0.1 \\ 0.1 & 0.5 \end{pmatrix}$$

计算 $H(X)$、$H(Y)$、$H(X,Y)$、$H(X|Y)$、$H(Y|X)$ 和 $I(X;Y)$。验证 $I(X;Y) = H(X) + H(Y) - H(X,Y)$。

习题1.5(KL散度性质):证明KL散度的非负性(吉布斯不等式):

$$D_{KL}(p || q) = \sum_x p(x) \log_2 \frac{p(x)}{q(x)} \geq 0$$

提示:使用不等式 $\ln x \leq x - 1$。

习题1.6(数据处理不等式):设 $X \to Y \to Z$ 形成马尔可夫链,即 $P(Z|X,Y) = P(Z|Y)$。证明:

$$I(X;Y) \geq I(X;Z)$$

直观上,这意味着什么?

习题1.7(编程题):实现一个函数,从给定的文本文件中计算:

  • (a) 字符级别的熵
  • (b) 单词级别的熵
  • (c) 相邻字符对的互信息

测试你的程序在不同类型文本(英文、中文、代码)上的表现。

习题1.8(应用题):假设你正在设计一个消息压缩系统。消息由4种符号组成,概率分别为 $[0.4, 0.3, 0.2, 0.1]$。

  • (a) 计算消息的熵
  • (b) 如果使用定长编码,每个符号需要多少比特?
  • (c) 设计一个Huffman编码,计算平均码长
  • (d) 压缩效率是多少?(熵/平均码长)

习题1.9(挑战题):设 $X$ 和 $Y$ 是两个独立的公平硬币投掷结果,$Z = X \oplus Y$(异或)。计算:

  • (a) $H(X)$、$H(Y)$、$H(Z)$
  • (b) $H(X,Y)$、$H(X,Z)$、$H(Y,Z)$
  • (c) $I(X;Y)$、$I(X;Z)$、$I(Y;Z)$
  • (d) $I(X,Y;Z)$

这个例子说明了互信息的什么特性?

习题1.10(开放性问题):研究信息论在以下任一领域的应用,撰写一篇500字左右的短文:

  • (a) 生物信息学(DNA序列分析)
  • (b) 金融学(市场效率与信息)
  • (c) 神经科学(神经元编码)
  • (d) 生态学(物种多样性度量)

重点讨论熵或互信息在该领域的具体应用和意义。

1/9熵与相对
0%