解压缩稀疏矩阵性能调整

时间:2020-10-01 18:09:15

标签: python performance numpy sparse-matrix

我正在使用由ING的数据科学家创建的sparse_dot_topn库来搜索大量公司名称(近150万条记录)中的几乎重复项。该库的最新更新现在使得可以使用多个线程来计算两个矩阵之间的叉积(即余弦相似度)。我运行了一个快速基准测试,性能提升非常明显(取决于一个人可以在其机器/远程服务器上使用多少个内核):

+-----------+--------------+
| # threads | time (%M:%S) |
+-----------+--------------+
| 32        | 03:43:12     |
+-----------+--------------+
| 16        | 05:16:97     |
+-----------+--------------+
| 8         | 08:11:69     |
+-----------+--------------+
| 4         | 13:32:72     |
+-----------+--------------+
| 2         | 24:02:28     |
+-----------+--------------+
| 1         | 47:11:30     |
+-----------+--------------+

为了轻松探究结果,我需要解压缩所得的稀疏矩阵。幸运的是,我发现了以下由 Chris van den Berg 编写的帮助程序功能,该功能恰好做到了(链接到Chris的博客文章here):

def get_matches_df(sparse_matrix, name_vector, top=100):
    non_zeros = sparse_matrix.nonzero()

    sparserows = non_zeros[0]
    sparsecols = non_zeros[1]

    if top:
        nr_matches = top
    else:
        nr_matches = sparsecols.size

    left_side = np.empty([nr_matches], dtype=object)
    right_side = np.empty([nr_matches], dtype=object)
    similairity = np.zeros(nr_matches)

    for index in range(0, nr_matches):
        left_side[index] = name_vector[sparserows[index]]
        right_side[index] = name_vector[sparsecols[index]]
        similairity[index] = sparse_matrix.data[index]

    return pd.DataFrame(
        {"left_side": left_side, "right_side": right_side, "similairity": similairity}
    )

上面的函数有一个可选参数,它只能查看前一个 n 值,但我必须在完整数据上运行它。我目前的问题是,这需要很长时间才能完成(大约1个小时)。

问:我想知道如何提高性能(如果可能)?尤其是因为我有很多核心,而我没有使用该核心。

关于性能调优,我不是专家。我探索的一种选择是Numba。我用@njit(parallel=True)装饰了函数,并使用Numba的prange而不是range来指定可以并行化循环,但是失败了。我的理解是Numba无法处理字符串值(即我的公司名称)。

对于提高性能的可能方法的任何帮助,将不胜感激。

2 个答案:

答案 0 :(得分:1)

没有一些示例,我不能确定这就是您想要的,但是我认为这就是您想要的。我对您的示例中的top感到困惑,因为它只获取第一个结果,而不是具有最大值的结果。

import pandas as pd
from scipy import sparse
import random
import string

arr = sparse.random(100,100,density=0.02).tocoo()
name_vec = pd.Series(''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)) for _ in range(100))

pd.DataFrame({"left_side": name_vec[arr.row].tolist(), 
              "right_side": name_vec[arr.col].tolist(), 
              "similairity": arr.data})

在运行时方面,您可以通过避免选择序列->列表->系列步骤来进一步清除此问题。

答案 1 :(得分:1)

我假设sparse_matrix是一个相关矩阵,所以sparse_matrix是对称的。

首先,创建一个name_vectorsparse_matrix以便使用

import string

N = 10

# create an array of names
name_vector = np.array(list(string.ascii_lowercase)[:N])
# create a correlation matrix (which is obviously symmetric)
sparse_matrix = np.random.rand(N,N)
sparse_matrix = (sparse_matrix + sparse_matrix.T)/2
zeros_mask = np.where(np.random.rand(N,N)>=0.5,False,True)
sparse_matrix[zeros_mask] = 0.

如您所见,name_vector是一个数组

array(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'], dtype='<U1')

对应于10家公司的名称。 sparse_matrix在构造上是对称的,并且其中某些条目由sparse_matrix[zeros_mask] = 0.分配为0。

有这两种成分,这是我的解决方法

top = None 

non_zeros = sparse_matrix.nonzero()
sparserows = non_zeros[0]
sparsecols = non_zeros[1]
sparse_idx = sparserows*sparse_matrix.shape[1]+sparsecols

if top:
    nr_matches = top
else:
    nr_matches = sparsecols.size

left_side = name_vector[sparserows[:nr_matches]]
right_side = name_vector[sparsecols[:nr_matches]]
similairity = np.take(sparse_matrix,sparse_idx[:nr_matches])

pd.DataFrame({"left_side": left_side, 
              "right_side": right_side, 
              "similairity": similairity})

和返回的DataFrame如下

left_side   right_side  similairity
0   a   c   0.760297
1   a   d   0.441365
2   a   g   0.669365
3   b   a   0.221993
4   b   c   0.840993
...

由于使用advanced indexing而不是for循环,它将更快。