目录
[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: float32 或 float64 - 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) ,在数学和深度学习中用于提取一个实数的符号(正、负、零)。
✅ 一、数学定义
对任意实数 ,符号函数定义为:
🔸 注意:这是一个分段常值函数 ,输出只有三种可能:
+1、0、-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 是合理且常见的做法,因为:
-
表示"当前预测完美,无需更新"
-
数值稳定(避免随机跳变)
-
与
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 如何计算梯度? | ✅ 在 |
| 这是否合理? | ✅ 是次梯度集合 |
| 是否影响训练? | ❌ 不影响,工程上稳定可靠 |
| 梯度公式是什么? |
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}( |
| 梯度来源 | reduction='mean') |
| 误差=0 的梯度 | 0(PyTorch 在不可导点选择次梯度 0) |
| 非零误差梯度 | |
| 是否合理 | ✅ 完全符合数学定义与 PyTorch 实现 |