ignore_index:忽略标签不等于删除类别
CrossEntropyLoss的ignore_index忽略的是目标标签对应的位置,不是从每个softmax里删除一个类别。把padding标签设为忽略值,可以不对这些位置计算监督损失;有效位置的类别归一化通常仍包含模型输出的全部类别。
文章目录
这一区别能解释“已经忽略pad,pad类别的logit为什么还影响loss”。下面用三类零logit算出log 3,再与真正移除一个类别后的log 2对照,推导两个操作不同在哪里。Read in English。
先标出位置轴与类别轴
官方定义对整数目标给出一个位置是否参与损失的指示项;该位置的softmax分母仍对全部C个类别求和。本文限定硬标签、无类别权重、无label smoothing,避免把不同损失模式的归约规则混在一起。
以序列标注为例,一个轴表示第几个token,另一个轴表示该token可能属于哪一类。忽略某个token的标签,和禁止其他token预测某一类,分别沿两个不同轴操作。名字里都有padding,很容易让工程代码把这两个意图误合并。
负数忽略值例如−100可以不属于类别范围;它是目标侧的哨兵值,不代表模型存在“第−100类”。输入仍需为每个实际类别提供logit。不要据此添加一个无意义输出列,也不要把这个哨兵值送入普通类别索引逻辑。
三类零logit给出一个最小反例
设某个有效位置的logits为[0,0,0],目标类为1。三个指数都为1,目标概率是1/3,损失是−log(1/3)=log 3。另一个位置的目标是ignore_index,只会让另一个位置不贡献这项监督。
如果真正删除类别0,并把原目标类1重新映射为剩余两类中的0,logits变为[0,0],目标概率才会成为1/2,损失是log 2。前者还在问“三类中目标的概率”;后者已经改变了类别空间。两者不相等,不能用同一个忽略参数表达。
再把有效位置中类别0的logit从0升到5。即便从不把类别0作为这个位置的正确标签,它仍通过softmax分母压低目标类概率,增加loss。是否希望这种竞争存在,是建模选择;ignore_index本身没有替你作出这个选择。
梯度也能看出类别仍在竞争
对未忽略、无权重的单个位置,交叉熵对logit zₖ的导数为pₖ−1[k=y]。一个不是目标的类别,只要仍在归一化集合中且概率非零,就有相应梯度;在零logit三类例子里,非目标类梯度为1/3。
这不表示被忽略位置也会产生同样的直接监督梯度。整行目标被忽略后,该位置对这项损失的直接贡献被移除。还应区分更早共享参数或注意力造成的间接影响:某个token不作为监督目标,不保证它从整个模型计算图的所有路径消失。
此前padding_idx与注意力mask的反例说明了输入侧的参与范围。本篇检查输出监督与类别归一化。一个完整padding方案通常需要分别定义这几处,而不是寻找一个能统管全部行为的参数。
九项检查同时验证忽略位置和保留类别
教学函数用None表示无监督位置,显式拒绝全忽略批次。这个拒绝策略是应用契约,不是对每个框架边界返回值的承诺。数值计算使用减最大值形式,避免直接计算exp(1000)。
# Hard-label, unweighted, unsmoothed teaching loss; no PyTorch execution.
from math import exp, log, isclose
def ce(logits, target, ignore=-100):
if target == ignore:
return None
if not 0 <= target < len(logits):
raise ValueError("invalid target")
m=max(logits)
return log(sum(exp(z-m) for z in logits))+m-logits[target]
def valid_mean(logits, targets):
if len(logits)!=len(targets):
raise ValueError("position count mismatch")
losses=[ce(row,y) for row,y in zip(logits,targets)]
kept=[x for x in losses if x is not None]
if not kept:
raise ValueError("no supervised positions")
return sum(kept)/len(kept)
assert isclose(ce([0.,0.,0.],1),log(3))
assert isclose(ce([0.,0.],0),log(2))
assert ce([99.,0.,0.],-100) is None
assert ce([5.,0.,0.],1)>ce([0.,0.,0.],1)
assert isclose(valid_mean([[0.,0.,0.]]*2,[1,-100]),log(3))
assert isclose(valid_mean([[0.,0.,0.]]*3,[1,-100,-100]),log(3))
try:
valid_mean([[0.,0.,0.]],[-100])
except ValueError:
pass
else:
raise AssertionError("expected empty supervision rejection")
assert isclose(ce([1000.,1000.,1000.],1),log(3))
try:
valid_mean([[0.,0.,0.]],[])
except ValueError:
pass
else:
raise AssertionError("expected shape rejection")
print("9 ignore-index arithmetic checks passed")
本地九项断言通过。它们比较log 3与log 2、验证忽略整行、检查竞争类别升高时loss增加,并证明添加被忽略位置不改变有效位置均值。还检查全忽略与位置数不匹配的拒绝路径,以及共同平移logit的稳定性。代码只复现上述特定数学定义,没有执行PyTorch训练。
平均损失的分母要跟监督集合一致
本文无权重情形中,未忽略位置损失求和后应除以有效位置数。若把忽略位置先填零,再对补齐后所有位置取平均,添加padding就会缩小平均值。这种数值下降没有证明有效token预测变好。
使用类别权重时,整数目标模式的mean还涉及有效目标类别权重之和,不能照搬简单有效计数。概率目标是另一种接口模式;ignore_index不适用于该模式。修改标签表达、加权或平滑后,应重新核对具体公式,而不是继续套用本篇的简化实现。
全忽略批次没有可用于这个平均的监督分母。应用可以跳过该训练单元,或按明确约定处理,但要与梯度累积、分布式归约和指标汇总一致。把异常结果强行替换成零,却仍算作一次普通更新,可能让训练步语义变得含糊。
若确实要排除类别,先定义新的问题
固定地移除某个输出类,需要同步定义输出列、目标重映射以及推理时类别空间。不能只删一列而不调整后续类别编号;否则loss可能仍可计算,却指向错误类别。若类别只是某些位置不允许,则还需要逐位置的合法类别集合。
对合法类别集合为空的行,应有单独策略。用有限大负数压低logit也不是语义上的删除证明;数值是否足够小与dtype、范围及后续运算有关。先决定是否存在真实的类别限制,再选择与实现相符的表示和测试。
语言模型词表里的pad符号也可能与其他用途共享ID,或由解码策略另外限制。是否能从训练归一化中删除,必须依据具体模型契约判断。不要为了让某个最小例子的loss更小,就自行更改预训练模型的词表语义。
把两个轴写进测试名称
准备一条有效目标和一条忽略目标,检查未约简损失。再只改有效行的非目标类别logit,确认目标损失是否变化;最后只增加忽略位置,检查有效均值。这三个测试分别定位位置过滤、类别竞争与归约分母,失败时指向不同环节。
测试记录要写清类别轴位置,尤其是序列张量从[N,T,C]改成其他布局时。检查输出loss形状与每个位置的对应关系,比只看最终标量更容易发现维度误用。最小例子应同时带上目标映射,避免“数值符合预期、标签含义却错位”。
若这些推导和复现步骤帮助你分清“忽略标签”与“删除类别”,欢迎通过文末支持区自愿支持。把最小反例留在项目回归测试里,可以让后续标签或词表调整更容易核验。
来源与补发说明
依据原计划时段前已有的PyTorch 2.8定义核对;推导、反例与图示为原创教学内容。保留2026年10月19日14:00文章日期,实际于10月19日晚补发。标准库代码已执行,没有运行PyTorch模型或测量设备性能。
支持
如果文中的解释或示例帮你解决了问题、节省了时间,欢迎自愿打赏支持本站。
二维码可点击放大。更多支持方式见支持页面。


