scipy.sparse.linalg.

svds#

scipy.sparse.linalg.svds(A, k=6, ncv=None, tol=0, which='LM', v0=None, maxiter=None, return_singular_vectors=True, solver='arpack', random_state=None, options=None)[源代码][源代码]#

稀疏矩阵的部分奇异值分解。

计算稀疏矩阵 A 的最大或最小的 k 个奇异值及其对应的奇异向量。返回的奇异值的顺序不保证。

在下面的描述中,设 M, N = A.shape

参数:
Andarray、稀疏矩阵或线性算子

要分解的浮点数值类型的矩阵。

kint, 默认值: 6

要计算的奇异值和奇异向量的数量。必须满足 1 <= k <= kmax,其中对于 solver='propack'kmax=min(M, N),否则 kmax=min(M, N) - 1

ncvint, 可选

solver='arpack' 时,这是生成的Lanczos向量的数量。详情请参见 ‘arpack’ 。当 solver='lobpcg'solver='propack' 时,此参数被忽略。

tolfloat, 可选

奇异值的容差。零(默认)表示机器精度。

哪个{‘LM’, ‘SM’}

要找到的 k 个奇异值:要么是最大幅值(’LM’)要么是最小幅值(’SM’)的奇异值。

v0ndarray,可选

迭代起始向量;参见方法特定的文档(’arpack’’lobpcg’),或 ‘propack’ 了解详情。

maxiterint, 可选

最大迭代次数;参见特定方法的文档(’arpack’’lobpcg’),或 ‘propack’ 了解详情。

return_singular_vectors{True, False, “u”, “vh”}

奇异值总是被计算并返回;此参数控制奇异向量的计算和返回。

  • True: 返回单一向量。

  • False: 不返回奇异向量。

  • "u": 如果 M <= N,则只计算左奇异向量,并为右奇异向量返回 None。否则,计算所有奇异向量。

  • "vh": 如果 M > N,则只计算右奇异向量,并为左奇异向量返回 None。否则,计算所有奇异向量。

如果 solver='propack',无论矩阵形状如何,该选项都会被尊重。

求解器{‘arpack’, ‘propack’, ‘lobpcg’}, 可选

使用的求解器。支持 ‘arpack’’lobpcg’‘propack’。默认值:’arpack’

random_state{None, int,}

用于生成重采样的伪随机数生成器状态。

如果 random_stateNone (或 np.random),则使用 numpy.random.RandomState 单例。如果 random_state 是整数,则使用一个新的 RandomState 实例,并以 random_state 为种子。如果 random_state 已经是 GeneratorRandomState 实例,则使用该实例。

选项dict, 可选

一个包含特定求解器选项的字典。目前不支持任何特定求解器的选项;此参数保留供将来使用。

返回:
undarray, 形状=(M, k)

具有左奇异向量作为列的酉矩阵。

sndarray, 形状=(k,)

奇异值。

vhndarray, 形状=(k, N)

具有右奇异向量作为行的酉矩阵。

注释

这是一个使用 ARPACK 或 LOBPCG 作为特征求解器的简单实现,针对矩阵 A.conj().T @ AA @ A.conj().T,取决于哪个矩阵尺寸更小,随后使用 Rayleigh-Ritz 方法进行后处理;参见 Rayleigh-Ritz 方法中的使用正规矩阵,(2022年11月19日),维基百科,https://w.wiki/4zms

或者,可以调用 PROPACK 求解器。

输入矩阵 A 的数值数据类型选择可能受限。只有 solver="lobpcg" 支持所有浮点数据类型,包括实数:’np.float32’, ‘np.float64’, ‘np.longdouble’ 和复数:’np.complex64’, ‘np.complex128’, ‘np.clongdouble’。而 solver="arpack" 仅支持 ‘np.float32’, ‘np.float64’ 和 ‘np.complex128’。

示例

从奇异值和向量构造矩阵 A

>>> import numpy as np
>>> from scipy import sparse, linalg, stats
>>> from scipy.sparse.linalg import svds, aslinearoperator, LinearOperator

从奇异值和向量构建一个稠密矩阵 A

>>> rng = np.random.default_rng()
>>> orthogonal = stats.ortho_group.rvs(10, random_state=rng)
>>> s = [1e-3, 1, 2, 3, 4]  # non-zero singular values
>>> u = orthogonal[:, :5]         # left singular vectors
>>> vT = orthogonal[:, 5:].T      # right singular vectors
>>> A = u @ np.diag(s) @ vT

