Pytorch Autograd 在使用 .clamp 而不是 torch.relu 时会给出不同的渐变

Posted

技术标签:

【中文标题】Pytorch Autograd 在使用 .clamp 而不是 torch.relu 时会给出不同的渐变【英文标题】:Pytorch Autograd gives different gradients when using .clamp instead of torch.relu 【发布时间】:2020-06-22 09:26:12 【问题描述】:

我仍在努力了解 PyTorch autograd 系统。我正在努力解决的一件事是了解为什么 .clamp(min=0)nn.functional.relu() 似乎有不同的向后传递。

这尤其令人困惑,因为 .clamp 在 PyTorch 教程中等同于 relu,例如 https://pytorch.org/tutorials/beginner/pytorch_with_examples.html#pytorch-nn。

我在分析具有一个隐藏层和一个 relu 激活(在输出层中为线性)的简单全连接网络的梯度时发现了这一点。

据我了解,以下代码的输出应该只是零。我希望有人能告诉我我缺少什么。

import torch
dtype = torch.float

x = torch.tensor([[3,2,1],
                  [1,0,2],
                  [4,1,2],
                  [0,0,1]], dtype=dtype)

y = torch.ones(4,4)

w1_a = torch.tensor([[1,2],
                     [0,1],
                     [4,0]], dtype=dtype, requires_grad=True)
w1_b = w1_a.clone().detach()
w1_b.requires_grad = True



w2_a = torch.tensor([[-1, 1],
                     [-2, 3]], dtype=dtype, requires_grad=True)
w2_b = w2_a.clone().detach()
w2_b.requires_grad = True


y_hat_a = torch.nn.functional.relu(x.mm(w1_a)).mm(w2_a)
y_a = torch.ones_like(y_hat_a)
y_hat_b = x.mm(w1_b).clamp(min=0).mm(w2_b)
y_b = torch.ones_like(y_hat_b)

loss_a = (y_hat_a - y_a).pow(2).sum()
loss_b = (y_hat_b - y_b).pow(2).sum()

loss_a.backward()
loss_b.backward()

print(w1_a.grad - w1_b.grad)
print(w2_a.grad - w2_b.grad)

# OUT:
# tensor([[  0.,   0.],
#         [  0.,   0.],
#         [  0., -38.]])
# tensor([[0., 0.],
#         [0., 0.]])
# 

【问题讨论】:

我还没有仔细分析你的代码,但是除非 pytorch 中存在错误,否则我看到的唯一潜在差异是 relu 和钳位在 0 处的梯度。激活函数是连续的,但在0. 从优化理论来看,这意味着我们应该在这一点上选择函数的次梯度,因为梯度不存在。直观地说,次梯度是在该点与函数相切的斜率。对于 relu/clamp,0 处的子梯度是区间 [0,1] 中的所有值。我的猜测是relu和clamp在这里选择不同的次梯度。 @jodag 你说得对,clamprelu0 处产生不同的渐变。我用标量张量 x = 0 检查了两个版本:(x.clamp(min=0) - 1.0).pow(2).backward()(relu(x) - 1.0).pow(2).backward()。对于 ReLU 版本,生成的 x.grad 为 0,但对于钳位版本,它为 -2。这意味着 ReLU 选择 x == 0 --> grad = 0clamp 选择 x == 0 --> grad = 1 @a_guest,好收获!您的评论应该是一个答案。 【参考方案1】:

原因是clamprelu0 处产生不同的渐变。使用标量张量 x = 0 检查两个版本:(x.clamp(min=0) - 1.0).pow(2).backward()(relu(x) - 1.0).pow(2).backward()。对于 relu 版本,生成的 x.grad0,但对于 clamp 版本,它是 -2。这意味着relu 选择x == 0 --> grad = 0clamp 选择x == 0 --> grad = 1

【讨论】:

以上是关于Pytorch Autograd 在使用 .clamp 而不是 torch.relu 时会给出不同的渐变的主要内容,如果未能解决你的问题,请参考以下文章

pytorch 笔记: 扩展torch.autograd

Pytorch 文档中的 Autograd 函数

[Pytorch系列-22]:Pytorch基础 - Autograd库 Autograd.Function与反向自动求导机制

Pytorch autograd.grad与autograd.backward详解

Pytorch中的autograd详解

pytorch 与 autograd.numpy