Matlab如何转置稀疏矩阵?

时间:2013-05-23 19:00:29

标签: matlab bsxfun

我一直想知道这个问题很长一段时间但是找不到参考:Matlab如何快速转置稀疏矩阵,因为它以CSC(压缩稀疏列)格式存储?

同样its documentation验证稀疏矩阵转置的效率:

  

要执行此操作(逐行访问),您可以转置矩阵,对列执行操作,然后重新转换结果...转置矩阵所需的时间可以忽略不计。

跟进(根据@Mikhail的建议修改):

我同意@Roger和@Milhail的说法,设置一个标志足以支持许多操作,例如BLAS或稀疏BLAS操作的接口。但在我看来,Matlab做了“实际”换位。例如,我有一个稀疏矩阵X,大小为m * n = 7984 * 12411,我想缩放每一列和每一行:

% scaling each column
t = 0;
for i = 1 : 1000
    A = X; t0 = tic;
    A = bsxfun(@times, A, rand(1,n));
    t = t + toc(t0);
end

t = 0.023636秒

% scaling each row
t = 0;
for i = 1 : 1000
    A = X; t0 = tic;
    A = bsxfun(@times, A, rand(m,1));
    t = t + toc(t0);
end

t = 138.3586秒

% scaling each row by transposing X and transforming back
t = 0;
for i = 1 : 1000
    A = X; t0 = tic;
    A = A'; A = bsxfun(@times, A, rand(1,m)); A = A';
    t = t + toc(t0);
end

t = 19.5433秒

此结果意味着逐列访问比逐行访问更快。这是有道理的,因为稀疏矩阵是逐列存储的。因此,X'列缩放速度快的唯一原因应该是X实际上转换为X'而不是设置标志。

此外,如果每个稀疏矩阵都以CSC格式存储,只需设置一个标志就不能以CSC格式制作X'。

有何评论?提前谢谢。

3 个答案:

答案 0 :(得分:8)

经过一周的探索,我对转置矩阵的内部机制的猜测就是排序。

假设A是稀疏矩阵,

[I, J, S] = find(A);
[sorted_I, idx] = sort(I);
J = J(idx);
S = S(idx);
B = sparse(J, sorted_I, S);

然后BA的转置。

上面的实现大约是我机器上Matlab内置transpose效率的一半。考虑到Matlab的内置函数是多线程的,我的猜测可能是合理的。

答案 1 :(得分:2)

我意识到我在游戏中有点迟了,但我想我可以帮助解决这个问题。转置稀疏矩阵实际上是一项简单的任务,可以在时间上与输入矩阵中非零元素的数量成比例地完成。假设A是以CSC格式存储的m×n矩阵,即A由三个数组定义:

  1. elemsA,长度为nnz(A),用于存储A
  2. 中的非零元素
  3. prowA,长度为nnz(A),用于存储A中非零元素的行索引
  4. pcolA,长度为n + 1,这样A列j中的所有非零元素都被[pcolA(j),pcolA(j + 1))
  5. 范围索引。

    如果B表示A的转置,那么我们的目标是定义类似的数组elemsB,prowB,pcolB。为此,我们使用A的行形成B的列的事实。令tmp为数组,使得tmp(1)= 0并且tmp(i + 1)是A的行i中的元素数量。 i = 1,...,m。然后,tmp(i + 1)是B列i中的元素数。因此,tmp的累积和与pcolB相同。现在假设tmp已被其累积总和覆盖。然后elemsB和prowB可以填充如下

        for j = 1,...,n
            for k = pcolA(j),...,pcolA(j + 1) - 1
                prowB(tmp(prowA(k))) = j
                elemsB(tmp(prowA(k))) = elemsA(k)
                tmp(prowA(k)) = tmp(prowA(k)) + 1
            end
        end
    

    数组tmp用于在添加新元素时索引到prowB和elemsB,然后相应地更新。完全放上这个,我们可以用C ++编写一个实现转置算法的mex文件:

    #include "mex.h"
    #include <vector>
    void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {     
        // check input output
        if (nrhs != 1)
           mexErrMsgTxt("One input argument required");
        if (nlhs > 1)
           mexErrMsgTxt("Too many output arguments");
    
        // get input sparse matrix A
        if (mxIsEmpty(prhs[0])) { // is A empty?
            plhs[0] = mxCreateSparse(0, 0, 0, mxREAL);
            return;
        }
        if (!mxIsSparse(prhs[0]) || mxIsComplex(prhs[0])) // is A real and sparse?
           mexErrMsgTxt("Input matrix must be real and sparse");
        double* A = mxGetPr(prhs[0]);           // real vector for A
        mwIndex* prowA = mxGetIr(prhs[0]);      // row indices for elements of A
        mwIndex* pcolindexA = mxGetJc(prhs[0]); // index into the columns
        mwSize M = mxGetM(prhs[0]);             // number of rows in A
        mwSize N = mxGetN(prhs[0]);             // number of columns in A
    
        // allocate memory for A^T
        plhs[0] = mxCreateSparse(N, M, pcolindexA[N], mxREAL);
        double* outAt = mxGetPr(plhs[0]);
        mwIndex* outprowAt = mxGetIr(plhs[0]);
        mwIndex* outpcolindexAt = mxGetJc(plhs[0]);
    
        // temp[j + 1] stores the number of nonzero elements in row j of A
        std::vector<mwSize> temp(M + 1, 0); 
        for(mwIndex i = 0; i != N; ++i) {
            for(mwIndex j = pcolindexA[i]; j < pcolindexA[i + 1]; ++j)
                ++temp[prowA[j] + 1];
        }
        outpcolindexAt[0] = 0;
        for(mwIndex i = 1; i <= M; ++i) {
            outpcolindexAt[i] = outpcolindexAt[i - 1] + temp[i];
            temp[i] = outpcolindexAt[i];
        }
        for(mwIndex i = 0; i != N; ++i) {
            for(mwIndex j = pcolindexA[i]; j < pcolindexA[i + 1]; ++j) {
                outprowAt[temp[prowA[j]]] = i;
                outAt[temp[prowA[j]]++] = A[j];
            }
        }
    }
    

    将此算法与Matlab的转置实现进行比较,我们观察到类似的执行时间。请注意,可以直接修改此算法以消除临时数组。

答案 2 :(得分:1)

我同意Roger Rowland在评论中提到的内容。为了解决这个问题,你可以从BLAS接口检查一些函数,MATLAB用它来进行矩阵运算。我不确定它使用了什么实现,但由于他们使用英特尔IPP进行图像处理,我认为他们也可以使用英特尔MKL来快速制作矩阵操作。

这里是mkl_?cscsv函数的文档,它解决了CSC格式的稀疏矩阵的线性方程组。请注意transa输入标志,它明确定义所提供的矩阵是否应被视为转置。