我正在移植一个在Matlab中工作的算法来调整并发现一个奇怪的行为。相关的代码段是
P = eye(4)*1e20;
A = [1 -0.015 -0.025 -0.035; 0.015 1 0.035 -0.025; 0.025 -0.035 1 0.015; 0.035 0.025 -0.015 1];
V1 = A*(P*A')
V2 = (A*P)*A'
当我使用Matlab运行时,此代码提供以下矩阵:
V1 = 1.0021e+20 0 -8.0000e+00 0
0 1.0021e+20 0 0
-8.0000e+00 0 1.0021e+20 0
0 0 0 1.0021e+20
V2 = 1.0021e+20 0 -8.0000e+00 0
0 1.0021e+20 0 0
-8.0000e+00 0 1.0021e+20 0
0 0 0 1.0021e+20
请注意,V1和V2与预期的相同。
当相同的代码在Octave中运行时,它提供:
V1 = 1.0021e+20 4.6172e+01 -1.3800e+02 1.8250e+02
-4.6172e+01 1.0021e+20 -1.8258e+02 -1.3800e+02
1.3801e+02 1.8239e+02 1.0021e+20 -4.6125e+01
-1.8250e+02 1.3800e+02 4.6125e+01 1.0021e+20
V2 = 1.0021e+20 -4.6172e+01 1.3801e+02 -1.8250e+02
4.6172e+01 1.0021e+20 1.8239e+02 1.3800e+02
-1.3800e+02 -1.8258e+02 1.0021e+20 4.6125e+01
1.8250e+02 -1.3800e+02 -4.6125e+01 1.0021e+20
在numpy中,细分变为
from numpy import array, dot, eye
A = numpy.array([[1, -0.015, -0.025, -0.035],[0.015, 1, 0.035, -0.025],[0.025, -0.035, 1, 0.015],[0.035, 0.025, -0.015, 1]])
P = numpy.eye(4)*1e20
print numpy.dot(A,numpy.dot(P,A.transpose()))
print numpy.dot(numpy.dot(A,P),A.transpose())
输出
[[ 1.00207500e+20 4.61718750e+01 -1.37996094e+02 1.82500000e+02]
[ -4.61718750e+01 1.00207500e+20 -1.82582031e+02 -1.38000000e+02]
[ 1.38011719e+02 1.82386719e+02 1.00207500e+20 -4.61250000e+01]
[ -1.82500000e+02 1.38000000e+02 4.61250000e+01 1.00207500e+20]]
[[ 1.00207500e+20 -4.61718750e+01 1.38011719e+02 -1.82500000e+02]
[ 4.61718750e+01 1.00207500e+20 1.82386719e+02 1.38000000e+02]
[ -1.37996094e+02 -1.82582031e+02 1.00207500e+20 4.61250000e+01]
[ 1.82500000e+02 -1.38000000e+02 -4.61250000e+01 1.00207500e+20]]
所以,Octave和numpy都提供了相同的答案,但它与Matlab的非常不同。第一点是V1!= V2,这似乎不对。另一点是,尽管非对角线元素比对角线元素小许多个数量级,但这似乎在我的算法中引起了一些问题。
有没有人知道numpy和Octave这样的行为?
答案 0 :(得分:6)
他们在内部使用双精度,只有大约15位数的精度。您的数学运算可能会超出此范围,从而导致数学上错误的结果。
值得一读:http://floating-point-gui.de/
编辑:从docs我收集到Numpy没有更高的精确度。似乎SymPy虽然may give you the needed precision - 如果该库也适合您。
答案 1 :(得分:4)
无论它值多少,我在64位系统上得到与matlab相同的结果:
[[ 1.00207500e+20 0.00000000e+00 -8.00000000e+00 0.00000000e+00]
[ 0.00000000e+00 1.00207500e+20 0.00000000e+00 0.00000000e+00]
[ -8.00000000e+00 0.00000000e+00 1.00207500e+20 0.00000000e+00]
[ 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00207500e+20]]
[[ 1.00207500e+20 0.00000000e+00 -8.00000000e+00 0.00000000e+00]
[ 0.00000000e+00 1.00207500e+20 0.00000000e+00 0.00000000e+00]
[ -8.00000000e+00 0.00000000e+00 1.00207500e+20 0.00000000e+00]
[ 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00207500e+20]]
[[ 1.00207500e+20 0.00000000e+00 -8.00000000e+00 0.00000000e+00]
如果您使用的是32位系统(或者您在64位系统上安装了32位版本的python和numpy),您将遇到精确问题,并得到不同的答案,如上所述由@Lucero在下面。在这种情况下,您可能会尝试显式指定64位浮点数(但操作会更慢)。例如,尝试使用np.array(..., dtype=np.float64)
。
如果您认为需要额外的精度,可以使用np.longdouble
(在64位系统上与np.float128
相同,在32位上使用np.float96
),但这可能不是在所有平台上都支持,许多线性代数函数会将事物截断回本机精度。
另外,您使用的BLAS库是什么? numpy和octave结果可能是相同的,因为它们使用相同的BLAS库。
最后,您可以将numpy代码简化为:
import numpy as np
A = np.array([[1, -0.015, -0.025, -0.035],
[0.015, 1, 0.035, -0.025],
[0.025, -0.035, 1, 0.015],
[0.035, 0.025, -0.015, 1]])
P = np.eye(4)*1e20
print A.dot(P.dot(A.T))
print A.dot(P).dot(A.T)
答案 2 :(得分:0)
np.einsum
更接近MATLAB
In [1299]: print(np.einsum('ij,jk,lk',A,P,A))
[[ 1.00207500e+20 0.00000000e+00 -5.07812500e-02 0.00000000e+00]
[ 0.00000000e+00 1.00207500e+20 5.46875000e-02 0.00000000e+00]
[ -5.46875000e-02 5.46875000e-02 1.00207500e+20 0.00000000e+00]
[ 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00207500e+20]]
行和列2中的非对角线项不同,但在其他地方具有相同的0。
使用双点,P.dot(A.T)
在添加产品时会产生舍入错误。那些传播到下一个dot
。 einsum
生成所有产品,只需一次总结。我怀疑MATLAB解释器能够识别这种情况,并执行一个特殊的计算,旨在最大限度地减少舍入误差。
Numpy Matrix Multiplication U*B*U.T Results in Non-symmetric Matrix - 是一个更近期的问题,也有相同的解释。