NumPy任意精度线性代数

21
我有一个numpy 2d数组[中/大型-比如500x500]。我想找到它的按元素指数函数的特征值。问题在于,一些值非常负(-800、-1000等),而它们的指数会下溢(这意味着它们非常接近于零,以至于numpy将它们视为零)。有没有办法在numpy中使用任意精度?
我梦想的方式:
import numpy as np

np.set_precision('arbitrary') # <--- Missing part
a = np.array([[-800.21,-600.00],[-600.00,-1000.48]])
ex = np.exp(a)  ## Currently warns about underflow
eigvals, eigvecs = np.linalg.eig(ex)

我已经使用gmpy和mpmath搜索了解决方案,但没有结果。欢迎任何建议。


3
我会寻找一个不涉及指数运算的解决方案。 - David Heffernan
1
你能详细说明一下吗?您的语句“ex = np.exp(a)”似乎超出了上下文,因为“eigvals,eigvecs = np.linalg.eig(a)”给出了完全合理的答案。但是,“ex”应该被视为一个2x2矩阵,其中所有元素都等于零。谢谢。 - eat
2
现在,如果一个矩阵只有零元素,你会期望它的特征值和特征向量是什么?谢谢。 - eat
2
重新调整问题的范围?对于几乎所有实际目的而言,exp(-1000)等于零。 - tillsten
http://www.cs.cornell.edu/cv/researchpdf/19ways+.pdf 提供了19种不同的计算矩阵指数的方法。 - tillsten
显示剩余3条评论
5个回答

20

SymPy可以进行任意精度计算:

from sympy import exp, N, S
from sympy.matrices import Matrix

data = [[S("-800.21"),S("-600.00")],[S("-600.00"),S("-1000.48")]]
m = Matrix(data)
ex = m.applyfunc(exp).applyfunc(lambda x:N(x, 100))
vecs = ex.eigenvects()
print vecs[0][0] # eigen value
print vecs[1][0] # eigen value
print vecs[0][2] # eigen vect
print vecs[1][2] # eigen vect

输出:

-2.650396553004310816338679447269582701529092549943247237903254759946483528035516341807463648841185335e-261
2.650396553004310816338679447269582701529092549943247237903254759946483528035516341807466621962539464e-261
[[-0.9999999999999999999999999999999999999999999999999999999999999999999999999999999999999994391176386872]
[                                                                                                      1]]
[[1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000560882361313]
[                                                                                                    1]]

你可以将N(x, 100)中的100更改为其他精度,但是当我尝试使用1000时,计算特征向量失败了。


6
顺便说一下,SymPy使用mpmath来处理任意精度浮点数。 - asmeurer
SymPy是解决许多数学问题的理想选择。同时,如果您需要任意精度的整数(int),在进行十几位数字的简单矩阵乘法时不会溢出,您可以使用dtype=object。当然,这对于正在处理任意精度的浮点数(float)的OP没有帮助。 - Tomasz Gandor

14
在64位系统上有一个numpy.float128数据类型。(我相信在32位系统上有一个float96数据类型)。虽然numpy.linalg.eig不支持128位浮点数,但是scipy.linalg.eig在某种程度上支持。
然而,从长远来看,所有这些都不重要。任何一般的特征值问题求解器都将是迭代的,而不是精确的,因此保留额外的精度并不能获得任何好处!np.linalg.eig适用于任何形状,但从不返回精确的解决方案。
如果您总是在解决2x2矩阵,则编写自己的求解器应该更加准确。我将在最后展示一个例子...
无论如何,继续前进到毫无意义的精确内存容器:
import numpy as np
import scipy as sp
import scipy.linalg

a = np.array([[-800.21,-600.00],[-600.00,-1000.48]], dtype=np.float128)
ex = np.exp(a)
print ex

eigvals, eigvecs = sp.linalg.eig(ex)

# And to test...
check1 = ex.dot(eigvecs[:,0])
check2 = eigvals[0] * eigvecs[:,0]
print 'Checking accuracy..'
print check1, check2
print (check1 - check2).dot(check1 - check2), '<-- Should be zero'

