当前位置: 首页 > news >正文

PyTorch nn.CrossEntropyLoss 实战:3种权重设置与标签平滑对比(附代码)

PyTorch nn.CrossEntropyLoss 实战:3种权重设置与标签平滑对比(附代码)

交叉熵损失函数是深度学习分类任务中最核心的组件之一。PyTorch框架中的nn.CrossEntropyLoss不仅实现了标准的交叉熵计算,还提供了weightlabel_smoothing两个关键参数来解决实际训练中的类别不平衡和过拟合问题。本文将深入解析这些高级参数的实现原理,并通过完整的代码对比展示不同配置下的训练效果差异。

1. 交叉熵损失函数核心原理回顾

在进入高级参数实战之前,我们需要明确几个关键概念:

  • Logits:模型最后一层线性层的原始输出值(未经过softmax)
  • Softmax:将logits转换为概率分布的函数:$p_i = \frac{e^{z_i}}{\sum_j e^{z_j}}$
  • 交叉熵:衡量预测概率分布与真实分布的差异:$H(p,q) = -\sum p_i \log q_i$

PyTorch的nn.CrossEntropyLoss实际上同时完成了softmax和交叉熵计算,其数学表达式为:

loss(x, class) = -log(exp(x[class]) / sum(exp(x[i]))) = -x[class] + log(sum(exp(x[i])))

这种实现方式在数值稳定性上优于分开计算softmax和交叉熵。

2. 权重参数的三种设置方式

类别不平衡是现实数据中的常见问题。weight参数允许我们为不同类别分配不同的损失权重,其核心公式变为:

$$ loss(x, class) = weight[class] \times (-x[class] + \log(\sum_j e^{x[j]})) $$

2.1 手动指定权重

最直接的方式是根据领域知识手动设置各类别权重。例如在医疗诊断场景中,罕见病类别的权重通常更高:

import torch import torch.nn as nn # 假设3分类问题:类别0、1、2的权重分别为1.0, 2.5, 1.8 weights = torch.tensor([1.0, 2.5, 1.8]) criterion = nn.CrossEntropyLoss(weight=weights) # 示例计算 logits = torch.tensor([[2.0, 1.0, 0.5], [0.5, 2.0, 1.5]]) # 2个样本的logits targets = torch.tensor([0, 1]) # 真实类别 loss = criterion(logits, targets) print(f"加权损失值: {loss.item():.4f}")

2.2 自动计算类别权重

更科学的方式是根据训练集统计自动计算权重,常用方法是反比于类别频率:

def calculate_weights(train_labels): class_counts = torch.bincount(train_labels) num_classes = len(class_counts) weights = 1. / (class_counts.float() / class_counts.float().sum()) return weights / weights.sum() * num_classes # 归一化 # 假设训练集中3个类别的样本数分别为100, 30, 20 train_labels = torch.cat([ torch.zeros(100), torch.ones(30), torch.full((20,), 2) ]).long() weights = calculate_weights(train_labels) print(f"自动计算权重: {weights}")

2.3 类别平衡权重

在极端不平衡场景下,可以使用更激进的平衡策略:

def balanced_weights(train_labels, beta=0.9): class_counts = torch.bincount(train_labels) effective_num = 1.0 - torch.pow(beta, class_counts) weights = (1.0 - beta) / effective_num return weights / weights.sum() * len(weights) weights = balanced_weights(train_labels) print(f"平衡权重: {weights}")

三种权重计算方式对比表:

方法类型优点缺点适用场景
手动指定灵活可控依赖领域知识类别重要性明确
自动计算数据驱动对极端少数类敏感一般不平衡数据
平衡权重强调少数类可能过度补偿极端不平衡数据

3. 标签平滑技术解析与实现

标签平滑(Label Smoothing)是一种正则化技术,通过"软化"one-hot编码来防止模型对标签的过度自信。其数学表达为:

$$ y' = (1 - \alpha) \times y + \alpha / K $$

其中$K$是类别数,$\alpha$是平滑系数。

3.1 基础实现

PyTorch 2.0+直接支持label_smoothing参数:

