我怎样才能获得与Matlab的'A \ b`(mldivide)算子使用numpy / scipy返回的欠定线性系统相同的“特殊”解?

时间:2015-11-09 17:07:58

标签: python matlab numpy linear-algebra

我找到了一个link,其中显示了一个示例,当线性方程组具有无限多个解时,Matlab mldivide运算符(\)给出“特殊”解。

例如:

A = [1 2 0; 0 4 3];
b = [8; 18];
c_mldivide = A \ b
c_pinv = pinv(A) * b

给出输出:

c_mldivide =
                 0
                 4
  0.66666666666667


c_pinv =

  0.918032786885245
  3.54098360655738
  1.27868852459016

解决方案是“特殊的”,因为解决方案c_mldivide中的非零条目数等于rank(A)(在本例中为2)。我使用numpy.linalg.lstsq在numpy中尝试了同样的事情,它给c_pinv提供了相同的结果。

有没有办法在Python中实现c_mldivide解决方案?

还有另一个非常相似的问题here,但我想对“特殊”一词的解释不够明确。 Another question询问mldivide运营商的内部运作情况,但接受的答案似乎并未解决此问题。

编辑1:numpy代码

In [149]: test_A = np.array([[1,2,0],[0,4,3]])
          test_b = np.array([[8],[18]])
          np.linalg.lstsq(test_A,test_b)

Out[149]:
(array([[ 0.918 ],
    [ 3.541 ],
    [ 1.2787]]), array([], dtype=float64), 2, array([ 5.2732,  1.4811]))

编辑2:使用scipy.optimize.nnls

In[189]:
from scipy.optimize import nnls
nnls(test_A,test_b)
Out[190]:
    ValueError                                Traceback (most recent call last)
<ipython-input-165-19ed603bd86c> in <module>()
      1 from scipy.optimize import nnls
      2 
----> 3 nnls(test_A,test_b)

C:\Users\abhishek\Anaconda\lib\site-packages\scipy\optimize\nnls.py in nnls(A, b)
     43         raise ValueError("expected matrix")
     44     if len(b.shape) != 1:
---> 45         raise ValueError("expected vector")
     46 
     47     m, n = A.shape

    ValueError: expected vector

2 个答案:

答案 0 :(得分:4)

(2,1) 

(2,)

#!/usr/bin/env python3
import numpy as np
from scipy.optimize import nnls
test_A = np.array([[1,2,0],[0,4,3]])

try:
    test_b = np.array([[8],[18]]) # wrong
    print(nnls(test_A,test_b))
except Exception as e:
    print(str(e))

test_b = np.array([8,18]) # sic!
print(nnls(test_A,test_b))

但你想要

expected vector
(array([ 0.        ,  4.        ,  0.66666667]), 0.0)

输出:

JFileChooser

答案 1 :(得分:4)

非负最小二乘(scipy.optimize.nnls是此问题的一般解决方案。如果所有可能的解决方案都包含负系数,那么它将失败的一个小问题是:

import numpy as np
from scipy.optimize import nnls

A = np.array([[1, 2, 0],
              [0, 4, 3]])
b = np.array([-1, -2])

print(nnls(A, b))
# (array([ 0.,  0.,  0.]), 2.23606797749979)

如果 A·x = b 未定,

x1, res, rnk, s = np.linalg.lstsq(A, b)

将选择 x' 的解决方案,最小化 || x || L2 || A·x - b || L2 = 0 。这不是我们正在寻找的特定解决方案,但我们可以线性转换它以获得我们想要的。为此,我们首先计算 A right null space,其中包含 A的所有可能解决方案的空间·x = b 。我们可以使用rank-revealing QR decomposition

来获取此信息
from scipy.linalg import qr

def qr_null(A, tol=None):
    Q, R, P = qr(A.T, mode='full', pivoting=True)
    tol = np.finfo(R.dtype).eps if tol is None else tol
    rnk = min(A.shape) - np.abs(np.diag(R))[::-1].searchsorted(tol)
    return Q[:, rnk:].conj()

Z = qr_null(A)

Z 是一个向量(或者,如果 n - rnk( A )> 1 ,一组跨越 A 子空间的基础向量,使 A·Z = <强> 0

print(A.dot(Z))
# [[  0.00000000e+00]
#  [  8.88178420e-16]]

换句话说, Z 的列是与 A 中的所有行正交的向量即可。这意味着对于任何解决方案 x' A·x = b ,然后 x' = x + Z ·c 也必须是任意比例因子 c 的解决方案。这意味着通过选择适当的 c 值,我们可以将解决方案中系数的任何 n - rnk( A 设置为零

例如,假设我们想要将最后一个系数的值设置为零:

c = -x1[-1] / Z[-1, 0]
x2 = x1 + Z * c
print(x2)
# [ -8.32667268e-17  -5.00000000e-01   0.00000000e+00]
print(A.dot(x2))
# [-1. -2.]

n - rnk( A )≤1的一般情况稍微复杂一点:

A = np.array([[1, 4, 9, 6, 9, 2, 7],
              [6, 3, 8, 5, 2, 7, 6],
              [7, 4, 5, 7, 6, 3, 2],
              [5, 2, 7, 4, 7, 5, 4],
              [9, 3, 8, 6, 7, 3, 1]])
x_exact = np.array([ 1,  2, -1, -2,  5,  0,  0])
b = A.dot(x_exact)
print(b)
# [33,  4, 26, 29, 30]

我们像以前一样获得 x' Z

x1, res, rnk, s = np.linalg.lstsq(A, b)
Z = qr_null(A)

现在为了最大化解向量中零值系数的数量,我们希望找到一个向量 C ,这样

  

x' = x + Z·C = [x' 0 ,x' 1 ,...,x' rnk(A)-1 ,0,...,0] T

如果 x' 中的最后一个 n - rnk( A 系数为0,则为强加于那个

  

Z {rnk(A),...,n} ·C = - x {rnk(A),...中,n}

我们可以解决 C (确切地说,因为我们知道Z[rnk:]必须是满级的):

C = np.linalg.solve(Z[rnk:], -x1[rnk:])

并计算 x'

x2 = x1 + Z.dot(C)
print(x2)
# [  1.00000000e+00   2.00000000e+00  -1.00000000e+00  -2.00000000e+00
#    5.00000000e+00   5.55111512e-17   0.00000000e+00]
print(A.dot(x2))
# [ 33.   4.  26.  29.  30.]

将所有内容组合成一个函数:

import numpy as np
from scipy.linalg import qr

def solve_minnonzero(A, b):
    x1, res, rnk, s = np.linalg.lstsq(A, b)
    if rnk == A.shape[1]:
        return x1   # nothing more to do if A is full-rank
    Q, R, P = qr(A.T, mode='full', pivoting=True)
    Z = Q[:, rnk:].conj()
    C = np.linalg.solve(Z[rnk:], -x1[rnk:])
    return x1 + Z.dot(C)