PyTorch nn.L1Loss 完全指南:MAE 原理、梯度计算与不可导点处理详解

目录

1、基本介绍

[2、sign 函数](#2、sign 函数)

[3、L1Loss 计算梯度 - reduction='sum'](#3、L1Loss 计算梯度 - reduction='sum')

[3、代码 - 示例](#3、代码 - 示例)

[4、L1Loss 计算梯度 - reduction='mean'](#4、L1Loss 计算梯度 - reduction='mean')


1、基本介绍

✅ 一、nn.L1Loss 是什么?

nn.L1Loss平均绝对误差(Mean Absolute Error, MAE) 损失函数的实现。 它用于回归任务(regression) ,衡量预测值与真实值之间的 L1 范数误差

✅ 二、数学定义(逐元素,就是单个样本的 预测值 减 真实值)

给定:

  • 预测张量:

  • 真实标签张量:

逐元素损失为:

✅ 三、API 输入要求(严格)

参数 要求
input(即 x,预测值) - dtype: float32float64 - shape: 任意,记为 S
target(即 y,真实值) - dtype: 必须与 input 相同 - shape: 必须与 input 完全一致

⚠️ 不要求取值范围(可为任意实数),因为这是回归任务。

✅ 四、reduction 参数行为

构造函数签名:

python 复制代码
torch.nn.L1Loss(reduction: str = 'mean')
reduction 输出形状 计算方式
'none' input 返回 ![\ell_i =
'sum' 标量 () ![\sum_{i=1}^N
'mean'(默认) 标量 () ![\frac{1}{N} \sum_{i=1}^N

✅ 五、是否支持 weight

不支持nn.L1Loss 没有 weight 参数,无法对不同样本或位置加权。 (若需加权 L1,需手动实现)

✅ 六、典型使用场景

  • 回归任务(如房价预测、坐标回归、温度预测等)

  • 对异常值(outliers)比 L2(MSE)更鲁棒

  • 输出是连续实数值(非概率、非类别)

✅ 七、关键结论

项目 说明
任务类型 回归(regression),不是分类
输入含义 预测值和真实值均为连续实数
是否需要激活函数 ❌ 不需要(如 sigmoid、softmax)
输出范围 无限制(可正可负,可大于1)
与分类损失区别 CrossEntropy / BCE 用于分类;L1Loss 用于回归

✅ 八、正确使用模板

python 复制代码
import torch
import torch.nn as nn

# 回归任务:预测连续值
y_true = torch.tensor([10.5, -2.3, 0.0], dtype=torch.float32)
y_pred = torch.tensor([10.7, -2.0, 0.1], requires_grad=True)

criterion = nn.L1Loss()  # MAE
loss = criterion(y_pred, y_true)
print(loss)  # tensor(0.1667)

2、sign 函数

sign 是符号函数(signum function) ,在数学和深度学习中用于提取一个实数的符号(正、负、零)

✅ 一、数学定义

对任意实数 ,符号函数定义为:

🔸 注意:这是一个分段常值函数 ,输出只有三种可能:+10-1

✅ 二、在 L1Loss 梯度中的作用

L1Loss 的逐元素损失为:

其对预测值 的导数(梯度)为:

但注意:

  • 当使用 reduction='mean' 时,总损失是

  • 因此最终梯度为:

✅ 三、PyTorch 中的行为(关键!)

在 PyTorch 中,torch.sign(z) 的实现严格遵循上述定义:

python 复制代码
import torch

print(torch.sign(torch.tensor(2.5)))   # tensor(1.)
print(torch.sign(torch.tensor(-3.1)))  # tensor(-1.)
print(torch.sign(torch.tensor(0.0)))   # tensor(0.)  ← 明确定义为 0

⚠️ 特别强调

尽管数学上 |z| 在 处不可导,但 PyTorch 显式规定 sign(0) = 0【sign函数详情在后面】, 这使得自动微分系统可以给出一个合法的次梯度(subgradient),用于反向传播。

✅ 四、举例说明

设:

计算

则:

reduction='mean'(N=3),梯度为:

✅ 五、总结(精准无模糊)

术语 含义
sign(z) 符号函数:z > 0 → +1,z < 0 → -1,z = 0 → 0
在 L1Loss 中的作用 给出绝对值函数 ![
PyTorch 实现 torch.sign(0.0) == 0.0(明确、可重现)
是否可导? 数学上在 0 处不可导,但工程上用 sign(0)=0 作为合法次梯度

3、L1Loss 计算梯度 - reduction='sum'

✅ 一、L1Loss 的数学定义与不可导点

损失函数(逐元素):

令误差 ,则:

其导数(对 x)为:

👉 在 e = 0(即预测值等于真实值)处,函数不可导(左右导数不一致)。

✅ 二、PyTorch 如何处理不可导点?

答案:在不可导点(e = 0)处,梯度定义为 0

这是 PyTorch(以及 TensorFlow、NumPy 等主流框架)的标准实现约定

官方依据:

  • PyTorch 源码中 abs 函数的反向传播(backward)实现使用 sign 函数,但将 sign(0) 定义为 0。

  • torch.sign(0.0) 返回 0.0(验证如下):

python 复制代码
import torch
print(torch.sign(torch.tensor(0.0)))  # tensor(0.)

而 L1Loss 的梯度 = sign(x - y)

因此:

  • 若 x > y → grad = +1

  • 若 x < y → grad = -1

  • 若 x = y → grad = 0 ✅

✅ 三、为什么可以这样定义?

虽然 |e| 在 e=0 处不可导,但它在该点存在 次梯度(subgradient)

次微分(subdifferential)定义:

函数 f(e) = |e| 在 e=0 处的次梯度集合为:

👉 任何 都可作为次梯度,用于优化算法。

工程选择:

  • 选择 g = 0 是合理且常见的做法,因为:

    1. 表示"当前预测完美,无需更新"

    2. 数值稳定(避免随机跳变)

    3. sign(0) = 0 的数值惯例一致

🔑 这不是"强行可导",而是在次梯度框架下选择了一个合法且实用的子梯度值

✅ 四、实验证明:PyTorch 梯度行为

python 复制代码
import torch
import torch.nn as nn

y_true = torch.tensor([1.0, 2.0, 3.0])
y_pred = torch.tensor([1.0, 2.5, 2.0], requires_grad=True)  # 第一个样本误差=0

criterion = nn.L1Loss(reduction='none')  # 逐元素看梯度
loss = criterion(y_pred, y_true)
loss.sum().backward()

print("y_pred.grad:", y_pred.grad)
# 输出: tensor([ 0.0000,  1.0000, -1.0000])

解释:

  • 样本0:1.0 - 1.0 = 0 → grad = 0.0

  • 样本1:2.5 - 2.0 = +0.5 > 0 → grad = +1.0

  • 样本2:2.0 - 3.0 = -1.0 < 0 → grad = -1.0

✅ 完全符合上述规则。

✅ 五、对优化的影响

  • 在绝大多数情况下,误差恰好为 0 的概率极低(浮点数连续空间)

  • 即使发生,设梯度为 0 不会破坏优化过程

  • 实践中,L1Loss 广泛用于稀疏建模(如 Lasso)、鲁棒回归,从未因该点导致训练失败

✅ 六、总结

问题 回答
L1Loss 在 0 处是否可导? ❌ 数学上不可导
PyTorch 如何计算梯度? ✅ 在 处,梯度 = 0
这是否合理? ✅ 是次梯度集合 中的一个合法选择
是否影响训练? ❌ 不影响,工程上稳定可靠
梯度公式是什么? ,其中

3、代码 - 示例

python 复制代码
import torch
import torch.nn as nn

# 真实值
y_true = torch.tensor(data=[3.4, 1.2, 5.3])

# 预测值, 原始 logits 得分
y_predict = torch.tensor(data=[3.4, 1.3, 5.5], requires_grad=True)   # 第0个样本误差为0

criterion = nn.L1Loss()   # 默认是 'mean', 即平均
loss = criterion(y_predict, y_true)
print(f'loss = {loss}')    # loss = 0.09999990463256836

loss.backward()           # 如何计算的梯度, 请看《L1Loss计算梯度 - reduction='mean'》
print(y_predict.grad)     # tensor([0.0000, 0.3333, 0.3333])

4、L1Loss 计算梯度 - reduction='mean'

✅ 一、先确认损失计算

python 复制代码
y_true    = [3.4, 1.2, 5.3]
y_predict = [3.4, 1.3, 5.5]

逐元素绝对误差:

  • |3.4 − 3.4| = 0.0

  • |1.3 − 1.2| = 0.1

  • |5.5 − 5.3| = 0.2

总和 = 0.0 + 0.1 + 0.2 = 0.3

nn.L1Loss() 默认 reduction='mean' → loss = 0.3 / 3 = 0.1

输出的 0.09999990463256836 是浮点精度误差(3.4 等无法精确表示为二进制浮点数),✅ 合理。

✅ 二、梯度计算原理(核心!)

L1Loss 的梯度公式(对 y_predict)为:

其中 N = 元素总数(此处 N=3),因为使用了 reduction='mean'

🔑 梯度会被 1/N 缩放!这是你看到 0.3333 而非 1.0 的原因。

✅ 三、逐样本验证梯度

样本 sign 梯度 = sign / N
0 3.4 3.4 0.0 0 0.0
1 1.3 1.2 +0.1 > 0 +1 +1/3 ≈ 0.3333
2 5.5 5.3 +0.2 > 0 +1 +1/3 ≈ 0.3333

✅ 完全匹配你的输出:tensor([0.0000, 0.3333, 0.3333])

❗ 四、纠正一个容易出错的地方

"误差为0时, 不可导, 给的导数值为 0"

✅ 这部分正确。

但你可能误以为其他位置梯度应为 ±1 ------ 其实不是,因为:

reduction='mean' 会将梯度除以样本数 N

如果你用 reduction='sum'

python 复制代码
criterion = nn.L1Loss(reduction='sum')
loss = criterion(y_predict, y_true)
loss.backward()
print(y_predict.grad)  # → tensor([0., 1., 1.])

这时梯度才是 ±1 或 0。

✅ 五、总结(精准无模糊)

项目 说明
损失值 ![\text{mean}(
梯度来源 ,其中 N=3(因 reduction='mean'
误差=0 的梯度 0(PyTorch 在不可导点选择次梯度 0)
非零误差梯度 (因为两个预测值均大于真实值)
是否合理 ✅ 完全符合数学定义与 PyTorch 实现
相关推荐
LDG_AGI33 分钟前
【推荐系统】深度学习训练框架(十三):模型输入——《特征索引》与《特征向量》的边界
人工智能·pytorch·分布式·深度学习·算法·机器学习
CoovallyAIHub34 分钟前
如何让SAM3在医学图像上比专用模型还强?一个轻量Adapter如何让它“秒变”专家?
深度学习·算法·计算机视觉
Feisy1 小时前
使用深度学习检测元器件是否缺失零件-怎么快速地批量采集深度学习训练用的图片
人工智能·深度学习
亚里随笔1 小时前
MiniRL:用LLM稳定强化学习的新范式与第一阶近似理论
人工智能·深度学习·机器学习·llm·rlhf·agentic
free-elcmacom1 小时前
用Python玩转GAN:让AI学会“造假”的艺术
人工智能·python·机器学习
一条破秋裤2 小时前
零样本学习指标
深度学习·学习·机器学习
Michelle80232 小时前
机器学习实战操作手册
人工智能·算法·机器学习
茶色岛^2 小时前
解析CLIP:从“看标签”到“读描述”
人工智能·深度学习·机器学习
极客BIM工作室2 小时前
Gemini 3 技术细节公布:架构、能力与未公开信息汇总
人工智能·机器学习