MATLAB 中的增量 SVD 实现
是的。Christopher Baker 在名为 IncPACK 的 MATLAB 包中实现了他的增量 SVD 方法(存档在 GitHub 上的 imtsl 项目中)。它实现了他的硕士论文中描述的方法。可以在Baker 等人2012 年的一篇论文中找到关于 Brand 算法为何倾向于累积误差的简要讨论。Chahlaoui 等人的相关方法讨论了左奇异子空间和奇异值的误差界限。
我已经在斯蒂芬回答的评论中提到了这些要点,但值得重复的是,贝克和查拉维的方法的截断秩 SVD矩阵。对于低秩近似,项占主导地位,并且根据算法变体,它的前导常数通常在 8 到 12 之间。
就像斯蒂芬的回答一样,Chahlaoui 的算法从 QR 分解开始。斯蒂芬的答案将适用于计算左奇异向量,但和中具有超线性复杂性(它将是),这可能会降低效率,但是更准确。
对于它的价值,我自己实现了 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/hapod。HAPOD 有严格的误差界限,一种特殊情况是增量变体。