信息论熵定义详解:从公理推导到编码压缩实战
信息论这门课,很多人第一次听“熵”的时候都是一脸懵——公式不长,但就是不知道它在说什么。我当年也是,教材上写“熵是平均不确定性的度量”,字都认识,连起来读却像玄学。后来自己动手把一堆例子从零算了一遍,又写代码跑了一遍,才真正把这块骨头啃下来。这篇就围绕“熵的定义”这一个点,把我踩过的坑、想明白的逻辑、以及可以直接抄作业的计算流程完整摊开讲。
熵这个概念,说白了就是给“意外程度”标价。你越猜不到下一秒会发生什么,熵就越大;事情越确定,熵就越小。它解决的核心问题是:怎么用一个数字,量化一份信息里到底含多少“新鲜内容”。适合正在学信息论与编码的学生、准备面试的求职者,以及任何想搞懂压缩、编码、机器学习里那些“熵”到底在算什么的从业者。下面从设计思路、核心细节、实操计算到常见坑,一层层拆。
1. 熵的定义到底在定义什么
1.1 从“猜结果”切入:为什么需要这样一个量
先别急着看公式。设想两个场景:第一个是抛一枚两面完全一样的硬币,第二个是抛一枚正常硬币。前者你闭着眼都能说出结果,后者你只能说“一半一半”。这两种情况带给你的“信息量”显然不同——但不同在哪?不同在不确定性的大小。
信息论想干的事,就是把这个模糊的“不确定性”变成一个可比较、可计算的数。熵就是这个数。它衡量的不是某一次具体结果的信息量,而是整个随机事件在长期平均意义上,每发生一次能带来多少新信息。这个“平均”二字非常关键,很多人第一次学的时候忽略它,后面理解信源编码定理就会卡住。
我习惯用一个类比:熵就像一家餐厅菜单的“惊喜指数”。如果菜单上只有一道菜,你每次去都知道吃啥,惊喜指数为零;如果菜单上有几十道且概率均匀,你每次点菜都像开盲盒,惊喜指数就高。熵算的就是这个惊喜指数的期望值。
1.2 三条公理倒推出的唯一形式
教材通常直接甩出公式 H(X) = -Σ p(x) log p(x),然后开始算。但我建议你先理解这个公式是被三条直觉要求“逼”出来的,这样才不会觉得它是天上掉的。
第一条,单调性:事件越不可能发生,一旦发生,它携带的信息量应该越大。比如“太阳从东边升起”几乎不携带信息,“某支冷门队伍夺冠”信息量就很大。所以信息量应该是概率的减函数。
第二条,可加性:两个独立事件同时发生的信息量,应该等于各自信息量之和。因为独立意味着它们互不提供线索,信息应该叠加。数学上满足“乘积变加法”的函数,自然指向对数——log(ab) = log a + log b。这就是公式里出现对数的根本原因,不是什么神秘选择。
第三条,连续性:概率稍微变一点,信息量不应该突变。这条保证了函数是连续的,排除了各种奇奇怪怪的分段定义。
把这三条凑一起,能严格证明满足条件的函数形式唯一(差一个常数因子),就是 -log p。再对随机变量所有取值按概率加权求平均,就得到熵的完整定义。我当年把这段推导自己推了一遍,比背十遍公式都管用。
1.3 单位是怎么来的:bit 不是随便叫的
公式里对数的底决定了熵的单位。底取 2,单位是比特(bit);底取 e,单位是奈特(nat);底取 10,单位是哈特利(hartley)。工程上最常用 bit,因为数字系统天然是二进制的。
这里有个容易被忽略的点:换底只改变数值,不改变熵的相对大小关系。换底公式 log₂p = ln p / ln 2,相当于整体乘一个常数 1/ln2 ≈ 1.4427。所以比较两个分布的熵谁大谁小,用哪个底都一样,只是数值不同。实际写代码时我一般统一用自然对数算,最后需要 bit 再除以 ln2,避免中间步骤反复换底出错。
提示:面试里如果被问“熵的单位是什么”,答 bit 时要补一句“取决于对数底”,这个细节能体现你真的理解而不是背公式。
2. 公式拆解与每个符号的真实含义
2.1 逐项拆:-p log p 到底在算什么
把 H(X) = -Σ p(xᵢ) log p(xᵢ) 拆开看,每一项是“某个结果的信息量”乘以“它发生的概率”。log p(xᵢ) 本身是负数(因为概率小于1),前面加负号变成正数,代表这个结果的自信息量 I(xᵢ) = -log p(xᵢ)。再乘 p(xᵢ) 加权,就是期望。
所以熵的本质是自信息量的数学期望。自信息量描述“单个结果有多意外”,熵描述“整个分布平均有多意外”。这两个概念一定要分清:自信息是针对某一个具体结果的,熵是针对整个随机变量的。
举个具体数字感受一下。一个结果概率 0.5,自信息量是 -log₂0.5 = 1 bit;概率 0.125,自信息量是 3 bit;概率 0.99,自信息量约 0.0145 bit。概率越接近1,自信息量越趋近0,符合“确定的事没信息”的直觉。
2.2 边界情况:0 和 1 的概率怎么处理
公式里有个数学上的小陷阱:当 p(xᵢ) = 0 时,p log p 这一项怎么算?直接算是 0 乘负无穷,未定义。但按极限 lim(p→0) p log p = 0,所以约定0 log 0 = 0。这个约定不是偷懒,是有严格极限依据的,而且符合直觉——一个根本不可能发生的结果,对平均不确定性的贡献理应为零。
另一头,p = 1 时,log 1 = 0,整项为 0,熵为 0。这对应“完全确定”的情形,没有任何不确定性。所以熵的取值范围是 [0, log n],n 是可能结果的个数。下界 0 在某个结果概率为1时取到,上界 log n 在所有结果等概率时取到。
这个上界非常重要,它是后面很多结论的基石。等概率分布熵最大,意味着“最不可预测”,这也解释了为什么均匀分布常被当作“最坏情况”来分析。
2.3 一个必须记住的对照表
为了把不同概率下的熵值建立直觉,我整理了一张常用对照表,建议初学者把这几个值记熟,做题时能快速估算:
| 分布情形 | 概率设置 | 熵(bit) | 直觉解释 |
|---|---|---|---|
| 完全确定 | {1, 0} | 0 | 毫无悬念 |
| 极度偏斜 | {0.99, 0.01} | 约 0.08 | 几乎能猜中 |
| 偏斜 | {0.9, 0.1} | 约 0.469 | 偶尔有意外 |
| 偏斜 | {0.75, 0.25} | 约 0.811 | 意外不算少 |
| 均匀 | {0.5, 0.5} | 1 | 完全猜不准 |
| 四等分 | {0.25×4} | 2 | 四种可能均等 |
| 八等分 | {0.125×8} | 3 | 八种可能均等 |
从表里能看出一个规律:等概率 n 个结果的熵正好是 log₂n。这也是为什么二进制下每多一位,可能性翻倍,熵加1。这个规律在做编码题时是心算利器。
3. 手算实操:从零把一个熵算出来
3.1 完整计算流程与步骤拆解
光看公式没用,得动手。我拿一个具体例子走一遍完整流程,你可以跟着算。设随机变量 X 的分布为 {0.5, 0.25, 0.125, 0.125},四个结果。
第一步,确认概率归一:0.5 + 0.25 + 0.125 + 0.125 = 1,没问题。第二步,逐项算 p log₂p:
- 0.5 × log₂0.5 = 0.5 × (-1) = -0.5
- 0.25 × log₂0.25 = 0.25 × (-2) = -0.5
- 0.125 × log₂0.125 = 0.125 × (-3) = -0.375
- 0.125 × log₂0.125 = 0.125 × (-3) = -0.375
第三步,求和取负:-(-0.5 - 0.5 - 0.375 - 0.375) = 1.75 bit。
这个结果 1.75 很有意思,它不是整数。说明这个分布的平均不确定性介于 1 bit 和 2 bit 之间,符合它“比两个等概率结果更不确定,但没到四个等概率那么不确定”的定位。手算时我建议先把每个 log 值单独列出来,再乘概率,最后求和,这样不容易错。
3.2 用代码验证,避免手算翻车
手算容易在 log 上栽跟头,尤其是概率不是 2 的整数次幂时。我习惯用一段短代码交叉验证。下面这段 Python 直接算上面那个例子:
import math def entropy(probs, base=2): h = 0.0 for p in probs: if p > 0: h -= p * math.log(p, base) return h probs = [0.5, 0.25, 0.125, 0.125] print(entropy(probs)) # 输出 1.75 print(entropy([0.5, 0.5])) # 输出 1.0 print(entropy([1.0])) # 输出 0.0代码里那个if p > 0就是处理 0 log 0 的边界,直接跳过零概率项,等价于约定其为0。这个写法比手动加 epsilon 更干净,不会引入数值误差。实测下来,用math.log(p, base)在概率很小时会有精度问题,更稳的做法是math.log(p) / math.log(base),尤其当 base 不是 e 的时候。
3.3 参数选择背后的计算逻辑
为什么工程上偏爱底 2?因为数字电路里一个二进制位恰好对应两种状态,熵为 1 bit 就意味着“平均需要 1 个二进制位来描述”。这个对应关系不是巧合,而是信源编码定理的直接结论:熵是无损压缩的理论下界。
举个能算的例子。上面那个 1.75 bit 的分布,理论上平均每个符号最少需要 1.75 个二进制位来编码。实际用霍夫曼编码,可能编出平均 1.75 位(如果码长恰好匹配)或略高。这就把“熵”和“压缩”直接挂钩了——熵告诉你压缩的极限在哪,任何编码方案都不可能低于它。理解这一点,后面学算术编码、区间编码时就不会觉得突兀。
注意:算熵时概率必须是归一化的。我见过有人拿频数直接代公式,结果算出个负数或者离谱的大数。频数要先除以总数变成概率,这一步千万别省。
4. 常见误区与排查实录
4.1 五个高频错误对照表
熵的定义看着简单,但实际用起来坑不少。我把这些年自己和身边人踩过的典型错误整理成表,遇到算不对时逐条对照:
| 错误现象 | 根本原因 | 正确做法 |
|---|---|---|
| 算出负熵 | 忘了最前面的负号,或概率未归一 | 检查求和是否为1,确认负号 |
| 结果偏大很多 | 用了频数而非概率 | 频数除以总数 |
| 0概率项报错 | 直接对0取对数 | 约定 0log0=0,跳过该项 |
| 单位混乱 | 换底时没同步换算 | 统一底数,最后再转换 |
| 与联合熵混淆 | 把 H(X,Y) 当 H(X) 算 | 明确随机变量维度 |
这张表里最隐蔽的是最后一条。联合熵 H(X,Y) 是对联合分布求熵,维度是笛卡尔积,不是简单把两个熵相加(除非独立)。我当初做习题时就把两个边缘熵直接加起来了,错得很自然,因为独立时确实相加,但不独立时差一个互信息。
4.2 独立与不独立:一个必须算一遍的对比
接着上面说,独立时 H(X,Y) = H(X) + H(Y),不独立时 H(X,Y) = H(X) + H(Y) - I(X;Y),其中 I 是互信息。这个减项就是“两个变量共享的信息”,共享越多,联合熵越小。
我建议你亲手算一个不独立的例子建立直觉。设 X 取 {0,1} 等概率,Y = X(完全相关)。那么联合分布只有 (0,0) 和 (1,1) 各 0.5,联合熵 = 1 bit。而 H(X) = 1,H(Y) = 1,两者相加是 2,但联合熵只有 1,差的 1 bit 就是互信息——因为知道 X 就完全知道 Y,没有新增不确定性。这个例子算一遍,比看十页推导都直观。
4.3 实操心得:先画分布再动笔
我个人的习惯是,拿到任何熵的题目,先画一个概率柱状图或者列出概率表,看清楚有几个结果、概率怎么分布,再决定用哪个公式。很多错误源于没看清随机变量的取值范围。比如连续型随机变量要用微分熵,公式变成 -∫ f(x) log f(x) dx,和离散的求和完全是两套东西,混用必错。
另外,算完之后做个 sanity check:结果是否落在 [0, log₂n] 区间内?如果 n=4 却算出 3 bit,那肯定哪里错了,因为上界是 2。这个快速检查能拦住大部分低级错误。
5. 熵在编码与压缩里的落点
5.1 从熵到编码长度:那条不等式
熵的定义之所以重要,是因为它给编码长度划了一条硬线。对任意唯一可译码,平均码长 L 满足 L ≥ H(X)。这条不等式的证明用到了克拉夫特不等式和 Gibbs 不等式,但结论很好记:你不可能用比熵更少的平均位数无损表示信息。
这条线在实际工作里非常有用。比如你设计一个压缩方案,算出信源熵是 3.2 bit/符号,那你的编码平均码长做到 3.3 就已经很接近理论极限了,再往下抠空间极小。反过来,如果做到 5 bit,说明还有很大优化余地。熵在这里扮演的是“标尺”角色,让你知道离最优还有多远。
5.2 一个能跑通的编码对照实验
光说不练假把式。我做过一个简单实验:对分布 {0.5, 0.25, 0.125, 0.125} 分别用定长编码和霍夫曼编码,比较平均码长和熵的差距。
定长编码四个符号需要 2 bit,平均码长 2。霍夫曼编码可以构造出码长 {1, 2, 3, 3},平均码长 = 0.5×1 + 0.25×2 + 0.125×3 + 0.125×3 = 1.75 bit。正好等于熵!这个例子特殊在概率都是 2 的负整数次幂,霍夫曼能达到理论下界。如果概率是 {0.4, 0.3, 0.2, 0.1},熵约 1.846 bit,霍夫曼平均码长会略高于它,因为码长必须是整数,无法完美贴合。
这个实验让我彻底明白:熵是下界,不是一定能达到的值。能不能达到,取决于概率分布是否“友好”(是否为2的负幂次)。这个认知在做实际压缩选型时很关键,别指望任何编码器都能压到熵。
5.3 扩展到条件熵与互信息
熵的定义是地基,往上盖的第一层楼就是条件熵 H(X|Y) = H(X,Y) - H(Y),表示知道 Y 之后 X 还剩多少不确定性。再往上就是互信息 I(X;Y) = H(X) - H(X|Y),表示 Y 能消除多少关于 X 的不确定性。
这三个量的关系可以用一个链条记住:H(X,Y) = H(X) + H(Y|X) = H(Y) + H(X|Y)。我习惯把它想成“总不确定性 = 一部分 + 另一部分”。条件熵永远不大于无条件熵(知道得越多,剩的越少),这个单调性也是从定义直接推出来的。把这几个量串起来理解,熵就不再是孤立的公式,而是一整套度量体系的入口。
6. 我个人踩过的坑与实用建议
学熵这块,我最大的教训是别一上来就背公式。我最初就是死记 -Σp log p,做题时稍微变个形就懵。后来逼自己从三条公理推了一遍,又手算了十几个分布,才真正内化。现在回头看,那点推导时间花得太值了。
第二个坑是混淆自信息和熵。自信息 -log p 是针对单个结果的,熵是期望。有次讨论编码时我把某个稀有符号的自信息量当成整体熵,结论完全跑偏。分清“点”和“平均”,是理解信息论所有后续概念的前提。
第三个实用建议:养成用代码验证手算的习惯。熵的计算涉及对数,手算极易出错,写个几行的函数跑一下,几秒钟就能确认。我现在的做法是手算一遍建立感觉,代码跑一遍确认数值,两边对上才放心。这个习惯延伸到后面算互信息、信道容量时同样管用。
最后分享一个小技巧:遇到概率分布比较复杂的题,先看有没有概率是 2 的负幂次。如果有,心算会快很多,因为 log₂(2⁻ᵏ) = -k,直接出整数。比如 0.125 就是 2⁻³,自信息量直接是 3 bit,不用掏计算器。这个观察能帮你在考试和面试里省下大量时间,也让你对数字更敏感。