Skip to content

知识点卡片:交叉熵 (Cross Entropy)

基本信息

属性内容
知识点交叉熵 (Cross Entropy)
掌握程度★★★★★
学习优先级P0
预估时间4小时
面试频率★★★★★

核心原理

交叉熵是分类问题中最常用的损失函数:

  • 定义:H(p,q) = -Σ p(x) log q(x)
  • 意义:用预测分布q来编码真实分布p的平均编码长度
  • 与MLE的关系:最小化交叉熵等价于最大化似然

数学定义

python
# 离散分布的交叉熵
# H(p, q) = -∑ p(x) · log(q(x))

import numpy as np

def cross_entropy(p, q):
    """p: 真实分布, q: 预测分布"""
    # 避免log(0)
    eps = 1e-15
    q = np.clip(q, eps, 1 - eps)
    return -np.sum(p * np.log(q))

# 二分类交叉熵
def binary_cross_entropy(y_true, y_pred):
    eps = 1e-15
    y_pred = np.clip(y_pred, eps, 1 - eps)
    return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))

# 多分类交叉熵
def categorical_cross_entropy(y_true, y_pred):
    eps = 1e-15
    y_pred = np.clip(y_pred, eps, 1 - eps)
    return -np.sum(y_true * np.log(y_pred))

PyTorch实现

python
import torch
import torch.nn as nn
import torch.nn.functional as F

# 方法1: 使用F.cross_entropy(推荐,内部自动softmax)
loss = F.cross_entropy(logits, targets)
# logits: (batch_size, num_classes),无需softmax
# targets: (batch_size,),可以是类索引或one-hot

# 方法2: 手动softmax + 二元交叉熵
probs = F.softmax(logits, dim=-1)
loss = F.binary_cross_entropy(probs, targets)

# 方法3: 使用nn.CrossEntropyLoss
criterion = nn.CrossEntropyLoss()
loss = criterion(logits, targets)

# 方法4: 手动实现
def cross_entropy_manual(logits, targets):
    # logits: (N, C)
    # targets: (N,) 类索引
    log_probs = F.log_softmax(logits, dim=-1)
    nll = F.nll_loss(log_probs, targets)  # negative log likelihood
    return nll

与其他概念的关系

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  熵 H(p) = -∑ p(x)log p(x)                                  │
│       = E[-log p(x)]                                         │
│       = 编码真实分布p所需的最小平均编码长度                    │
│                                                             │
│  交叉熵 H(p,q) = -∑ p(x)log q(x)                            │
│              = H(p) + KL(p‖q)                               │
│       = 用分布q编码真实分布p的平均编码长度                     │
│                                                             │
│  KL散度 KL(p‖q) = H(p,q) - H(p)                             │
│                = ∑ p(x)log(p(x)/q(x))                       │
│       = 额外需要的编码长度(q相对p的"浪费")                   │
│                                                             │
│  关系:                                                      │
│  - 当q=p时,H(p,q) = H(p),KL(p‖q) = 0                       │
│  - 因为KL(p‖q) ≥ 0,所以 H(p,q) ≥ H(p)                       │
│  - 最小化H(p,q) ⇔ 最小化KL(p‖q)(因为H(p)是常数)             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

为什么分类用交叉熵?

1. 概率解释

python
# 假设我们有一组观测数据,模型预测为q,真实分布为p
# 最大化似然 = 最大化 ∏ q(x_i)
# 取对数 = 最大化 ∑ log q(x_i)
# 取负 = 最小化 -∑ log q(x_i) = 最小化交叉熵

# 因此:交叉熵 = 负对数似然 (NLL)
# 最小化交叉熵 = MLE(最大似然估计)

2. 梯度特性

python
# 假设softmax + cross_entropy
# 对第i个类别的logit求梯度:

# ∂L/∂logits_i = softmax(logits)_i - y_i
#              = pred_i - true_i

# 这个梯度非常简洁!
# 当预测错误时,梯度大,收敛快
# 当预测正确时,梯度趋近0

3. 对比MSE

