TensorFlow 添加大数
TensorFlow add large numbers
在我的模型中,我可以将小数字与一些大数字相加。但这会产生不正确的值。这是一个简化的例子:
import tensorflow as tf
a = tf.Variable(-1.55786165e+14 ,dtype=np.float32)
b = tf.Variable(-112522840.,dtype=np.float32)
c = tf.Variable(-34. ,dtype=np.float32)
a+b+c
这给出 <tf.Tensor: shape=(), dtype=float32, numpy=-155786280000000.0>
其中正确答案应该是 -155786277522874
我该如何纠正?
可以直接使用 numpy
观察到相同的行为。但是,这仅在使用 dtype=np.float32
而不是 dtype=np.float64
时出现。将您的 dtype
更改为 np.float64
以更正问题。
为了理解其中的原因,您必须了解浮点数是如何存储在内存中的。让我们考虑用单精度和双精度表示时的 a
:
import numpy as np
a = -1.55786165e+14
a_single = np.array([a], dtype=np.float32)
a_double = np.array([a], dtype=np.float64)
a_single[0], a_double[0], a
# The line above prints:
# (-155786160000000.0, -155786165000000.0, -155786165000000.0)
如您所见,a
在使用单精度时被截断了。但这是为什么呢?
abs(a)
的以2为底的对数介于47和48之间。因此,a
可以写成-1 * 2^47 * 1.x
。在表示浮点数时,必须对指数(48)和分数(x)进行编码。
在我们的例子中,.x
大约等于:
-a / pow(2, 47) - 1
等于0.1069272787267437
。现在,我们想要的是将这个数字写成 2 的负幂之和,从 -1 开始。也就是说如果我们用N
位来表示的话,我们会把0.1069272787267437 * pow(2, N)
.
的整数部分存入内存
在单精度中,我们使用N = 23
位来表示这个数字。由于0.1069272787267437 * pow(2, 23)
的整数部分为896971,其二进制展开为11011010111111001011
,长度为20位,因此内存中存储的数为00011011010111111001011
.
然而,当使用双精度时,存储在内存中的数字是 0001101101011111100101100000110100101110100000000000
。请注意,大量尾随零可能表示存储了 a
的确切值(因为我们不需要更高的精度来表示它),这里就是这种情况。
也就是说,这解释了为什么 a
在表示为单精度浮点数时被截断。同样的推理适用于将 b
添加到 a
。由于生成的浮点数的指数将为 47
,这意味着您可以在单精度中声明的最小可能精度为 2^47 * 2^-23=2^24
,而在双精度中您可以声明的最小可能精度为 [=37] =].由于您使用的是整数,这就解释了为什么您会得到双精度的准确结果和单精度的错误结果。
在我的模型中,我可以将小数字与一些大数字相加。但这会产生不正确的值。这是一个简化的例子:
import tensorflow as tf
a = tf.Variable(-1.55786165e+14 ,dtype=np.float32)
b = tf.Variable(-112522840.,dtype=np.float32)
c = tf.Variable(-34. ,dtype=np.float32)
a+b+c
这给出 <tf.Tensor: shape=(), dtype=float32, numpy=-155786280000000.0>
其中正确答案应该是 -155786277522874
我该如何纠正?
可以直接使用 numpy
观察到相同的行为。但是,这仅在使用 dtype=np.float32
而不是 dtype=np.float64
时出现。将您的 dtype
更改为 np.float64
以更正问题。
为了理解其中的原因,您必须了解浮点数是如何存储在内存中的。让我们考虑用单精度和双精度表示时的 a
:
import numpy as np
a = -1.55786165e+14
a_single = np.array([a], dtype=np.float32)
a_double = np.array([a], dtype=np.float64)
a_single[0], a_double[0], a
# The line above prints:
# (-155786160000000.0, -155786165000000.0, -155786165000000.0)
如您所见,a
在使用单精度时被截断了。但这是为什么呢?
abs(a)
的以2为底的对数介于47和48之间。因此,a
可以写成-1 * 2^47 * 1.x
。在表示浮点数时,必须对指数(48)和分数(x)进行编码。
在我们的例子中,.x
大约等于:
-a / pow(2, 47) - 1
等于0.1069272787267437
。现在,我们想要的是将这个数字写成 2 的负幂之和,从 -1 开始。也就是说如果我们用N
位来表示的话,我们会把0.1069272787267437 * pow(2, N)
.
在单精度中,我们使用N = 23
位来表示这个数字。由于0.1069272787267437 * pow(2, 23)
的整数部分为896971,其二进制展开为11011010111111001011
,长度为20位,因此内存中存储的数为00011011010111111001011
.
然而,当使用双精度时,存储在内存中的数字是 0001101101011111100101100000110100101110100000000000
。请注意,大量尾随零可能表示存储了 a
的确切值(因为我们不需要更高的精度来表示它),这里就是这种情况。
也就是说,这解释了为什么 a
在表示为单精度浮点数时被截断。同样的推理适用于将 b
添加到 a
。由于生成的浮点数的指数将为 47
,这意味着您可以在单精度中声明的最小可能精度为 2^47 * 2^-23=2^24
,而在双精度中您可以声明的最小可能精度为 [=37] =].由于您使用的是整数,这就解释了为什么您会得到双精度的准确结果和单精度的错误结果。