继承 nn.module 写损失函数必须重写 forward 方法,接收 input 和 target 张量并返回标量,注意维度匹配、避免中断计算图、正确处理 reduction 和 ignore_index 等参数。

继承 nn.Module 写损失函数,必须重写 forward 方法
PyTorch 的损失函数本质是可调用对象,但不是随便写个函数就行——nn.Module 子类只有实现 forward,才能被模型训练流程(如 loss.backward())正确追踪梯度。漏掉 forward 或写成 __call__,会直接报 TypeError: object is not callable 或梯度为 None。
实操建议:
-
forward必须接收input和target两个张量(顺序不能反),返回标量torch.Tensor(shape 为torch.Size([])) - 别在
forward里调用.item()、.numpy()或print(),会中断计算图 - 如果需要内部状态(比如动态权重),用
self.register_buffer()而非普通属性
自定义损失里用到 torch.nn.functional 函数要小心维度
很多人直接套用 F.cross_entropy 或 F.mse_loss,却忽略它们对输入 shape 的隐含要求。比如 F.cross_entropy(input, target) 要求 input 是 [N, C],target 是 [N] 且 dtype=torch.long;而你自定义的 forward 接收的可能是 batch-first 的序列输出或带 channel 维的分割图,维度不匹配就爆 RuntimeError: Expected input batch_size (16) to match target batch_size (32)。
实操建议:
- 先用
print(input.shape, target.shape, input.dtype, target.dtype)确认输入形态,再选F函数 - 对分割任务,
F.binary_cross_entropy_with_logits要求target是 float 类型、同 shape,不能直接喂long标签 - 多任务联合损失时,各子项 loss 要用
.mean()或.sum()降维成标量,再加权相加
训练中 loss 不下降?检查是否忘了 reduction 参数
默认情况下,nn.MSELoss、nn.BCEWithLogitsLoss 的 reduction='mean',但你自己写的损失若手动用了 torch.mean 却又没关掉底层 loss 的 reduction,就会重复平均,导致梯度变小、收敛慢甚至不动。
实操建议:
- 如果内部已用
torch.mean(loss_per_sample),初始化底层 loss 时显式传reduction='none' - 反过来,如果依赖底层 loss 自动 reduction,就别在外层再
.mean()—— 否则 loss 值会比预期小一个 batch size 倍数 - 调试时临时把
reduction='none',打印loss.shape看是不是[batch_size],能快速定位维度问题
想支持 ignore_index 或标签平滑?得自己解析参数
内置损失如 nn.CrossEntropyLoss(ignore_index=-100) 是靠构造时存参、forward 里调用底层 C++ 实现来处理的。你继承 nn.Module 自定义时,这些参数不会自动生效——ignore_index 不会跳过对应位置,label_smoothing=0.1 更是完全无效。
实操建议:
- 把需要的参数(如
ignore_index、smooth_eps)作为__init__参数保存为self.ignore_index - 在
forward中用torch.where或布尔索引手动 mask 掉 ignore 位置的 loss;标签平滑则需手动构造软目标分布 - 注意
ignore_index在 target 为[N, H, W]时,要按像素级 mask,而不是简单target != ignore_index后 reshape 错乱
真正麻烦的是多维 target + ignore_index + grad 检查三者叠加,这时候哪怕一行索引写错,loss 就静默失效——得用 torch.autograd.gradcheck 验证自定义 loss 的导数是否合理。











