如何在PyTorch中添加L1 / L2正则化,而无需手动计算?
如何在PyTorch中添加L1 / L2正则化,而无需手动计算?
使用 weight_decay > 0
进行L2正则化:
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-5)
请查看文档。向优化器中添加weight_decay
参数以进行L2正则化。
keras
层所提供的那样)。torch.optim.SGD
源代码(目前作为功能优化过程),特别是这部分内容:for i, param in enumerate(params):
d_p = d_p_list[i]
# L2 weight decay specified HERE!
if weight_decay != 0:
d_p = d_p.add(param, alpha=weight_decay)
d_p
被修改和重新分配,以便进行更快的计算(不保存临时变量)。O(N)
,没有像pow
这样的复杂数学运算。
它不涉及autograd
,无需扩展图形。O(n)
的**2
操作,还参与反向传播。
alpha
的L2
方程(当然也可以对L1进行相同操作):
如果我们对任何带有L2正则化的损失函数对参数w求导(它与损失函数无关),我们得到:
所以,对于每个权重的梯度,只需简单地将alpha乘以权重进行相加!这正是PyTorch所做的!WeightDecay
接口位于torchlayers第三方库中,提供了一些只对权重/偏置/特定命名参数进行正则化的功能(免责声明:我是作者),但下面概述的思想的本质如下(请参见注释):class L1(torch.nn.Module):
def __init__(self, module, weight_decay):
super().__init__()
self.module = module
self.weight_decay = weight_decay
# Backward hook is registered on the specified module
self.hook = self.module.register_full_backward_hook(self._weight_decay_hook)
# Not dependent on backprop incoming values, placeholder
def _weight_decay_hook(self, *_):
for param in self.module.parameters():
# If there is no gradient or it was zeroed out
# Zeroed out using optimizer.zero_grad() usually
# Turn on if needed with grad accumulation/more safer way
# if param.grad is None or torch.all(param.grad == 0.0):
# Apply regularization on it
param.grad = self.regularize(param)
def regularize(self, parameter):
# L1 regularization formula
return self.weight_decay * torch.sign(parameter.data)
def forward(self, *args, **kwargs):
# Simply forward and args and kwargs to module
return self.module(*args, **kwargs)
layer = L1(torch.nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3))
torchlayers
创建一个新的标签,并发布带有 L1
和 L2
的版本,因为它们在一年多之前发布的 0.1.1 版本中仍然缺失? - Maxim EgorushkinL1
和L2
,谢谢。不过有一个警告:~/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py:785: UserWarning: Using a non-full backward hook when outputs are generated by different autograd Nodes is deprecated and will be removed in future versions. This hook will be missing some grad_output. Please use register_full_backward_hook to get the documented behavior.
- Maxim Egorushkin对于L2正则化,
l2_lambda = 0.01
l2_reg = torch.tensor(0.)
for param in model.parameters():
l2_reg += torch.norm(param)
loss += l2_lambda * l2_reg
参考资料:
torch.norm
使用的是 2-范数,而不是 2-范数的平方。因此,我认为应该对范数进行平方以获得正确的正则化结果。 - John Liu是的,PyTorch的优化器有一个称为weight_decay
的参数,对应着L2正则化因子:
sgd = torch.optim.SGD(model.parameters(), weight_decay=weight_decay)
对于L1正则化,没有类似的参数,但是手动实现起来很简单:
loss = loss_fn(outputs, labels)
l1_lambda = 0.001
l1_norm = sum(torch.linalg.norm(p, 1) for p in model.parameters())
loss = loss + l1_lambda * l1_norm
L2的等效手动实现将是:
l2_reg = sum(p.pow(2).sum() for p in model.parameters())
来源:使用PyTorch进行深度学习(8.5.2)
l2_norm
,难道不应该平方参数而不是取 2-范数吗?即 sum(p**2 for p in model.parameters())
。 - Loqzl2_norm
是不正确的,因为权重矩阵的 L2 范数并不等同于扁平化权重向量的 L2 范数。据我所知,机器学习文献中将正则化称为权重向量的平方 L2 范数,即每个单独的权重元素的平方之和。为了修复你的代码,我会使用 norm(p.flatten(), 2)**2
而不是 norm(p, 2)
。 - A Kareem对于L1正则化,只包括weight
:
l1_reg = torch.tensor(0., requires_grad=True)
for name, param in model.named_parameters():
if 'weight' in name:
l1_reg = l1_reg + torch.linalg.norm(param, 1)
total_loss = total_loss + 10e-4 * l1_reg
有趣的是,在CPU上torch.norm
比直接方法慢,在GPU上比直接方法快。
import torch
x = torch.randn(1024,100)
y = torch.randn(1024,100)
%timeit torch.sqrt((x - y).pow(2).sum(1))
%timeit torch.norm(x - y, 2, 1)
输出:
1000 loops, best of 3: 910 µs per loop
1000 loops, best of 3: 1.76 ms per loop
另一方面:
import torch
x = torch.randn(1024,100).cuda()
y = torch.randn(1024,100).cuda()
%timeit torch.sqrt((x - y).pow(2).sum(1))
%timeit torch.norm(x - y, 2, 1)
输出:
10000 loops, best of 3: 50 µs per loop
10000 loops, best of 3: 26 µs per loop
在好的答案上进行扩展:正如所说,将L2范数添加到损失中等同于权重衰减,但前提是你使用的是没有动量的普通SGD。否则,例如使用Adam优化器时,情况就不完全相同了。AdamW论文[1]指出,权重衰减实际上更加稳定。这就是为什么你应该使用权重衰减,它是优化器的一个选项。并且考虑使用AdamW
而不是Adam
。
另外请注意,你可能不希望对所有参数(model.parameters()
)进行权重衰减,而只想对其中的一部分进行。请参考这里的示例:
weight_decay
来实现 L2 正则化。但是在 Adam 优化器中,weight_decay
和 L2 正则化是不同的。更多内容可以在这里阅读:https://openreview.net/pdf?id=rk6qdGgCZ - Ashishweight_decay
和 L2 正则化是不同的,但在 PyTorch 的 Adam 实现中,他们实际上实现了 L2 正则化而不是真正的权重衰减。请注意,在优化器步骤之前,权重衰减项被应用于梯度 此处。 - Eric Wiener