// matrix/sp-matrix.h // Copyright 2009-2011 Ondrej Glembek; Microsoft Corporation; Lukas Burget; // Saarland University; Ariya Rastrow; Yanmin Qian; // Jan Silovsky // See ../../COPYING for clarification regarding multiple authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED // WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, // MERCHANTABLITY OR NON-INFRINGEMENT. // See the Apache 2 License for the specific language governing permissions and // limitations under the License. #ifndef KALDI_MATRIX_SP_MATRIX_H_ #define KALDI_MATRIX_SP_MATRIX_H_ #include #include #include "matrix/packed-matrix.h" namespace kaldi { /// \addtogroup matrix_group /// @{ template class SpMatrix; /** * @brief Packed symetric matrix class */ template class SpMatrix : public PackedMatrix { friend class CuSpMatrix; public: // so it can use our assignment operator. friend class std::vector >; SpMatrix(): PackedMatrix() {} /// Copy constructor from CUDA version of SpMatrix /// This is defined in ../cudamatrix/cu-sp-matrix.h explicit SpMatrix(const CuSpMatrix &cu); explicit SpMatrix(MatrixIndexT r, MatrixResizeType resize_type = kSetZero) : PackedMatrix(r, resize_type) {} SpMatrix(const SpMatrix &orig) : PackedMatrix(orig) {} template explicit SpMatrix(const SpMatrix &orig) : PackedMatrix(orig) {} #ifdef KALDI_PARANOID explicit SpMatrix(const MatrixBase & orig, SpCopyType copy_type = kTakeMeanAndCheck) : PackedMatrix(orig.NumRows(), kUndefined) { CopyFromMat(orig, copy_type); } #else explicit SpMatrix(const MatrixBase & orig, SpCopyType copy_type = kTakeMean) : PackedMatrix(orig.NumRows(), kUndefined) { CopyFromMat(orig, copy_type); } #endif /// Shallow swap. void Swap(SpMatrix *other); inline void Resize(MatrixIndexT nRows, MatrixResizeType resize_type = kSetZero) { PackedMatrix::Resize(nRows, resize_type); } void CopyFromSp(const SpMatrix &other) { PackedMatrix::CopyFromPacked(other); } template void CopyFromSp(const SpMatrix &other) { PackedMatrix::CopyFromPacked(other); } #ifdef KALDI_PARANOID void CopyFromMat(const MatrixBase &orig, SpCopyType copy_type = kTakeMeanAndCheck); #else // different default arg if non-paranoid mode. void CopyFromMat(const MatrixBase &orig, SpCopyType copy_type = kTakeMean); #endif inline Real operator() (MatrixIndexT r, MatrixIndexT c) const { // if column is less than row, then swap these as matrix is stored // as upper-triangular... only allowed for const matrix object. if (static_cast(c) > static_cast(r)) std::swap(c, r); // c<=r now so don't have to check c. KALDI_ASSERT(static_cast(r) < static_cast(this->num_rows_)); return *(this->data_ + (r*(r+1)) / 2 + c); // Duplicating code from PackedMatrix.h } inline Real &operator() (MatrixIndexT r, MatrixIndexT c) { if (static_cast(c) > static_cast(r)) std::swap(c, r); // c<=r now so don't have to check c. KALDI_ASSERT(static_cast(r) < static_cast(this->num_rows_)); return *(this->data_ + (r * (r + 1)) / 2 + c); // Duplicating code from PackedMatrix.h } using PackedMatrix::operator =; using PackedMatrix::Scale; /// matrix inverse. /// if inverse_needed = false, will fill matrix with garbage. /// (only useful if logdet wanted). void Invert(Real *logdet = NULL, Real *det_sign= NULL, bool inverse_needed = true); // Below routine does inversion in double precision, // even for single-precision object. void InvertDouble(Real *logdet = NULL, Real *det_sign = NULL, bool inverse_needed = true); /// Returns maximum ratio of singular values. inline Real Cond() const { Matrix tmp(*this); return tmp.Cond(); } /// Takes matrix to a fraction power via Svd. /// Will throw exception if matrix is not positive semidefinite /// (to within a tolerance) void ApplyPow(Real exponent); /// This is the version of SVD that we implement for symmetric positive /// definite matrices. This exists for historical reasons; right now its /// internal implementation is the same as Eig(). It computes the eigenvalue /// decomposition (*this) = P * diag(s) * P^T with P orthogonal. Will throw /// exception if input is not positive semidefinite to within a tolerance. void SymPosSemiDefEig(VectorBase *s, MatrixBase *P, Real tolerance = 0.001) const; /// Solves the symmetric eigenvalue problem: at end we should have (*this) = P /// * diag(s) * P^T. We solve the problem using the symmetric QR method. /// P may be NULL. /// Implemented in qr.cc. /// If you need the eigenvalues sorted, the function SortSvd declared in /// kaldi-matrix is suitable. void Eig(VectorBase *s, MatrixBase *P = NULL) const; /// This function gives you, approximately, the largest eigenvalues of the /// symmetric matrix and the corresponding eigenvectors. (largest meaning, /// further from zero). It does this by doing a SVD within the Krylov /// subspace generated by this matrix and a random vector. This is /// a form of the Lanczos method with complete reorthogonalization, followed /// by SVD within a smaller dimension ("lanczos_dim"). /// /// If *this is m by m, s should be of dimension n and P should be of /// dimension m by n, with n <= m. The *columns* of P are the approximate /// eigenvectors; P * diag(s) * P^T would be a low-rank reconstruction of /// *this. The columns of P will be orthogonal, and the elements of s will be /// the eigenvalues of *this projected into that subspace, but beyond that /// there are no exact guarantees. (This is because the convergence of this /// method is statistical). Note: it only makes sense to use this /// method if you are in very high dimension and n is substantially smaller /// than m: for example, if you want the 100 top eigenvalues of a 10k by 10k /// matrix. This function calls Rand() to initialize the lanczos /// iterations and also for restarting. /// If lanczos_dim is zero, it will default to the greater of: /// s->Dim() + 50 or s->Dim() + s->Dim()/2, but not more than this->Dim(). /// If lanczos_dim == this->Dim(), you might as well just call the function /// Eig() since the result will be the same, and Eig() would be faster; the /// whole point of this function is to reduce the dimension of the SVD /// computation. void TopEigs(VectorBase *s, MatrixBase *P, MatrixIndexT lanczos_dim = 0) const; /// Takes log of the matrix (does eigenvalue decomposition then takes /// log of eigenvalues and reconstructs). Will throw of not +ve definite. void Log(); // Takes exponential of the matrix (equivalent to doing eigenvalue // decomposition then taking exp of eigenvalues and reconstructing). void Exp(); /// Returns the maximum of the absolute values of any of the /// eigenvalues. Real MaxAbsEig() const; void PrintEigs(const char *name) { Vector s((*this).NumRows()); Matrix P((*this).NumRows(), (*this).NumCols()); SymPosSemiDefEig(&s, &P); KALDI_LOG << "PrintEigs: " << name << ": " << s; } bool IsPosDef() const; // returns true if Cholesky succeeds. void AddSp(const Real alpha, const SpMatrix &Ma) { this->AddPacked(alpha, Ma); } /// Computes log determinant but only for +ve-def matrices /// (it uses Cholesky). /// If matrix is not +ve-def, it will throw an exception /// was LogPDDeterminant() Real LogPosDefDet() const; Real LogDet(Real *det_sign = NULL) const; /// rank-one update, this <-- this + alpha v v' template void AddVec2(const Real alpha, const VectorBase &v); /// rank-two update, this <-- this + alpha (v w' + w v'). void AddVecVec(const Real alpha, const VectorBase &v, const VectorBase &w); /// Does *this = beta * *thi + alpha * diag(v) * S * diag(v) void AddVec2Sp(const Real alpha, const VectorBase &v, const SpMatrix &S, const Real beta); /// diagonal update, this <-- this + diag(v) template void AddDiagVec(const Real alpha, const VectorBase &v); /// rank-N update: /// if (transM == kNoTrans) /// (*this) = beta*(*this) + alpha * M * M^T, /// or (if transM == kTrans) /// (*this) = beta*(*this) + alpha * M^T * M /// Note: beta used to default to 0.0. void AddMat2(const Real alpha, const MatrixBase &M, MatrixTransposeType transM, const Real beta); /// Extension of rank-N update: /// this <-- beta*this + alpha * M * A * M^T. /// (*this) and A are allowed to be the same. /// If transM == kTrans, then we do it as M^T * A * M. void AddMat2Sp(const Real alpha, const MatrixBase &M, MatrixTransposeType transM, const SpMatrix &A, const Real beta = 0.0); /// This is a version of AddMat2Sp specialized for when M is fairly sparse. /// This was required for making the raw-fMLLR code efficient. void AddSmat2Sp(const Real alpha, const MatrixBase &M, MatrixTransposeType transM, const SpMatrix &A, const Real beta = 0.0); /// The following function does: /// this <-- beta*this + alpha * T * A * T^T. /// (*this) and A are allowed to be the same. /// If transM == kTrans, then we do it as alpha * T^T * A * T. /// Currently it just calls AddMat2Sp, but if needed we /// can implement it more efficiently. void AddTp2Sp(const Real alpha, const TpMatrix &T, MatrixTransposeType transM, const SpMatrix &A, const Real beta = 0.0); /// The following function does: /// this <-- beta*this + alpha * T * T^T. /// (*this) and A are allowed to be the same. /// If transM == kTrans, then we do it as alpha * T^T * T /// Currently it just calls AddMat2, but if needed we /// can implement it more efficiently. void AddTp2(const Real alpha, const TpMatrix &T, MatrixTransposeType transM, const Real beta = 0.0); /// Extension of rank-N update: /// this <-- beta*this + alpha * M * diag(v) * M^T. /// if transM == kTrans, then /// this <-- beta*this + alpha * M^T * diag(v) * M. void AddMat2Vec(const Real alpha, const MatrixBase &M, MatrixTransposeType transM, const VectorBase &v, const Real beta = 0.0); /// Floors this symmetric matrix to the matrix /// alpha * Floor, where the matrix Floor is positive /// definite. /// It is floored in the sense that after flooring, /// x^T (*this) x >= x^T (alpha*Floor) x. /// This is accomplished using an Svd. It will crash /// if Floor is not positive definite. Returns the number of /// elements that were floored. int ApplyFloor(const SpMatrix &Floor, Real alpha = 1.0, bool verbose = false); /// Floor: Given a positive semidefinite matrix, floors the eigenvalues /// to the specified quantity. A previous version of this function had /// a tolerance which is now no longer needed since we have code to /// do the symmetric eigenvalue decomposition and no longer use the SVD /// code for that purose. int ApplyFloor(Real floor); bool IsDiagonal(Real cutoff = 1.0e-05) const; bool IsUnit(Real cutoff = 1.0e-05) const; bool IsZero(Real cutoff = 1.0e-05) const; bool IsTridiagonal(Real cutoff = 1.0e-05) const; /// sqrt of sum of square elements. Real FrobeniusNorm() const; /// Returns true if ((*this)-other).FrobeniusNorm() <= /// tol*(*this).FrobeniusNorma() bool ApproxEqual(const SpMatrix &other, float tol = 0.01) const; // LimitCond: // Limits the condition of symmetric positive semidefinite matrix to // a specified value // by flooring all eigenvalues to a positive number which is some multiple // of the largest one (or zero if there are no positive eigenvalues). // Takes the condition number we are willing to accept, and floors // eigenvalues to the largest eigenvalue divided by this. // Returns #eigs floored or already equal to the floor. // Throws exception if input is not positive definite. // returns #floored. MatrixIndexT LimitCond(Real maxCond = 1.0e+5, bool invert = false); // as LimitCond but all done in double precision. // returns #floored. MatrixIndexT LimitCondDouble(Real maxCond = 1.0e+5, bool invert = false) { SpMatrix dmat(*this); MatrixIndexT ans = dmat.LimitCond(maxCond, invert); (*this).CopyFromSp(dmat); return ans; } Real Trace() const; /// Tridiagonalize the matrix with an orthogonal transformation. If /// *this starts as S, produce T (and Q, if non-NULL) such that /// T = Q A Q^T, i.e. S = Q^T T Q. Caution: this is the other way /// round from most authors (it's more efficient in row-major indexing). void Tridiagonalize(MatrixBase *Q); /// The symmetric QR algorithm. This will mostly be useful in internal code. /// Typically, you will call this after Tridiagonalize(), on the same object. /// When called, *this (call it A at this point) must be tridiagonal; at exit, /// *this will be a diagonal matrix D that is similar to A via orthogonal /// transformations. This algorithm right-multiplies Q by orthogonal /// transformations. It turns *this from a tridiagonal into a diagonal matrix /// while maintaining that (Q *this Q^T) has the same value at entry and exit. /// At entry Q should probably be either NULL or orthogonal, but we don't check /// this. void Qr(MatrixBase *Q); private: void EigInternal(VectorBase *s, MatrixBase *P, Real tolerance, int recurse) const; }; /// @} end of "addtogroup matrix_group" /// \addtogroup matrix_funcs_scalar /// @{ /// Returns tr(A B). float TraceSpSp(const SpMatrix &A, const SpMatrix &B); double TraceSpSp(const SpMatrix &A, const SpMatrix &B); template inline bool ApproxEqual(const SpMatrix &A, const SpMatrix &B, Real tol = 0.01) { return A.ApproxEqual(B, tol); } template inline void AssertEqual(const SpMatrix &A, const SpMatrix &B, Real tol = 0.01) { KALDI_ASSERT(ApproxEqual(A, B, tol)); } /// Returns tr(A B). template Real TraceSpSp(const SpMatrix &A, const SpMatrix &B); // TraceSpSpLower is the same as Trace(A B) except the lower-diagonal elements // are counted only once not twice as they should be. It is useful in certain // optimizations. template Real TraceSpSpLower(const SpMatrix &A, const SpMatrix &B); /// Returns tr(A B). /// No option to transpose B because would make no difference. template Real TraceSpMat(const SpMatrix &A, const MatrixBase &B); /// Returns tr(A B C) /// (A and C may be transposed as specified by transA and transC). template Real TraceMatSpMat(const MatrixBase &A, MatrixTransposeType transA, const SpMatrix &B, const MatrixBase &C, MatrixTransposeType transC); /// Returns tr (A B C D) /// (A and C may be transposed as specified by transA and transB). template Real TraceMatSpMatSp(const MatrixBase &A, MatrixTransposeType transA, const SpMatrix &B, const MatrixBase &C, MatrixTransposeType transC, const SpMatrix &D); /** Computes v1^T * M * v2. Not as efficient as it could be where v1 == v2 * (but no suitable blas routines available). */ /// Returns \f$ v_1^T M v_2 \f$ /// Not as efficient as it could be where v1 == v2. template Real VecSpVec(const VectorBase &v1, const SpMatrix &M, const VectorBase &v2); /// @} \addtogroup matrix_funcs_scalar /// \addtogroup matrix_funcs_misc /// @{ /// This class describes the options for maximizing various quadratic objective /// functions. It's mostly as described in the SGMM paper "the subspace /// Gaussian mixture model -- a structured model for speech recognition", but /// the diagonal_precondition option is newly added, to handle problems where /// different dimensions have very different scaling (we recommend to use the /// option but it's set false for back compatibility). struct SolverOptions { BaseFloat K; // maximum condition number BaseFloat eps; std::string name; bool optimize_delta; bool diagonal_precondition; bool print_debug_output; explicit SolverOptions(const std::string &name): K(1.0e+4), eps(1.0e-40), name(name), optimize_delta(true), diagonal_precondition(false), print_debug_output(true) { } SolverOptions(): K(1.0e+4), eps(1.0e-40), name("[unknown]"), optimize_delta(true), diagonal_precondition(false), print_debug_output(true) { } void Check() const; }; /// Maximizes the auxiliary function /// \f[ Q(x) = x.g - 0.5 x^T H x \f] /// using a numerically stable method. Like a numerically stable version of /// \f$ x := Q^{-1} g. \f$ /// Assumes H positive semidefinite. /// Returns the objective-function change. template Real SolveQuadraticProblem(const SpMatrix &H, const VectorBase &g, const SolverOptions &opts, VectorBase *x); /// Maximizes the auxiliary function : /// \f[ Q(x) = tr(M^T P Y) - 0.5 tr(P M Q M^T) \f] /// Like a numerically stable version of \f$ M := Y Q^{-1} \f$. /// Assumes Q and P positive semidefinite, and matrix dimensions match /// enough to make expressions meaningful. /// This is mostly as described in the SGMM paper "the subspace Gaussian mixture /// model -- a structured model for speech recognition", but the /// diagonal_precondition option is newly added, to handle problems /// where different dimensions have very different scaling (we recommend to use /// the option but it's set false for back compatibility). template Real SolveQuadraticMatrixProblem(const SpMatrix &Q, const MatrixBase &Y, const SpMatrix &P, const SolverOptions &opts, MatrixBase *M); /// Maximizes the auxiliary function : /// \f[ Q(M) = tr(M^T G) -0.5 tr(P_1 M Q_1 M^T) -0.5 tr(P_2 M Q_2 M^T). \f] /// Encountered in matrix update with a prior. We also apply a limit on the /// condition but it should be less frequently necessary, and can be set larger. template Real SolveDoubleQuadraticMatrixProblem(const MatrixBase &G, const SpMatrix &P1, const SpMatrix &P2, const SpMatrix &Q1, const SpMatrix &Q2, const SolverOptions &opts, MatrixBase *M); /// @} End of "addtogroup matrix_funcs_misc" } // namespace kaldi // Including the implementation (now actually just includes some // template specializations). #include "matrix/sp-matrix-inl.h" #endif // KALDI_MATRIX_SP_MATRIX_H_