如何对生成scipy稀疏矩阵的代码进行矢量化?

时间:2018-04-16 00:44:46

标签: python performance numpy machine-learning scipy

让我先解释一下我想做什么。我正在尝试构建一个基于m个包的推荐系统,每个包都有n个功能,存储在m x n稀疏矩阵X中。为此,我尝试运行kNN以获得包的k最接近的匹配。我想构建一个m x m稀疏矩阵K,其中K[i, j]是行X[i]X[j]的点积,如果X[j]是返回的包按kNN表示X[i],否则为0。

这是我写的代码:

X = ...
knn = NearestNeighbors(n_neighbors=self.n_neighbors, metric='l2')
knn.fit(X)
knn_indices = knn.kneighbors(X, return_distance=False)

m, k = X.shape[0], self.n_neighbors
K = lil_matrix((m, m))

for i, indices in enumerate(knn_indices):
    xi = X.getrow(i)
    for j in indices:
        xj = X.getrow(j)
        K[i, j] = xi.dot(xj.T)[0, 0]

我正在试图弄清楚如何提高效率。在我的方案中,m约为120万,n约为50000,k为500,因此性能非常重要。

我填充K的最后一部分是我的程序的瓶颈。 getrow似乎表现得非常糟糕;根据scipy文档,它会复制该行,因此getrow调用每次调用时都可以复制多达50k个元素。此外,在最里面的循环中,我无法弄清楚如何找回dot的标量,而不是创建一个全新的1x1稀疏矩阵。

如何避免这些问题并加速/矢量化此代码的最后部分?感谢。

1 个答案:

答案 0 :(得分:1)

In [21]: from scipy import sparse
In [22]: M = sparse.random(10,10,.2,'csr')
In [23]: M
Out[23]: 
<10x10 sparse matrix of type '<class 'numpy.float64'>'
    with 20 stored elements in Compressed Sparse Row format>

查看M.A,我选择了这个小knn_indices数组进行测试:

In [45]: knn = np.array([[4],[2],[],[1,3]])

你的双循环:

In [46]: for i, indices in enumerate(knn):
    ...:     xi = M[i,:]
    ...:     for j in indices:
    ...:         xj = M[j,:]
    ...:         print((xi*xj.T).A)
    ...:         
[[0.35494592]]
[[0.]]
[[0.08112133]]
[[0.56905781]]

内循环可以压缩:

In [47]: for i, indices in enumerate(knn):
    ...:     xi = M[i,:]
    ...:     xj = M[indices,:]
    ...:     print((xi*xj.T).A)
    ...:         
[[0.35494592]]
[[0.]]
[]
[[0.08112133 0.56905781]]

以及作业:

In [49]: k = sparse.lil_matrix((4,5))
In [50]: for i, indices in enumerate(knn):
    ...:     xi = M[i,:]
    ...:     for j in indices:
    ...:         xj = M[j,:]
    ...:         k[i,j] = (xi*xj.T)[0,0]
    ...:         
    ...:         
In [51]: k.A
Out[51]: 
array([[0.        , 0.        , 0.        , 0.        , 0.35494592],
       [0.        , 0.        , 0.        , 0.        , 0.        ],
       [0.        , 0.        , 0.        , 0.        , 0.        ],
       [0.        , 0.08112133, 0.        , 0.56905781, 0.        ]])

第二个循环

 k[i,indices] = (xi*xj.T)

做同样的事情。

也可以使用i循环执行某些操作,但这至少是一个开始。

knn不需要数组。使用不同的内部列表长度,无论如何都是对象dtype。最好把它留作清单。

填充此lil矩阵的替代方法是在i样式数组中累积indicescoo和点积。

In [64]: r,c,d = [],[],[]
In [65]: for i, indices in enumerate(knn):
    ...:     xi = M[i,:]
    ...:     xj = M[indices,:]
    ...:     t = (xi*xj.T).data
    ...:     if len(t)>0:
    ...:         r.extend([i]*len(indices))
    ...:         c.extend(indices)
    ...:         d.extend(t)
    ...:         
In [66]: r,c,d
Out[66]: 
([0, 3, 3],
 [4, 1, 3],
 [0.3549459176547072, 0.08112132851228658, 0.5690578146292733])
In [67]: sparse.coo_matrix((d,(r,c))).A
Out[67]: 
array([[0.        , 0.        , 0.        , 0.        , 0.35494592],
       [0.        , 0.        , 0.        , 0.        , 0.        ],
       [0.        , 0.        , 0.        , 0.        , 0.        ],
       [0.        , 0.08112133, 0.        , 0.56905781, 0.        ]])

在我的测试用例中,第二行没有任何非零值,需要在循环中进行额外的测试。我不知道这是否比lil方法更快。