MATLAB 中的增量 SVD 实现

计算科学 线性代数 matlab svd
2021-12-12 09:25:34

是否有任何库/工具箱在 MATLAB 中实现了增量 SVD。我已经实现了这篇论文,它很快但效果不佳。我试过这个,但在这个错误中也传播得很快(在更新 5-10 点内错误很高)。

4个回答

是的。Christopher Baker 在名为 IncPACK 的 MATLAB 包中实现了他的增量 SVD 方法(存档在 GitHub 上的 imtsl 项目中)。它实现了他的硕士论文中描述的方法。可以在Baker 等人2012 年的一篇论文中找到关于 Brand 算法为何倾向于累积误差的简要讨论Chahlaoui 等人的相关方法讨论了左奇异子空间和奇异值的误差界限。

我已经在斯蒂芬回答的评论中提到了这些要点,但值得重复的是,贝克和查拉维的方法截断秩 SVD矩阵。对于低秩近似,项占主导地位,并且根据算法变体,它的前导常数通常在 8 到 12 之间。O(mnk+nk3)kmnmnk

就像斯蒂芬的回答一样,Chahlaoui 的算法从 QR 分解开始。斯蒂芬的答案将适用于计算左奇异向量,但中具有超线性复杂性(它将是),这可能会降低效率,但是更准确。RmnO(mn2)

对于它的价值,我自己实现了 Brand 的算法,它对用于等级截断的内积容差有些敏感。我没有使用贝克的包,但我相信它会更好,因为贝克算法(或密切相关的算法)而不是布兰德算法存在误差估计,并且因为贝克算法的等级截断容差是奇异值,而不是内部产品。

计算矩阵 svd 的一种方法X是首先X=QR使用 QR 分解(为了稳定性,使用旋转,所以这是[Q,R,E] = qr(X,0)在 Matlab 中),然后计算 svd 的 svd R如果矩阵在任何一个中都是非常矩形的,那么最昂贵的计算是 QR 分解。

因此,如果你X用另一行或另一列增加你的矩阵(这就是你的意思,对吗?),你可以用 Matlab 的qrinsert函数更新 QR 分解,然后重新做R.

如果你有一个大的方阵,这个方法就没有那么有用了,因为重新做 SVDR会很耗时。

这是一种可以处理列添加的方法:http: //pcc.byu.edu/resources.html我更新了它来处理行添加:

function [Up1,Sp,Vp1] = addblock_svd_update2( Uarg, Sarg, Varg, Aarg, force_orth )

  U = Varg;
  V = Uarg;
  S = Sarg;
  A = Aarg';

  current_rank = size( U, 2 );
  m = U' * A;
  p = A - U*m;
  P = orth( p );
  P = [ P zeros(size(P,1), size(p,2)-size(P,2)) ];
  Ra = P' * p;
  z = zeros( size(m) );
  K = [ S m ; z' Ra ];
  [tUp,tSp,tVp] = svds( K, current_rank );
  Sp = tSp;
  Up = [ U P ] * tUp;
  Vp = V * tVp( 1:current_rank, : );
  Vp = [ Vp ; tVp( current_rank+1:size(tVp,1), : ) ];
  if ( force_orth )
    [UQ,UR] = qr( Up, 0 );
    [VQ,VR] = qr( Vp, 0 );
    [tUp,tSp,tVp] = svds( UR * Sp * VR', current_rank );
    Up = UQ * tUp;
    Vp = VQ * tVp;
    Sp = tSp;
  end;

  Up1 = Vp;
  Vp1 = Up;

return;

测试它

X = [[ 2.180116   2.493767  -0.047867;
       -1.562426  2.292670   0.139761;
       0.919099  -0.887082  -1.197149;
       0.333190  -0.632542  -0.013330]];

A = [1 1 1];
X2 = [X; A];
[U,S,V] = svds(X);

[Up,Sp,Vp] = addblock_svd_update2(U, S, V, A, true);

Up
Sp
Vp

[U2,S2,V2] = svds(X2);
U2
S2
V2

你会看到两边的 U,S,V 结果是一样的。

还有 Python 版本,

import numpy as np
import scipy.linalg as lin

def  addblock_svd_update( Uarg, Sarg, Varg, Aarg, force_orth = False):
  U = Varg
  V = Uarg
  S = np.eye(len(Sarg),len(Sarg))*Sarg
  A = Aarg.T

  current_rank = U.shape[1]
  m = np.dot(U.T,A)
  p = A - np.dot(U,m)
  P = lin.orth(p)
  Ra = np.dot(P.T,p)
  z = np.zeros(m.shape)
  K = np.vstack(( np.hstack((S,m)), np.hstack((z.T,Ra)) ))
  tUp,tSp,tVp = lin.svd(K);
  tUp = tUp[:,:current_rank]
  tSp = np.diag(tSp[:current_rank])
  tVp = tVp[:,:current_rank]
  Sp = tSp
  Up = np.dot(np.hstack((U,P)),tUp)
  Vp = np.dot(V,tVp[:current_rank,:])
  Vp = np.vstack((Vp, tVp[current_rank:tVp.shape[0], :]))

  if force_orth:
    UQ,UR = lin.qr(Up,mode='economic')
    VQ,VR = lin.qr(Vp,mode='economic')
    tUp,tSp,tVp = lin.svd( np.dot(np.dot(UR,Sp),VR.T));
    tSp = np.diag(tSp)
    Up = np.dot(UQ,tUp)
    Vp = np.dot(VQ,tVp)
    Sp = tSp;

  Up1 = Vp;
  Vp1 = Up;

  return Up1,Sp,Vp1

增量 SVD 的替代方法是 Hierarchical Approximate Proper Orthogonal Decomposition HAPOD,其实现可以在 github 上找到:http: //git.io/hapodHAPOD 有严格的误差界限,一种特殊情况是增量变体。