然而,你会发现得到的结果与仅执行np.linalg.eig(ex.astype(np.float64)完全相同。事实上,我相当肯定这就是scipy正在做的,而numpy则会引发错误而不是默默地处理它。虽然我可能非常错误...

如果您不想使用scipy,则一种解决方法是在指数运算之后但求解特征值之前重新调整事物的比例,将它们转换为“普通”浮点数,求解特征值,然后在之后将事物重新转换为float128并进行重新缩放。

例如:

import numpy as np

a = np.array([[-800.21,-600.00],[-600.00,-1000.48]], dtype=np.float128)
ex = np.exp(a)
factor = 1e300
ex_rescaled = (ex * factor).astype(np.float64)

eigvals, eigvecs = np.linalg.eig(ex_rescaled)
eigvals = eigvals.astype(np.float128) / factor

# And to test...
check1 = ex.dot(eigvecs[:,0])
check2 = eigvals[0] * eigvecs[:,0]
print 'Checking accuracy..'
print check1, check2
print (check1 - check2).dot(check1 - check2), '<-- Should be zero'

最后,如果你只需要解决 2x2 或 3x3 矩阵,你可以编写自己的求解器,它 将会 返回这些矩阵的精确值。

import numpy as np

def quadratic(a,b,c):
    sqrt_part = np.lib.scimath.sqrt(b**2 - 4*a*c)
    root1 = (-b + sqrt_part) / (2 * a)
    root2 = (-b - sqrt_part) / (2 * a)
    return root1, root2

def eigvals(matrix_2x2):
    vals = np.zeros(2, dtype=matrix_2x2.dtype)
    a,b,c,d = matrix_2x2.flatten()
    vals[:] = quadratic(1.0, -(a+d), (a*d-b*c))
    return vals

def eigvecs(matrix_2x2, vals):
    a,b,c,d = matrix_2x2.flatten()
    vecs = np.zeros_like(matrix_2x2)
    if (b == 0.0) and (c == 0.0):
        vecs[0,0], vecs[1,1] = 1.0, 1.0
    elif c != 0.0:
        vecs[0,:] = vals - d
        vecs[1,:] = c
    elif b != 0:
        vecs[0,:] = b
        vecs[1,:] = vals - a
    return vecs

def eig_2x2(matrix_2x2):
    vals = eigvals(matrix_2x2)
    vecs = eigvecs(matrix_2x2, vals)
    return vals, vecs

a = np.array([[-800.21,-600.00],[-600.00,-1000.48]], dtype=np.float128)
ex = np.exp(a)
eigvals, eigvecs =  eig_2x2(ex) 

# And to test...
check1 = ex.dot(eigvecs[:,0])
check2 = eigvals[0] * eigvecs[:,0]
print 'Checking accuracy..'
print check1, check2
print (check1 - check2).dot(check1 - check2), '<-- Should be zero'

这个方法可以返回一个完全精确的解,但只适用于2x2矩阵。然而,它是唯一从额外精度中获益的方法!


我认为你还需要对 c != 0 and b != 0 进行特殊处理,以确定正确的特征值,或者我有什么遗漏吗? - BR123

8
据我所知,numpy不支持高于双精度(float64)的精度,如果未指定,则默认为双精度。你可以尝试使用这个库:http://code.google.com/p/mpmath/,它有以下特性之一:

算术运算:

  • 任意精度的实数和复数
  • 无限制的指数大小/数量级

这绝对是迄今为止最有趣的答案,但我在mpmath中找不到特征值求解器。你知道它是否已经包含其中一个,还是我必须自己构建它? - jarondl
是的,我也没有找到。我认为numpy本身也无法帮助你,在与mpmath或bc(Spike的答案)结合使用时也是如此,因此您需要绕过它以获得结果。您可以查看numpy源代码以了解linalg.eig()的外观。在您的程序中实现它可能不太困难。 - milancurcic
1
有一个 numpy.float128,不过它的价值如何还需商榷。我认为它不被 numpy.linalg 支持,但对于基本操作是支持的。 - Joe Kington
1
@milancurcic - 就算是值得一提的,numpy.linalg.eig 也会调用 LAPACK 程序库。(这就是为什么 numpy.linalg 不支持 128 位精度的原因)。无论如何,查看 numpy.linalg.eig 的源代码都不太可能帮助您实现基本的特征值求解器。 - Joe Kington
@Joe Kington - 这是新版本的numpy(1.6.*)有numpy.float128吗?我所拥有的版本(1.5.1)没有这种数据类型。 - milancurcic
2
@milancurcic - 不,numpy已经有float128很长一段时间了(至少从1.1开始,我相信在那之前很长一段时间就已经存在了)。但是,它只存在于64位系统上。否则,在32位系统上可能有一个float96?不过,我对float96部分可能非常错误... - Joe Kington

-3

所以你需要350位的精度。使用IEEE浮点数(numpy正在使用的)无法达到这个要求。但是你可以使用bc程序来实现:

$ bc -l
bc 1.06
Copyright 1991-1994, 1997, 1998, 2000 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
scale=350
e(-800)
.<hundreds of zeros>00366

-4

我对numpy没有特别的经验,但是添加一定数量的零来表示小数点可能会有所帮助。例如,使用1.0000代替1。在我遇到这个问题的普通python脚本中,这种方法很有效,所以除非你的问题是由于numpy的奇怪性质而与python无关,否则这应该会有所帮助。

祝你好运!


1
谢谢@Sinthet,但那并没有解决问题。这些数字已经是浮点数了,只是exp(-800)大约是3.7E-348,这比numpy(或python)的精度要小。 - jarondl

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