R 中大型稀疏矩阵的特征值

Eigenvalues of a large sparse matrix in R

我有一些大的(对称实数)稀疏矩阵,我想计算一些最大和最小(按量级)的特征值。我查看了犰狳,但是 links 到 ARPACK 库,它是用 Fortran 编写的。作为 Widnows 用户,我手边没有 Fortran 编译器,所以我决定使用为 Windows.

预编译的 ARPACK R 包

我的矩阵是以MatrixMarket格式存储的,在R中,我是这样读的:

library(Matrix)
f <- file("file://./lambda.mtx", "r")
str(m <- readMM(f))
close(f)
# read sparse matrix

矩阵是对称的,只有下半部分实际存储在mtx文件中。我试过使用对称矩阵,但我一无所获。所以我写了以下(对我来说相当可怕)代码来形成稀疏矩阵的两半:

str(A <- sparseMatrix(c(m@i, m@j), c(m@j, m@i), index1 = FALSE, x = c(m@x, m@x), giveCsparse = TRUE, use.last.ij = TRUE))
# do not treat it as symmetric, this doubles the memory but avoids
# conversion to a full matrix in eigs() so it is a good deal ...

str(nnzM <- length(m@x))
str(nnzA <- A@p[A@Dim[2] + 1])
if(nnzM * 2 - m@Dim[2] != nnzA) {
    stop("failed to form full matrix A")
}
if(A@x[1] != m@x[1]) {
    if(A@x[1] == 2 * m@x[1])
        warning("failed to form full matrix A: diagonal elements added")
    else
        warning("failed to form full matrix A: bad diagonal value")
}
# make sure the repeated values of the diagonal were eliminated instead of added (use.last.ij = TRUE)

最后,我使用 rARPACK 计算特征值:

library(rARPACK)
str(le <- eigs(A, k = 5, which = "LM", opts = list(retvec = FALSE))) # or dsyMatrix
str(se <- eigs(A, k = 5, sigma = 0, opts = list(retvec = FALSE))) # or dsyMatrix
le$values[1]
se$values[se$nconv]

这适用于小矩阵。但是在大矩阵上,我在 eigs() 中得到 bad_alloc 个错误。请注意,我是 运行 64 位版本的 R,盒子里有 16 GB 的 RAM(有问题的稀疏矩阵是 174515 x 174515,有 9363966 个非零值,mtx 中有超过 300 MB(文本格式)).

我以前在使用对称稀疏矩阵时遇到过内存问题。我的猜测是矩阵的格式不正确,仍然会在某处转换为密集矩阵。否则我不明白它怎么会需要这么多内存。任何帮助将不胜感激,这是迄今为止我用 R 编写的第一个也是唯一一个东西。

根据要求,我可以将矩阵上传到某处并分享 link。我可以在 Matlab 中计算相同矩阵的特征值,但这主要是一个手动过程,我必须将矩阵传输到另一台机器(也是 16 GB 的 RAM,但 Matlab 是 32 位的,所以理论上它有更多的限制working space), 而这台机器恰好在欧洲,而我在澳大利亚,所以它需要很长时间。如果可能的话,我更喜欢在我的本地机器上使用 R。

编辑

复制问题所需的所有数据和脚本都在 https://sourceforge.net/projects/slamfrontend/files/data/eigenvalues/。此外,该进程实际上在它死亡之前确实分配了大约 12 GB 的内存。

编辑 2

这是脚本的输出。居然在计算最小特征值的时候崩溃,最大的计算速度很快,没有崩溃:

E:\my-projects\Robotics\MonaschGit\wdir>"C:\Program Files\R\R-3.2.2\bin\x64\R" --no-save -q --slave  0<get_lambda_eigenvalues.R
Formal class 'dsTMatrix' [package "Matrix"] with 7 slots
  ..@ i       : int [1:9363966] 0 1 2 3 4 5 6 1 2 3 ...
  ..@ j       : int [1:9363966] 0 0 0 0 0 0 0 1 1 1 ...
  ..@ Dim     : int [1:2] 174515 174515
  ..@ Dimnames:List of 2
  .. ..$ : NULL
  .. ..$ : NULL
  ..@ x       : num [1:9363966] 1.62e+08 -2.49e+01 -2.49e+06 2.19e+07 1.79e+09 ...
  ..@ uplo    : chr "L"
  ..@ factors : list()
