我一直想知道这个问题很长一段时间但是找不到参考: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'。
有何评论?提前谢谢。
答案 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);
然后B
是A
的转置。
上面的实现大约是我机器上Matlab内置transpose
效率的一半。考虑到Matlab的内置函数是多线程的,我的猜测可能是合理的。
答案 1 :(得分:2)
我意识到我在游戏中有点迟了,但我想我可以帮助解决这个问题。转置稀疏矩阵实际上是一项简单的任务,可以在时间上与输入矩阵中非零元素的数量成比例地完成。假设A是以CSC格式存储的m×n矩阵,即A由三个数组定义:
如果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
输入标志,它明确定义所提供的矩阵是否应被视为转置。