我什么时候应该使用`稀疏`?

时间:2015-04-21 21:51:27

标签: matlab sparse-matrix

我一直在查看Matlab的sparse documentation,试图找出是否有任何指导方针来确定何时使用稀疏表示而不是完整表示。

例如,我有一个矩阵data,其中包含大约30%的非零条目。我可以查看使用的内存。

whos data
  Name             Size                 Bytes  Class     Attributes

  data      84143929x11            4394073488  double    sparse    

data = full(data);
whos data
  Name             Size                 Bytes  Class     Attributes

  data      84143929x11            7404665752  double              

在这里,我明显地节省了内存,但是对于任何具有30%非零条目的矩阵都是如此吗? 50%的非零条目怎么样?是否有一个经验法则,我应该以百分比的形式切换到完整的矩阵?

计算怎么样?使用稀疏矩阵进行矩阵乘法通常更慢或更快吗? Sparse Matrix Operations

  

稀疏运算的计算复杂度与之成正比   nnz,矩阵中非零元素的数量。计算   复杂性也线性地取决于行大小m和列大小n   矩阵的数量,但与产品m * n,总数无关   零和非零元素。

如果不了解更多细节,很难与完整矩阵进行比较。

Scipy的稀疏矩阵库解释了每种稀疏格式的优缺点。例如csc_matrix

  

CSC格式的优点

     
      
  • 高效算术运算CSC + CSC,CSC * CSC等
  •   
  • 高效列切片
  •   
  • 快速矩阵矢量积(CSR,BSR可能更快)
  •   
     

CSC格式的缺点

     
      
  • 慢行切片操作(考虑CSR)
  •   
  • 稀疏结构的变化很昂贵(考虑LIL或DOK)
  •   

是否存在有关Matlab sparse实施的类似信息?如果是这样我在哪里可以找到它?

3 个答案:

答案 0 :(得分:3)

完整矩阵上的许多操作都使用疯狂优化且难以击败的BLAS / LAPACK库调用。实际上,稀疏矩阵的运算只会在特殊情况下优于完整矩阵的运算,这些情况可以充分利用(i)稀疏性和(ii)特殊矩阵结构。

随便使用稀疏可能会让你变得更糟。 示例:哪个更快,将10000x10000全矩阵添加到10000x10000全矩阵?或者将10000x10000全矩阵添加到完全稀疏(即一切都为零)10000x10000矩阵?试试吧!在我的系统上,全+完全更快!

稀疏CRUSHES已满的情况有哪些例子?

示例1:求解线性系统A * x = b其中A是5000x5000但是是由500个5x5块构成的块对角矩阵。设置代码:

As = sparse(rand(5, 5));
for(i=1:999)
   As = blkdiag(As, sparse(rand(5,5))); 
end;                         %As is made up of 500 5x5 blocks along diagonal
Af = full(As); b = rand(5000, 1);

然后你可以测试速度差异:

As \ b % operation on sparse As takes .0012 seconds
Af \ b % solving with full Af takes about 2.3 seconds

一般来说,5000变量线性系统有点困难,但1000个独立的5个可变线性系统是微不足道的。后者基本上是在稀疏案例中得到解决的问题。

总体情况是,如果你有特殊的矩阵结构,并且可以巧妙地利用稀疏性,那么就有可能解决那些难以解决的大问题。如果你有一个足够大的专门问题,有一个足够稀疏的矩阵,并且线性代数很聪明(为了保持稀疏性),稀疏类型矩阵可以非常强大。

另一方面,随机投入稀疏而不深入,仔细考虑几乎肯定会使你的代码变慢。

答案 1 :(得分:2)

我不是使用sparse矩阵的专家,但Mathworks确实有some documentation与操作和计算效率有关。

他们的计算复杂性描述:

  

稀疏运算的计算复杂度与之成正比   nnz,矩阵中非零元素的数量。计算   复杂性也线性地取决于行大小m和列大小n   矩阵的数量,但与产品m * n,总数无关   零和非零元素。

     

相当复杂的操作的复杂性,例如解决方案   稀疏线性方程组涉及排序和排序等因素   填写,在上一节中讨论。一般来说,   但是,稀疏矩阵运算所需的计算机时间是   与非零的算术运算次数成正比   量。

如果没有厌烦你的算法细节,another answer建议你不要为稀疏只有25%非零的数组而烦恼。他们提供了一些代码供您测试。有关详细信息,请参阅他们的帖子。

A = sprand(2000,2000,0.25);
tic,B = A*A;toc
Elapsed time is 1.771668 seconds.

Af = full(A);
tic,B = Af*Af;toc
Elapsed time is 0.499045 seconds.

答案 2 :(得分:0)

