使用 SVD 计算旋转



Background

我正在从事一个涉及求解大型未定方程组的项目。

我目前的算法计算代表给定系统的矩阵的SVD(numpy.linalg.svd),然后使用其结果来计算摩尔-彭罗斯伪逆和矩阵的右零空间。我使用空空间来查找具有唯一解决方案的所有变量,并使用伪逆空间来找出它的值。

但是,MPP(摩尔彭罗斯伪逆)非常密集,对于我的服务器来说有点太大了。

问题

我找到了以下论文,其中详细介绍了一个稀疏伪逆,它保持了MPP的大部分基本属性。这显然是我非常感兴趣的,但我根本没有数学背景来理解他是如何计算伪逆的。是否可以使用 SVD 进行计算?如果没有,最好的方法是什么?

这些是我认为可能相关的论文中的几行,但我不够过时,无法理解

  • spinv(A) = arg min ||B||受制于 BA = 在 其中 ||B||表示 B 的条目 l1 范数

  • 这通常是一个难以处理的问题,因此我们使用标准线性松弛与 l1 范数

  • sspinv(A) = ητ {[spinv(A)]}, with ητ (u) = u1|u|≥τ

编辑

在此处查找我的代码和有关实际实现的更多详细信息

据我了解,以下是论文对稀疏伪逆的描述:

它说

我们的目标是最小化spinv(A)中的非零数量

这意味着你应该采用 L0 范数(参见 David Donoho 的定义:非零条目的数量),这使得问题变得棘手。

spinv(A) = argmin ||B||_0 subject to B.A = I

因此,他们转向凸松弛这个问题,以便可以通过线性规划来解决。

这通常是一个无法处理的问题,因此我们使用标准 线性松弛与'1范数。

那么放松的问题是

spinv(A) = argmin ||B||_1 subject to B.A = I (6)

这有时被称为基追求,并且倾向于产生稀疏解(参见Boyd和Vandenberghe的凸优化,第6.2节最小范数问题)。

所以,解决这个松弛的问题。

线性规划(6)是可分的,可以通过计算一个来解决 一次 B 行

因此,您可以解决以下形式的一系列问题以获得解决方案。

spinv(A)_i = argmin ||B_i||_1 subject to B_i.A = I_i

其中_i表示矩阵的第 i 行。

请参阅此处了解如何将此绝对值问题转换为线性程序。

在下面的代码中,我稍微将问题更改为spinv(A)_i = argmin ||B_i||_1 subject to A.B_i = I_i其中_i是矩阵的第 i 列,因此问题变得spinv(A) = argmin ||B||_1 subject to A.B = I.老实说,我不知道两者之间是否有区别。在这里,我使用的是 scipy 的linprog单纯形方法。我不知道单纯形的内部结构是否使用 SVD。

import numpy as np
from scipy import optimize
# argmin ||B_i||_1 stubect to A.B_i = I_i, where _i is the ith column
# let B_i = u_i - v_i where u_i >= 0 and v_i >= 0
# then ||B_i||_1 = [1' 1'][u_i;v_i] which is the objective function
# and A.B_i = I_i becomes
# A.[u_i - v_i] = I_i
# [A -A][u_i;v_i] = I_i which is the equality constraint
# and [u_i;v_i] >= 0 the bounds
# here A is n x m (as opposed to m x n in paper)
A = np.random.randn(4, 6)
n, m = A.shape
I = np.eye(n)
Aeq = np.hstack((A, -A))
# objective
c = np.ones((2*m))
# spinv
B = np.zeros((m, n))
for i in range(n):
beq = I[:, i]
result = optimize.linprog(c, A_eq=Aeq, b_eq=beq)
x = result.x[0:m]-result.x[m:2*m]
B[:, i] = x
print('spinv(A) = n' + str(B))
print('pinv(A) = n' + str(np.linalg.pinv(A)))
print('A.B = n' + str(np.dot(A, B)))

下面是一个输出。spinv(A)pinv(A)稀疏。

spinv(A) = 
[[ 0.         -0.33361925  0.          0.        ]
[ 0.04987467  0.          0.12741509  0.02897778]
[ 0.          0.         -0.52306324  0.        ]
[ 0.43848257  0.12114828  0.15678815 -0.19302049]
[-0.16814546  0.02911103 -0.41089271  0.50785258]
[-0.05696924  0.13391736  0.         -0.43858428]]
pinv(A) = 
[[ 0.05626402 -0.1478497   0.19953692 -0.19719524]
[ 0.04007696 -0.07330993  0.19903311  0.14704798]
[ 0.01177361 -0.05761487 -0.23074996  0.15597663]
[ 0.44471989  0.13849828  0.18733242 -0.20824972]
[-0.1273604   0.15615595 -0.24647117  0.38047901]
[-0.04638221  0.09879972  0.21951122 -0.33244635]]
A.B = 
[[ 1.00000000e+00 -1.82225048e-17  6.73349443e-18 -2.39383542e-17]
[-5.20584593e-18  1.00000000e+00 -3.70118759e-16 -1.62063433e-15]
[-8.83342417e-18 -5.80049814e-16  1.00000000e+00  3.56175852e-15]
[ 2.31629738e-17 -1.13459832e-15 -2.28503999e-16  1.00000000e+00]]

为了进一步稀疏化矩阵,我们可以逐个硬应用 阈值,从而牺牲反相特性并计算 近似稀疏伪逆

如果您不想在稀疏 pinv 中保留小条目,可以像这样删除它们:

Bt = B.copy()
Bt[np.abs(Bt) < 0.1] = 0
print('sspinv_0.1(A) = n' + str(Bt))
print('A.Bt = n' + str(np.dot(A, Bt)))

要得到

sspinv_0.1(A) = 
[[ 0.         -0.33361925  0.          0.        ]
[ 0.          0.          0.12741509  0.        ]
[ 0.          0.         -0.52306324  0.        ]
[ 0.43848257  0.12114828  0.15678815 -0.19302049]
[-0.16814546  0.         -0.41089271  0.50785258]
[ 0.          0.13391736  0.         -0.43858428]]
A.Bt = 
[[ 9.22717491e-01  1.17555372e-02  6.73349443e-18 -1.10993934e-03]
[ 1.24361576e-01  9.41538212e-01 -3.70118759e-16  1.15028494e-02]
[-8.76662313e-02 -1.36349311e-02  1.00000000e+00 -7.48302663e-02]
[-1.54387852e-01 -3.27969169e-02 -2.28503999e-16  9.39161039e-01]]

希望我回答了您的问题,如果您需要更多详细信息,请提供足够的参考资料。如果您有任何问题,请告诉我。我不是专家,所以如果你对我的主张有任何疑问,你可以随时询问数学堆栈交换专家(当然没有任何代码),请告诉我。

这是一个有趣的问题。它使我能够复习我的线性代数和我知道的很少的优化,所以谢谢你:)

最新更新