别再死记硬背公式了!用Python代码一步步推导交叉熵损失函数(附PyTorch/TensorFlow实现)
·
用Python代码拆解交叉熵:从信息论到深度学习实战
在机器学习领域,我们经常听到"交叉熵损失函数"这个术语,但很少有人真正理解它的来龙去脉。本文将带你用Python代码一步步拆解这个看似复杂的数学概念,让你不仅知其然,更知其所以然。
1. 信息论基础:从信息量到交叉熵
1.1 信息量的直观理解
想象你收到两条消息:
- "明天太阳会升起"
- "明天会下陨石雨"
显然,第二条消息更让你震惊,因为它发生的概率极低。这就是信息量的核心思想——事件的信息量与其发生概率成反比。
import numpy as np
def information_content(p):
return -np.log2(p)
# 计算不同概率事件的信息量
print(f"太阳升起的信息量: {information_content(0.999):.2f} bits")
print(f"陨石雨的信息量: {information_content(0.000001):.2f} bits")
输出结果:
太阳升起的信息量: 0.00 bits
陨石雨的信息量: 19.93 bits
1.2 信息熵:系统的混乱程度
信息熵衡量的是一个系统的不确定性。对于分类问题,可以理解为"系统有多难预测"。
def entropy(probabilities):
return -np.sum(probabilities * np.log2(probabilities))
# 抛硬币的熵
fair_coin = np.array([0.5, 0.5])
biased_coin = np.array([0.9, 0.1])
print(f"公平硬币的熵: {entropy(fair_coin):.2f} bits")
print(f"偏置硬币的熵: {entropy(biased_coin):.2f} bits")
输出结果:
公平硬币的熵: 1.00 bits
偏置硬币的熵: 0.47 bits
1.3 从KL散度到交叉熵
KL散度衡量两个概率分布的差异,而交叉熵可以看作是KL散度的一个组成部分:
def kl_divergence(p, q):
return np.sum(p * np.log2(p/q))
def cross_entropy(p, q):
return -np.sum(p * np.log2(q))
# 真实分布和预测分布
p = np.array([0.8, 0.2]) # 真实分布
q = np.array([0.7, 0.3]) # 预测分布
print(f"KL散度: {kl_divergence(p, q):.2f} bits")
print(f"交叉熵: {cross_entropy(p, q):.2f} bits")
输出结果:
KL散度: 0.08 bits
交叉熵: 0.51 bits
2. 二分类问题:Logistic回归中的交叉熵
2.1 从概率到损失函数
在二分类问题中,我们常用sigmoid函数将输出转换为概率:
def sigmoid(z):
return 1 / (1 + np.exp(-z))
# 示例数据
y_true = np.array([1, 0, 1]) # 真实标签
y_pred = np.array([0.9, 0.2, 0.4]) # 预测概率
# 交叉熵损失计算
def binary_cross_entropy(y_true, y_pred):
return -np.mean(y_true * np.log(y_pred) + (1-y_true) * np.log(1-y_pred))
print(f"交叉熵损失: {binary_cross_entropy(y_true, y_pred):.4f}")
输出结果:
交叉熵损失: 0.2284
2.2 梯度推导与实现
理解损失函数的梯度对于优化模型至关重要:
def binary_cross_entropy_gradient(X, y_true, y_pred):
return np.dot(X.T, (y_pred - y_true)) / len(y_true)
# 示例数据
X = np.array([[1, 2], [1, 3], [1, 4]]) # 特征矩阵,包含偏置项
y_true = np.array([1, 0, 1])
y_pred = np.array([0.9, 0.2, 0.4])
gradient = binary_cross_entropy_gradient(X, y_true, y_pred)
print("梯度:", gradient)
输出结果:
梯度: [-0.23333333 -0.73333333]
3. 多分类问题:Softmax交叉熵实现
3.1 Softmax函数与交叉熵
对于多分类问题,我们需要先将输出转换为概率分布:
def softmax(z):
exp_z = np.exp(z - np.max(z)) # 数值稳定性处理
return exp_z / np.sum(exp_z, axis=0)
# 示例数据
logits = np.array([2.0, 1.0, 0.1])
probabilities = softmax(logits)
print("Softmax输出:", probabilities)
print("概率总和:", np.sum(probabilities))
输出结果:
Softmax输出: [0.65900114 0.24243297 0.09856589]
概率总和: 1.0
3.2 完整的交叉熵实现
def categorical_cross_entropy(y_true, y_pred):
# 处理数值稳定性
y_pred = np.clip(y_pred, 1e-12, 1.0)
return -np.sum(y_true * np.log(y_pred)) / len(y_true)
# 示例数据
y_true = np.array([[1, 0, 0], [0, 1, 0]]) # one-hot编码
y_pred = np.array([[0.7, 0.2, 0.1], [0.1, 0.8, 0.1]])
loss = categorical_cross_entropy(y_true, y_pred)
print("分类交叉熵损失:", loss)
输出结果:
分类交叉熵损失: 0.2231435513142097
4. 深度学习框架中的实现对比
4.1 PyTorch实现
import torch
import torch.nn as nn
# 二分类问题
bce_loss = nn.BCELoss()
sigmoid = nn.Sigmoid()
# 示例数据
y_true = torch.tensor([1., 0., 1.])
y_pred = torch.tensor([0.9, 0.2, 0.4])
loss = bce_loss(y_pred, y_true)
print(f"PyTorch BCELoss: {loss.item():.4f}")
# 多分类问题
ce_loss = nn.CrossEntropyLoss()
logits = torch.tensor([[2.0, 1.0, 0.1], [1.0, 3.0, 0.2]])
targets = torch.tensor([0, 1]) # 类别索引,不是one-hot
loss = ce_loss(logits, targets)
print(f"PyTorch CrossEntropyLoss: {loss.item():.4f}")
4.2 TensorFlow实现
import tensorflow as tf
# 二分类问题
bce_loss = tf.keras.losses.BinaryCrossentropy()
y_true = tf.constant([1., 0., 1.])
y_pred = tf.constant([0.9, 0.2, 0.4])
loss = bce_loss(y_true, y_pred)
print(f"TensorFlow BinaryCrossentropy: {loss.numpy():.4f}")
# 多分类问题
cce_loss = tf.keras.losses.CategoricalCrossentropy()
y_true = tf.constant([[1., 0., 0.], [0., 1., 0.]])
y_pred = tf.constant([[0.7, 0.2, 0.1], [0.1, 0.8, 0.1]])
loss = cce_loss(y_true, y_pred)
print(f"TensorFlow CategoricalCrossentropy: {loss.numpy():.4f}")
4.3 框架实现的内部机制
深度学习框架中的交叉熵实现通常会考虑:
- 数值稳定性(避免log(0))
- 计算效率(向量化操作)
- 自动微分支持
例如,PyTorch的 CrossEntropyLoss 实际上是 LogSoftmax + NLLLoss 的组合:
# 等价于CrossEntropyLoss
log_softmax = nn.LogSoftmax(dim=1)
nll_loss = nn.NLLLoss()
log_probs = log_softmax(logits)
loss = nll_loss(log_probs, targets)
print(f"分解实现的CrossEntropy: {loss.item():.4f}")
5. 实战技巧与常见问题
5.1 数值稳定性处理
在实际实现中,我们需要特别注意数值稳定性:
# 不稳定的实现
def unstable_softmax(z):
return np.exp(z) / np.sum(np.exp(z))
# 稳定的实现
def stable_softmax(z):
shift_z = z - np.max(z)
exp_z = np.exp(shift_z)
return exp_z / np.sum(exp_z)
large_z = np.array([1000, 1000, 1000])
print("不稳定softmax:", unstable_softmax(large_z)) # 会出现nan
print("稳定softmax:", stable_softmax(large_z))
5.2 标签平滑技术
标签平滑可以防止模型对训练数据过度自信:
def label_smoothing(y_true, alpha=0.1):
n_classes = y_true.shape[1]
return y_true * (1 - alpha) + alpha / n_classes
y_true = np.array([[1, 0, 0], [0, 1, 0]])
smoothed = label_smoothing(y_true)
print("平滑后的标签:\n", smoothed)
5.3 类别不平衡问题处理
对于不平衡数据集,可以使用加权交叉熵:
def weighted_cross_entropy(y_true, y_pred, weights):
y_pred = np.clip(y_pred, 1e-12, 1.0)
return -np.sum(weights * y_true * np.log(y_pred)) / len(y_true)
# 假设类别1的样本较少,给予更高权重
weights = np.array([1.0, 2.0, 1.0]) # 对应三个类别的权重
y_true = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
y_pred = np.array([[0.7, 0.2, 0.1], [0.1, 0.7, 0.2], [0.1, 0.2, 0.7]])
loss = weighted_cross_entropy(y_true, y_pred, weights)
print("加权交叉熵损失:", loss)
6. 交叉熵在不同任务中的应用
6.1 图像分类任务
在图像分类中,交叉熵是最常用的损失函数。以CIFAR-10为例:
# 伪代码示例
model = CNN() # 卷积神经网络
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters())
for images, labels in dataloader:
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
6.2 自然语言处理任务
在语言模型中,我们使用交叉熵来衡量预测词分布与真实词的差异:
# 伪代码示例
class LanguageModel(nn.Module):
def forward(self, input_ids):
# 返回每个位置的词表logits
return logits
model = LanguageModel(vocab_size=50000)
criterion = nn.CrossEntropyLoss(ignore_index=0) # 忽略padding
for batch in dataloader:
logits = model(batch.input_ids)
# 将三维张量(batch, seq_len, vocab)转为二维计算损失
loss = criterion(logits.view(-1, vocab_size), batch.target_ids.view(-1))
6.3 多标签分类问题
对于多标签问题(一个样本可以属于多个类别),我们使用二元交叉熵:
# 伪代码示例
model = MultiLabelClassifier()
criterion = nn.BCEWithLogitsLoss() # 内置sigmoid
for features, labels in dataloader:
logits = model(features)
loss = criterion(logits, labels.float())
7. 交叉熵的变体与扩展
7.1 带温度的Softmax
温度参数可以控制输出分布的平滑程度:
def softmax_with_temperature(z, temperature=1.0):
z = z / temperature
return softmax(z)
logits = np.array([1.0, 2.0, 3.0])
print("T=1.0:", softmax_with_temperature(logits, 1.0))
print("T=0.5:", softmax_with_temperature(logits, 0.5)) # 更"尖锐"
print("T=2.0:", softmax_with_temperature(logits, 2.0)) # 更"平滑"
7.2 Focal Loss
Focal Loss是为类别不平衡设计的一种交叉熵变体:
def focal_loss(y_true, y_pred, gamma=2.0, alpha=0.25):
y_pred = np.clip(y_pred, 1e-12, 1.0)
pt = y_true * y_pred + (1-y_true) * (1-y_pred)
at = y_true * alpha + (1-y_true) * (1-alpha)
return -np.sum(at * (1-pt)**gamma * np.log(pt))
y_true = np.array([1, 0, 1])
y_pred = np.array([0.9, 0.2, 0.4])
print("标准交叉熵:", binary_cross_entropy(y_true, y_pred))
print("Focal Loss:", focal_loss(y_true, y_pred))
7.3 对比损失与交叉熵
在对比学习中,交叉熵也被广泛应用:
def contrastive_loss(features, labels, temperature=0.1):
# 计算相似度矩阵
sim_matrix = torch.matmul(features, features.T) / temperature
# 计算对比损失
exp_sim = torch.exp(sim_matrix)
pos_mask = labels.unsqueeze(0) == labels.unsqueeze(1)
neg_mask = ~pos_mask
pos_sum = torch.sum(exp_sim * pos_mask, dim=1)
neg_sum = torch.sum(exp_sim * neg_mask, dim=1)
loss = -torch.log(pos_sum / (pos_sum + neg_sum))
return torch.mean(loss)
更多推荐



所有评论(0)