如果你有一个固定维度的矩阵,那么建立可靠答案的最佳方法就是试错。但是,如果你不知道矩阵/向量的维数,那么经验法则是

  

您的稀疏向量应该具有有效恒定数量的非零条目

对于矩阵来说意味着什么

  

您的N x N稀疏矩阵应该有<= c * N个非零条目,其中c是常数&#34;更少&#34;比N

让我们对这条规则进行伪理论解释。我们将考虑一个相当容易的任务,即制作具有双值坐标的两个向量的标量(或点)乘积。现在,如果你有两个长度相同的密集向量N,你的代码看起来像

//define vectors vector, wector as double arrays of length N 
double sum = 0;
for (int i = 0; i < N; i++)
{
    sum += vector[i] * wector[i];
}

这相当于N次增加,N次增殖和N条件分支(循环操作)。这里最昂贵的操作是条件分支,这是非常昂贵的,我们可能忽略乘法和更多的增加。在this question的答案中解释了它如此昂贵的原因。

  

UPD:实际上,在for周期中,您可能会在周期结束时选择一个错误的分支,因为根据定义,要选择的默认分支将进入循环。每个标量产品操作最多可以重启1个管道。

现在让我们看看如何在BLAS中实现稀疏向量。在那里,每个向量由两个数组编码:一个值和一个相应的索引,类似于

1.7    -0.8    3.6
171     83     215

(加上一个告诉假设长度N的整数)。在BLAS文档中指出,索引的排序在这里不起作用,因此数据

-0.8    3.6    1.7
 83     215    171

编码相同的向量。这句话提供了足够的信息来重建标量积的算法。给定由数据int[] indices, double[] valuesint[] jndices, double[] walues编码的两个稀疏向量,人们将在这个&#34;代码&#34;

的行中计算他们的标量积。
double sum = 0;
for (int i = 0; i < indices.length; i++)
{
    for (int j = 0; j < jndices.length; j++)
    {
        if(indices[i] == jndices[j])
        {
            sum += values[indices[i]] * walues[jndices[j]];
        }
    }
}

它为我们提供了总共indices.length * jndices.length * 2 + indices.length条件分支。这意味着,为了应对密集算法,您的向量最多只有sqrt(N)个非零条目。这里的要点是对N的依赖已经是非线性的,所以询问你是否需要1%或10%或25%的填充是没有意义的。对于长度为10的向量,10%是完美的,对于长度为50的长度来说仍然是好的,并且对于长度为100的已经完全毁了。

  

UPD。在此代码段中,您有一个if分支,并且采用错误路径的概率为50%。因此,两个稀疏矢量的标量乘积将是每个稀疏矢量管道重启的平均非零项数的0.5到1倍,这取决于矢量的稀疏程度。数字将被调整:在没有if的{​​{1}}语句中,将首先采用最短的指令,即&#34;什么都不做&#34; ,但是仍然。

请注意,最有效的操作是稀疏和密集向量的标量积。给定elseindices的稀疏向量以及密集向量values,您的代码看起来像

dense

即。你有double sum = 0; for (int i = 0; i < indices.length; i++) { sum += values[indices[i]] * dense[indices[i]]; } 条件分支,这很好。

  

UPD。再一次,我打赌你每次操作最多只能有一次管道重启。另请注意,现代多核处理器中的afaik在两个不同的内核上并行执行,因此在备用分支中,您只需等待最长的分支即可完成。

现在,当矩阵与向量相乘时,你基本上采用了#rows矢量的标量积。通过向量乘法将矩阵乘以矩阵量乘以矩阵中的#((非零)列)。欢迎你自己弄清楚复杂性。

所以这里是所有黑魔法不同矩阵存储的深层理论开始的地方。您可以将稀疏矩阵存储为稀疏行的密集阵列,作为密集行的稀疏数组或稀疏行的稀疏数组。列也一样。问题中引用的Scipy所有有趣的缩写都与此有关。

你会&#34;永远&#34;如果将由稀疏行构建的矩阵与密集矩阵或密集列矩阵相乘,则在速度方面具有优势。您可能希望将稀疏矩阵数据存储为对角线的密集向量 - 因此在convolution neural networks的情况下 - 然后您需要完全不同的算法。您可能希望使矩阵成为块矩阵 - BLAS也是如此 - 并获得合理的计算提升。您可能希望将数据存储为两个矩阵 - 例如,对角线和稀疏矩阵,finite element method就是这种情况。如果总是将行存储的矩阵乘以列向量,则可以使用稀疏性用于一般神经网络(如。快进,extreme learning machineecho state network),但避免乘以矩阵。并且,你将永远&#34;永远&#34;如果你遵循经验法则,通过使用稀疏矩阵获得优势 - 它适用于有限元和卷积网络,但不适用于储层计算。