方面交叉熵MSE
梯度pred - label(恒定)2x·(1-x)·(pred-label)(易消失)
收敛速度
概率解释自然(负对数似然)人为
适用场景分类回归
python
# MSE的梯度问题
# ∂L/∂pred = 2 * pred * (1-pred) * (pred - label)
# 当pred → 0或pred → 1时,pred*(1-pred) → 0
# 导致梯度消失,收敛变慢

面试高频问题

Q1: 推导Cross Entropy对logits的梯度

设模型输出logits z,预测概率p = softmax(z)

损失 L = -∑ y_i log p_i,其中y是真实标签(one-hot或概率分布)

推导

p_i = exp(z_i) / ∑_j exp(z_j)  (softmax定义)

∂L/∂z_k = -∑ y_i * ∂log(p_i)/∂z_k
        = -∑ y_i * (1/p_i) * ∂p_i/∂z_k

∂p_i/∂z_k = p_i * (δ_ik - p_k)  (softmax的梯度性质)
           其中δ_ik = 1 if i=k, else 0

所以:
∂L/∂z_k = -∑ y_i * (δ_ik - p_k) / p_i * p_i
         = -∑ y_i * (δ_ik - p_k)
         = -y_k + p_k * ∑ y_i
         = p_k - y_k

结论:∂L/∂z = softmax(z) - y = pred - label


Q2: 为什么用log_softmax而不是直接softmax?

  1. 数值稳定性:当logits很大时,exp可能溢出
  2. 计算效率:log_softmax可以利用log-sum-exp trick
python
# 直接softmax可能溢出
def softmax_unstable(x):
    exp_x = np.exp(x - np.max(x))  # 减最大值是必要的
    return exp_x / exp_x.sum()

# log_softmax更稳定
def log_softmax(x):
    # log( exp(x_i) / ∑exp(x_j) ) = x_i - log∑exp(x_j)
    # log∑exp(x_j)可以用log-sum-exp trick计算
    return x - np.log(np.sum(np.exp(x - np.max(x)))) - np.max(x)

# PyTorch内部使用log_softmax实现cross_entropy
# F.cross_entropy = log_softmax + nll_loss
# 两者结合可以共享计算,提高效率

Q3: Label Smoothing是什么?为什么用它?

: Label Smoothing将硬标签转换为软标签:

python
# 硬标签:[0, 1, 0, 0](one-hot)
# Label Smoothing (ε=0.1):
# [ε/(K-1), 1-ε+ε/K, ε/(K-1), ε/(K-1)]
# = [0.033, 0.9, 0.033, 0.033]

作用

  1. 防止模型对标签过于自信(regularization)
  2. 提高泛化能力
  3. 让学习更平滑
python
class LabelSmoothingCrossEntropy(nn.Module):
    def __init__(self, smoothing=0.1):
        super().__init__()
        self.smoothing = smoothing

    def forward(self, x, target):
        confidence = 1. - self.smoothing
        logprobs = F.log_softmax(x, dim=-1)
        nll_loss = -logprobs.gather(dim=-1, index=target.unsqueeze(1))
        nll_loss = nll_loss.squeeze(1)
        smooth_loss = -logprobs.mean(dim=-1)
        loss = confidence * nll_loss + self.smoothing * smooth_loss
        return loss.mean()

代码练习

python
import torch
import torch.nn.functional as F

# 1. 验证cross_entropy的梯度
logits = torch.randn(3, 5, requires_grad=True)
targets = torch.tensor([1, 2, 3])

loss = F.cross_entropy(logits, targets)
loss.backward()

print(f"梯度: {logits.grad}")
# 应该接近: softmax(logits) - one_hot(targets)

# 2. 实现Focal Loss(解决类别不平衡)
def focal_loss(logits, targets, gamma=2.0, alpha=0.25):
    ce_loss = F.cross_entropy(logits, targets, reduction='none')
    pt = torch.exp(-ce_loss)
    focal = alpha * (1 - pt) ** gamma * ce_loss
    return focal.mean()

# 3. 对比不同loss在不平衡数据上的效果
num_samples = 1000
num_classes = 3
logits = torch.randn(100, 3)
# 类别0有90个样本,类别1和2各有5个
targets = torch.cat([torch.zeros(90).long(),
                     torch.ones(5).long(),
                     torch.full((5,), 2).long()])

相关知识点