获取numpy稀疏矩阵行的范数

6

我有一个稀疏矩阵,是通过使用Sklearn的TfidfVectorizer对象获得的:

vect = TfidfVectorizer(sublinear_tf=True, max_df=0.5, analyzer='word', vocabulary=my_vocab, stop_words='english')
tfidf = vect.fit_transform([my_docs])

稀疏矩阵是指非零元素数目相对于总元素数目较小的矩阵,具体内容如下(为了通用性已删除数字):
<sparse matrix of type '<type 'numpy.float64'>'
with stored elements in Compressed Sparse Row format>]

我将尝试为每一行获取一个数字值,以告诉我文档中包含我正在查找的术语的数量有多高。我并不在意它包含哪些单词,我只想知道它包含了多少个单词。因此,我希望获得每行或行*行.T的范数。然而,我很难使用numpy来实现这一点。
我的第一个方法只是简单地执行:
tfidf[i] * numpy.transpose(tfidf[i])

然而,据说numpy不会转置少于一个维度的数组,因此只会平方向量。因此我尝试了以下方法:
tfidf[i] * numpy.transpose(numpy.atleast_2d(tfidf[0]))

但是numpy.transpose(numpy.atleast_2d(tfidf[0]))仍然无法转置行。

我尝试获取行的范数(这种方法可能更好)。我的初始方法是使用numpy.linalg。

numpy.linalg.norm(tfidf[0])

但是这给了我一个“维度不匹配”的错误。因此,我尝试手动计算范数。我首先将一个变量设置为稀疏矩阵的NumPy数组版本,并打印第一行的长度:

my_array = numpy.array(tfidf)
print my_array
print len(my_array[0])

它正确地打印出了我的数组,但当我尝试访问长度时,它告诉我:
IndexError: 0-d arrays can't be indexed

我只是想获取fit_transform返回的稀疏矩阵中每行的数字值,最好能得到范数。非常感谢您的帮助。

它是哪种稀疏矩阵类型? - askewchan
嗨,感谢回复。我编辑了我的答案以显示类型。 - Sterling
我可以解释最后一个错误:调用 np.array(tfidf) 会将整个矩阵作为对象生成一个1元素和0维数组。要将其转换为普通数组,请使用 tfidf.toarray(),或者要获取矩阵,请使用 tfidf.todense() - askewchan
太好了!那样可以用linalg.norm()函数计算每一行的范数。现在我只需要弄清楚如何使每一行的范数不等于1。我原以为整个tfidf矩阵被归一化,而不是每个个体的行... - Sterling
我找到了一种使用sparse形式获取范数的方法,即tfidf.multiply(tfidf).sum(1)。请查看我的答案以获取更多细节。 - askewchan
3个回答

11

一些简单的虚假数据:

a = np.arange(9.).reshape(3,3)
s = sparse.csr_matrix(a)

要从稀疏矩阵中获取每行的范数,可以使用以下代码:

np.sqrt(s.multiply(s).sum(1))

并且重新调整后的s将会是:

s.multiply(1/np.sqrt(s.multiply(s).sum(1)))

或者在重新归一化之前保持其稀疏:

s.multiply(sparse.csr_matrix(1/np.sqrt(s.multiply(s).sum(1))))

要从中获取普通矩阵或数组,请使用:

m = s.todense()
a = s.toarray()

如果您有足够的内存来使用密集矩阵版本,则可以使用以下方法获取每行的范数:

n = np.sqrt(np.einsum('ij,ij->i',a,a))
或者
n = np.apply_along_axis(np.linalg.norm, 1, a)

进行规范化,可以这样做:

an = a / n[:, None]

或者,为了原地规范化初始数组:

a /= n[:, None]

[:, None] 的作用是将 n 转换成一个垂直的数组。


6

scipy.sparse是一个很好的包,每个版本都在不断完善,但仍有许多东西还不够成熟。如果你自己实现一些算法,可以得到显著的性能提升。例如,与@askewchan使用scipy函数的实现相比,可以提高7倍的效率:

In [18]: a = sps.rand(1000, 1000, format='csr')

In [19]: a
Out[19]: 
<1000x1000 sparse matrix of type '<type 'numpy.float64'>'
    with 10000 stored elements in Compressed Sparse Row format>

In [20]: %timeit a.multiply(a).sum(1)
1000 loops, best of 3: 288 us per loop

In [21]: %timeit np.add.reduceat(a.data * a.data, a.indptr[:-1])
10000 loops, best of 3: 36.8 us per loop

In [24]: np.allclose(a.multiply(a).sum(1).ravel(),
    ...:             np.add.reduceat(a.data * a.data, a.indptr[:-1]))
Out[24]: True

您可以通过以下方式在原地对数组进行相似的标准化操作:
norm_rows = np.sqrt(np.add.reduceat(a.data * a.data, a.indptr[:-1]))
nnz_per_row = np.diff(a.indptr)
a.data /= np.repeat(norm_rows, nnz_per_row)

如果您经常使用稀疏矩阵,请阅读维基百科关于压缩稀疏格式的页面,通常可以找到比默认方式更好的方法来完成任务。


2

这是一个矩阵范数 - CutePoison

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