Pytorch:如何向激活函数中添加L1正则化?

36
我想在 ReLU 的激活输出中添加 L1 正则化。更一般地说,如何仅将正则化器添加到网络中的特定层?

相关资料:

  • 这篇类似的帖子提到了添加 L2 正则化,但似乎将正则化惩罚添加到了网络的所有层。

  • nn.modules.loss.L1Loss() 看起来很相关,但我还不知道如何使用它。

  • 旧版模块 L1Penalty 也似乎很相关,但为什么已经被弃用了呢?


对于一个相对高级的解决方案,您可以查看链接。这为您提供了一个类似于Keras的接口,可以轻松地在PyTorch中完成许多事情,并特别添加各种正则化器。 - Amir Rosenfeld
5个回答

41

以下是如何操作:

  • 在您的模块中,对于希望应用L1正则化的最终输出和层输出,在forward返回中进行处理
  • loss变量将是输出相对于目标的交叉熵损失和L1惩罚的总和。

下面是一个示例代码:

import torch
from torch.autograd import Variable
from torch.nn import functional as F


class MLP(torch.nn.Module):
    def __init__(self):
        super(MLP, self).__init__()
        self.linear1 = torch.nn.Linear(128, 32)
        self.linear2 = torch.nn.Linear(32, 16)
        self.linear3 = torch.nn.Linear(16, 2)

    def forward(self, x):
        layer1_out = F.relu(self.linear1(x))
        layer2_out = F.relu(self.linear2(layer1_out))
        out = self.linear3(layer2_out)
        return out, layer1_out, layer2_out

batchsize = 4
lambda1, lambda2 = 0.5, 0.01

model = MLP()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)

# usually following code is looped over all batches 
# but let's just do a dummy batch for brevity

inputs = Variable(torch.rand(batchsize, 128))
targets = Variable(torch.ones(batchsize).long())

optimizer.zero_grad()
outputs, layer1_out, layer2_out = model(inputs)
cross_entropy_loss = F.cross_entropy(outputs, targets)

all_linear1_params = torch.cat([x.view(-1) for x in model.linear1.parameters()])
all_linear2_params = torch.cat([x.view(-1) for x in model.linear2.parameters()])
l1_regularization = lambda1 * torch.norm(all_linear1_params, 1)
l2_regularization = lambda2 * torch.norm(all_linear2_params, 2)

loss = cross_entropy_loss + l1_regularization + l2_regularization
loss.backward()
optimizer.step()

1
谢谢,我没有意识到您可以更改像forward()这样的核心函数的“签名”。 - Bull
3
这是否不会使该层的权重规范化?我猜原帖作者想要规范化一层的输出而不是权重。在PyTorch中,如何仅对激活进行规范化(使其稀疏)? - M_Gorky
5
答案似乎有错误。对于norm(all_linear2_params, 2),torch返回的是L2正则化的平方根。也就是说,表达式应该被求平方。 - Yuval Atzmon
5
为什么在 forward 函数中需要返回 layer1_out 和 layer2_out 这些变量,即使它们没有被使用到? - Omroth
5
这会对权重进行正则化,你应该对返回的层输出(即激活)进行正则化。这就是你最初将它们返回的原因! 正则化项应该长这样: l1_regularization = lambda1 * torch.norm(layer1_out, 1) l2_regularization = lambda2 * torch.norm(layer2_out, 2) - אלימלך שרייבר
显示剩余4条评论

22

其他所有(当前的)回答都在某种程度上是错误的,因为问题是关于将正则化添加到激活函数中的。 这个答案 是最接近的,因为它建议对输出的范数求和,这是正确的,但代码对权重的范数求和是不正确的。

正确的方法不是修改网络代码,而是通过前向钩子来捕获输出,就像 OutputHook 类中所示。从那里开始,对输出的范数求和很简单,但需要注意每次迭代清除已捕获的输出。

import torch


class OutputHook(list):
    """ Hook to capture module outputs.
    """
    def __call__(self, module, input, output):
        self.append(output)


