知识点卡片:交叉熵 (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
# 这个梯度非常简洁!
# 当预测错误时,梯度大,收敛快
# 当预测正确时,梯度趋近03. 对比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?
答:
- 数值稳定性:当logits很大时,exp可能溢出
- 计算效率: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]作用:
- 防止模型对标签过于自信(regularization)
- 提高泛化能力
- 让学习更平滑
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()])相关知识点
- → 熵 - 信息量度量
- → KL散度 - 分布差异
- → 最大似然估计 - 与交叉熵的关系
- → softmax/Sigmoid - 分类输出层