PyTorch 中的自定义损失函数

Custom loss function in PyTorch

我有三个简单的问题。

  1. 如果我的自定义损失函数不可微会怎样? pytorch会通过错误还是做其他事情?
  2. 如果我在我的自定义函数中声明了一个代表模型最终损失的损失变量,我应该为该变量放置 requires_grad = True 吗?或者没关系?如果没关系,那为什么?
  3. 我看到人们有时会编写一个单独的层并在 forward 函数中计算损失。哪种方法更可取,编写函数还是层?为什么?

我需要对这些问题有一个清晰而好的解释来解决我的困惑。请帮忙

让我试一试。

  1. 这取决于你所说的"non-differentiable"是什么意思。这里第一个有意义的定义是 PyTorch 不知道如何计算梯度。尽管如此,如果您尝试计算梯度,则会引发错误。两种可能的情况是:

    a) 您正在使用尚未实现梯度的自定义 PyTorch 操作,例如torch.svd()。在这种情况下,您将得到 TypeError:

    import torch
    from torch.autograd import Function
    from torch.autograd import Variable
    
    A = Variable(torch.randn(10,10), requires_grad=True)
    u, s, v = torch.svd(A) # raises TypeError
    

    b) 你实现了自己的操作,但没有定义backward()。在这种情况下,您将得到 NotImplementedError:

    class my_function(Function): # forgot to define backward()
    
        def forward(self, x):
            return 2 * x
    
    A = Variable(torch.randn(10,10))
    B = my_function()(A)
    C = torch.sum(B)
    C.backward() # will raise NotImplementedError
    

    第二个有意义的定义是"mathematically non-differentiable"。显然,数学上不可微分的操作不应该实现 backward() 方法或合理的子梯度。考虑例如 torch.abs()backward() 方法 returns 次梯度 0 在 0:

    A = Variable(torch.Tensor([-1,0,1]),requires_grad=True)
    B = torch.abs(A)
    B.backward(torch.Tensor([1,1,1]))
    A.grad.data
    

    对于这些情况,你应该直接参考PyTorch文档,直接挖掘出各自操作的backward()方法。

  2. 没关系。使用 requires_grad 是为了避免对子图进行不必要的梯度计算。如果一个操作的单个输入需要梯度,那么它的输出也需要梯度。相反,只有当所有输入都不需要梯度时,输出也不需要梯度。从不在子图中执行反向计算,其中所有变量都不需要梯度。

    因为,很可能有一些 Variables(例如 nn.Module() 的子类的参数),您的 loss 变量也将自动需要渐变。但是,您应该注意到,对于 requires_grad 的工作方式(再次参见上文),您只能更改图表的叶变量 requires_grad

  3. 所有自定义 PyTorch 损失函数都是 _Loss 的子类,_Lossnn.Module 的子类。 See here. 如果您想遵守此约定,您应该在定义自定义损失函数时将 _Loss 子类化。除了一致性之外,一个优点是如果您没有将目标变量标记为 volatilerequires_grad = False,您的子类将引发 AssertionError。另一个优点是您可以将损失函数嵌套在 nn.Sequential() 中,因为它是 nn.Module 出于这些原因我会推荐这种方法。