class MLP(torch.nn.Module):
    def __init__(self):
        super(MLP, self).__init__()
        self.linear1 = torch.nn.Linear(128, 32)
        self.linear2 = torch.nn.Linear(32, 16)
        self.linear3 = torch.nn.Linear(16, 2)
        # Instantiate ReLU, so a hook can be registered to capture its output.
        self.relu = torch.nn.ReLU()

    def forward(self, x):
        layer1_out = self.relu(self.linear1(x))
        layer2_out = self.relu(self.linear2(layer1_out))
        out = self.linear3(layer2_out)
        return out


batch_size = 4
l1_lambda = 0.01

model = MLP()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
# Register hook to capture the ReLU outputs. Non-trivial networks will often
# require hooks to be applied more judiciously.
output_hook = OutputHook()
model.relu.register_forward_hook(output_hook)

inputs = torch.rand(batch_size, 128)
targets = torch.ones(batch_size).long()

optimizer.zero_grad()
outputs = model(inputs)
cross_entropy_loss = torch.nn.functional.cross_entropy(outputs, targets)

# Compute the L1 penalty over the ReLU outputs captured by the hook.
l1_penalty = 0.
for output in output_hook:
    l1_penalty += torch.norm(output, 1)
l1_penalty *= l1_lambda

loss = cross_entropy_loss + l1_penalty
loss.backward()
optimizer.step()
output_hook.clear()

1
我不是很明白为什么你将L1正则化计算为输出的范数之和,因为众所周知L1正则化应该是权重的范数之和。你能详细解释一下吗? - Tung Vs
1
@TungVs 权重的L1正则化是权重的总和或平均L1范数。激活的L1正则化是激活的总和或平均L1范数。它们都是合法的正则化方法。关于这个问题有很多文献资料。可以参考这个最近的例子。还有很多其他的资料可供参考。 - ndronen

7
正则化应该是模型每一层的加权参数,而不是每一层的输出。请看下面链接:正则化
import torch
from torch.autograd import Variable
from torch.nn import functional as F


class MLP(torch.nn.Module):
    def __init__(self):
        super(MLP, self).__init__()
        self.linear1 = torch.nn.Linear(128, 32)
        self.linear2 = torch.nn.Linear(32, 16)
        self.linear3 = torch.nn.Linear(16, 2)
    def forward(self, x):
        layer1_out = F.relu(self.linear1(x))
        layer2_out = F.relu(self.linear2(layer1_out))
        out = self.linear3(layer2_out)
        return out

batchsize = 4
lambda1, lambda2 = 0.5, 0.01

model = MLP()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)

inputs = Variable(torch.rand(batchsize, 128))
targets = Variable(torch.ones(batchsize).long())
l1_regularization, l2_regularization = torch.tensor(0), torch.tensor(0)

optimizer.zero_grad()
outputs = model(inputs)
cross_entropy_loss = F.cross_entropy(outputs, targets)
for param in model.parameters():
    l1_regularization += torch.norm(param, 1)**2
    l2_regularization += torch.norm(param, 2)**2

loss = cross_entropy_loss + l1_regularization + l2_regularization
loss.backward()
optimizer.step()

1
看起来答案有误。对于 norm(param, 2):torch 返回 L2 正则化的 平方根。也就是说,表达式应该被提高到 2 的幂次。 - Yuval Atzmon
1
正则化权重更标准,但有研究表明(L1)正则化激活函数更可取。 - daknowles
1
正如上面的回答所指出的,任何事情都可能受到规范化的影响。 - Eelco Hoogendoorn

3
我认为原帖想要使ReLU的输出规范化,因此正则化器应该在输出上而不是网络权重上。它们并不相同!
使用L1-norm对权重进行正则化训练神经网络将得到稀疏权重。
使用L1-norm对层的输出进行正则化会训练一个具有稀疏输出的网络,仅针对特定的层。
以上答案(包括被接受的答案)要么忽略了重点,要么我误解了原帖问题。

1
您可以使用以下代码将模型my_layer的权重应用L1正则化到损失函数中的单个层:
def l1_penalty(params, l1_lambda=0.001):
    """Returns the L1 penalty of the params."""
    l1_norm = sum(p.abs().sum() for p in params)
    return l1_lambda*l1_norm

loss = loss_fn(outputs, labels) + l1_penalty(my_layer.parameters())

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接