开始前,会这些就够了
- 知道概率总和为 1
- 能区分原始分数和概率
- 先读 Softmax 分类与损失:先了解原始分数如何变成概率,以及交叉熵为什么要读取目标分布。
一张图片的正确类别是猫。模型已经给猫 99.3% 的概率,为什么训练还可能要求它收回一些把握?关键不在“猜没猜对”,而在损失现在要求它接近哪一组概率。
本篇固定两个类别,直接计算一个样本的损失。我们会看到:采用本文的标签平滑后,90% 是这个独立小问题的最优目标;继续推到 99.3%,损失反而上升。它不是对整张神经网络每个输出设置了 90% 的硬上限。
把例子带到自己的电脑上
下载完整练习包
需要 Python 3.10 或更新版本,无第三方依赖;实测 Windows / Python 3.14.0,Python 3.10 仅做语法兼容检查。使用标准库,不需要安装第三方包。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。
下载 ZIP · 5 个文件包里有什么
- demo.py:完整可运行源码
- README.md:运行步骤与练习
- observed.stdout.txt:本次实际输出
- verification.json:源码和运行验证记录
- SHA256SUMS.txt:文件摘要
在这个文件夹打开 PowerShell,逐行运行
python --version
python -B demo.py解压后,在含 demo.py 的目录打开终端。命令分别回车执行。双类别自由分数的标签平滑标量目标;未训练神经网络,也未验证校准或准确率。
找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。
给另一类留一点概率,最优分数差会停在哪里?
正在准备实验控件…
固定两类、真类为 0,直接优化一条样本的交叉熵,d=z₀−z₁。只研究这个数学目标;真实模型可能无法独立拟合每条样本,不包含正则项,也不证明模型校准或准确率会提高。
先把“平滑 0.2”写成具体数字
原始标签是 [1, 0]。本文采用原论文与 PyTorch 文档的均匀混合定义:保留 1−epsilon 份原标签,再把 epsilon 平均分给全部 K 个类别,正确类也分到一份。
公式是 q_k=(1−epsilon)×1[k=y]+epsilon/K。K=2、epsilon=0.2、正确类 y=0 时,目标 q=[0.9, 0.1]。q 是训练时希望接近的分布,不是声称这张图有 10% 概率真的是另一类。
还有一种实现把 epsilon 只分给其他类别,得到 [0.8, 0.2]。这也是可定义的目标,但同样填写 0.2 时与本文不是同一个损失;读代码时要看公式,不能只看参数名。
两个分数,只需要看它们相差多少
设正确类分数为 z0,另一类为 z1,分数差 d=z0−z1。两个分数同时加一百,不会改变 softmax 概率,因此我们把 z1 固定为零,只研究 d。
此时正确类概率 p0=1/(1+exp(−d)),另一类是 1−p0。d=0 时各占一半;d 越大,正确类概率越接近一。这里的分数差不表示百分比。
目标 [0.9,0.1] 对应损失 L=−0.9×ln(p0)−0.1×ln(1−p0)。第二项会处罚“把另一类概率压得几乎为零”,所以损失不能只保留正确类那一项。
为什么最优分数差会停在有限位置
对 d 求导得到 dL/dd=p0−0.9。p0 低于 0.9 时,梯度为负;按梯度下降减去梯度,d 会变大。p0 高于 0.9 时,梯度为正,d 会被拉回来。
把 p0=0.9 代回概率公式,得到 d*=ln(0.9/0.1)=ln(9),约 2.197225。这里求的是一个样本、两个自由分数、无额外惩罚的最优点。所有类别概率都为正时,更一般地有 z_y−z_j=ln(q_y/q_j)。
这也解释了硬标签的区别:目标 [1,0] 要求 p0 趋近一,有限的 d 达不到精确的一;这个独立问题的损失下确界要在 d 趋向正无穷时逼近。真实模型还受其他样本、参数共享和正则项影响。
从 90% 推到 99.3%,会发生什么
实验在 d=0、ln(9)、5 三处计算。平滑损失依次约为 0.693147、0.325083、0.506715。第三处仍然预测正确,但因为过度偏离软目标,损失高于中间那一点。
d=5 时,p0≈0.993307。平滑目标下梯度约 +0.093307,会把分数差往回拉;硬标签下梯度约 −0.006693,仍推动分数差变大。两者方向不同,是目标改变后的正常结果。
不能把这两种训练损失的绝对数值直接当作模型优劣排名。它们评价的目标已经不同,至少要在相同测试目标下另算指标。
改平滑方式,也就改了最优点
如果把 0.2 只分给另一类,q=[0.8,0.2],最优分数差变为 ln(4)≈1.386294。即使两个实现都叫 label smoothing,同一个数值参数也可能代表不同强度。
epsilon=0 时回到硬标签;epsilon=1 时,本文均匀混合目标是 [0.5,0.5],最优分数差为零。多类别下也要把 K 写清楚,不能直接照搬二分类的 0.9。
如果数据流程已经生成了平滑后的概率目标,再让损失函数做一次平滑,目标会再次改变。检查一条具体样本最终进入损失的目标分布,比仅确认开关已经打开更可靠。
运行完整实验,核对数值和方向
保存为 demo.py,用 Python 3.14 标准库运行 python demo.py,无需第三方包。代码同时用小扰动核对导数,并对分数差 1000 检查稳定的对数计算。
这里没有训练神经网络、读取图片或评估准确率。代码验证的是明确写出的标量目标与十个数值条件;输出保留六位小数,断言使用更高精度。
展开收起代码 · 小实验
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
"""Two-class label smoothing: an exact objective, not a trained network."""
import math
def smooth_target(classes, correct, epsilon):
if classes < 2 or not 0 <= correct < classes or not 0 <= epsilon <= 1:
raise ValueError("invalid class count, label, or epsilon")
return [epsilon / classes + (1 - epsilon if i == correct else 0)
for i in range(classes)]
def loss_and_gradient(margin, target):
# The second logit is fixed at zero. Compute log probabilities stably.
normalizer = max(margin, 0) + math.log1p(math.exp(-abs(margin)))
log_p = (margin - normalizer, -normalizer)
loss = -sum(q * lp for q, lp in zip(target, log_p))
probability = math.exp(log_p[0])
return loss, probability - target[0], probability
def main():
target = smooth_target(2, 0, 0.2)
optimum = math.log(target[0] / target[1])
optimal_loss, optimal_grad, optimal_p = loss_and_gradient(optimum, target)
hard = smooth_target(2, 0, 0)
loss_zero, grad_zero, _ = loss_and_gradient(0, target)
loss_large, grad_large, _ = loss_and_gradient(5, target)
hard_large, hard_grad, _ = loss_and_gradient(5, hard)
alternative = (0.8, 0.2) # A DIFFERENT convention: smooth only other classes.
alternative_margin = math.log(alternative[0] / alternative[1])
h = 1e-5
numerical = (loss_and_gradient(5 + h, target)[0]
- loss_and_gradient(5 - h, target)[0]) / (2 * h)
checks = {
"uniform_mixture_includes_true_class": target == [0.9, 0.1],
"target_sums_to_one": math.isclose(sum(target), 1),
"finite_stationary_point": abs(optimal_grad) < 1e-12,
"stationary_point_is_better_than_both_sides": optimal_loss < min(loss_zero, loss_large),
"gradient_changes_sign": grad_zero < 0 < grad_large,
"hard_target_still_pushes_up": hard_grad < 0 and hard_large < optimal_loss,
"finite_difference_agrees": math.isclose(numerical, grad_large, abs_tol=1e-9),
"different_convention_different_margin": alternative_margin < optimum,
"epsilon_one_is_uniform": smooth_target(2, 0, 1) == [0.5, 0.5],
"finite_large_margin_loss": math.isfinite(loss_and_gradient(1000, target)[0]),
}
assert all(checks.values()), checks
print("convention=uniform mixture over ALL classes")
print("target=[0.900000, 0.100000]")
print(f"optimal_margin={optimum:.6f} probability={optimal_p:.6f} loss={optimal_loss:.6f}")
for margin in (0, optimum, 5):
loss, gradient, probability = loss_and_gradient(margin, target)
print(f"margin={margin:.6f} p_true={probability:.6f} loss={loss:.6f} gradient={gradient:.6f}")
print(f"hard_target_at_5: loss={hard_large:.6f} gradient={hard_grad:.6f}")
print(f"other_classes_only: target=[0.800000, 0.200000] optimal_margin={alternative_margin:.6f}")
print(f"PASS {len(checks)} checks; scalar objective only, no model training")
if __name__ == "__main__":
main()
怎样运行:在独立练习文件夹中,依次执行下载区的命令。2026-10-23 使用 Python 3.10 或更新版本,无第三方依赖;实测 Windows / Python 3.14.0,Python 3.10 仅做语法兼容检查 复跑这份源码并核对完整输出。双类别自由分数的标签平滑标量目标;未训练神经网络,也未验证校准或准确率。
运行后对照结果
convention=uniform mixture over ALL classes
target=[0.900000, 0.100000]
optimal_margin=2.197225 probability=0.900000 loss=0.325083
margin=0.000000 p_true=0.500000 loss=0.693147 gradient=-0.400000
margin=2.197225 p_true=0.900000 loss=0.325083 gradient=0.000000
margin=5.000000 p_true=0.993307 loss=0.506715 gradient=0.093307
hard_target_at_5: loss=0.006715 gradient=-0.006693
other_classes_only: target=[0.800000, 0.200000] optimal_margin=1.386294
PASS 10 checks; scalar objective only, no model training
目标更温和,不等于结果一定更可靠
标签平滑改变的是训练目标。有限最优分数差不是预测概率的硬限幅;共享参数的模型不一定能让每个样本同时达到自己的最优分布。
论文研究过校准与蒸馏的变化,但一个两类算式不能证明你的真实任务准确率更高、概率已校准,或教师模型更适合蒸馏。应分别测任务指标、可靠性和下游用途。
学习时先问三个具体问题:质量要求是什么;平滑后的每类目标是多少;训练和测试是否用了相同的评价约定。这样就能解释“预测正确却仍被往回拉”,而无需把它当作优化器故障。
记住这三点
- 标签平滑的公式决定真实目标,参数名不足以说明约定。
- 本文二分类 epsilon=0.2 得到 [0.9,0.1],独立样本的最优分数差为
ln(9)。 - 梯度方向由预测概率减去目标概率决定;已经分类正确仍可继续被调整。
别把例子用过头
- 把 0.2 均摊全部类别误写成正确类必定 0.8。
- 把独立样本的最优概率当成模型输出硬上限。
- 只取正确类负对数,漏掉软标签的其他类别。
- 跨不同训练目标比较 loss 数值来断定模型好坏。
自己试一小步
把类别数改为 4、epsilon 改为 0.1,怎样核对正确类与任一其他类的最优分数差?
- 先分别写出均摊全部类与只分其他类的目标分布。
- 检查每份目标是否和为一,再推导两种分数差。
- 示例的
loss_and_gradient只计算两类。先把它扩成四类 softmax,并把四类的交叉熵都加起来;不能只修改 classes。 - 为两个候选分数差各选一个更大和更小的邻点,比较损失与梯度。
做到这些,就算完成
- 记录你使用的平滑公式和 K。
- 不要把目标概率直接填入 logits。
- 说明实验验证了什么,以及没有验证什么。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- Rethinking the Inception Architecture for Computer Vision:第 7 节 (新标签页)↗
标签与均匀分布混合的定义、交叉熵梯度,以及无限增大真类分数差的代价。
- PyTorch 2.9:CrossEntropyLoss (新标签页)↗
label_smoothing采用原标签与均匀分布混合;输入是 logits,概率目标需由使用者保证合法。 - When Does Label Smoothing Help?:定义与分数差 (新标签页)↗
第 1.1、2 节讨论平滑目标与类别分数差;校准与蒸馏的结果属于论文各自实验条件。