【问题标题】:Adding L1/L2 regularization in PyTorch?在 PyTorch 中添加 L1/L2 正则化?
【发布时间】:2017-07-30 23:10:51
【问题描述】:

有什么方法可以在 PyTorch 中添加简单的 L1/L2 正则化?我们可以通过简单地将data_lossreg_loss 相加来计算正则化损失,但是有没有任何明确的方法,PyTorch 库的任何支持可以更容易地做到这一点而无需手动操作?

【问题讨论】:

    标签: python machine-learning pytorch loss-function


    【解决方案1】:

    以下应该有助于 L2 正则化:

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

    【讨论】:

    • 在SGD优化器中,L2正则化可以通过weight_decay得到。但是weight_decay 和 L2 正则化对于 Adam 优化器是不同的。更多内容可以在这里阅读:openreview.net/pdf?id=rk6qdGgCZ
    • @Ashish 您的评论是正确的,weight_decay 和 L2 正则化是不同的,但是在 PyTorch 的 Adam 实现的情况下,它们实际上实现了 L2 正则化而不是真正的权重衰减。请注意,权重衰减项在优化器步骤之前应用于梯度here
    【解决方案2】:

    这在 PyTorch 的 the documentation 中介绍。您可以使用 weight_decay 参数将 L2 损失添加到优化函数中。

    【讨论】:

    • Adagrad 是一种优化技术,我说的是正则化。你能给我一个关于 L1 和 L2 损失的具体例子吗?
    • 是的,L2 正则化被神秘地添加到优化函数中,因为在优化期间使用了损失函数。你可以在这里找到讨论discuss.pytorch.org/t/simple-l2-regularization/139/3
    • 我有一些使用 L2 loss 的分支,所以这没有用。 (我有不同的损失函数)
    • 如果我想使用 L1 或其他损失进行正则化怎么办?
    • @mrgloom 你可以自己实现。它不包含在优化器中。
    【解决方案3】:

    以前的答案虽然在技术上是正确的,但在性能方面效率低下,并且不是太模块化(很难在每层基础上应用,例如,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 操作、加法以及参与反向传播进行比较。

    数学

    让我们看看L2 方程和alpha 正则化因子(L1 ofc 也可以这样做):

    如果我们使用 L2 正则化 w.r.t 对任何损失进行导数。参数w(与loss无关),我们得到:

    所以它只是为每个权重的梯度添加alpha * weight这正是 PyTorch 在上面所做的!

    L1 正则化层

    使用这个(和一些 PyTorch 魔法),我们可以提出非常通用的 L1 正则化层,但让我们先看看 L1 的一阶导数(sgn 是符号函数,返回 1 表示正输入和-1 表示否定,0 表示 0):

    带有WeightDecay 接口的完整代码位于torchlayers third party library 中,提供诸如仅正则化权重/偏差/特定命名参数之类的东西(免责声明:我是作者),但这个想法的本质概述如下(见 cmets):

    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)
    

    如果需要,请阅读有关 hooks 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),则通常会对权重值进行阈值处理(只需为其分配零值)

    【讨论】:

    • 您是否愿意为torchlayers 创建一个新标签并使用L1L2 发布它,因为它们在一年前发布的0.1.1 版本中仍然缺失?跨度>
    • @MaximEgorushkin 你能试试夜间版本吗?它应该在那里,虽然尚未经过彻底测试,新版本计划在未来 2 个月内发布(与其他库一起)
    • Nightly 有L1L2,谢谢。虽然有一个警告~/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.
    • @MaximEgorushkin 见this PR,您可以相应地更新您的版本(如果测试通过,它将在今天@00:00 GMT 发布)。
    【解决方案4】:

    对于 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 范数的平方。所以我认为规范应该平方以获得正确的正则化。
    • 没有 requires_grad 并使用 += 会导致错误。这对我有用: l2_reg = torch.tensor(0., requires_grad=True) l2_reg = l2_reg + torch.norm(param)
    【解决方案5】:

    仅用于 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
    

    【讨论】:

      【解决方案6】:

      开箱即用的 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(p.abs().sum() for p in model.parameters())
      
      loss = loss + l1_lambda * l1_norm
      

      L2 的等效手动实现是:

      l2_norm = sum(p.pow(2.0).sum() for p in model.parameters())
      

      来源:Deep Learning with PyTorch (8.5.2)

      【讨论】:

        【解决方案7】:

        有趣的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
        

        【讨论】:

        • 我也确认了这一点。在这个例子中,torch.norm 大约慢了 60%。
        猜你喜欢
        • 2021-05-05
        • 1970-01-01
        • 2017-11-22
        • 2020-01-30
        • 2020-12-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-17
        相关资源
        最近更新 更多