资讯详情

PyTorch CrossEntropyLoss原理、参数与避坑实战

📅 2026/10/26 3:18:21 | 华诺云谱 👁 阅读
PyTorch CrossEntropyLoss原理、参数与避坑实战
第一次在别人的训练脚本里看到nn.CrossEntropyLoss()这行代码时我其实没太当回事——不就是个损失函数吗一行搞定能有什么学问。直到后来自己动手改模型loss 卡在 2.3 死活不降、把 one-hot 标签直接喂进去报了个形状错误、在类别极不平衡的数据上训出一个全猜多数类的废模型我才回过头来认真拆这个东西。nn.CrossEntropyLoss()是 PyTorch 里做单标签多分类任务最常用的损失函数它把 Softmax、取对数、负号、按 batch 求平均这一整串操作打包成了一个黑盒。它的价值在于把数值稳定性和梯度形式都处理好了但代价是你如果不知道盒子里装了什么出了问题连从哪查都不知道。这篇文章面向的是刚接触深度学习分类任务、或者已经用过但被它坑过的同学我会从它在算什么、内部怎么实现、参数怎么配、实操怎么跑、坑怎么排这几个角度把它彻底拆开。1. 先搞清楚它到底在算什么从一句话定义到公式拆解这节先把概念层面的事说透。很多人用nn.CrossEntropyLoss()用了很久但对它的理解停留在分类任务就写这个一旦遇到回归任务、多标签任务、序列标注任务就不知道该不该继续用。搞清楚它的定义边界比记住 API 更重要。1.1 名字里那个 Cross 到底跨了什么交叉熵这个名字容易吓人其实它衡量的是两个概率分布之间的差异。你有一个真实分布 q比如某个样本的标签是第 2 类那真实分布就是 [0, 0, 1, 0, 0]你还有一个预测分布 p是模型经过 Softmax 输出的各类概率比如 [0.05, 0.10, 0.70, 0.10, 0.05]。交叉熵的定义是 H(q, p) -Σ q_i · log(p_i)。因为 q 是 one-hot 的只有正确类别那一项是 1其余全是 0所以求和一下就退化成 -log(p_正确类别)。这意味着交叉熵只关心一件事模型给正确类别的概率有多大。给了 0.7损失就是 -log(0.7) ≈ 0.357给了 0.1损失就是 -log(0.1) ≈ 2.303给了 0.99损失就是 0.01。这个函数在 p 接近 1 的时候极其平缓在 p 接近 0 的时候急剧爆炸正好起到错得越离谱罚得越狠的效果。这也是为什么它能自然地把模型的输出往正确类别概率更高的方向推而不需要额外设计惩罚项。生活化的类比假设你是一个诊断医生对每个病例输出我有几成把握是肺炎。交叉熵就是一套评分规则它不管你给其他病打了多少分只看你给真实那个病打了多少分。你给了 90% 把握扣一点点你给了 10% 把握重扣。整套规则的核心思想就是逼你在关键判断上不要含糊。1.2 为什么分类任务不用均方误差刚入门的时候很容易想预测错了就用预测值减真实值平方求和这不挺直观吗回归任务确实是这么干的MSE 好使得很。但把它搬到分类任务上会碰到两个问题。第一个问题是梯度消失。如果把 Softmax 的输出和 MSE 组合起来损失函数对 logits 的梯度里会带上一个 Softmax 导数项而 Softmax 的导数在预测概率接近 0 或 1 的时候会变得非常小。结果就是模型错得很离谱的时候梯度反而很小参数几乎不动训练陷入怎么都不学的状态。交叉熵配合 Softmax 时这两个函数的导数恰好抵消掉一项最终梯度化简成极其干净的softmax(logits) - onehot后面第 2 节会推导错误越大梯度越大学习的动力源源不断。第二个问题是最小化目标和分类正确率不对齐。MSE 会去优化所有类别的概率数值包括那些无关类别的分数而分类任务真正关心的是正确类别的排序和概率。交叉熵天然只盯着正确类别优化的方向更直接。所以你在实际项目里几乎看不到Softmax MSE这种组合就是因为它在绝大多数情况下训不动。1.3 把一个 batch 的计算拆成三步具体到一个 batch 的输入nn.CrossEntropyLoss()做的事情可以拆成三步理解这三步基本就理解了它。第一步对每个样本的 logits 做 Softmax转成概率。第二步从概率里把每个样本正确类别那一个位置的概率取出来取负对数。第三步按reduction的设定把这些值聚合成一个标量默认是求平均。用一组具体数字走一遍。假设 batch 里有 2 个样本3 个类别logits 是logits torch.tensor([[2.0, 1.0, 0.1], [0.5, 2.5, 0.3]]) target torch.tensor([0, 2])第一行 exp 后是 [7.389, 2.718, 1.105]和是 11.212概率是 [0.659, 0.242, 0.099]正确类别 0损失 -log(0.659) ≈ 0.417。第二行 exp 后是 [1.649, 12.183, 1.350]和是 15.181概率是 [0.109, 0.802, 0.089]正确类别 2损失 -log(0.089) ≈ 2.420。两者平均得 1.4185。这个数值你拿criterion(logits, target)跑一下得到的就是 1.4185 左右一分不差。把这三步记住后面所有关于数值稳定性、梯度形式、参数效果的讨论都能挂到这三步上。2. PyTorch 内部实现拆解它其实是两个函数的组合想知道nn.CrossEntropyLoss到底做了什么最直接的办法是打开文档看它等价于什么。它的定义写得很清楚等价于nn.LogSoftmax()接nn.NLLLoss()。这个等价关系不是随便写的理解了它很多看起来莫名其妙的行为就都解释得通了。2.1 log_softmax 加 nll_loss 的拼装逻辑NLLLoss的全称是 Negative Log Likelihood Loss负对数似然。它的输入必须已经是 log 概率也就是说它只负责取正确类别那个位置的值、加负号、聚合这三件事不负责把 logits 变成概率。所以单独用NLLLoss的时候模型最后一层必须手动接一个log_softmax否则你传进去的是 logits 而不是 log 概率算出来的损失毫无意义但 PyTorch 不会报错——它只检查形状不检查你有没有做归一化。这是我见过最高频的静默错误之一损失值看起来像模像样训练就是不动。CrossEntropyLoss相当于把log_softmax和nll_loss粘在了一起。这么设计有两个好处一是省掉了中间那一步显式存储概率张量的内存对大规模类别数比如推荐系统里几万类来说这个开销不小二是在 fused 实现里可以使用更稳定的计算方式避免中间结果溢出。如果你曾经自己拼过log_softmax nll_loss然后发现结果和CrossEntropyLoss一致那就说明你已经理解这层等价关系了。反过来如果你自己接了个softmax再传给NLLLoss那你得到的是-log(softmax(softmax(x)))损失会被压得很低模型几乎学不到东西。2.2 数值稳定性那个减最大值的小动作标准 Softmax 的公式是 exp(x_i) / Σ exp(x_j)。当 x_i 的数值比较大时exp(x_i) 会迅速超出浮点数的表示范围。比如 x 100exp(100) 约为 2.7e43float32 最大约 3.4e38直接变 inf然后 inf/inf 得 nan整个训练报废。解决办法是一个数学上等价的小技巧先给所有 logits 减去这一行里的最大值再做 exp。因为 exp(a - c) / Σ exp(x_j - c) exp(a) / Σ exp(x_j)分子分母同乘 exp(-c)结果完全一样但减去最大值之后最大的一项变成 exp(0) 1其余项都小于 1不会溢出。在 log_softmax 里这个操作更彻底因为 log(Σ exp(x_i - c)) 里的项都在可控范围最终结果是x - c - log(Σ exp(x - c))全程不会出现 inf 或 nan。这个细节对使用者的实际意义在于你完全可以把原始 logits 丢给CrossEntropyLoss不需要自己做任何归一化或者截断它内部已经处理好了。反过来如果你手写 Softmax 之后再 log遇到极端 logits 就会得到 -inf进而 loss 变 nan。我见过有人为了稳定给 logits 加torch.clamp(-10, 10)其实是画蛇添足反而破坏了原始分布。2.3 梯度长什么样为什么反向传播这么简单推导一下损失对 logits 的梯度这个结果值得烂熟于心。设第 i 个样本的 logits 为 zSoftmax 输出 p_j exp(z_j) / Σ exp(z_k)损失 L -log(p_y)其中 y 是正确类别。先对 log 求导∂L/∂p_y -1/p_y。再对 Softmax 求导Softmax 对 z_j 的偏导有两类当 j y 时∂p_y/∂z_j p_y(1 - p_y)当 j ≠ y 时∂p_y/∂z_j -p_y · p_j。把链式法则拼起来得到 ∂L/∂z_j p_j - 1{j y}。这个式子太干净了梯度就是预测概率向量减去 one-hot 标签向量。预测得越准梯度越小预测得越离谱梯度越大而且梯度里没有任何可能消失的项。当reductionmean时再整体除以 batch size N。这就是为什么分类任务训起来通常比回归任务稳因为梯度尺度自动被 Softmax 的输出范围约束住了。从工程角度看这个性质还带来一个好处你不需要担心梯度爆炸。Softmax 输出在 0 到 1 之间减去 one-hot 后梯度绝对值不超过 1所以哪怕学习率设得偏大一点也不至于一步把参数炸飞。当然这说的是梯度本身如果前面层结构复杂、激活函数选得不好梯度还是可能出问题但至少损失这一层是安全的。3. 参数逐个过五个参数怎么配nn.CrossEntropyLoss的签名不长主要参数就weight、ignore_index、reduction、label_smoothing加上一些标志位。每个参数背后都对应一类真实场景配错了不会报错但结果是错的。3.1 weight类别不平衡的第一道防线先看reductionmean下带权重的公式loss Σ_i w_{y_i} · (-log p_{y_i}) / Σ_i w_{y_i}。注意分母是权重之和不是样本数量 N。这个细节我踩过坑以为设了权重之后平均还是按 N 算结果算出来的损失对不上排查了半天才发现是分母不一样。权重的取值经验上可以用类别频率的倒数比如三类样本数分别是 1000、100、100频率倒数是 1/1000、1/100、1/100归一化到均值为 1 得到大约 [0.11, 1.11, 1.11]具体数值按你的数据算。也可以直接用w_c N / (C · n_c)其中 N 是总样本数C 是类别数n_c 是该类样本数这样算出来的权重天然均值为 1比较直观。但要注意两点。第一权重不是万能的极端不平衡时比如 1:1000光靠权重往往把模型推向另一个极端——开始疯狂预测少数类准确率反而崩。这时候更该配合重采样、阈值调整或者 focal loss。第二reductionmean下如果权重之和很小损失的绝对尺度会变化学习率要跟着调。我在一个 8 类、最少类只有几十个样本的数据集上设过权重lr 还是老值前几百步 loss 直接冲到 5 以上后来把 lr 降了一半才稳下来。3.2 ignore_indexpadding 位置的正确处理方式序列标注、机器翻译、图像分割这类任务里target 里会有大量无效位置padding 位它们不应该参与损失计算。ignore_index的默认值是 -100你把这个位置填成 -100PyTorch 会在计算时跳过它。注意是跳过不是当成 0 参与计算这两者有本质区别当成 0 相当于强制模型在 padding 位置上预测第 0 类白白浪费模型容量还会污染梯度。实战中我习惯统一用 -100 作为无效标签的占位符因为这是 PyTorch 的默认值不用特意传参而且它和多数序列标注的数据处理流程兼容。自己拼 target 张量的时候记得用torch.full_like(seq, fill_value-100)初始化再往里填真实标签这样不容易漏。还有个细节如果整个 batch 全是 ignore 位置reductionmean时 PyTorch 会返回 nan分母为 0这个错误很难看出根因。在变长序列的 batch 里我一般会加一行保护逻辑判断有效位置的数量或者干脆设reductionsum再自己按有效数量除。3.3 reductionmean 的陷阱和 sum 的适用场景三个取值none 不做聚合返回逐样本的损失张量mean 求平均sum 求和。none在需要按样本加权比如难例挖掘、把多个样本的损失混合时很有用我在做样本级别的损失分析时经常用它来定位哪些样本一直在贡献高损失。一个容易忽略的坑是多卡训练、梯度累积场景下不同 batch 的有效样本数可能不一样。如果每步都用mean再做梯度累积平均实际等效的是一个由 batch 大小加权的平均而不是等权平均。序列任务里这个偏差很明显因为每个 batch 的 token 数差别很大。我的做法是统一用sum除以全局有效 token 数这样跨 batch、跨卡都是严格等价的。训练和评估的 reduction 建议分开训练用你需要的聚合方式评估时用none或者自己累加总和与计数这样能精确算出整个验证集的平均损失不受最后一个不满 batch 的影响。3.4 label_smoothing一个参数顶几行代码这个参数是较新版本 PyTorch 才加的作用是给 one-hot 标签掺一点噪声真实类别概率设成 1 - ε ε/C其余类别各设 ε/C其中 C 是类别数ε 是平滑系数常见取值 0.05 到 0.1。它的效果是防止模型对训练标签过度自信缓解过拟合通常能换来一点泛化提升。为什么它有效因为 one-hot 标签强制模型把正确类别的概率推到 1这需要 logits 之间的差距趋向无穷大模型会被迫去学一些很细碎的特征来区分那些本来就容易混淆的样本。加了平滑之后目标变成了正确类别的概率显著高于其他模型不必把差距拉到无穷学到的表示更平滑。这也是很多蒸馏、对比学习工作里用 smoothing 的原因。代价是收敛会慢一点训练损失的下限变了不再是 0所以你不能拿加平滑后的训练损失和不加平滑的值直接比。另外如果你的任务是评估 top-1 准确率平滑通常有正收益如果下游任务极度依赖精确的概率输出比如需要做概率校准或者把概率当作后续流程的输入平滑会破坏校准性要谨慎。3.5 参数速查表参数默认值什么时候必须动常见错误weightNone类别严重不平衡以为 mean 的分母是样本数ignore_index-100序列/图像有 padding 位全 ignore 导致 nanreductionmean梯度累积、变长序列多 batch 平均权重不一致label_smoothing0.0想抑制过拟合拿平滑后的 loss 和旧实验对比size_average已废弃不要用和 reduction 同时设置冲突4. 实操环节从零跑通一个多分类训练概念和参数都讲完了接下来把代码跑起来。这一节我会用一个最小的多分类例子把模型、损失、训练循环串起来再把官方实现和手写实现的结果对照一遍——这一步非常值得做对照过一次你对这个函数的信任度会完全不同。4.1 数据与模型的最小可运行骨架import torch import torch.nn as nn import torch.nn.functional as F torch.manual_seed(0) num_classes 5 feature_dim 16 batch_size 8 model nn.Sequential( nn.Linear(feature_dim, 64), nn.ReLU(), nn.Linear(64, num_classes), # 注意最后不加 softmax ) x torch.randn(batch_size, feature_dim) y torch.randint(0, num_classes, (batch_size,), dtypetorch.long) criterion nn.CrossEntropyLoss() logits model(x) loss criterion(logits, y) print(logits.shape, loss.item())这里有两个非常关键的点我单独拎出来说。模型最后一层绝对不能加 Softmax 或 Sigmoid。因为CrossEntropyLoss内部已经做了 log_softmax你再做一次就成了 softmax(softmax(x))概率分布被严重压扁模型几乎学不到东西而且 loss 会异常地低且不降。这是新手最常犯的错没有之一。判断方法很简单如果你的模型在第一个 epoch 里 loss 就只有 0.0x 且在训练集上准确率极低八成是这里出了问题。target 的 dtype 必须是torch.long。CrossEntropyLoss的 target 是类别索引不是 one-hot 向量。如果你传 one-hot形状变成 (N, C)PyTorch 会把它当成每个样本有 C 个维度需要分类的高维情况处理然后报一个看起来完全不相关的维度错误。如果你传 float 类型的索引会直接抛出类型错误。标签处理这一步推荐在 Dataset 的__getitem__里就转成 long别拖到训练循环里。4.2 手工实现交叉熵并与官方 API 对照def manual_cross_entropy(logits, target): # 步骤1数值稳定版 log_softmax shifted logits - logits.max(dim1, keepdimTrue).values log_probs shifted - torch.log(torch.exp(shifted).sum(dim1, keepdimTrue)) # 步骤2按 target 取出正确类别的 log 概率 picked log_probs.gather(1, target.unsqueeze(1)).squeeze(1) # 步骤3取负号并求平均 return -picked.mean() manual_loss manual_cross_entropy(logits.detach(), y) official_loss criterion(logits.detach(), y) print(manual_loss.item(), official_loss.item()) # 两个值应该在 1e-6 量级内一致如果两者对不上通常是这三个原因gather的索引维度没对上必须是 (N, 1)、少了keepdimTrue导致广播出错、或者shifted里忘记在 dim 上做减法。这个手写版本建议你亲手敲一遍跑通之后再回头看官方文档那种原来是这么回事的感觉是看多少篇教程都给不了的。进阶一点把带权重和 ignore_index 的版本也手写一遍。带权重的关键在于F.cross_entropy(..., reductionnone)拿到逐样本损失之后用weight[target]乘上去再按权重和归一化ignore_index 的关键在于先构造一个 mask把无效位置挖掉再算均值。这两个手写过程能让你彻底搞清楚参数的真实语义。4.3 训练循环里的三个细节第一个细节输出 logits 不要 detach 后再算损失。有人在训练循环里习惯性地logits.detach()做指标统计然后顺手把 detach 后的张量传给 criterion结果梯度根本没回传到模型训练看起来在跑但参数一动不动。指标统计用 detach损失计算用原始张量这两件事要严格分开。for step in range(num_epochs): model.train() optimizer.zero_grad() logits model(x) loss criterion(logits, y) # 不 detach loss.backward() optimizer.step() with torch.no_grad(): # 指标统计才用 no_grad pred logits.argmax(dim1) acc (pred y).float().mean()第二个细节学习率和损失尺度要一起看。前面说过带权重时损失的绝对值会变但不带权重的场景下也有类似问题——batch size 从 32 改到 256reductionmean下损失期望不变但梯度噪声变小了最优学习率通常会跟着变大。我一般的做法是改 batch size 时同步按线性或平方根规则缩放 lr然后观察前 100 步的损失曲线是否和基线形状接近。第三个细节前几步的损失值可以作为健康检查的锚点。如果是 C 类均匀分布、模型随机初始化理论初始损失应该接近 log(C)。五分类就是 1.61 左右十分类是 2.30 左右。如果你看到初始损失是 0.2 或者 8.0基本可以断定有 bug前者可能是标签泄漏或者重复做了 softmax后者可能是 logits 数值异常大或者标签映射错了。这个检查我几乎每个新项目都会做一遍五秒钟的事能省掉半天排查。4.4 验证集评估别拿损失当唯一指标训练损失降了不代表模型变好了这在分类任务里尤其明显。因为交叉熵优化的是概率分布模型可以通过对训练集里的噪声标签也给出高置信度来降低损失这在验证集上会立刻暴露。所以评估阶段我至少同时看三个数验证损失、top-1 准确率、以及各类别的召回率。验证损失升高但准确率还在涨通常说明模型开始过度自信了这时候可以加 label smoothing 或者早停验证损失和准确率同时变差那就是真过拟合该加正则或者减容量。只盯一个数容易误判。还有个小技巧用reductionnone拿到逐样本损失按损失值排序看看最高的那批样本长什么样。十次里有八次你能发现是标注错了或者样本本身极度模糊。这个方法比看整体指标有用得多尤其是在数据质量不确定的项目里。5. 踩坑记录这些问题我几乎每个都遇到过这一节按问题类型整理把我这些年在这个函数上踩过的坑和排查路径列清楚遇到问题时可以直接对号入座。5.1 形状与类型类问题速查报错信息关键词真实原因解决方式Expected target size / target size mismatchtarget 是 one-hot 或维度多了一层用 argmax 转成索引形状对齐到 (N,)Expected object of scalar type Longtarget 是 float 或 int32.long()转一下Target out of bounds标签值 ≥ 类别数或为负检查标签映射注意 0-basedinput has invalid shape输入是 (N,) 而不是 (N, C)检查模型最后一层输出维度1D target tensor expected输入被当成高维情况确认 input 维度 ≥ 2Target out of bounds 这个错我印象最深。当时做一个从 1 开始编号的数据集标签是 1 到 10模型输出 10 类看起来完全匹配但 PyTorch 的索引是 0-based标签 10 直接越界。修法很简单把所有标签减 1但这个错在训练开始几步之后才报前面已经白跑了一段时间。现在我拿到任何新数据集第一件事就是打印y.min()和y.max()确认范围在 [0, C-1] 内。5.2 数值异常nan 和 inf 的三种来源第一种logits 里有 nan 或 inf。这通常是前面某层算炸了比如学习率太大导致参数溢出或者输入数据里有异常值。排查方式是给模型加 forward hook逐层打印torch.isnan(x).any()找到第一层出 nan 的位置。第二种全部位置都被 ignore。上一节提过reductionmean时分母为 0返回 nan。这种情况多半发生在变长序列的短样本上加个保护判断就行。第三种fp16 混合精度下的溢出。用 autocast 训练时Softmax 那一步在 fp16 下容易溢出PyTorch 的 fused 实现内部会做类型提升但如果你自己手写了 Softmax 再传给 NLLLoss就可能踩到。正确做法是直接用CrossEntropyLoss配合 autocast让框架处理类型提升不要自己拼。如果必须手写把 log_softmax 那一步强制转成 float32。5.3 损失不降的排查路径这是个开放式问题我按从快到慢的顺序列一条排查链命中率从高到低。第一步看初始损失是否接近 log(C)。差得远先查标签和输出维度是否对得上、有没有重复 softmax。第二步拿一个极小数据集过拟合。取 20 个样本把模型容量放大训练 200 步如果损失降不到接近 0说明要么梯度断了检查有没有误用 detach、有没有requires_gradFalse的参数要么损失函数用错了比如分类任务用了 MSE。第三步检查优化器是否真的在更新参数。训练前后打印某个参数张量的均值如果完全没变说明optimizer.step()没生效或者参数没注册进优化器。用nn.Sequential手搓模型时忘了把新层加进优化器的parameters()里这个坑我踩过一次。第四步看梯度范数。torch.nn.utils.clip_grad_norm_的返回值能告诉你梯度有多大如果长期接近 0可能是学习率太小或者梯度消失如果动辄上百是爆炸需要调小 lr 或者加梯度裁剪。5.4 二分类、多标签、序列标注分别该用什么这个边界必须拎清用错了损失函数模型结构再对也白搭。单标签多分类每张图属于且仅属于一类CrossEntropyLosstarget 是 long 索引。二分类用BCEWithLogitsLoss模型输出 1 个 logitstarget 是 0/1 的 float。不要用两类的CrossEntropyLoss加 Softmax虽然数学上等价但 BCE 在类别不平衡时配pos_weight更方便而且省一个输出维度。多标签分类一张图可能同时有多个标签必须用BCEWithLogitsLoss每个类别独立做 sigmoid各类别之间不竞争。这里用CrossEntropyLoss是彻底错的因为 Softmax 强制所有类别概率加起来为 1模型被迫在猫和狗之间二选一而它们本来可以同时存在。这个错误在迁移学习场景里特别常见——把 ImageNet 的单标签模型结构直接拿去做多标签任务忘了换损失。序列标注用CrossEntropyLoss把 batch 和序列维展平成 (N·L, C)target 展平成 (N·L,)padding 位置设 ignore_index。或者直接用 (N, C, L) 和 (N, L) 的形状它支持多维情况。图像分割形状是 (N, C, H, W) 和 (N, H, W)直接传给CrossEntropyLoss就行不用自己展平。知识蒸馏用KLDivLoss配log_softmax不要用交叉熵直接对软标签算虽然形式上接近但 KL 散度才是标准做法。6. 更进一步从交叉熵到 focal loss 与 label smoothing 的取舍基础用法掌握之后实际项目里往往还要做一些变体。这一节讲两个最常用的扩展以及它们和标准交叉熵的关系。6.1 手动实现 focal loss 的思路focal loss 的出发点是标准交叉熵对容易分类的样本也会给出不小的损失当易分样本占绝大多数时它们的损失总和会淹没难分样本的贡献。做法是在交叉熵前面乘一个调制因子 (1 - p_t)^γ其中 p_t 是正确类别的预测概率γ 常取 2。容易分的样本 p_t 接近 1调制因子接近 0损失被压下去难分的样本 p_t 小调制因子接近 1损失基本保留。def focal_loss(logits, target, gamma2.0, weightNone): log_probs F.log_softmax(logits, dim1) log_pt log_probs.gather(1, target.unsqueeze(1)).squeeze(1) pt log_pt.exp() loss -((1 - pt) ** gamma) * log_pt if weight is not None: w weight[target] loss loss * w return loss.sum() / w.sum() return loss.mean()这里有两点值得提醒。一是必须用log_softmax再exp得到 pt不要先softmax再log后者在 pt 接近 1 时会损失精度。二是 focal loss 的绝对尺度和标准交叉熵不同切换时学习率要重新调不能直接沿用。6.2 平滑与硬标签的收益边界label smoothing 的收益在数据集标注有噪声、类别间边界模糊时最明显。我印象比较深的一次是做一个细粒度分类任务类别之间本来就容易混加了 0.1 的平滑之后验证准确率涨了将近两个点训练损失下降变慢但曲线更稳。反过来说在一个类别定义非常清晰、标注质量很高的任务上加平滑几乎没有收益甚至会让模型在需要高置信输出的场景下表现变差。还有一个容易被忽略的点平滑系数不要设太大。0.1 已经算比较激进了0.2 以上会显著改变优化目标模型可能学到所有类别都差不多的退化解。我一般从 0.05 开始试有效果再加到 0.1再往上就很少见了。最后说一个我在实际使用中的体会nn.CrossEntropyLoss这个函数本身写得非常稳绝大多数训练不收敛的问题根因都不在它身上而在标签处理、模型输出结构、优化器配置这些外围环节。所以我现在排查问题的顺序永远是——先打印标签范围和类型再确认模型最后一层没加 softmax然后看初始损失是否接近 log(C)最后才去怀疑损失函数本身。这条顺序帮我省下的时间比读十篇讲损失函数原理的文章加起来都多。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