我觉得熵这个概念极其迷人。但是,将公式 与其关于前缀码和信息量的“直观”解释对应起来,并非显而易见。这里,我想探讨几种独立引出这一概念的思路。
信息的性质
假设我们想定义一个函数
,它表示事件的信息量。
抛开事件的具体细节,我们可以用一个指标来比较不同事件,那就是它们发生的概率。因此,
可以是从概率
到
的映射。基于这个框架,以下要求是合理的:
- 。如果一个事件必然发生,它并不有趣,提供的信息也很少。
- 应在 上连续且单调递减。越常见的事件信息量越少。
- 两个概率分别为 和 的独立事件,其信息量应为 。
最后一条要求最能说明问题。根据定义,两个独立事件同时发生的概率是 ,因此
由于函数必须是连续的,这只在以下形式下成立:
如果我们希望 单调递减,则
必须为负。由于 为正, 必须为负。令 ,则
因为 ,其中分母为常数,我们可以认为 编码了对数的底数。为方便起见,我们令 ,并令 表示以 2 为底的对数。
熵 就是 在分布 上的期望值:
我们同样基于连续性假设 。
例如,考虑伯努利随机变量 ,它以概率 取值 ,以概率 取值 。如果我们绘制其熵的曲线:
绘图代码
from pathlib import Path
import numpy as np
import plotly.graph_objects as go
# 计算伯努利变量熵的函数
def bernoulli_entropy(p):
return -p * np.log2(p) - (1 - p) * np.log2(1 - p)
# 生成从 0 到 1 的 p 值
p_values = np.linspace(0.01, 0.99, 100)
entropy_values = bernoulli_entropy(p_values)
# 两种主题共享的轨迹
trace = go.Scatter(
x=p_values,
y=entropy_values,
mode="lines",
name="熵",
line=dict(color="red"),
)
# 输出目录(仓库根目录/static)
output_dir = Path(__file__).resolve().parents[3] / "static"
output_dir.mkdir(parents=True, exist_ok=True)
themes = [
{
"template": "plotly_white",
"font_color": "#111111",
"axis": {
"linecolor": "#111111",
"tickcolor": "#111111",
"tickfont": {"color": "#111111"},
"title_font": {"color": "#111111"},
"showgrid": True,
"gridcolor": "rgba(17, 17, 17, 0.2)",
"gridwidth": 1,
"zeroline": False,
},
"filename": "bernoulli_entropy_plot_light.html",
},
{
"template": "plotly_dark",
"font_color": "#f5f5f5",
"axis": {
"linecolor": "#f5f5f5",
"tickcolor": "#f5f5f5",
"tickfont": {"color": "#f5f5f5"},
"title_font": {"color": "#f5f5f5"},
"showgrid": True,
"gridcolor": "rgba(245, 245, 245, 0.2)",
"gridwidth": 1,
"zeroline": False,
},
"filename": "bernoulli_entropy_plot_dark.html",
},
]
for theme in themes:
fig = go.Figure(data=[trace])
fig.update_layout(
title="伯努利随机变量的熵",
xaxis_title="p",
yaxis_title="熵",
template=theme["template"],
font=dict(color=theme["font_color"]),
paper_bgcolor="rgba(0, 0, 0, 0)",
plot_bgcolor="rgba(0, 0, 0, 0)",
xaxis=dict(showline=True, **theme["axis"]),
yaxis=dict(showline=True, **theme["axis"]),
)
fig.write_html(output_dir / theme["filename"])
我们可以看到,当分布均匀时熵最大,而当分布几乎确定时熵最小。
前缀码
假设我们有一组符号 ,需要通过二进制信道传输。我们构建的信道一次只能发送 或 。我们希望为 找到一个最优的编码方案,但有一个要求:它必须是前缀码。
定义一个编码函数 ,它将符号映射为长度 的二进制字符串。如果没有任何一个码字是另一个码字的前缀,我们就称该编码是前缀码。例如, 不是前缀码,因为 是 的前缀。而 则是前缀码。
前缀码意味着该编码是唯一可解码的,无需在符号之间添加额外的分隔符,这是一个非常理想的特性。
我们还注意到,一个二进制前缀码可以由一棵二叉树唯一确定:
其中,从根节点到符号的路径决定了码字,并且符号总是位于叶子节点。请自行验证,任何这样的构造都会产生一个前缀码。
现在我们将证明,对于 上的任何前缀码,其期望码长 满足以下界限:
其中 是一个随机变量,以概率 从集合 中取值。最重要的是,我们看到 的熵是该分布可以被压缩的下界,或者说,它包含了多少信息。
克拉夫特不等式
假设 是第 个码字的长度。如果该编码是前缀无关的:
证明:
令 为最长码字的长度。我们注意到:
- 在第 层最多有 个节点。
- 对于任何长度为 的码字,它在第 层有 个后代节点。
- 每个码字的后代节点集合是互不相交的(因为一个码字永远不会是另一个码字的祖先)。
这些事实意味着:
为什么是 而不是等号?因为有可能第 层的某个节点不是任何码字的后代(考虑编码 的树)!
L 的下界
现在,考虑期望码字长度
我们将证明熵是 的一个下界,即
证明:
其中,最终的不等式成立是由于:1) KL 散度非负;2) 根据 Kraft 不等式有 。 需要注意的一点是,如果 ,则 ,即理论最小值。我们无法总是达到这个值的原因是 不一定为整数,而这显然是必须的。
L 的上界
注意到我们可以构造一个长度如下的前缀码:
因为它们满足克拉夫特不等式:
根据取整函数的定义:
对 取期望,我们得到:
这表明 是 L 的一个良好上界!
总之,熵是使用前缀码编码一个分布时所需平均比特数的下界,也是一个合理的估计。
参考文献
- Alon Orlitsky 在 UCSD 的 ECE 255A 课程讲座
- Cover, T. M., & Thomas, J. A. (2006). 信息论基础. Wiley-Interscience.