1. 什么是L2范数?
直观理解
想象你有一个向量(比如嵌入向量 [1.2, -0.5, 3.1]
),L2范数就是计算这个向量的 “几何长度”。
就像在三维空间中,计算点 (1.2, -0.5, 3.1)
到原点 (0,0,0)
的距离。
数学公式
对于一个向量 x = [x₁, x₂, ..., xn]
,它的L2范数是:
∥
x
∥
2
=
x
1
2
+
x
2
2
+
⋯
+
x
n
2
\|x\|_2 = \sqrt{x_1^2 + x_2^2 + \cdots + x_n^2}
∥x∥2=x12+x22+⋯+xn2
例子
import torch
x = torch.tensor([3.0, 4.0])
l2_norm = torch.norm(x, p=2) # 计算 sqrt(3² + 4²) = 5.0
print(l2_norm) # 输出: tensor(5.)
2. 为什么需要L2正则化?
在机器学习中,L2正则化(也叫权重衰减)的作用是:
- 防止过拟合:惩罚过大的参数值,让模型权重保持较小。
- 稳定训练:避免某些特征权重过大而主导模型。
类比
假设你在考试复习:
- 不控制学习量(无正则化):疯狂刷题到凌晨3点,结果考试时过度依赖特定题型,遇到新题就懵。
- 控制学习量(L2正则化):每天适度学习,知识掌握更均衡,考试更稳定。
3. EmbLoss
类的作用
这个类计算所有输入嵌入矩阵的 L2范数之和(或L2范数的平方和),作为正则化损失。
相当于对模型说:“嵌入向量不要太大,否则我会惩罚你!”
参数解释
参数 | 作用 |
---|---|
norm=2 | 指定使用L2范数(设为1则用L1范数) |
require_pow | True 时计算 ‖embedding‖₂²(平方和),False 时计算 ‖embedding‖₂(原始范数) |
4. 分步拆解代码
场景设定
假设我们有:
- 用户嵌入矩阵
user_emb
:形状(100, 64)
(100个用户,每个用户64维向量) - 物品嵌入矩阵
item_emb
:形状(200, 64)
(200个物品,每个物品64维向量)
(1) 当 require_pow=False
loss_func = EmbLoss(norm=2)
loss = loss_func(user_emb, item_emb, require_pow=False)
计算过程:
- 对
user_emb
计算L2范数:- 对每个用户的64维向量求L2范数 → 得到100个标量
- 对这100个标量求平均值
- 对
item_emb
同样操作 - 将两部分结果相加
最简公式版
L
=
1
N
∑
k
∥
E
k
∥
p
\mathcal{L} = \frac{1}{N} \sum_{k} \|E_k\|_p
L=N1k∑∥Ek∥p
数学表达式:
L
=
1
100
∑
u
=
1
100
∥
u
u
∥
2
+
1
200
∑
i
=
1
200
∥
v
i
∥
2
\mathcal{L} = \frac{1}{100} \sum_{u=1}^{100} \|\mathbf{u}_u\|_2 + \frac{1}{200} \sum_{i=1}^{200} \|\mathbf{v}_i\|_2
L=1001u=1∑100∥uu∥2+2001i=1∑200∥vi∥2
(2) 当 require_pow=True
loss = loss_func(user_emb, item_emb, require_pow=True)
计算过程:
- 对
user_emb
计算L2范数的平方:- 先对每个用户向量求L2范数 → 100个标量
- 对这些标量平方 → 100个平方值
- 求平均值后除以2(
self.norm
)
- 对
item_emb
同样操作 - 将两部分结果相加
最简公式版
L = 1 N ⋅ p ∑ k ∥ E k ∥ p p \mathcal{L} = \frac{1}{N \cdot p} \sum_{k} \|E_k\|_p^p L=N⋅p1k∑∥Ek∥pp
数学表达式:
L
=
1
100
×
2
∑
u
=
1
100
∥
u
u
∥
2
2
+
1
200
×
2
∑
i
=
1
200
∥
v
i
∥
2
2
\mathcal{L} = \frac{1}{100 \times 2} \sum_{u=1}^{100} \|\mathbf{u}_u\|_2^2 + \frac{1}{200 \times 2} \sum_{i=1}^{200} \|\mathbf{v}_i\|_2^2
L=100×21u=1∑100∥uu∥22+200×21i=1∑200∥vi∥22
5. 为什么需要 require_pow
选项?
(1) require_pow=False
(直接L2范数)
- 特点:更温和的正则化,对大数值的惩罚是线性的。
- 适用场景:需要稀疏性时(某些维度接近0)。
(2) require_pow=True
(L2范数的平方)
- 特点:对大数值惩罚更严厉(平方放大差异)。
- 数学优势:与标准L2正则化一致(如PyTorch的
weight_decay
)。 - 物理意义:实际计算的是 所有嵌入向量的平方和(即Frobenius范数)。
6. 完整示例代码
import torch
import torch.nn as nn
# 定义损失类
class EmbLoss(nn.Module):
def __init__(self, norm=2):
super().__init__()
self.norm = norm
def forward(self, *embeddings, require_pow=False):
if require_pow:
loss = sum(
torch.norm(embedding, p=self.norm).pow(self.norm) # ‖E‖²
for embedding in embeddings
) / sum(embedding.shape[0] for embedding in embeddings) / self.norm
else:
loss = sum(
torch.norm(embedding, p=self.norm) # ‖E‖
for embedding in embeddings
) / sum(embedding.shape[0] for embedding in embeddings)
return loss
# 模拟数据
user_emb = torch.randn(100, 64) # 100个用户嵌入
item_emb = torch.randn(200, 64) # 200个物品嵌入
# 计算损失
loss_func = EmbLoss(norm=2)
print("L2 Norm Loss:", loss_func(user_emb, item_emb, require_pow=False))
print("L2 Squared Loss:", loss_func(user_emb, item_emb, require_pow=True))
7. 输出结果的意义
假设输出:
L2 Norm Loss: tensor(8.02) # 所有嵌入向量的平均L2长度
L2 Squared Loss: tensor(64.33) # 所有嵌入向量的平均平方和/2
L2 Norm Loss
:平均每个向量的长度为8.02(希望这个值不要太大)L2 Squared Loss
:平方和后更敏感(值更大),用于严格限制大权重
总结
- L2范数:衡量嵌入向量的“长度”,用于控制模型复杂度。
EmbLoss
:计算所有嵌入矩阵的平均L2范数(或平方),作为正则项。- 如何选择:
- 常规用途:
require_pow=True
(标准L2正则化) - 需要稀疏性:
norm=1
(L1范数)
- 常规用途:
通过这个类,你可以灵活地控制嵌入向量的规模,从而提升模型的泛化能力!