tensorflow tf.reduce_sum很难以数值稳定的方式使用

0lvr5msh  于 2022-10-29  发布在  其他
关注(0)|答案(7)|浏览(132)
  • 请确保这是一个错误。根据我们的

GitHub Policy
我们仅解决代码/文档错误、性能问题、功能请求和
GitHub上的构建/安装问题。tag:bug_template*

编辑以添加:

正如第一条评论所阐明的,由于浮点错误的累积,所描述的行为可能是“按预期工作”的,这在许多常见情况下(但不是所有情况下)得到了缓解,导致了令人惊讶的不一致。
但是,我仍然认为至少有两件事需要改进:

  • tf.reduce_sum可以在其文档中包含一个注解来解释此行为,如np.sum
  • tf.reduce_sum可以采用dtype参数来提高累加器和输出的精度,而不需要增加整个输入Tensor的精度,也像np.sum一样

原始报告如下。

系统信息

此错误在标准Colab上可重现,因此我仅包含以下系统信息:

  • TensorFlow版本:2.5.0
  • Python版本:3.7.11
    要重新生成的代码
with tf.device("cpu:0"):
  print(tf.reduce_sum(tf.ones((20_000_000, 2)), axis=0))

在支持GPU的虚拟机上的colab中完整再现:https://gist.github.com/nfelt/e6edc45736740ed899eea95a36bea359

描述当前行为

在CPU上,tf.reduce_sum在一个秩为2的float 32Tensor上,沿着一个包含超过2^24个值为1.0的元素的轴,发出一个被截断为2^24(float 32中可精确表示的最大整数)的错误输出。

描述预期行为

它应该发出与在几乎所有其他上下文中相同的答案,这是数字上正确的答案,直到浮点表示错误,在本例中为tf.Tensor([20000000. 20000000.], shape=(2,), dtype=float32)
特别是,我们在以下所有情况下都得到了预期的答案:

  • 在GPU而非CPU上运行reduce_sum
  • 使用float64而不是float32
  • 对超过2^24个值为1.0的元素的秩为1的Tensor求和
  • 对秩为2的Tensor沿着轴求和,其元素少于2^24个,但单个元素大于2^24个,例如tf.reduce_sum(tf.ones((10_000_000, 2)) * 2.0, axis=0)
    备注

在2^24处停止的问题似乎影响了求和的元素数,而不是实际的和本身,基于以下情况:

with tf.device("cpu:0"):
  print(tf.reduce_sum(tf.ones((20_000_000, 2)) * 0.5, axis=0))
>>> tf.Tensor([8388608. 8388608.], shape=(2,), dtype=float32)

特别是,它似乎只包括尾部的2^24个元素,基于此:

tensor_with_one_big_value = tf.concat([[[10_000_000, 0]], tf.ones((20_000_000, 2))], axis=0)
with tf.device("cpu:0"):
  print(tf.reduce_sum(tensor_with_one_big_value, axis=0))
with tf.device("cpu:0"):
  print(tf.reduce_sum(tf.reverse(tensor_with_one_big_value, axis=[0]), axis=0))
>>> tf.Tensor([16777216. 16777216.], shape=(2,), dtype=float32)
>>> tf.Tensor([26777216. 16777216.], shape=(2,), dtype=float32)
sauutmhj

sauutmhj1#

这是浮点运算。在float32中的16777216上加1不会执行任何操作,因为16777217不在float32中。请参阅here

83qze16e

83qze16e2#

@HVoltBb谢谢你指出这一点,我想你是对的,这解释了行为。我仍然认为这是相当令人惊讶的,它是如此不一致-在CPU和GPU之间,以及在等级1和等级2 tf.reduce_sum()调用之间-但是看一下numpy docs,我发现它有一个类似的警告,即求和轴的选择会影响它是否使用部分和优化来如果tf.reduce_sum文档中有一个类似的注解,那就更好了,但这更像是一个文档修复请求,而不是一个bug修复。

5cnsuln7

5cnsuln73#

@nfelt我假设当任务被卸载到GPU时,底层算法会将总和分解成多个块,从而隐式地为该操作实现更高的精度,从而避免了这个问题。我还没有研究rank-1与rank-2的差异,但稍后会做。

uqjltbpv

uqjltbpv4#

FWIW,我想到的另一件事是np.sum支持累加器和结果的dtype参数,而tf.reduce_sum不支持,这迫使将整个输入Tensor转换为float64以避免这个问题。除非有一些奇怪的懒惰行为发生在幕后,或者推迟转换,与numpy的方法相比,这似乎需要更多的内存开销。因此,也许这也可以重新定义为添加该参数的FR。
我还没有看过一级和二级的区别,但稍后会做。
基于np.sum中关于部分和优化是特定于轴的注解,我假设秩1与秩2的差异实际上是内存对齐轴的差异,因为从我的测试来看,在秩2的情况下,只有在沿着轴0减少时才会产生影响,我认为这将是非内存对齐方向。

6ss1mwsb

6ss1mwsb5#

@nfelt我想你对一级对二级的解释可能就是这里的情况。

zphenhs4

zphenhs46#

我今天偶然遇到了这个解决方案。这个解决方案就是所谓的补偿和,它已经在tensorflow_probability中实现了。你可以使用下面的代码片段来得到正确的答案。

import tensorflow_probability as tfp

with tf.device("cpu:0"):
  print(tfp.math.reduce_kahan_sum(tf.ones((20_000_000, 2)), axis=0))
e5njpo68

e5njpo687#

我使用tf.reduce_sum和complex 64 dtype对许多数字求和(1400 x1400矩阵),我很惊讶地看到从CPU和执行numpy和的结果是多么的不同,CPU的结果是完全错误的。在GPU中,更大的差异是复杂的结果,而真实的部相差30%。老实说,我不知道它怎么会错得这么离谱。顺便说一句,我假设 numpy 答案是最接近正确的答案,因为它已经被更多的测试和实施。
而且,使用tensorflor_probabilty,刚刚又给出了一个错误的答案......

相关问题