)
用Python代码可视化交叉熵从信息论到深度学习实战第一次看到交叉熵公式时那种被各种log和求和符号支配的恐惧感我还记忆犹新。直到有一天我决定用Python代码把这些抽象符号变成可视化的图形一切突然变得清晰起来。本文将带你用代码画出交叉熵的完整演变过程——从最基础的信息量概念到PyTorch和TensorFlow中的实际应用对比。1. 从信息量到交叉熵的代码实现1.1 信息量的直观理解信息量衡量的是事件的不确定性。一个简单例子明天太阳会升起 vs 明天会下雨。前者几乎确定信息量低后者不确定信息量高。用Python实现import numpy as np import matplotlib.pyplot as plt def information_content(p): return -np.log2(p) # 生成概率从0.01到1的数组 probabilities np.linspace(0.01, 1, 100) info information_content(probabilities) plt.figure(figsize(10,6)) plt.plot(probabilities, info) plt.title(信息量随概率变化曲线) plt.xlabel(概率 p(x)) plt.ylabel(信息量 I(x) (bits)) plt.grid(True) plt.show()这段代码会生成一条曲线展示信息量如何随概率变化。当概率接近0时信息量趋近于无穷大概率为1时信息量为0。1.2 信息熵系统的不确定性信息熵是信息量的期望值衡量整个系统的不确定性。假设有一个不公平硬币def entropy(probabilities): return -np.sum(probabilities * np.log2(probabilities)) # 不公平硬币的概率 p_heads 0.7 p_tails 1 - p_heads coin_probs np.array([p_heads, p_tails]) print(f信息熵: {entropy(coin_probs):.4f} bits) # 可视化不同概率下的熵 p_range np.linspace(0.01, 0.99, 100) entropies [entropy(np.array([p, 1-p])) for p in p_range] plt.figure(figsize(10,6)) plt.plot(p_range, entropies) plt.title(二元系统信息熵) plt.xlabel(事件概率 p) plt.ylabel(熵 H (bits)) plt.axvline(x0.5, colorr, linestyle--) plt.grid(True) plt.show()你会看到当p0.5时熵最大系统最不确定向两边逐渐减小。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和预测分布q p np.array([0.5, 0.3, 0.2]) q np.array([0.4, 0.4, 0.2]) kl kl_divergence(p, q) h_p entropy(p) h_pq cross_entropy(p, q) print(fKL散度: {kl:.4f} bits) print(f交叉熵: {h_pq:.4f} bits) print(f验证H(p,q) H(p) D_KL(p||q): {h_p kl:.4f} {h_pq:.4f})2. 交叉熵在分类问题中的应用2.1 二分类逻辑回归案例让我们实现一个简单的逻辑回归交叉熵损失def sigmoid(x): return 1 / (1 np.exp(-x)) def binary_cross_entropy(y_true, y_pred, epsilon1e-15): y_pred np.clip(y_pred, epsilon, 1 - epsilon) return -np.mean(y_true * np.log(y_pred) (1 - y_true) * np.log(1 - y_pred)) # 生成模拟数据 np.random.seed(42) y_true np.random.randint(0, 2, size100) y_pred np.random.uniform(0, 1, size100) loss binary_cross_entropy(y_true, y_pred) print(f二分类交叉熵损失: {loss:.4f}) # 可视化不同预测值下的损失 y_true_sample 1 y_pred_range np.linspace(0.01, 0.99, 100) losses [binary_cross_entropy(np.array([y_true_sample]), np.array([y_pred])) for y_pred in y_pred_range] plt.figure(figsize(10,6)) plt.plot(y_pred_range, losses) plt.title(真实y1时的交叉熵损失) plt.xlabel(预测概率 p(y1)) plt.ylabel(损失) plt.grid(True) plt.show()2.2 多分类Softmax交叉熵实现对于多分类问题我们需要实现Softmax函数def softmax(x): e_x np.exp(x - np.max(x)) # 防止数值溢出 return e_x / e_x.sum(axis0) def categorical_cross_entropy(y_true, y_pred, epsilon1e-15): y_pred np.clip(y_pred, epsilon, 1 - epsilon) return -np.sum(y_true * np.log(y_pred)) # 模拟3分类问题 y_true np.array([1, 0, 0]) # 真实类别是第0类 logits np.array([2.0, 1.0, 0.1]) # 模型输出的原始分数 probs softmax(logits) loss categorical_cross_entropy(y_true, probs) print(f预测概率分布: {probs}) print(f多分类交叉熵损失: {loss:.4f}) # 可视化不同预测下的损失 def plot_softmax_loss(true_class0): x np.linspace(-5, 5, 100) y np.zeros((100, 3)) y[:, true_class] 1 # 真实类别 losses [] for i in range(100): logits np.array([x[i], 0, 0]) # 只改变目标类的logit probs softmax(logits) loss categorical_cross_entropy(y[i], probs) losses.append(loss) plt.figure(figsize(10,6)) plt.plot(x, losses) plt.title(f真实类别{true_class}时的Softmax交叉熵损失) plt.xlabel(目标类别的logit值) plt.ylabel(损失) plt.grid(True) plt.show() plot_softmax_loss(true_class0)3. PyTorch与TensorFlow实现对比3.1 PyTorch中的交叉熵PyTorch提供了几种交叉熵实现方式import torch import torch.nn as nn # 二分类情况 bce_loss nn.BCELoss() # 需要先经过sigmoid bce_logits_loss nn.BCEWithLogitsLoss() # 内置sigmoid # 多分类情况 ce_loss nn.CrossEntropyLoss() # 内置softmax # 示例多分类问题 logits torch.tensor([[2.0, 1.0, 0.1]], requires_gradTrue) target torch.tensor([0]) # 类别索引形式 loss ce_loss(logits, target) print(fPyTorch交叉熵损失: {loss.item():.4f}) # 反向传播演示 loss.backward() print(flogits的梯度:\n{logits.grad})PyTorch的CrossEntropyLoss实际上结合了LogSoftmax和NLLLoss因此输入应该是原始logits而非概率。3.2 TensorFlow/Keras实现TensorFlow中的交叉熵也有多种形式import tensorflow as tf from tensorflow.keras import losses # 二分类 bce_loss losses.BinaryCrossentropy(from_logitsFalse) bce_logits_loss losses.BinaryCrossentropy(from_logitsTrue) # 多分类 cce_loss losses.CategoricalCrossentropy(from_logitsFalse) sparse_cce_loss losses.SparseCategoricalCrossentropy(from_logitsTrue) # 示例多分类问题 logits tf.constant([[2.0, 1.0, 0.1]]) target tf.constant([0]) # 稀疏标签 loss sparse_cce_loss(target, logits) print(fTensorFlow稀疏分类交叉熵: {loss.numpy():.4f}) # 计算梯度 with tf.GradientTape() as tape: tape.watch(logits) loss sparse_cce_loss(target, logits) grads tape.gradient(loss, logits) print(flogits的梯度:\n{grads})3.3 reduction参数的影响框架中的reduction参数控制如何聚合多个样本的损失reduction模式行为适用场景mean (默认)计算所有样本损失的平均值大多数训练场景sum计算所有样本损失的总和需要自定义加权时none返回每个样本的独立损失需要逐样本处理时# 比较不同reduction模式 logits torch.tensor([[2.0, 1.0], [1.0, 2.0]]) targets torch.tensor([0, 1]) loss_mean nn.CrossEntropyLoss(reductionmean)(logits, targets) loss_sum nn.CrossEntropyLoss(reductionsum)(logits, targets) loss_none nn.CrossEntropyLoss(reductionnone)(logits, targets) print(fmean模式: {loss_mean.item():.4f}) print(fsum模式: {loss_sum.item():.4f}) print(fnone模式: {loss_none.numpy()})4. 交叉熵的优化技巧与常见问题4.1 数值稳定性问题计算交叉熵时可能遇到的数值问题log(0)问题当预测概率为0时log计算会得到负无穷指数溢出计算Softmax时大的logits会导致指数溢出解决方案对比问题解决方案实现方式log(0)裁剪概率值np.clip(probs, eps, 1-eps)Softmax溢出减去最大值x - np.max(x)数值精度使用log-sum-exp技巧log(sum(exp(x))) max(x) log(sum(exp(x - max(x))))4.2 标签平滑技术标签平滑可以防止模型对标签过于自信def label_smoothing(y_true, alpha0.1, num_classes3): return y_true * (1 - alpha) alpha / num_classes # 原始标签 y_true np.array([1, 0, 0]) # 平滑后标签 y_smooth label_smoothing(y_true) print(f平滑后标签: {y_smooth}) # 比较损失变化 logits np.array([3.0, 1.0, 0.5]) probs softmax(logits) original_loss categorical_cross_entropy(y_true, probs) smooth_loss categorical_cross_entropy(y_smooth, probs) print(f原始损失: {original_loss:.4f}) print(f平滑损失: {smooth_loss:.4f})4.3 类别不平衡问题当类别分布不均衡时可以加权交叉熵def weighted_cross_entropy(y_true, y_pred, weights): return -np.sum(weights * y_true * np.log(y_pred)) # 假设类别0样本很少给予更高权重 class_weights np.array([2.0, 1.0, 1.0]) weighted_loss weighted_cross_entropy(y_true, probs, class_weights) print(f加权交叉熵: {weighted_loss:.4f})PyTorch和TensorFlow都内置了权重支持# PyTorch实现 weights torch.tensor([2.0, 1.0, 1.0]) ce_loss nn.CrossEntropyLoss(weightweights) # TensorFlow实现 ce_loss tf.keras.losses.CategoricalCrossentropy( from_logitsFalse, label_smoothing0.1 )