仅用四个奇异值/向量,SVD 就能近似原始矩阵。

>>> u4, s4, vT4 = svds(A, k=4)
>>> A4 = u4 @ np.diag(s4) @ vT4
>>> np.allclose(A4, A, atol=1e-3)
True

通过所有五个非零奇异值/向量,我们可以更准确地重现原始矩阵。

>>> u5, s5, vT5 = svds(A, k=5)
>>> A5 = u5 @ np.diag(s5) @ vT5
>>> np.allclose(A5, A)
True

奇异值与预期的奇异值相匹配。

>>> np.allclose(s5, s)
True

由于在这个例子中奇异值彼此不接近,每个奇异向量都如预期般匹配,仅在符号上有所不同。

>>> (np.allclose(np.abs(u5), np.abs(u)) and
...  np.allclose(np.abs(vT5), np.abs(vT)))
True

奇异向量也是正交的。

>>> (np.allclose(u5.T @ u5, np.eye(5)) and
...  np.allclose(vT5 @ vT5.T, np.eye(5)))
True

如果存在(几乎)多个单值,相应的单个奇异向量可能是不稳定的,但包含所有这些奇异向量的整个不变子空间可以通过 ‘subspace_angles’ 计算得到,并且可以通过子空间之间的角度来测量其准确性。

>>> rng = np.random.default_rng()
>>> s = [1, 1 + 1e-6]  # non-zero singular values
>>> u, _ = np.linalg.qr(rng.standard_normal((99, 2)))
>>> v, _ = np.linalg.qr(rng.standard_normal((99, 2)))
>>> vT = v.T
>>> A = u @ np.diag(s) @ vT
>>> A = A.astype(np.float32)
>>> u2, s2, vT2 = svds(A, k=2, random_state=rng)
>>> np.allclose(s2, s)
True

单个精确和计算奇异向量之间的角度可能不会那么小。要检查使用:

>>> (linalg.subspace_angles(u2[:, :1], u[:, :1]) +
...  linalg.subspace_angles(u2[:, 1:], u[:, 1:]))
array([0.06562513])  # may vary
>>> (linalg.subspace_angles(vT2[:1, :].T, vT[:1, :].T) +
...  linalg.subspace_angles(vT2[1:, :].T, vT[1:, :].T))
array([0.06562507])  # may vary

与这些向量所跨越的二维不变子空间之间的角度相反,对于右奇异向量来说,这些角度很小。

>>> linalg.subspace_angles(u2, u).sum() < 1e-6
True

以及用于左奇异向量。

>>> linalg.subspace_angles(vT2.T, vT.T).sum() < 1e-6
True

下一个示例遵循 ‘sklearn.decomposition.TruncatedSVD’ 的方式。

>>> rng = np.random.RandomState(0)
>>> X_dense = rng.random(size=(100, 100))
>>> X_dense[:, 2 * np.arange(50)] = 0
>>> X = sparse.csr_matrix(X_dense)
>>> _, singular_values, _ = svds(X, k=5, random_state=rng)
>>> print(singular_values)
[ 4.3293...  4.4491...  4.5420...  4.5987... 35.2410...]

该函数可以在不显式构造输入矩阵转置的情况下被调用。

>>> rng = np.random.default_rng()
>>> G = sparse.rand(8, 9, density=0.5, random_state=rng)
>>> Glo = aslinearoperator(G)
>>> _, singular_values_svds, _ = svds(Glo, k=5, random_state=rng)
>>> _, singular_values_svd, _ = linalg.svd(G.toarray())
>>> np.allclose(singular_values_svds, singular_values_svd[-4::-1])
True

最节省内存的情况是既不显式构造原始矩阵,也不显式构造其转置。我们的示例计算了由numpy函数’np.diff’构造的’LinearOperator’的最小奇异值和向量,该函数按列使用以与’LinearOperator’对列的操作保持一致。

>>> diff0 = lambda a: np.diff(a, axis=0)

让我们从 ‘diff0’ 创建矩阵,仅用于验证。

>>> n = 5  # The dimension of the space.
>>> M_from_diff0 = diff0(np.eye(n))
>>> print(M_from_diff0.astype(int))
[[-1  1  0  0  0]
 [ 0 -1  1  0  0]
 [ 0  0 -1  1  0]
 [ 0  0  0 -1  1]]

