quicsvd.cc

Go to the documentation of this file.
00001 /* MLPACK 0.2
00002  *
00003  * Copyright (c) 2008, 2009 Alexander Gray,
00004  *                          Garry Boyer,
00005  *                          Ryan Riegel,
00006  *                          Nikolaos Vasiloglou,
00007  *                          Dongryeol Lee,
00008  *                          Chip Mappus, 
00009  *                          Nishant Mehta,
00010  *                          Hua Ouyang,
00011  *                          Parikshit Ram,
00012  *                          Long Tran,
00013  *                          Wee Chin Wong
00014  *
00015  * Copyright (c) 2008, 2009 Georgia Institute of Technology
00016  *
00017  * This program is free software; you can redistribute it and/or
00018  * modify it under the terms of the GNU General Public License as
00019  * published by the Free Software Foundation; either version 2 of the
00020  * License, or (at your option) any later version.
00021  *
00022  * This program is distributed in the hope that it will be useful, but
00023  * WITHOUT ANY WARRANTY; without even the implied warranty of
00024  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00025  * General Public License for more details.
00026  *
00027  * You should have received a copy of the GNU General Public License
00028  * along with this program; if not, write to the Free Software
00029  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00030  * 02110-1301, USA.
00031  */
00039 #include "quicsvd.h"
00040 #include <cmath>
00041 
00042 QuicSVD::QuicSVD(const Matrix& A, double targetRelErr) : root_(A) {
00043   A_.Alias(A);
00044 
00045   basis_.Init(); // init empty basis
00046   UTA_.Init();   // and empty projection, too
00047   projMagSq_.InitRepeat(0, n_cols()); // projected magnitude squares are zero's
00048   sumProjMagSq_ = 0;
00049   targetRelErr_ = targetRelErr;
00050   dataNorm2_ = root_.getSumL2(); // keep the Frobenius norm of A
00051   //printf("Done Init ncols = %d projMagSq_ = %d\n", n_cols(), projMagSq_.size());
00052 }
00053 
00054 // Modified Gram-Schmidt method, calculate the orthogonalized
00055 // new basis vector
00056 bool MGS(const ArrayList<Vector>& basis, const Vector& newVec, 
00057          Vector* newBasisVec) {
00058   //ot::Print(basis, "basis", stdout);
00059   //ot::Print(newVec, "newVec", stdout);
00060   newBasisVec->Copy(newVec);
00061   for (index_t i = 0; i < basis.size(); i++) {
00062     double prod = - la::Dot(basis[i], *newBasisVec);
00063     la::AddExpert(prod, basis[i], newBasisVec);
00064   }
00065   //ot::Print(*newBasisVec, "--newBasisVec--", stdout);
00066 
00067   // check of the remaning vector is zero vector (dependent)
00068   // if not, normalize it. Otherwise, signal the caller
00069   double L2 = la::LengthEuclidean(*newBasisVec);
00070   if (L2 > 1e-12) {
00071     la::Scale(1.0/L2, newBasisVec);
00072     return true;
00073   }
00074   else
00075     return false;
00076 }
00077 
00078 void QuicSVD::addBasisFrom(const CosineNode& node) {
00079   Vector nodeBasis;
00080   // check if new vector are independent and orthogonalize it
00081   if (MGS(basis_, node.getMean(), &nodeBasis)) {
00082     Vector av;
00083     basis_.PushBackCopy(nodeBasis);  // add to the basis
00084     la::MulInit(nodeBasis, A_, &av); // calculate projection of A
00085     UTA_.PushBackCopy(av);           // on the new basis vector and save
00086     for (index_t i_col = 0; i_col < n_cols(); i_col++) {
00087       double magSq = math::Sqr(av[i_col]);  // magnitude square of the i-th
00088       projMagSq_[i_col] += magSq;           // column of A in the new subspace
00089       sumProjMagSq_ += magSq;               // spanned by the basis is increased
00090     }
00091   }
00092 }
00093 
00094 // Calculate relative error |A|-|A'|/|A|
00095 double QuicSVD::curRelErr() {
00096   return sqrt((dataNorm2_ - sumProjMagSq_) / dataNorm2_);
00097 }
00098 
00099 // Add a node to the priority queue after calculating its Frobenius error
00100 // when projected on the subspace span by the basis
00101 void QuicSVD::addToQueue(CosineNode* node) {
00102   if (node == NULL) return;
00103   node->setL2Err(calL2Err(*node));
00104   leaves_.push(node);
00105 }
00106 
00107 // Calculate Frobenius error of a node when projected on the subspace
00108 // spanned by the current basis
00109 double QuicSVD::calL2Err(const CosineNode& node) {
00110   double nodeSumL2 = node.getSumL2();
00111   double nodeSumProjL2 = 0;
00112   for (index_t i_col = 0; i_col < node.n_cols(); i_col++)
00113     nodeSumProjL2 += projMagSq_[node.getOrigIndex(i_col)];
00114   return nodeSumL2 - nodeSumProjL2;
00115 }
00116 
00117 // Compute a subspace such that when the Frobenius relative error 
00118 // of matrix A when projected onto this subspace is less than the
00119 // target relative error
00120 void QuicSVD::ComputeSVD(Vector* s, Matrix* U, Matrix* VT) {
00121   leaves_.push(&root_);  // the root is the first element of the queue
00122   addBasisFrom(root_);   // at begining, the basis has at most 1 vector
00123 
00124   //printf("Add Root\n");
00125 
00126   //ot::Print(curRelErr(), "curRelErr = ", stdout);
00127   while (curRelErr() > targetRelErr_) {
00128     CosineNode* node = leaves_.top(); 
00129     leaves_.pop();  // pop the top of the queue
00130 
00131     node->Split();  // split it
00132 
00133     // only add the basis from the right node as the left node 
00134     // mean vector is well represented by the parent
00135     if (node->hasRight()) addBasisFrom(*(node->getRight()));
00136     
00137     // add both left child and right child to the queue
00138     addToQueue(node->getLeft());
00139     addToQueue(node->getRight());
00140     
00141     //ot::Print(curRelErr(), "curRelErr = ", stdout);
00142   } 
00143   //ot::Print(basis_, "basis", stdout);
00144 
00145   // after achieve the desire subspace, SVD on this subspace
00146   extractSVD(s, U, VT);
00147 }
00148 
00149 // Compute a square matrix UTA2 = UTA*UTA' that has the same
00150 // left singular vectors with UTA and has singluar values that are 
00151 // squares of singular values of UTA.
00152 // SVD on UTA2 is more efficient as it is a square matrix 
00153 // with smaller dimension
00154 void createUTA2(const ArrayList<Vector>& UTA, Matrix* UTA2) {
00155   UTA2->Init(UTA.size(), UTA.size());
00156   for (int i = 0; i < UTA.size(); i++)
00157     for (int j = 0; j < UTA.size(); j++)
00158       UTA2->ref(i, j) = la::Dot(UTA[i], UTA[j]);
00159 }
00160 
00161 // Matrix multiplication of a list of vector and a matrix
00162 // C = AB
00163 void MulInit(const ArrayList<Vector>& A, const Matrix& B,
00164              Matrix* C) {
00165   index_t m = A[0].length();
00166   index_t n = A.size();
00167   index_t p = B.n_cols();
00168   C->Init(m, p);
00169   for (index_t i = 0; i < m; i++)
00170     for (index_t j = 0; j < p; j++) {
00171       C->ref(i, j) = 0;
00172       for (index_t k = 0; k < n; k++) 
00173         C->ref(i, j) += A[k][i] * B.get(k, j);
00174     }
00175 }
00176 
00177 // Matrix multiplication C = B' A'
00178 void MulTransCInit(const ArrayList<Vector>& A, const Matrix& B,
00179                    Matrix* C) {
00180   index_t m = A[0].length();
00181   index_t n = A.size();
00182   index_t p = B.n_cols();
00183   C->Init(p, m);
00184   for (index_t i = 0; i < m; i++)
00185     for (index_t j = 0; j < p; j++) {
00186       C->ref(j, i) = 0;
00187       for (index_t k = 0; k < n; k++) 
00188         C->ref(j, i) += A[k][i] * B.get(k, j);
00189     }
00190 }
00191 
00192 // Inverse scale the rows of a matrix
00193 void InverseRowScale(const Vector& s, Matrix* A) {
00194   for (index_t i = 0; i < A->n_rows(); i++)
00195     for (index_t j = 0; j < A->n_cols(); j++)
00196       A->ref(i, j) /= s[i];
00197 }
00198 
00199 // Extract SVD of a matrix when projected to a subspace
00200 // spanned by the basis
00201 void QuicSVD::extractSVD(Vector* s, Matrix* U, Matrix* VT) {
00202   //ot::Print(UTA_, "UTA", stdout);
00203 
00204   Matrix UTA2;
00205   Matrix Uprime, VprimeT;
00206   Vector sprime;
00207 
00208   // as we only need the left singular vectors,
00209   // we will SVD on UTA2 = UTA_ UTA_'
00210   createUTA2(UTA_, &UTA2);  
00211   
00212   la::SVDInit(UTA2, &sprime, &Uprime, &VprimeT);
00213   
00214   s->Init(sprime.length());
00215   for (index_t i = 0; i < sprime.length(); i++)
00216     (*s)[i] = sqrt(sprime[i]); // the original singular values are square roots
00217                                // of that of UTA2
00218   
00219   MulInit(basis_, Uprime, U);  // transform back to the original space
00220   MulTransCInit(UTA_, Uprime, VT);
00221   InverseRowScale(*s, VT);
00222 
00223   //ot::Print(*U, "U=", stdout);
00224   //ot::Print(*s, "s=", stdout);
00225   //ot::Print(*VT, "VT=", stdout);
00226 }
00227 
00228 
00229 // Init matrices by Singular Value Decomposition, 
00230 // mimic la::SVDInit(A, s, U, VT) interface
00231 double QuicSVD::SVDInit(const Matrix& A, double targetRelErr,
00232                       Vector* s, Matrix* U, Matrix* VT) {
00233   // check if we need to transpose A to save some computation
00234   bool transpose = A.n_rows() > A.n_cols() * 1.1;
00235 
00236   if (!transpose) {
00237     QuicSVD svd(A, targetRelErr);
00238     svd.ComputeSVD(s, U, VT);
00239     return svd.curRelErr();
00240   }
00241   else {
00242     Matrix AT, UT, V;
00243     la::TransposeInit(A, &AT);
00244     QuicSVD svd(AT, targetRelErr);
00245     svd.ComputeSVD(s, &V, &UT);
00246     la::TransposeInit(UT, U);
00247     la::TransposeInit(V, VT);
00248     return svd.curRelErr();
00249   }
00250 }
Generated on Mon Jan 24 12:04:38 2011 for FASTlib by  doxygen 1.6.3