PyTorch中的L1/L2正则化

eni9jsuy  于 2022-11-09  发布在  其他
关注(0)|答案(7)|浏览(159)

如何在PyTorch中添加L1/L2正则化而无需手动计算?

f1tvaqid

f1tvaqid1#

使用weight_decay > 0进行L2正则化:

optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-5)
fgw7neuy

fgw7neuy2#

请参阅文档。将weight_decay参数添加到优化器以进行L2正则化。

ifsvaxew

ifsvaxew3#

以前的答案虽然在技术上是正确的,但在性能方面效率低下,而且模块化程度不高(很难按层应用,例如keras层)。

PyTorch L2实现

为什么PyTorch在torch.optim.Optimizer示例中实现了L2

让我们来看一下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 ofc也可以这样做):

如果我们对具有L2正则化的任何损失对参数w求导(它与损失无关),我们得到:

**因此,它只是一个简单的添加alpha * weight梯度的每一个权重!**这正是PyTorch所做的上面!

L1规则化层

使用这个函数(以及一些PyTorch魔法),我们可以得到非常通用的L1正则化层,但让我们先看一下L1的一阶导数(sgn是正负号函数,对于正输入返回1,对于负输入返回-1,对于0返回0):

带有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)

阅读更多关于钩子in this answer或相应的PyTorch文档(如果需要)。
用法也很简单(应适用于梯度累积和PyTorch层):

layer = L1(torch.nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3))

便笺

此外,作为一个侧记,L1正则化没有实现,因为它实际上不会导致稀疏(丢失引用,这是PyTorch repo上的一些GitHub问题,我想,如果有人有,请编辑),正如权重等于零所理解的。
更常见的情况是,如果权重值达到某个较小的预定义幅度(例如0.001),则对权重值设置阈值(简单地为其分配零值)

xytpbqjk

xytpbqjk4#

对于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

参考文献:

l5tcr1uw

l5tcr1uw5#

现成的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)

ykejflvf

ykejflvf6#

对于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
krugob8w

krugob8w7#

有趣的是,与直接方法相比,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

相关问题