【问题标题】:Group by sparse matrix in scipy and return a matrix在 scipy 中按稀疏矩阵分组并返回一个矩阵
【发布时间】:2017-01-31 17:20:28
【问题描述】:

关于使用groupby 处理稀疏矩阵有几个问题。然而,输出似乎是列表、dictionariesdataframes 和其他对象。

我正在处理一个 NLP 问题,并希望在处理过程中将所有数据保存在稀疏 scipy 矩阵中,以防止内存错误。

这是上下文:

我已经对一些文档进行了矢量化处理(sample data here):

import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer

df = pd.read_csv('groupbysparsematrix.csv')
docs = df['Text'].tolist()

vectorizer = CountVectorizer()
train_X = vectorizer.fit_transform(docs)

print("Dimensions of training set: {0}".format(train_X.shape))
print type(train_X)

Dimensions of training set: (8, 180)
<class 'scipy.sparse.csr.csr_matrix'>

从原始数据框中,我使用日期(一年中的一天)来创建我想总结的组:

from scipy import sparse, hstack    

df['Date'] = pd.to_datetime(df['Date'])
groups = df['Date'].apply(lambda x: x.strftime('%j'))
groups_X = sparse.csr_matrix(groups.astype(float)).T
train_X_all = sparse.hstack((train_X, groups_X))

print("Dimensions of concatenated set: {0}".format(train_X_all.shape))

Dimensions of concatenated set: (8, 181)

现在我想应用groupby(或类似函数)来查找每天每个令牌的总和。我希望输出是另一个稀疏的 scipy 矩阵。

输出矩阵为 3 x 181,如下所示:

 1, 1, 1, ..., 2, 1, 3
 2, 1, 3, ..., 1, 1, 4
 0, 0, 0, ..., 1, 2, 5

其中第 1 到 180 列代表令牌,第 181 列代表一年中的某一天。

【问题讨论】:

  • 你说的是pandasgroupby吗?你能举一个密集数组的工作例子吗?有一个 pandas 稀疏格式,但它与稀疏矩阵的交互仍在开发中。
  • 第 181 列 - 是否稀疏?
  • column 181 (groups_X) 是一个scipy.sparse.csc.csc_matrix,但实际上它是密集的,因为每个观察都有一个日期。

标签: python matrix scipy nlp


【解决方案1】:

