12.1 自信息、熵与编码:不确定性怎样变成比特
同样长的一条日志,天天出现和一年才出现一次,提供的意外程度不同。信息论从概率模型出发,度量观察结果消除了多少不确定性。本课讨论有限离散结果集合,避免把离散熵直接套到连续密度上。
本课目标
- 区分单个结果的自信息与随机变量的熵;
- 解释熵和无损编码平均长度的关系;
- 理解联合熵、条件熵与互信息;
- 区分交叉熵、KL 散度及其在模型训练中的作用。
1. 越意外的结果,自信息越大
概率为
以 2 为底时单位是 bit。这个定义满足三个直觉:
公式先取 p(x)>0;p(x)=0 的结果在模型内不会出现,若用扩展值表示,其自信息为正无穷。实际观察到它,说明模型的零概率断言需要重新检查。
- 必然事件
的自信息为 0; - 事件越罕见,自信息越大;
- 独立事件同时发生时,概率相乘,自信息相加。
例如某告警每天出现的概率为
2. 熵是平均自信息
离散随机变量
约定
公平硬币有两个等概率结果:
若正面概率为 0.9,熵约为 0.469 bit。结果更可预测,平均不确定性更低。但不要把“概率为 50% 的天气预报”说成一条消息本身信息更多;必须先明确随机变量、先验分布以及实际观察到的结果。
对固定的
3. 熵为什么与压缩有关
对至少含两个正概率符号的有限字母表,二进制唯一可译码的平均长度
例如四个符号的概率为 1/2、1/4、1/8、1/8,代码 0、10、110、111 没有任何码字是另一码字的前缀。下面用 Python 3 检查这个码表,平均长度恰好等于熵:
import math
probabilities = [0.5, 0.25, 0.125, 0.125]
codes = ["0", "10", "110", "111"]
entropy = -math.fsum(p * math.log2(p) for p in probabilities)
average_length = math.fsum(p * len(c) for p, c in zip(probabilities, codes))
assert all(not b.startswith(a) for a in codes for b in codes if a != b)
print(entropy, average_length) # 1.75 1.75这不表示任意一个文件都能压缩到恰好
- 熵依赖信源概率模型;
- 单个有限文件会有码表和容器开销;
- 未利用符号之间的相关性,会错过可压缩结构;
- 已压缩或接近随机的数据可能越压越大。
算术编码、范围编码可以更细致地逼近概率模型给出的码长。压缩系统的核心竞争力往往不是“编码器公式”,而是能否准确预测下一个符号的分布。
若
4. 条件熵与互信息
联合熵
表示知道
互信息定义为:
互信息非负,且为零当且仅当两个变量独立;它捕捉的不限于线性关系。例如
互信息并不自动说明因果方向。两个变量都受第三个因素影响时也可能有高互信息;在有限样本上估计互信息还会受到分箱、密度估计和偏差影响。
5. 交叉熵与 KL 散度
真实分布为
KL 散度为:
在定义良好、对数底相同的条件下:
对固定
这个二元例子直接核对分解和不对称性。辅助函数只服务于给定的、同长度且已归一化分布,Q 在 P 的正概率处也为正;它们没有实现通用分布校验:
import math
P = [0.75, 0.25]
Q = [0.5, 0.5]
def entropy_bits(probabilities):
return -math.fsum(p * math.log2(p) for p in probabilities if p > 0)
def kl_bits(p, q):
return math.fsum(a * math.log2(a / b) for a, b in zip(p, q) if a > 0)
h = entropy_bits(P)
cross = -math.fsum(p * math.log2(q) for p, q in zip(P, Q))
print(f"{h:.6f} {cross:.6f} {kl_bits(P, Q):.6f} {kl_bits(Q, P):.6f}")
# 0.811278 1.000000 0.188722 0.207519
assert math.isclose(cross, h + kl_bits(P, Q))若存在 log_softmax,而不是先算出可能舍入为零的概率再取对数。
6. 一个二分类计算
对真实标签
import math
def binary_cross_entropy_from_probability(y: int, q: float) -> float:
if y not in (0, 1):
raise ValueError("y must be 0 or 1")
if not 0.0 < q < 1.0:
raise ValueError("q must lie strictly between 0 and 1")
return -(y * math.log(q) + (1 - y) * math.log1p(-q))
print(f"{binary_cross_entropy_from_probability(1, 0.9):.6f}") # 0.105361
print(f"{binary_cross_entropy_from_probability(1, 0.1):.6f}") # 2.302585代码使用自然对数,所以单位是 nat;换成以 2 为底才是 bit。机器学习损失用哪种底通常只差一个固定比例,不改变最优点,但会改变数值大小和梯度尺度。
这段概率接口主动拒绝端点;数学上正确类别概率为 1 时损失为 0,真实类别被赋零概率时损失为正无穷。若输入本来就是有限 logit z,可继续运行下面的等价公式,避免先形成饱和到 0 或 1 的概率:
def binary_cross_entropy_from_logit(y: int, z: float) -> float:
if y not in (0, 1) or not math.isfinite(z):
raise ValueError("require a binary label and a finite logit")
return max(z, 0.0) - y * z + math.log1p(math.exp(-abs(z)))
print(binary_cross_entropy_from_logit(1, 1000.0)) # 0.0: tiny positive loss underflows
print(binary_cross_entropy_from_logit(0, 1000.0)) # 1000.0换对数底只把这项损失乘一个正常数;若目标还含未同步缩放的正则项,总目标的相对权重会改变。这里的稳定实现仍有下溢边界,正类 logit 为 1,000 时显示零,不表示精确数学损失等于零。
常见误区
- 熵就是混乱程度:这是直觉比喻,计算时必须给出随机变量和分布。
- 低熵数据一定有价值:熵度量不确定性,不度量业务价值或真实性。
- 交叉熵等于错误率:它会惩罚概率置信度,和 0/1 错误率不是同一指标。
- KL 散度是对称距离:交换
与 通常会改变结果。
练习
- 计算均匀四面骰子的熵,并给出最自然的定长编码。
- 比较正面概率为
0.5、0.9和0.99的硬币熵。 - 构造两个分布,验证
。 - 解释为什么一个压缩器无法保证让每个输入文件都变短。
小结
自信息度量一次结果的意外程度,熵是平均自信息,条件熵和互信息刻画变量共享的不确定性。交叉熵与 KL 散度把概率模型的偏差转化为可优化目标,但它们都依赖明确的分布和数值实现。
下一课把注意力从“需要多少比特表达消息”转向“有限比特怎样近似实数”。