Formal class 'dgCMatrix' [package "Matrix"] with 6 slots
  ..@ i       : int [1:18553417] 0 1 2 3 4 5 6 7 8 9 ...
  ..@ p       : int [1:174516] 0 14362 28724 43086 57448 71810 86172 100534 115525 130516 ...
  ..@ Dim     : int [1:2] 174515 174515
  ..@ Dimnames:List of 2
  .. ..$ : NULL
  .. ..$ : NULL
  ..@ x       : num [1:18553417] 1.62e+08 -2.49e+01 -2.49e+06 2.19e+07 1.79e+09 ...
  ..@ factors : list()
 int 9363966
 int 18553417
List of 4
 $ values : num [1:5] 1.38e+11 1.31e+11 1.30e+11 1.22e+11 1.16e+11
 $ vectors: NULL
 $ nconv  : int 5
 $ niter  : int 60
Error: std::bad_alloc
Execution halted

这也可能意味着问题与用于最小特征值的移位和反转模式有关。它实际上是否在此过程中反转了矩阵?如果是这样,我明白为什么它会耗尽内存,这样的矩阵的逆将是完全密集的。

在使用了 rARPACK 包的 discussion with the developer 之后,很明显问题不在于矩阵被转换为密集矩阵,而是在于 LU 分解必须显着改变稀疏矩阵的顺序以避免数值问题,从而大量填充矩阵。

新版包会使用LDLT分解,貌似没有这个问题,而且计算特征值很快

在发布新版本之前,可以获取 C++ sources 的副本并将它们用于:

class CSymmetricSparseMatrix_InvProduct {
protected:
    typedef Eigen::SparseMatrix<double, Eigen::ColMajor> SpMat;
    typedef Eigen::SimplicialLDLT<SpMat> SpLDLTSolver;

    const size_t n;
    SpMat m_mat;
    SpLDLTSolver solver;

public:
    CSymmetricSparseMatrix_InvProduct(const cs *p_matrix)
        :m_mat(int(p_matrix->m), int(p_matrix->n)), n(p_matrix->n)
    {
        // could maybe use Eigen::internal::viewAsEigen(p_matrix);

        _ASSERTE(p_matrix->m == p_matrix->n);
        // must be square

        std::vector<Eigen::Triplet<double> > triplets;
        triplets.reserve(p_matrix->p[p_matrix->n]);
        for(size_t i = 0; i < n; ++ i) {
            for(size_t p = p_matrix->p[i], e = p_matrix->p[i + 1]; p < e; ++ p) {
                double f_val = p_matrix->x[p];
                size_t n_row = p_matrix->i[p], n_col = i;
                triplets.push_back(Eigen::Triplet<double>(int(n_row), int(n_col), f_val));
            }
        }
        // expand the CSparse matrix to triplets

        m_mat.setFromTriplets(triplets.begin(), triplets.end());
        // fill the Eigen matrix

        m_mat.makeCompressed();
        // compress the Eigen matrix
    }

    size_t rows() const
    {
        return n;
    }

    size_t cols() const
    {
        return n;
    }

    void set_shift(double sigma)
    {
        SpMat I((int)n, (int)n);
        I.setIdentity();

        solver.compute(m_mat - I * sigma); // also better use solver.setShift(-sigma, 1.0);

        /*size_t n_fac_nnz = ((SpMat)solver.matrixL()).nonZeros(); // type cast required, otherwise it loops forever
        fprintf(stderr, "debug: the LDLT factorization has " PRIsize " nonzeros\n", n_fac_nnz);*/
    }

    // y_out = inv(A - sigma * I) * x_in
    void perform_op(const double *x_in, double *y_out) const
    {
        Eigen::Map<const Eigen::VectorXd, Eigen::DontAlign> x(x_in, n);
        Eigen::Map<Eigen::VectorXd, Eigen::DontAlign> y(y_out, n);
        y.noalias() = solver.solve(x);
    }

private:
    CSymmetricSparseMatrix_InvProduct(const CSymmetricSparseMatrix_InvProduct &r_other); // no-copy
    CSymmetricSparseMatrix_InvProduct &operator =(const CSymmetricSparseMatrix_InvProduct &r_other); // no-copy
};