计算csr 稀疏矩阵的选定列(或行)之和的最佳方法是矩阵乘积与另一个稀疏矩阵相乘,该矩阵在您想要求和的位置为 1。事实上csr sum(对于整行或整列)是通过矩阵乘积来工作的,索引行(或列)也是通过乘积来完成的(https://stackoverflow.com/a/39500986/901925

所以我将日期数组分组,并使用该信息来构造求和“掩码”。

为了便于讨论,考虑这个密集数组:

In [117]: A
Out[117]: 
array([[0, 2, 7, 5, 0, 7, 0, 8, 0, 7],
       [0, 0, 3, 0, 0, 1, 2, 6, 0, 0],
       [0, 0, 0, 0, 2, 0, 5, 0, 0, 0],
       [4, 0, 6, 0, 0, 5, 0, 0, 1, 4],
       [0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
       [0, 7, 0, 8, 1, 0, 9, 0, 2, 4],
       [9, 0, 8, 4, 0, 0, 0, 0, 9, 7],
       [0, 0, 0, 1, 2, 0, 2, 0, 4, 7],
       [3, 0, 1, 0, 0, 0, 0, 0, 0, 2],
       [0, 0, 1, 8, 5, 0, 0, 0, 8, 0]])

制作稀疏副本:

In [118]: M=sparse.csr_matrix(A)

根据最后一列生成一些组; collections.defaultdict 是一个方便的工具:

In [119]: grps=defaultdict(list)
In [120]: for i,v in enumerate(A[:,-1]):
     ...:     grps[v].append(i)

In [121]: grps
Out[121]: defaultdict(list, {0: [1, 2, 4, 9], 2: [8], 4: [3, 5], 7: [0, 6, 7]})

我可以迭代这些组,收集 M 的行,对这些行求和并生成:

In [122]: {k:M[v,:].sum(axis=0) for k, v in grps.items()}
Out[122]: 
{0: matrix([[0, 0, 4, 8, 7, 2, 7, 6, 8, 0]], dtype=int32),
 2: matrix([[3, 0, 1, 0, 0, 0, 0, 0, 0, 2]], dtype=int32),
 4: matrix([[4, 7, 6, 8, 1, 5, 9, 0, 3, 8]], dtype=int32),
 7: matrix([[ 9,  2, 15, 10,  2,  7,  2,  8, 13, 21]], dtype=int32)}

在最后一列中,值包括 2*4 和 3*7

所以有 2 个任务 - 收集组,无论是使用这个 defaultdict,还是itertools.groupby(在这种情况下需要排序),或者pandas groupby。其次,这个行集合和求和。这个字典迭代在概念上很简单。

掩码矩阵可能像这样工作:

In [141]: mask=np.zeros((10,10),int)
In [142]: for i,v in enumerate(A[:,-1]): # same sort of iteration
     ...:     mask[v,i]=1
     ...:     
In [143]: Mask=sparse.csr_matrix(mask)
...
In [145]: Mask.A
Out[145]: 
array([[0, 1, 1, 0, 1, 0, 0, 0, 0, 1],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
       ....
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)
In [146]: (Mask*M).A
Out[146]: 
array([[ 0,  0,  4,  8,  7,  2,  7,  6,  8,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 3,  0,  1,  0,  0,  0,  0,  0,  0,  2],
       [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 4,  7,  6,  8,  1,  5,  9,  0,  3,  8],
       [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 9,  2, 15, 10,  2,  7,  2,  8, 13, 21],
       [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0]], dtype=int32)

Mask*M 具有与字典行相同的值,但多了一个 0。我可以用lil 格式隔离非零值:

In [147]: (Mask*M).tolil().data
Out[147]: 
array([[4, 8, 7, 2, 7, 6, 8], [], [3, 1, 2], [],
       [4, 7, 6, 8, 1, 5, 9, 3, 8], [], [],
       [9, 2, 15, 10, 2, 7, 2, 8, 13, 21], [], []], dtype=object)

我可以直接使用coo稀疏风格的输入构造Mask矩阵:

Mask = sparse.csr_matrix((np.ones(A.shape[0],int),
    (A[:,-1], np.arange(A.shape[0]))), shape=(A.shape))

这样应该更快,避免内存错误(没有循环或大的密集数组)。

【讨论】:

  • 很好的解决方案。这些在小型数据集上运行良好,但是否有替代方案可以将它们应用于更大的数据集?我的特殊问题是 (69424, 685296) 稀疏矩阵。当我尝试 {k:M[v,:].sum(axis=0) for k, v in grps.items()} 时 jupyter 挂起,当我尝试制作 (69424, 685296) 零掩码时出现 MemoryError。
  • 我添加了一种直接使用coo 输入样式构造组Mask 矩阵的方法。
  • 我已经尝试使用示例 A 数据和我自己的数据进行编辑,但我收到了 ValueError: setting an array element with a sequence. 我已经搜索过 SO 并尝试通过更改数据类型和重新排列语法来解决问题,但可以不要让它工作。有什么想法吗?
  • 我必须查看错误堆栈。通常,该错误是由像x[i] = np.array([1,2,3]) 这样的语句产生的,其中x 是1d。 RHS 需要一个标量值,但 LHS 给出的是一个数组或列表(即序列)。所以有些东西的维度比预期的要多。
  • 感谢您的帮助。 csr_matrix((data, (row, col)), shape=()) 解决方案的问题在于它使用 A 这是一个 np 数组。如果您尝试使用M - csr 矩阵 - 您会得到ValueError: setting an array element with a sequence。我的想法是任何使用密集矩阵的解决方案都将在大型数据集上失败。
【解决方案2】:

这是一个使用LabelBinarizer 和矩阵乘法的技巧。

from sklearn.preprocessing import LabelBinarizer
lb = LabelBinarizer(sparse_output=True)
grouped = lb.fit_transform(groups).T.dot(train_X)

grouped 是大小为 3 x 180 的输出稀疏矩阵。您可以在 lb.classes_ 中找到其组的列表。

【讨论】:

    猜你喜欢
    • 2016-05-26
    • 1970-01-01
    • 1970-01-01
    • 2019-10-04
    • 1970-01-01
    • 2023-04-10
    • 1970-01-01
    • 2012-05-15
    • 2017-07-21
    相关资源
    最近更新 更多