criterion = nn.CrossEntropyLoss(label_smoothing=0.1) # 等效手动实现 def smooth_one_hot(labels, alpha, num_classes): smoothed = torch.full((labels.size(0), num_classes), alpha / (num_classes-1)) smoothed.scatter_(1, labels.unsqueeze(1), 1.0 - alpha) return smoothed smoothed_targets = smooth_one_hot(targets, 0.1, 3)

3.2 与权重的组合使用

标签平滑可以与权重参数协同工作:

criterion = nn.CrossEntropyLoss( weight=torch.tensor([1.0, 2.0, 1.5]), label_smoothing=0.1 )

3.3 不同平滑系数的效果对比

我们通过一个简单的实验观察不同$\alpha$值的影响:

import matplotlib.pyplot as plt def test_smoothing_effects(logits, targets): alphas = [0, 0.05, 0.1, 0.2, 0.3] losses = [] for alpha in alphas: criterion = nn.CrossEntropyLoss(label_smoothing=alpha) loss = criterion(logits, targets) losses.append(loss.item()) plt.plot(alphas, losses, marker='o') plt.xlabel('Smoothing Alpha') plt.ylabel('Loss Value') plt.title('Label Smoothing Effect on Loss') plt.grid(True) plt.show() test_smoothing_effects(logits, targets)

典型情况下,损失值会随$\alpha$增大而先降后升,最优值通常在0.05-0.2之间。

4. 完整训练案例对比

现在我们实现一个完整的图像分类训练流程,对比不同参数配置的效果。使用CIFAR-10数据集,人为制造类别不平衡:

import torchvision from torch.utils.data import WeightedRandomSampler # 创建不平衡的CIFAR-10数据集 transform = torchvision.transforms.Compose([ torchvision.transforms.ToTensor(), torchvision.transforms.Normalize((0.5,0.5,0.5), (0.5,0.5,0.5)) ]) dataset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) # 人为制造不平衡:各类别样本数指数递减 class_counts = [5000] + [5000 // (2**i) for i in range(1,10)] print("各类别样本数:", class_counts) # 创建带权重的DataLoader weights = 1. / torch.tensor(class_counts, dtype=torch.float) samples_weights = weights[torch.tensor(dataset.targets)] sampler = WeightedRandomSampler(samples_weights, len(samples_weights)) train_loader = torch.utils.data.DataLoader(dataset, batch_size=64, sampler=sampler)

定义测试函数:

def train_and_evaluate(criterion, epochs=10): model = torchvision.models.resnet18(num_classes=10) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) train_losses = [] accuracies = [] for epoch in range(epochs): model.train() total_loss = 0 for inputs, labels in train_loader: optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() # 评估 model.eval() correct = 0 total = 0 with torch.no_grad(): for inputs, labels in test_loader: outputs = model(inputs) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() acc = correct / total train_losses.append(total_loss/len(train_loader)) accuracies.append(acc) print(f"Epoch {epoch+1}: Loss={train_losses[-1]:.4f}, Acc={acc:.4f}") return train_losses, accuracies

配置对比实验:

# 实验配置 configs = [ {"name": "Baseline", "criterion": nn.CrossEntropyLoss()}, {"name": "Weighted", "criterion": nn.CrossEntropyLoss(weight=weights)}, {"name": "Smoothing(0.1)", "criterion": nn.CrossEntropyLoss(label_smoothing=0.1)}, {"name": "Weighted+Smoothing", "criterion": nn.CrossEntropyLoss(weight=weights, label_smoothing=0.1)} ] # 运行实验 results = {} for config in configs: print(f"\nRunning {config['name']}...") losses, accs = train_and_evaluate(config["criterion"]) results[config["name"]] = {"loss": losses, "acc": accs}

可视化结果:

plt.figure(figsize=(12,5)) plt.subplot(1,2,1) for name, res in results.items(): plt.plot(res["loss"], label=name) plt.xlabel("Epoch") plt.ylabel("Training Loss") plt.legend() plt.subplot(1,2,2) for name, res in results.items(): plt.plot(res["acc"], label=name) plt.xlabel("Epoch") plt.ylabel("Test Accuracy") plt.legend() plt.tight_layout() plt.show()

