最近在看 verl 的源码学习强化学习的相关实现,看到了一个计算熵的函数,感觉挺有意思:
python
def entropy_from_logits(logits: torch.Tensor):
"""Calculate entropy from logits."""
pd = torch.nn.functional.softmax(logits, dim=-1)
entropy = torch.logsumexp(logits, dim=-1) - torch.sum(pd * logits, dim=-1)
return entropy
在人工智能深度学习领域,模型训练有很多数学的理论做支撑,但从数学公式转化为具体的代码实现,往往不能直接按原公式的计算逻辑编写代码进行计算,因为数学是比较理想的,而真正要把数据放到计算机中进行处理,则需要考虑很多问题,比如计算是否稳定、数值会不会溢出等等,因此通常需要对原公式做一些转换,熵的计算就是如此,当然还有很多其他的例子,比如 softmax 的计算等等。本文就借此机会聊聊与熵相关的一些内容。
熵
熵(Entropy)是信息论中的一个核心概念,最早由克劳德·香农(Claude Shannon)在1948年提出。它用来量化一个概率分布的不确定性或"混乱程度"。下面从直观含义、数学定义和实际作用三个方面来解释。
一、熵的直观含义
想象你面对一个随机事件,比如:
- 抛一枚公平硬币 :正面或反面各50%。结果很难预测 → 不确定性高 → 熵大。
- 抛一枚作弊硬币 :99% 是正面。结果几乎可以猜到 → 不确定性低 → 熵小。
- 如果某个事件必然发生 (概率为1),那完全没有不确定性 → 熵为0。
✅ 所以:熵越大,表示结果越不可预测;熵越小,表示结果越确定。
二、数学定义
对于一个离散随机变量 X X X,其可能取值为 { x 1 , x 2 , . . . , x n } \{x_1, x_2, ..., x_n\} {x1,x2,...,xn},对应的概率为 p i = P ( X = x i ) p_i = P(X = x_i) pi=P(X=xi),则香农熵定义为:
H ( X ) = − ∑ i = 1 n p i log p i H(X) = -\sum_{i=1}^n p_i \log p_i H(X)=−i=1∑npilogpi
- 单位:若对数以 2 为底,单位是 比特(bit) ;以自然对数(e)为底,单位是 纳特(nat)。
- 约定:当 p i = 0 p_i = 0 pi=0 时,定义 0 log 0 = 0 0 \log 0 = 0 0log0=0(因为极限为0)。
三、熵的作用与应用场景
1. 衡量模型的置信度(机器学习)
- 在分类任务中,模型输出一个概率分布(如 softmax 后的结果)。
- 如果熵很低(接近0):模型对某个类别非常确信(如 [0.99, 0.01])。
- 如果熵很高:模型犹豫不决(如 [0.33, 0.33, 0.34])。
- ✅ 应用:主动学习(Active Learning)中,选择熵高的样本让人工标注,因为模型最"困惑"。
2. 强化学习中的探索(Exploration)
- 在策略梯度方法(如 PPO、A3C)中,常在损失函数中加入熵正则项:
L = 原始损失 + β ⋅ H ( π ) \mathcal{L} = \text{原始损失} + \beta \cdot H(\pi) L=原始损失+β⋅H(π)
- 目的:鼓励策略保持一定随机性,避免过早收敛到次优解(即"鼓励探索")。
- 如果策略熵太低,说明 agent 总是选同一个动作,可能陷入局部最优。
3. 数据压缩(信息论)
- 熵代表了编码该随机变量所需的最小平均比特数。
- 例如:公平硬币的熵是 1 bit,意味着每个结果至少需要 1 bit 来编码。
- 这是 ZIP、JPEG 等压缩算法的理论基础。
4. 特征选择与决策树(如 ID3、C4.5)
- 决策树使用信息增益(Information Gain)来选择分裂特征:
IG = H ( 父节点 ) − ∑ ∣ 子集 ∣ ∣ 总 ∣ H ( 子节点 ) \text{IG} = H(\text{父节点}) - \sum \frac{|子集|}{|总|} H(\text{子节点}) IG=H(父节点)−∑∣总∣∣子集∣H(子节点)
- 信息增益越大,说明该特征能最大程度减少不确定性(降低熵)。
5. 异常检测 / 不确定性估计
- 高熵的预测可能意味着输入是异常样本 或分布外数据(OOD)。
- 可用于安全关键系统(如自动驾驶)中判断"模型是否知道自己不知道"。
四、举个例子 🌰
假设一个三分类问题,模型输出三种概率分布:
| 情况 | 概率分布 | 熵(近似) | 含义 |
|---|---|---|---|
| A | [0.9, 0.05, 0.05] | ≈ 0.37 | 非常确信,低不确定性 |
| B | [0.33, 0.33, 0.34] | ≈ 1.10 | 完全犹豫,高不确定性 |
| C | [1.0, 0.0, 0.0] | = 0.0 | 完全确定 |
→ 熵帮助我们量化这种"犹豫程度"。
总结
| 角度 | 熵的意义 |
|---|---|
| 信息论 | 信息的平均"惊喜度";不确定性度量 |
| 机器学习 | 模型置信度、探索 vs 利用、正则化 |
| 工程应用 | 压缩、加密、异常检测、主动学习 |
💡 简单记住:熵 = 不确定性 = 混乱程度 = 信息量 。
越"乱",熵越大;越"确定",熵越小。
熵与信息量
熵(Entropy)和信息量(Information Content)是信息论中紧密相关但不同层次的概念。它们的关系可以用一句话概括:
熵是信息量的期望值(平均信息量)。
下面从定义、直观理解到数学关系一步步解释。
一、信息量(自信息,Self-Information)
定义
对于一个具体事件 ( x ) (x) (x),其发生概率为 p ( x ) p(x) p(x),它的信息量(也叫"自信息")定义为:
I ( x ) = − log p ( x ) I(x) = -\log p(x) I(x)=−logp(x)
直观理解
- 越不可能发生的事件,一旦发生,带来的信息量越大 。
- 例如:"太阳从东边升起"(概率≈1)→ 信息量 ≈ 0(不惊讶)。
- "明天下陨石雨"(概率≈0.0001)→ 信息量很大(非常惊讶!)。
- 单位:比特(bit,以2为底)、纳特(nat,以e为底)。
✅ 信息量描述的是单个事件的"惊讶程度"或"信息价值"。
二、熵(Entropy)
定义
如前一节所述,熵是量化一个概率分布的不确定性或"混乱程度",而这种不确定性也衡量了整个概率分布的平均信息量,即信息量的数学期望:
H ( X ) = E x ∼ P [ I ( x ) ] = E [ − log p ( x ) ] = − ∑ x p ( x ) log p ( x ) H(X) = \mathbb{E}_{x \sim P}[I(x)] = \mathbb{E}[-\log p(x)] = -\sum_x p(x) \log p(x) H(X)=Ex∼P[I(x)]=E[−logp(x)]=−x∑p(x)logp(x)
直观理解
- 熵衡量的是:在知道结果之前,你预期会获得多少信息。
- 它不是针对某一次结果,而是对整个随机变量 X 的不确定性的度量。
- 比如抛硬币:
- 公平硬币:每次结果都可能带来 1 bit 信息,平均就是 1 bit → 熵 = 1。
- 作弊硬币(99% 正面):大多数时候结果无聊(信息少),偶尔反面很惊喜(信息多),但平均下来信息量较低 → 熵 < 1。
✅ 熵 = 平均信息量 = 预期惊喜程度 = 不确定性大小
三、类比帮助理解
| 概念 | 类比 | 特点 |
|---|---|---|
| 信息量 | 某一天的天气是否让你惊讶 | 针对单次事件 |
| 熵 | 这个城市天气的"不可预测程度" | 针对整个分布 |
就像:
- "今天中了彩票" → 信息量极大(因为概率极低)。
- 但"买彩票能中奖"这件事的熵其实很低(因为中奖概率太小,几乎不会发生,所以平均信息量不高)。
四、数学关系总结
| 符号 | 含义 | 公式 |
|---|---|---|
| I ( x ) I(x) I(x) | 事件 x x x 的信息量 | I ( x ) = − log p ( x ) I(x) = -\log p(x) I(x)=−logp(x) |
| H ( X ) H(X) H(X) | 随机变量 X X X 的熵 | H ( X ) = E [ I ( x ) ] = − ∑ p ( x ) log p ( x ) H(X) = \mathbb{E}[I(x)] = -\sum p(x) \log p(x) H(X)=E[I(x)]=−∑p(x)logp(x) |
👉 所以:熵是信息量在概率分布下的加权平均。
五、实际例子
假设一个四面骰子,有以下两种情况:
情况1:公平骰子
- 每面概率 = 0.25
- 每次掷出任意一面的信息量: I = − log 2 ( 0.25 ) = 2 I = -\log_2(0.25) = 2 I=−log2(0.25)=2 bits
- 熵: H = 0.25 × 2 + 0.25 × 2 + 0.25 × 2 + 0.25 × 2 = 2 bits H = 0.25 \times 2 + 0.25 \times 2 + 0.25 \times 2 + 0.25 \times 2 = 2\text{ bits} H=0.25×2+0.25×2+0.25×2+0.25×2=2 bits
✅ 每次结果都同样"惊喜",平均信息量 = 2 bits。
情况2:作弊骰子
- P(1)=0.5, P(2)=0.3, P(3)=0.15, P(4)=0.05
- 各事件信息量:
- I(1) = -log₂(0.5) = 1 bit
- I(2) = -log₂(0.3) ≈ 1.74 bits
- I(3) ≈ 2.74 bits
- I(4) ≈ 4.32 bits
- 熵: H = 0.5 × 1 + 0.3 × 1.74 + 0.15 × 2.74 + 0.05 × 4.32 ≈ 1.65 bits H = 0.5×1 + 0.3×1.74 + 0.15×2.74 + 0.05×4.32 ≈ 1.65 \text{ bits} H=0.5×1+0.3×1.74+0.15×2.74+0.05×4.32≈1.65 bits
✅ 虽然"掷出4"信息量很大,但它很少发生,所以平均信息量(熵)反而比公平骰子低。
六、延伸:为什么叫"信息量"?
香农认为:
信息是用来消除不确定性的。
- 如果一件事本来就很确定(p≈1),告诉你结果并不会减少你的不确定性 → 信息量小。
- 如果一件事非常不确定(p≈0.5 或均匀分布),告诉你结果会大幅减少不确定性 → 信息量大。
而熵就是"在得知结果前,你预期能消除多少不确定性"------也就是平均能获得多少信息。
总结
| 概念 | 作用对象 | 含义 | 关系 |
|---|---|---|---|
| 信息量 | 单个事件 x x x | 该事件发生带来的"惊讶程度" | I ( x ) = − log p ( x ) I(x) = -\log p(x) I(x)=−logp(x) |
| 熵 | 整个分布 X X X | 平均能获得多少信息(不确定性) | H ( X ) = E [ I ( x ) ] H(X) = \mathbb{E}[I(x)] H(X)=E[I(x)] |
🔑 熵是信息量的期望;信息量是熵的基本构成单元。
熵的计算的代码实现
回到开头的 python 函数 entropy_from_logits,这个函数的作用是从 logits 计算对应的 概率分布的熵(entropy)。下面我们逐步解释其原理和实现细节。
1. 背景知识
- Logits:在机器学习中,logits 通常指模型最后一层输出的原始值(尚未经过 softmax 归一化)。它们可以看作是未归一化的概率。
- Softmax:将 logits 转换为概率分布:
p i = e z i ∑ j e z j p_i = \frac{e^{z_i}}{\sum_j e^{z_j}} pi=∑jezjezi
其中 z i z_i zi 是第 i i i 个 logit, p i p_i pi 是对应的概率。
- 熵(Entropy) :衡量一个概率分布的不确定性。从前面的定义可以看到,对于离散分布 p = ( p 1 , . . . , p n ) p = (p_1, ..., p_n) p=(p1,...,pn),熵定义为:
H ( p ) = − ∑ i p i log p i H(p) = -\sum_i p_i \log p_i H(p)=−i∑pilogpi
2. 函数逐行解释
python
def entropy_from_logits(logits: torch.Tensor):
"""Calculate entropy from logits."""
- 输入:
logits是一个 PyTorch 张量,形状通常是(batch_size, num_classes)或更高维,但最后一个维度是类别维度。 - 目标:计算每个样本(沿最后一个维度)对应的概率分布的熵。
第一步:计算概率分布
python
pd = torch.nn.functional.softmax(logits, dim=-1)
- 使用 softmax 将 logits 转换为概率分布
pd(probability distribution)。 dim=-1表示在最后一个维度上做 softmax,即对每个样本的类别 logits 做归一化。
第二步:计算熵
python
entropy = torch.logsumexp(logits, dim=-1) - torch.sum(pd * logits, dim=-1)
这里用了一个数值稳定 且避免显式计算 log§ 的技巧。
我们从熵的定义出发:
H ( p ) = − ∑ i p i log p i H(p) = -\sum_i p_i \log p_i H(p)=−i∑pilogpi
而由于 p i = e z i ∑ j e z j p_i = \frac{e^{z_i}}{\sum_j e^{z_j}} pi=∑jezjezi,所以:
log p i = z i − log ( ∑ j e z j ) \log p_i = z_i - \log\left(\sum_j e^{z_j}\right) logpi=zi−log(j∑ezj)
代入熵公式:
H ( p ) = − ∑ i p i ( z i − log ∑ j e z j ) = − ∑ i p i z i + log ∑ j e z j ⋅ ∑ i p i H(p) = -\sum_i p_i (z_i - \log\sum_j e^{z_j}) = -\sum_i p_i z_i + \log\sum_j e^{z_j} \cdot \sum_i p_i H(p)=−i∑pi(zi−logj∑ezj)=−i∑pizi+logj∑ezj⋅i∑pi
因为 ∑ i p i = 1 \sum_i p_i = 1 ∑ipi=1,所以:
H ( p ) = log ( ∑ j e z j ) − ∑ i p i z i H(p) = \log\left(\sum_j e^{z_j}\right) - \sum_i p_i z_i H(p)=log(j∑ezj)−i∑pizi
这正是代码中的表达式!
torch.logsumexp(logits, dim=-1)计算的是$\log\left(\sum_j e^{z_j}\right)$,数值稳定。torch.sum(pd * logits, dim=-1)计算的是$\sum_i p_i z_i$。- 两者相减即为熵。
注意:这里没有显式计算
log(p),避免了在概率接近 0 时log(0)的数值问题。
3. 示例
python
import torch
logits = torch.tensor([[1.0, 2.0, 3.0]])
entropy = entropy_from_logits(logits)
print(entropy) # 输出一个标量(张量)
等价于:
python
p = torch.softmax(logits, dim=-1)
H = -torch.sum(p * torch.log(p), dim=-1)
但原函数更数值稳定,尤其当 logits 数值较大或较小时。
4. 总结
- 该函数高效、稳定地从 logits 计算熵。
- 利用了恒等式:
H = log ∑ e z i − ∑ p i z i H = \log\sum e^{z_i} - \sum p_i z_i H=log∑ezi−∑pizi
- 避免了直接计算
log(softmax(logits)),从而提升数值稳定性。