Scipy.sparse.csr_matrix:如何获取前十个值和索引?

22

我有一个大的csr_matrix,我想获取每一行的前十个值及其索引。但我没有找到一个合适的方法来处理这个矩阵。

以下是我的当前解决方案,主要思路是逐行处理:

row = csr_matrix.getrow(row_number).toarray()[0].ravel()
top_ten_indicies = row.argsort()[-10:]
top_ten_values = row[row.argsort()[-10:]]

这样做并没有充分利用csr_matrix的优势,更像是一种暴力解决方案。


当您没有提供解决方案时,很难建议更好的解决方案。我的猜测是您将不得不使用密集版本进行工作,或者逐行处理(可能从 lil 格式开始)。 - hpaulj
@hpaulj 已更新问题,谢谢。 - Patrick
我发现另一个SO问题,它要求整个稀疏矩阵的前几个值。其中一个答案建议使用argpartionargsort更快。但仍然有一个问题,那就是你是否可以比逐行迭代更好。 lilcsr是最快的两种格式。 - hpaulj
3个回答

9
我不认为在这种情况下使用csr格式有什么优势。当然,所有非零值都被收集在一个.data数组中,并且相应的列索引在.indices中。但是它们以不同长度的块存在。这意味着它们无法并行处理或使用numpy数组步幅。
一种解决方案是将这些块填充到相同长度的块中。这就是.toarray()所做的。然后您可以使用argsort(axis=1)或argpartition找到最大值。
另一种方法是将它们分成按行大小的块,并处理每个块。这就是您使用.getrow的方式。另一种分割它们的方法是转换为lil格式,并处理.data和.rows数组的子列表。
可能的第三个选择是使用ufunc reduceat方法。这使您可以将ufunc reduction方法应用于数组的连续块。已经有了像np.add这样的已建立的ufunc可以利用这一点。argsort不是这样的函数。但是有一种方法可以从Python函数构造ufunc,并获得比常规Python迭代快一些的速度。[我需要查找最近的SO问题来说明这一点。]
我将使用一个更简单的函数对其进行说明,即对行求和。
如果A2是csr矩阵。
A2.sum(axis=1)  # the fastest compile csr method
A2.A.sum(axis=1)  # same, but with a dense intermediary
[np.sum(l.data) for l in A2]  # iterate over the rows of A2
[np.sum(A2.getrow(i).data) for i in range(A2.shape[0])]  # iterate with index
[np.sum(l) for l in A2.tolil().data]  # sum the sublists of lil format
np.add.reduceat(A2.data, A2.indptr[:-1])  # with reduceat
A2.sum(axis=1) 是通过矩阵乘法实现的。虽然这与排序问题无关,但这是一个有趣的看待求和问题的方式。请记住,csr格式是为了有效地进行乘法而开发的。
对于我当前的示例矩阵(为另一个 SO 稀疏问题创建),
<8x47752 sparse matrix of type '<class 'numpy.float32'>'
     with 32 stored elements in Compressed Sparse Row format>

一些比较时间如下:
In [694]: timeit np.add.reduceat(A2.data, A2.indptr[:-1])
100000 loops, best of 3: 7.41 µs per loop

In [695]: timeit A2.sum(axis=1)
10000 loops, best of 3: 71.6 µs per loop

In [696]: timeit [np.sum(l) for l in A2.tolil().data]
1000 loops, best of 3: 280 µs per loop

其他一切操作的执行时间都是1毫秒或更长。

我建议专注于开发您的单行函数,例如:

def max_n(row_data, row_indices, n):
    i = row_data.argsort()[-n:]
    # i = row_data.argpartition(-n)[-n:]
    top_values = row_data[i]
    top_indices = row_indices[i]  # do the sparse indices matter?
    return top_values, top_indices, i

然后看看它如何适用于这些迭代方法中的一个。 tolil() 看起来最有前途。
我还没有解决如何收集这些结果的问题。 它们应该是列表中的列表,具有 10 列的数组,每行具有 10 个值的另一个稀疏矩阵等吗?
按列索引排序大量稀疏行并保存前 K 个值的类似问题 - 几年前的类似问题,但无人回答。 scipy 稀疏矩阵每行或每列的 argmax - 最近提出的问题,寻求 csr 行的 argmax。 我讨论了一些相同的问题。 如何加快 numpy 中的循环? - 使用 np.frompyfunc 创建 ufunc 的示例。 我不知道生成的函数是否具有 .reduceat 方法。 增加稀疏矩阵中前 k 个元素的值 - 获取 csr 的前 k 个元素(不按行)。 使用 argpartition
使用 np.frompyfunc 实现的行求和:
In [741]: def foo(a,b):
    return a+b  
In [742]: vfoo=np.frompyfunc(foo,2,1)
In [743]: timeit vfoo.reduceat(A2.data,A2.indptr[:-1],dtype=object).astype(float)
10000 loops, best of 3: 26.2 µs per loop

这是一个相当不错的速度。但我想不出通过归约来实现argsort(需要两个参数的二进制函数)的方法。因此,对于这个问题来说,这可能是一个死胡同。


8

为了回答原始问题(对于像我这样找到这个问题的人来说),以下是一种使用基于@hpaulj's建议的多进程解决方案,该方案将数据转换为lil_matrix,并迭代行。

from multiprocessing import Pool

def _top_k(args):
    """
    Helper function to process a single row of top_k
    """
    data, row = args
    data, row = zip(*sorted(zip(data, row), reverse=True)[:k])
    return data, row

def top_k(m, k):
    """
    Keep only the top k elements of each row in a csr_matrix
    """
    ml = m.tolil()
    with Pool() as p:
        ms = p.map(_top_k, zip(ml.data, ml.rows))
    ml.data, ml.rows = zip(*ms)
    return ml.tocsr()

2
[:k] 未定义。 - luky
顺便提一下,可以通过使用heapq而不是对整行进行排序然后取前k个值来改进_top_k。即使矩阵是稀疏的,仍然可能有一些行比k具有更多的条目。 - Jeremy Lane

5

需要迭代每一行并分别获取每行的顶部索引。但是,这个循环可以进行jit优化(和并行化)以获得极快的函数。

@nb.njit(cache=True)
def row_topk_csr(data, indices, indptr, K):
    m = indptr.shape[0] - 1
    max_indices = np.zeros((m, K), dtype=indices.dtype)
    max_values = np.zeros((m, K), dtype=data.dtype)

    for i in nb.prange(m):
        top_inds = np.argsort(data[indptr[i] : indptr[i + 1]])[::-1][:K]
        max_indices[i] = indices[indptr[i] : indptr[i + 1]][top_inds]
        max_values[i] = data[indptr[i] : indptr[i + 1]][top_inds]

    return max_indices, max_values

这样调用:

top_pred_indices, _ = row_topk_csr(csr_mat.data, csr_mat.indices, csr_mat.indptr, K)

我需要经常执行这个操作,而且这个函数对我来说足够快,在1百万行 x 40万列的稀疏矩阵上运行时间小于1秒。

希望对您有所帮助。


谢谢@Deepak Saini,不幸的是,当K大于给定行中的非零值时(对于非常稀疏的矩阵并不少见),这种方法无法奏效。您有什么解决办法吗?我把赋值改成了max_indices和max_values到“max_indices[i, :len(top_inds)]”,但这样显然会留下0,也是错误的。 - SimonCW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接