PyTorch梯度与手动计算的梯度不同

6
我正在尝试计算1/x的梯度,但不使用Pytorch的自动求导。我使用公式grad(1/x, x) = -1/x**2。当我将这个公式得到的结果与Pytorch的自动求导给出的梯度进行比较时,它们是不同的。
以下是我的代码:
a = torch.tensor(np.random.randn(), dtype=dtype, requires_grad=True)
loss = 1/a
loss.backward()
print(a.grad - (-1/(a**2)))

输出结果如下:
tensor(5.9605e-08, grad_fn=<ThAddBackward>)

有人能解释一下问题是什么吗?

1个回答

7

我猜你期望的结果是0。但仔细观察,你会发现这个值非常接近0。在二进制系统(计算机)中进行数字除法时,通常会出现舍入误差。

让我们看一下你的例子并添加一个打印语句

a = torch.tensor(np.random.randn(), requires_grad=True)
loss = 1/a
loss.backward()
print(a.grad, (-1/(a**2)))
print(a.grad - (-1/(a**2)))

因为您使用了随机输入,输出也是随机的。
(所以您不会得到完全相同的数字,但只需重复此实验,您就会得到类似的示例) 有时您将得到零作为结果。但这并不是您最初示例的情况。
tensor(-0.9074) tensor(-0.9074, grad_fn=<MulBackward>)
tensor(5.9605e-08, grad_fn=<ThSubBackward>)

尽管两者显示的数字相同,但它们在最后一位小数上有所不同。这就是为什么在减去两者时会得到非常小的差异。
作为计算机的一般问题,某些分数有许多或无限多个小数位,但您的计算机内存没有那么大,因此它们在某个点处被截断。
因此,您在此经历的实际上是缺乏精度。而精度取决于您使用的数值数据类型(即torch.float32torch.float64)。
您也可以在此处查看更多信息:
https://en.wikipedia.org/wiki/Double-precision_floating-point_format 但这并不特定于PyTorch或其他任何语言,以下是一个Python示例:
print(29/100*100)

结果为:

28.999999999999996

编辑:

正如 @HOANG GIANG 指出的那样,将公式更改为 -(1/a)*(1/a) 效果很好,结果为零。 这可能是因为用于计算梯度的计算与此情况下的 -(1/a)*(1/a) 非常相似(或完全相同)。因此,它共享相同的舍入误差,因此差异为零。

因此,以下是比上面更适合的另一个示例。尽管 -(1/x)*(1/x) 在数学上等价于 -1/x^2,但在计算机上计算时,取决于 x 的值,两者并不总是相同的:

import numpy as np
print('e1 == e2','x value', '\t'*2, 'round-off error', sep='\t')
print('='*70)
for i in range(10):
    x = np.random.randn()
    e1 = -(1/x)*(1/x)
    e2 = (-1/(x**2))
    print(e1 == e2, x, e1-e2, sep='\t\t')

输出:

e1 == e2    x value                 round-off error
======================================================================
True        0.2934154339948173      0.0
True        -1.2881863891014191     0.0
True        1.0463038021843876      0.0
True        -0.3388766143622498     0.0
True        -0.6915415747192347     0.0
False       1.3299049850551317      1.1102230246251565e-16
True        -1.2392046539563553     0.0
False       -0.42534236747121645    8.881784197001252e-16
True        1.407198823994324       0.0
False       -0.21798652132356966    3.552713678800501e-15

尽管“舍入误差”似乎有点减少(我尝试了不同的随机值,很少有超过十个中有两个存在“舍入误差”),但仅计算1/x时就已经存在小差异:
import numpy as np
print('e1 == e2','x value', '\t'*2, 'round-off error', sep='\t')
print('='*70)
for i in range(10):
    x = np.random.randn()
    # calculate 1/x
    result = 1/x
    # apply inverse function
    reconstructed_x = 1/result
    # mathematically this should be the same as x
    print(x == reconstructed_x, x, x-reconstructed_x, sep='\t\t')

输出:

e1 == e2    x value             round-off error
======================================================================
False       0.9382823115235075      1.1102230246251565e-16
True        -0.5081217386356917     0.0
True        -0.04229436058156134    0.0
True        1.1121100294357302      0.0
False       0.4974618312372863      -5.551115123125783e-17
True        -0.20409933212316553    0.0
True        -0.6501652554924282     0.0
True        -3.048057937738731      0.0
True        1.6236075700470816      0.0
True        0.4936926651641918      0.0

我发现当我改变计算顺序(即我的公式变为 -(1/a)*(1/a))时,差异变为零(即==0)。 - HOANG GIANG
@HOANGGIANG 是的,那是个好观点!即使-(1/x)*(1/x)在数学上等同于-1/x^2,但在计算机上计算时并不总是如此。我在我的答案末尾进行了编辑。 - MBT
@HOANGGIANG 如果您能就“有谁能解释一下问题是什么?”这个问题给我一些反馈,那将非常棒。如果您觉得解释有用,我会很高兴,如果您接受答案并对所做的努力表示赞赏,谢谢! - MBT
1
抱歉 :) 我刚刚给你的回答点了赞,却忘记将其接受为正确答案。 - HOANG GIANG

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