典型实验结果会显示:

  • 加权方法能显著提升少数类的识别率
  • 标签平滑有助于提高最终准确率
  • 组合使用可能获得最佳平衡

5. 高级技巧与注意事项

5.1 权重归一化

当使用较大权重值时,建议进行归一化以避免梯度爆炸:

weights = weights / weights.sum() * len(weights)

5.2 动态权重调整

在训练过程中可以动态调整权重:

def dynamic_weight_adjustment(epoch, max_epoch): base_weights = torch.tensor([...]) # 基础权重 # 随训练进程逐渐降低权重影响 factor = 1.0 - min(epoch / max_epoch, 0.8) return base_weights * factor

5.3 标签平滑与MixUp的结合

MixUp数据增强与标签平滑有协同效应:

def mixup_data(x, y, alpha=0.2): lam = np.random.beta(alpha, alpha) batch_size = x.size(0) index = torch.randperm(batch_size) mixed_x = lam * x + (1 - lam) * x[index] y_a, y_b = y, y[index] return mixed_x, y_a, y_b, lam # 在训练循环中 inputs, targets_a, targets_b, lam = mixup_data(inputs, targets) outputs = model(inputs) loss = lam * criterion(outputs, targets_a) + (1 - lam) * criterion(outputs, targets_b)

5.4 类别权重与学习率

对于高权重类别,可以相应降低学习率:

optimizer = torch.optim.SGD([ {'params': model.base.parameters()}, {'params': model.classifier.parameters(), 'lr': 1e-3 * weights.mean()} ], lr=1e-3)
http://www.jsqmd.com/news/1157397/

相关文章:

  • 【JAVA毕设源码分享】基于SpringBoot的动物园管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 蓝牙 5.4 协议栈深度解析:从 HCI 到 L2CAP 的 7 层数据流
  • OpenClaw本地化部署:xParse文档解析引擎实战指南
  • Codex AI工具磁盘I/O冲突排查:解决系统卡顿与启动故障
  • Caspar:基于符号编程的GPU加速非线性优化库
  • npm全局命令not found的7个精准修复方案
  • 51单片机+DS1302+LCD1602:3种时间显示方案与闰年自动处理代码对比
  • R语言环境安装全指南:R、Rtools与RStudio三步协同配置
  • WSL2 Kali Linux 2025.1 空间优化:从C盘迁移至D盘并释放10GB+
  • 安徽省豆包排名哪家实力强 - 招财兔数字员工
  • 大模型稳定输出JSON的实战方案:从原理到95%+正确率实现
  • 基于MA12070与PIC24FJ的高保真音频系统设计
  • OpenClaw龙虾AI:中文友好型零代码智能体框架实战指南
  • 基于springboot的留学信息推荐系统的设计与实现
  • Quartus 18.1 .qxp 网表文件:3种导入方式对比与命令行自动化脚本
  • Windows Docker加速与Portainer图形化管理实战指南
  • Python车道线检测实战包:带实拍视频、测试图和分步可视化脚本
  • 我为什么放弃使用大模型
  • 本体语义搜索——企业AI怎么从“搜关键词“到“搜语义“
  • Android扫码枪开发套件:支持UHF RFID读写与一维二维条码扫描
  • 2026 徐州本地废铜、废铁、废铝回收优质公司推荐榜单(本地工厂优选) - 星际AI
  • ClaudeCode中实时监控智谱API配额的状态栏实现
  • DMXAPI+requests高可用调用实战:破解429限流与128K上下文陷阱
  • k8集群一键重置
  • 豆包智能体对话怎么批量导出?聊天记录和人格设定备份方法(第5版) - 【DS随心转】
  • 多延迟ASL数据一键处理工具:CBF定量+CO₂反应性分析(含MNI模板与临床案例脚本)
  • Matlab毕设实战:PEMFC电压稳定控制的MPC仿真工程包(含论文+答辩PPT+多版文档)
  • 2026年黑龙江省采购管理职业资格培训选择指南:证书类型、资质核验与课程参考 - 企智芯
  • MySQL生产级安装与GTID复制链路打通实战
  • 博途V17安装与授权全解:系统配置、ALM服务与硬件绑定原理