(十八)logits、softmax 与交叉熵
回归输出一个连续数,分类要在竖线、横线和十字中选择一个类别。先看一个样本输出的三个原始分数 [2,1,0],这组分数称为 logits。
1. 把分数解释成概率
softmax 对每项取指数,再除以指数和。这里的 约为 2.71828, 把任意实数分数变成正数;之后归一化,各项就能相加为一。为了让数值计算更稳定,可以先减去本行最大分数 2:
如果正确类别编号为 0,我们希望第一项概率增加。 是自然对数,与 互为反向运算:、、。用正确类别概率的负对数作为损失,得到:
正确类别概率越接近一,损失越接近零;给正确类别很小的概率时,损失会明显增加。
再推导这三项分数各自收到的梯度。记指数和为 ,则正确类别为零时,。对某项分数 , 的变化率为 , 对 的变化率为 ,沿路径相乘便是 。
最后还有减去 这条路径:对分数零的贡献是 -1,对另外两项是 0。把两条路径贡献相加,得到“预测概率减去正确类别指示值”:
沿梯度反方向走,正确类分数上升,另外两类分数下降。这与(七)通过误差把变化传回参数的思路相同,只是损失函数换成了分类需要的形式。
# 项目文件:demos/logits_loss.py
import torch
from torch import nn
logits = torch.tensor([[2.0, 1.0, 0.0]], requires_grad=True)
target = torch.tensor([0], dtype=torch.int64) # 一个样本的正确类别编号为 0。
probabilities = logits.softmax(dim=1)
loss = nn.CrossEntropyLoss()(logits, target)
loss.backward()
print("概率", [round(value, 6) for value in probabilities[0].detach().tolist()])
print("损失", round(loss.item(), 6))
print("梯度", [round(value, 6) for value in logits.grad[0].tolist()])
print("类别", logits.argmax(dim=1).tolist())2. 把形状与损失接口连起来
nn.CrossEntropyLoss 直接接收 (B,C) logits 和 (B,) 整数类别编号,在内部完成相应的对数概率计算。训练调用前保留原始分数;只有显示概率时才调用 softmax。再传一次 softmax 会改变损失实际接收的分数。
argmax(dim=1) 取每行最大项的编号,softmax 不改变大小次序,因此判断类别时可以直接对 logits 使用它。接口细节见 CrossEntropyLoss。