def testSvdSoft(self): A = scipy.sparse.rand(10, 10, 0.2) A = A.tocsc() lmbda = 0.1 k = 6 U, s, V = SoftImpute.svdSoft(A, lmbda, k) ATilde = U.dot(numpy.diag(s)).dot(V.T) #Now comput the same matrix using numpy #Pick first k singular vectors/values A = A.todense() U2, s2, V2 = numpy.linalg.svd(A) inds = numpy.flipud(numpy.argsort(s2))[0:k] U2, s2, V2 = Util.indSvd(U2, s2, V2, inds) s2 = s2 - lmbda s2 = numpy.clip(s, 0, numpy.max(s2)) ATilde2 = U2.dot(numpy.diag(s2)).dot(V2.T) nptst.assert_array_almost_equal(s, s) nptst.assert_array_almost_equal(ATilde, ATilde2)
def testSvd(self): n = 100 A = scipy.sparse.rand(n, n, 0.1) ks = [10, 20, 30, 40] q = 2 lastError = numpy.linalg.norm(A.todense()) for k in ks: U, s, V = RandomisedSVD.svd(A, k, q) nptst.assert_array_almost_equal(U.T.dot(U), numpy.eye(k)) nptst.assert_array_almost_equal(V.T.dot(V), numpy.eye(k)) A2 = (U*s).dot(V.T) error = numpy.linalg.norm(A - A2) self.assertTrue(error <= lastError) lastError = error #Compare versus exact svd U, s, V = numpy.linalg.svd(numpy.array(A.todense())) inds = numpy.flipud(numpy.argsort(s))[0:k*2] U, s, V = Util.indSvd(U, s, V, inds) Ak = (U*s).dot(V.T) error2 = numpy.linalg.norm(A - Ak) self.assertTrue(error2 <= error)
def eigenAdd(omega, Q, Y, k): """ Perform an eigen update of the form A*A + Y*Y in which Y is a low-rank matrix and A^*A = Q Omega Q*. We use the rank-k approximation of A: Q_k Omega_k Q_k^* and then approximate [A^*A_k Y^*Y]_k. """ #logging.debug("< eigenAdd >") Parameter.checkInt(k, 0, omega.shape[0]) #if not numpy.isrealobj(omega) or not numpy.isrealobj(Q): # raise ValueError("Eigenvalues and eigenvectors must be real") if omega.ndim != 1: raise ValueError("omega must be 1-d array") if omega.shape[0] != Q.shape[1]: raise ValueError("Must have same number of eigenvalues and eigenvectors") if __debug__: Parameter.checkOrthogonal(Q, tol=EigenUpdater.tol, softCheck=True, arrayInfo="input Q in eigenAdd()") #Taking the abs of the eigenvalues is correct inds = numpy.flipud(numpy.argsort(numpy.abs(omega))) omega, Q = Util.indEig(omega, Q, inds[numpy.abs(omega)>EigenUpdater.tol]) Omega = numpy.diag(omega) YY = Y.conj().T.dot(Y) QQ = Q.dot(Q.conj().T) Ybar = Y - Y.dot(QQ) Pbar, sigmaBar, Qbar = numpy.linalg.svd(Ybar, full_matrices=False) inds = numpy.flipud(numpy.argsort(numpy.abs(sigmaBar))) inds = inds[numpy.abs(sigmaBar)>EigenUpdater.tol] Pbar, sigmaBar, Qbar = Util.indSvd(Pbar, sigmaBar, Qbar, inds) SigmaBar = numpy.diag(sigmaBar) Qbar = Ybar.T.dot(Pbar) Qbar = Qbar.dot(numpy.diag(numpy.diag(Qbar.T.dot(Qbar))**-0.5)) r = sigmaBar.shape[0] YQ = Y.dot(Q) Zeros = numpy.zeros((r, omega.shape[0])) D = numpy.c_[Q, Qbar] YYQQ = YY.dot(QQ) Z = D.conj().T.dot(YYQQ + YYQQ.conj().T).dot(D) F = numpy.c_[numpy.r_[Omega - YQ.conj().T.dot(YQ), Zeros], numpy.r_[Zeros.T, SigmaBar.conj().dot(SigmaBar)]] F = F + Z pi, H = scipy.linalg.eigh(F) inds = numpy.flipud(numpy.argsort(numpy.abs(pi))) H = H[:, inds[0:k]] pi = pi[inds[0:k]] V = D.dot(H) #logging.debug("</ eigenAdd >") return pi, V
def eigenAdd2(omega, Q, Y1, Y2, k, debug= False): """ Compute an approximation of the eigendecomposition A^*A + Y1Y2^* +Y2Y1^* in which Y1, Y2 are low rank matrices, Y1^*Y2=0 and A^*A = Q Omega Q*. We use the rank-k approximation of A^*A: Q_k Omega_k Q_k^* and then find [A^*A_k + Y1Y2^* + Y2Y1^*]. If debug=False then pi, V are returned which respectively correspond to all the eigenvalues/eigenvectors of [A^*A_k + Y1Y2^* + Y2Y1^*]. """ #logging.debug("< eigenAdd2 >") Parameter.checkInt(k, 0, float('inf')) Parameter.checkClass(omega, numpy.ndarray) Parameter.checkClass(Q, numpy.ndarray) Parameter.checkClass(Y1, numpy.ndarray) Parameter.checkClass(Y2, numpy.ndarray) if not numpy.isrealobj(omega) or not numpy.isrealobj(Q): logging.warn("Eigenvalues or eigenvectors are not real") if not numpy.isrealobj(Y1) or not numpy.isrealobj(Y2): logging.warn("Y1 or Y2 are not real") if omega.ndim != 1: raise ValueError("omega must be 1-d array") if omega.shape[0] != Q.shape[1]: raise ValueError("Must have same number of eigenvalues and eigenvectors") if Q.shape[0] != Y1.shape[0]: raise ValueError("Q must have the same number of rows as Y1 rows") if Q.shape[0] != Y2.shape[0]: raise ValueError("Q must have the same number of rows as Y2 rows") if Y1.shape[1] != Y2.shape[1]: raise ValueError("Y1 must have the same number of columns as Y2 columns") if __debug__: Parameter.checkArray(omega, softCheck=True, arrayInfo="omega as input in eigenAdd2()") Parameter.checkArray(Q, softCheck=True, arrayInfo="Q as input in eigenAdd2()") Parameter.checkOrthogonal(Q, tol=EigenUpdater.tol, softCheck=True, arrayInfo="Q as input in eigenAdd2()") Parameter.checkArray(Y1, softCheck=True, arrayInfo="Y1 as input in eigenAdd2()") Parameter.checkArray(Y2, softCheck=True, arrayInfo="Y2 as input in eigenAdd2()") #Get first k eigenvectors/values of A^*A omega, Q = Util.indEig(omega, Q, numpy.flipud(numpy.argsort(omega))[0:k]) QY1 = Q.conj().T.dot(Y1) Y1bar = Y1 - Q.dot(QY1) P1bar, sigma1Bar, Q1bar = Util.safeSvd(Y1bar) inds = numpy.arange(sigma1Bar.shape[0])[numpy.abs(sigma1Bar)>EigenUpdater.tol] P1bar, sigma1Bar, Q1bar = Util.indSvd(P1bar, sigma1Bar, Q1bar, inds) # checks on SVD decomposition of Y1bar if __debug__: Parameter.checkArray(QY1, softCheck=True, arrayInfo="QY1 in eigenAdd2()") Parameter.checkArray(Y1bar, softCheck=True, arrayInfo="Y1bar in eigenAdd2()") Parameter.checkArray(P1bar, softCheck=True, arrayInfo="P1bar in eigenAdd2()") if not Parameter.checkOrthogonal(P1bar, tol=EigenUpdater.tol, softCheck=True, arrayInfo="P1bar in eigenAdd2()", investigate=True): print ("corresponding sigma: ", sigma1Bar) Parameter.checkArray(sigma1Bar, softCheck=True, arrayInfo="sigma1Bar in eigenAdd2()") Parameter.checkArray(Q1bar, softCheck=True, arrayInfo="Q1bar in eigenAdd2()") if not Parameter.checkOrthogonal(Q1bar, tol=EigenUpdater.tol, softCheck=True, arrayInfo="Q1bar in eigenAdd2()"): print ("corresponding sigma: ", sigma1Bar) del Y1bar P1barY2 = P1bar.conj().T.dot(Y2) QY2 = Q.conj().T.dot(Y2) Y2bar = Y2 - Q.dot(QY2) - P1bar.dot(P1barY2) P2bar, sigma2Bar, Q2bar = Util.safeSvd(Y2bar) inds = numpy.arange(sigma2Bar.shape[0])[numpy.abs(sigma2Bar)>EigenUpdater.tol] P2bar, sigma2Bar, Q2bar = Util.indSvd(P2bar, sigma2Bar, Q2bar, inds) # checks on SVD decomposition of Y1bar if __debug__: Parameter.checkArray(P1barY2, softCheck=True, arrayInfo="P1barY2 in eigenAdd2()") Parameter.checkArray(QY2, softCheck=True, arrayInfo="QY2 in eigenAdd2()") Parameter.checkArray(Y2bar, softCheck=True, arrayInfo="Y2bar in eigenAdd2()") Parameter.checkArray(P2bar, softCheck=True, arrayInfo="P2bar in eigenAdd2()") Parameter.checkOrthogonal(P2bar, tol=EigenUpdater.tol, softCheck=True, arrayInfo="P2bar in eigenAdd2()") Parameter.checkArray(sigma2Bar, softCheck=True, arrayInfo="sigma2Bar in eigenAdd2()") Parameter.checkArray(Q2bar, softCheck=True, arrayInfo="Q2bar in eigenAdd2()") Parameter.checkOrthogonal(Q2bar, tol=EigenUpdater.tol, softCheck=True, arrayInfo="Q2bar in eigenAdd2()") del Y2bar r = omega.shape[0] p = Y1.shape[1] p1 = sigma1Bar.shape[0] p2 = sigma2Bar.shape[0] D = numpy.c_[Q, P1bar, P2bar] del P1bar del P2bar # rem: A*s = A.dot(diag(s)) ; A*s[:,new] = diag(s).dot(A) DStarY1 = numpy.r_[QY1, sigma1Bar[:,numpy.newaxis] * Q1bar.conj().T, numpy.zeros((p2, p))] DStarY2 = numpy.r_[QY2, P1barY2, sigma2Bar[:,numpy.newaxis] * Q2bar.conj().T] DStarY1Y2StarD = DStarY1.dot(DStarY2.conj().T) del DStarY1 del DStarY2 r = omega.shape[0] F = numpy.zeros((r+p1+p2, r+p1+p2)) F[range(r),range(r)] = omega F = F + DStarY1Y2StarD + DStarY1Y2StarD.conj().T #A check to make sure DFD^T is AA_k + Y1Y2 + Y2Y1 #assert numpy.linalg.norm(D.dot(F).dot(D.T) - Q.dot(numpy.diag(omega).dot(Q.T)) - Y1.dot(Y2.T) - Y2.dot(Y1.T)) < 10**-6 # checks on F if __debug__: #Parameter.checkArray(DStarY1, softCheck=True, arrayInfo="DStarY1 in eigenAdd2()") #Parameter.checkArray(DStarY2, softCheck=True, arrayInfo="DStarY2 in eigenAdd2()") Parameter.checkArray(DStarY1Y2StarD, softCheck=True, arrayInfo="DStarY1Y2StarD in eigenAdd2()") Parameter.checkArray(F, softCheck=True, arrayInfo="F in eigenAdd2()") Parameter.checkSymmetric(F, tol=EigenUpdater.tol, softCheck=True, arrayInfo="F in eigenAdd2()") pi, H = scipy.linalg.eigh(F) # remove too small eigenvalues pi, H = Util.indEig(pi, H, numpy.arange(pi.shape[0])[numpy.abs(pi)>EigenUpdater.tol]) # keep greatest eigenvalues #pi, H = Util.indEig(pi, H, numpy.flipud(numpy.argsort(pi))[:min(k,pi.shape[0])]) V = D.dot(H) if __debug__: if not Parameter.checkOrthogonal(D, tol=EigenUpdater.tol, softCheck=True, investigate=True, arrayInfo="D in eigenAdd2()"): print("pi:\n", pi) if not Parameter.checkOrthogonal(H, tol=EigenUpdater.tol, softCheck=True, investigate=True, arrayInfo="H in eigenAdd2()"): print("pi:\n", pi) if ProfileUtils.memory() > 10**9: ProfileUtils.memDisplay(locals()) #logging.debug("</ eigenAdd2 >") if debug: return pi, V, D, DStarY1Y2StarD + DStarY1Y2StarD.conj().T else: return pi, V