矩阵 ‘M_from_diff0’ 是双对角的,可以通过其他方式直接创建。

>>> M = - np.eye(n - 1, n, dtype=int)
>>> np.fill_diagonal(M[:,1:], 1)
>>> np.allclose(M, M_from_diff0)
True

它的转置

>>> print(M.T)
[[-1  0  0  0]
 [ 1 -1  0  0]
 [ 0  1 -1  0]
 [ 0  0  1 -1]
 [ 0  0  0  1]]

可以被视为关联矩阵;参见关联矩阵,(2022年11月19日),维基百科,https://w.wiki/5YXU,一个具有5个顶点和4条边的线性图。因此,5x5的正规矩阵 M.T @ M

>>> print(M.T @ M)
[[ 1 -1  0  0  0]
 [-1  2 -1  0  0]
 [ 0 -1  2 -1  0]
 [ 0  0 -1  2 -1]
 [ 0  0  0 -1  1]]

图拉普拉斯算子,而在 ‘svds’ 中实际使用的是较小尺寸的 4x4 正规矩阵 M @ M.T

>>> print(M @ M.T)
[[ 2 -1  0  0]
 [-1  2 -1  0]
 [ 0 -1  2 -1]
 [ 0  0 -1  2]]

这就是所谓的基于边的拉普拉斯算子;参见拉普拉斯矩阵中的通过关联矩阵的对称拉普拉斯算子,(2022年11月19日),维基百科,https://w.wiki/5YXW

LinearOperator 的设置需要矩阵转置 M.T 的乘法选项 ‘rmatvec’ 和 ‘rmatmat’,但我们希望节省内存而不使用矩阵,因此了解 M.T 的结构后,我们手动构建了以下函数用于 rmatmat=diff0t

>>> def diff0t(a):
...     if a.ndim == 1:
...         a = a[:,np.newaxis]  # Turn 1D into 2D array
...     d = np.zeros((a.shape[0] + 1, a.shape[1]), dtype=a.dtype)
...     d[0, :] = - a[0, :]
...     d[1:-1, :] = a[0:-1, :] - a[1:, :]
...     d[-1, :] = a[-1, :]
...     return d

我们检查我们的矩阵转置函数 ‘diff0t’ 是否有效。

>>> np.allclose(M.T, diff0t(np.eye(n-1)))
True

现在我们设置我们的无矩阵 ‘LinearOperator’ 称为 ‘diff0_func_aslo’,以及用于验证的基于矩阵的 ‘diff0_matrix_aslo’。

>>> def diff0_func_aslo_def(n):
...     return LinearOperator(matvec=diff0,
...                           matmat=diff0,
...                           rmatvec=diff0t,
...                           rmatmat=diff0t,
...                           shape=(n - 1, n))
>>> diff0_func_aslo = diff0_func_aslo_def(n)
>>> diff0_matrix_aslo = aslinearoperator(M_from_diff0)

并在 ‘LinearOperator’ 中验证矩阵及其转置。

>>> np.allclose(diff0_func_aslo(np.eye(n)),
...             diff0_matrix_aslo(np.eye(n)))
True
>>> np.allclose(diff0_func_aslo.T(np.eye(n-1)),
...             diff0_matrix_aslo.T(np.eye(n-1)))
True

在验证了 ‘LinearOperator’ 设置后,我们运行求解器。

>>> n = 100
>>> diff0_func_aslo = diff0_func_aslo_def(n)
>>> u, s, vT = svds(diff0_func_aslo, k=3, which='SM')

奇异值的平方和奇异向量是已知的;参见纯狄利克雷边界条件,在特征值和特征向量中的二阶导数,(2022年11月19日),维基百科,https://w.wiki/5YX6,因为‘diff’对应于一阶导数,其较小的n-1 x n-1正规矩阵 M @ M.T 表示具有狄利克雷边界条件的离散二阶导数。我们使用这些解析表达式进行验证。

>>> se = 2. * np.sin(np.pi * np.arange(1, 4) / (2. * n))
>>> ue = np.sqrt(2 / n) * np.sin(np.pi * np.outer(np.arange(1, n),
...                              np.arange(1, 4)) / n)
>>> np.allclose(s, se, atol=1e-3)
True
>>> print(np.allclose(np.abs(u), np.abs(ue), atol=1e-6))
True