PyTorch 中的自定义损失函数
Custom loss function in PyTorch
我有三个简单的问题。
- 如果我的自定义损失函数不可微会怎样? pytorch会通过错误还是做其他事情?
- 如果我在我的自定义函数中声明了一个代表模型最终损失的损失变量,我应该为该变量放置
requires_grad = True
吗?或者没关系?如果没关系,那为什么?
- 我看到人们有时会编写一个单独的层并在
forward
函数中计算损失。哪种方法更可取,编写函数还是层?为什么?
我需要对这些问题有一个清晰而好的解释来解决我的困惑。请帮忙
让我试一试。
这取决于你所说的"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()
方法。
没关系。使用 requires_grad
是为了避免对子图进行不必要的梯度计算。如果一个操作的单个输入需要梯度,那么它的输出也需要梯度。相反,只有当所有输入都不需要梯度时,输出也不需要梯度。从不在子图中执行反向计算,其中所有变量都不需要梯度。
因为,很可能有一些 Variables
(例如 nn.Module()
的子类的参数),您的 loss
变量也将自动需要渐变。但是,您应该注意到,对于 requires_grad
的工作方式(再次参见上文),您只能更改图表的叶变量 requires_grad
。
所有自定义 PyTorch 损失函数都是 _Loss
的子类,_Loss
是 nn.Module
的子类。 See here. 如果您想遵守此约定,您应该在定义自定义损失函数时将 _Loss
子类化。除了一致性之外,一个优点是如果您没有将目标变量标记为 volatile
或 requires_grad = False
,您的子类将引发 AssertionError
。另一个优点是您可以将损失函数嵌套在 nn.Sequential()
中,因为它是 nn.Module
出于这些原因我会推荐这种方法。
我有三个简单的问题。
- 如果我的自定义损失函数不可微会怎样? pytorch会通过错误还是做其他事情?
- 如果我在我的自定义函数中声明了一个代表模型最终损失的损失变量,我应该为该变量放置
requires_grad = True
吗?或者没关系?如果没关系,那为什么? - 我看到人们有时会编写一个单独的层并在
forward
函数中计算损失。哪种方法更可取,编写函数还是层?为什么?
我需要对这些问题有一个清晰而好的解释来解决我的困惑。请帮忙
让我试一试。
这取决于你所说的"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()
方法。没关系。使用
requires_grad
是为了避免对子图进行不必要的梯度计算。如果一个操作的单个输入需要梯度,那么它的输出也需要梯度。相反,只有当所有输入都不需要梯度时,输出也不需要梯度。从不在子图中执行反向计算,其中所有变量都不需要梯度。因为,很可能有一些
Variables
(例如nn.Module()
的子类的参数),您的loss
变量也将自动需要渐变。但是,您应该注意到,对于requires_grad
的工作方式(再次参见上文),您只能更改图表的叶变量requires_grad
。所有自定义 PyTorch 损失函数都是
_Loss
的子类,_Loss
是nn.Module
的子类。 See here. 如果您想遵守此约定,您应该在定义自定义损失函数时将_Loss
子类化。除了一致性之外,一个优点是如果您没有将目标变量标记为volatile
或requires_grad = False
,您的子类将引发AssertionError
。另一个优点是您可以将损失函数嵌套在nn.Sequential()
中,因为它是nn.Module
出于这些原因我会推荐这种方法。