与常见的词典相比,为什么lil_matrix和dok_matrix如此之慢?

时间:2015-01-04 22:09:23

标签: python numpy scipy

我想迭代地构建稀疏矩阵,并注意到根据SciPy文档有两个合适的选项:

LiL matrix

  

class scipy.sparse.lil_matrix(arg1,shape = None,dtype = None,   copy = False)[source]基于行的链表稀疏矩阵

     

这是构造稀疏矩阵的有效结构   递增。

DoK matrix

  

class scipy.sparse.dok_matrix(arg1,shape = None,dtype = None,   copy = False)[source]基于密钥的字典稀疏矩阵。

     

这是构造稀疏矩阵的有效结构   递增。

但是,当我运行基准测试比建立一个价值字典字典(后来可以很容易地转换为稀疏矩阵)时,后者比使用任何一个字符串快10到20倍。稀疏矩阵模型:

from scipy.sparse import dok_matrix, lil_matrix
from timeit import timeit
from collections import defaultdict

def common_dict(rows, cols):
    freqs = defaultdict(lambda: defaultdict(int))
    for row, col in zip(rows, cols):
        freqs[row][col] += 1

    return freqs

def dok(rows, cols):
    freqs = dok_matrix((1000,1000))
    for row, col in zip(rows, cols):
        freqs[row,col] += 1

    return freqs

def lil(rows, cols):
    freqs = lil_matrix((1000,1000))
    for row, col in zip(rows, cols):
        freqs[row,col] += 1

    return freqs


def benchmark():
    cols = range(1000)
    rows = range(1000)

    res = timeit("common_dict({},{})".format(rows, cols), 
                 "from __main__ import common_dict", 
                 number=100)

    print("common_dict: {}".format(res))

    res = timeit("dok({},{})".format(rows, cols), 
                 "from __main__ import dok", 
                 number=100)

    print("dok: {}".format(res))

    res = timeit("lil({},{})".format(rows, cols), 
                 "from __main__ import lil", 
                 number=100)

    print("lil: {}".format(res))

结果:

benchmark()

common_dict: 0.11778324202168733
dok: 2.2927695910912007
lil: 1.3541790939634666

什么导致矩阵模型产生这样的开销,是否有某种方法可以加速它?是否存在使用dok或lil比使用dicts的常见词典更喜欢的用例?

2 个答案:

答案 0 :(得分:13)

当我将2个稀疏数组的+=更改为=时:

for row, col in zip(rows, cols):
    #freqs[row,col] += 1
    freqs[row,col] = 1
他们各自的时间减少了一半。消耗最多时间的是索引。使用+=时,必须同时执行__getitem____setitem__

当文档说doklil更适合迭代构建时,它们意味着扩展其基础数据结构比其他格式更容易。

当我尝试使用您的代码制作csr矩阵时,我得到了一个:

  

/usr/lib/python2.7/dist-packages/scipy/sparse/compressed.py:690:SparseEfficiencyWarning:更改csr_matrix的稀疏结构非常昂贵。 lil_matrix效率更高。     SparseEfficiencyWarning)

速度减慢30倍。

因此速度声明与csr等格式相关,而不是相对于纯Python或numpy结构。

您可能希望查看dok_matrix.__get_item__dok_matrix.__set_item__的Python代码,了解执行freq[r,c]时会发生什么。


构建dok的更快捷方式是:

freqs = dok_matrix((1000,1000))
d = dict()
for row, col in zip(rows, cols):
    d[(row, col)] = 1
freqs.update(d)

利用dok是子类字典的事实。请注意,dok矩阵不是字典字典。它的键是(50,50)等元组。

构建相同稀疏数组的另一种快速方法是:

freqs = sparse.coo_matrix((np.ones(1000,int),(rows,cols)))

换句话说,既然您已经拥有rowscols数组(或范围),请计算相应的data数组,然后构造稀疏数组。

但是如果您必须在增量增长步骤之间对矩阵执行稀疏操作,那么doklil可能是您的最佳选择。


为线性代数问题开发了稀疏矩阵,例如求解具有大稀疏矩阵的线性方程。几年前我在MATLAB中使用它们来解决有限差分问题。对于这项工作,计算友好csr格式是最终目标,coo格式是一种方便的初始化格式。

现在,许多SO scipy稀疏问题都来自scikit-learn和文本分析问题。它们还用于生物数据库文件中。但仍然(data),(row,col)定义方法效果最好。

因此,稀疏矩阵从未用于快速增量创建。像字典和列表这样的传统Python结构要好得多。


这是一个利用其字典方法的更快dok次迭代。 update似乎与普通词典一样快。 get的等效索引(freq[row,col])快约3倍。索引可能使用get,但必须有很多开销。

def fast_dok(rows, cols):
    freqs = dok_matrix((1000,1000))
    for row, col in zip(rows,cols):
         i = freqs.get((row,col),0)
         freqs.update({(row,col):i+1})
    return freqs

跳过get,然后执行

         freqs.update({(row,col): 1)

甚至更快 - 比defaultdict示例的defaultdict更快,并且几乎与简单字典初始化({(r, c):1 for r,c in zip(rows, cols)}

一样快

答案 1 :(得分:1)

您的测试不公平的原因有多种。首先,您将包含构造稀疏矩阵的开销作为定时循环的一部分。

其次,可以说更重要的是,您应该使用设计用的数据结构,同时对整个数组进行操作。也就是说,不是迭代行和列并且每次添加1,而只需将1添加到整个数组。