在 PyTorch 中添加 L1/L2 正则化?
Adding L1/L2 regularization in PyTorch?
有什么办法可以在 PyTorch 中添加简单的 L1/L2 正则化吗?我们可以通过简单地将 data_loss
与 reg_loss
相加来计算正则化损失,但是是否有任何明确的方法,PyTorch 库的任何支持可以更轻松地完成它而无需手动进行?
这在 the documentation 中针对 PyTorch 进行了介绍。您可以使用 weight_decay
参数将 L2 损失添加到优化函数。
以下应该有助于 L2 正则化:
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-5)
对于 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
在 CPU 上较慢,在 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
用于 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.norm(param, 1)
total_loss = total_loss + 10e-4 * L1_reg
开箱即用的 L2 正则化
是的,pytorch optimizers 有一个参数叫做 weight_decay
对应于 L2 正则化因子:
sgd = torch.optim.SGD(model.parameters(), weight_decay=weight_decay)
L1 正则化实现
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_norm = sum(torch.linalg.norm(p, 2) for p in model.parameters())
来源:Deep Learning with PyTorch (8.5.2)
以前的答案虽然在技术上是正确的,但在性能方面效率低下并且不是太模块化(很难按层应用,例如 keras
层提供的)。
PyTorch L2 实现
为什么 PyTorch 在 torch.optim.Optimizer
个实例中实现 L2
?
我们来看看torch.optim.SGD
source code(目前作为功能优化程序),特别是这部分:
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 ofc 进行):
如果我们使用 L2
正则化 w.r.t 对任何损失求导数。参数w
(与损失无关),我们得到:
所以它只是为每个权重的梯度添加 alpha * weight
! 而这正是 PyTorch 在上面所做的!
L1 正则化层
使用这个(和一些 PyTorch 魔法),我们可以得到非常通用的 L1 正则化层,但让我们先看看 L1
的一阶导数(sgn
是符号函数,返回 1
表示正输入,-1
表示负输入,0
表示 0
):
带有 WeightDecay
接口的完整代码位于 torchlayers third party library 中,仅提供正则化 weights/biases/specifically 命名参数(免责声明:我是作者),但下面概述了这个想法的本质(见评论):
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)
阅读更多关于钩子的信息 或者如果需要的话阅读相应的 PyTorch 文档。
而且用法也很简单(应该适用于梯度累积和 PyTorch 层):
layer = L1(torch.nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3))
旁注
此外,作为旁注,L1
正则化未实现,因为它 实际上不会引起稀疏性 (引用丢失,有些 GitHub PyTorch repo 上的问题我认为,如果有人有它,请编辑)正如权重等于零所理解的那样。
更常见的是,如果权重值达到某个较小的预定义幅度(例如 0.001
),则会对其设置阈值(简单地为其分配零值)
有什么办法可以在 PyTorch 中添加简单的 L1/L2 正则化吗?我们可以通过简单地将 data_loss
与 reg_loss
相加来计算正则化损失,但是是否有任何明确的方法,PyTorch 库的任何支持可以更轻松地完成它而无需手动进行?
这在 the documentation 中针对 PyTorch 进行了介绍。您可以使用 weight_decay
参数将 L2 损失添加到优化函数。
以下应该有助于 L2 正则化:
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-5)
对于 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
在 CPU 上较慢,在 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
用于 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.norm(param, 1)
total_loss = total_loss + 10e-4 * L1_reg
开箱即用的 L2 正则化
是的,pytorch optimizers 有一个参数叫做 weight_decay
对应于 L2 正则化因子:
sgd = torch.optim.SGD(model.parameters(), weight_decay=weight_decay)
L1 正则化实现
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_norm = sum(torch.linalg.norm(p, 2) for p in model.parameters())
来源:Deep Learning with PyTorch (8.5.2)
以前的答案虽然在技术上是正确的,但在性能方面效率低下并且不是太模块化(很难按层应用,例如 keras
层提供的)。
PyTorch L2 实现
为什么 PyTorch 在 torch.optim.Optimizer
个实例中实现 L2
?
我们来看看torch.optim.SGD
source code(目前作为功能优化程序),特别是这部分:
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 ofc 进行):
如果我们使用 L2
正则化 w.r.t 对任何损失求导数。参数w
(与损失无关),我们得到:
所以它只是为每个权重的梯度添加 alpha * weight
! 而这正是 PyTorch 在上面所做的!
L1 正则化层
使用这个(和一些 PyTorch 魔法),我们可以得到非常通用的 L1 正则化层,但让我们先看看 L1
的一阶导数(sgn
是符号函数,返回 1
表示正输入,-1
表示负输入,0
表示 0
):
带有 WeightDecay
接口的完整代码位于 torchlayers third party library 中,仅提供正则化 weights/biases/specifically 命名参数(免责声明:我是作者),但下面概述了这个想法的本质(见评论):
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)
阅读更多关于钩子的信息
而且用法也很简单(应该适用于梯度累积和 PyTorch 层):
layer = L1(torch.nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3))
旁注
此外,作为旁注,L1
正则化未实现,因为它 实际上不会引起稀疏性 (引用丢失,有些 GitHub PyTorch repo 上的问题我认为,如果有人有它,请编辑)正如权重等于零所理解的那样。
更常见的是,如果权重值达到某个较小的预定义幅度(例如 0.001
),则会对其设置阈值(简单地为其分配零值)