【问题标题】:Adding a very repetitive matrix to a sparse one in numpy/scipy?在 numpy/scipy 中向稀疏矩阵添加一个非常重复的矩阵?
【发布时间】:2013-02-20 18:43:32
【问题描述】:

我正在尝试在 NumPy/Scipy 中实现一个函数,以在单个(训练)向量和大量其他(观察)向量之间计算 Jensen-Shannon divergence。观察向量存储在一个非常大的 (500,000x65536) Scipy sparse matrix 中(密集矩阵不适合内存)。

作为算法的一部分,我需要为每个观察向量 Oi 计算 T+Oi,其中 T 是训练向量。我无法使用 NumPy 的常用广播规则找到一种方法,因为稀疏矩阵似乎不支持这些(如果 T 保留为密集数组,Scipy 会尝试首先使稀疏矩阵密集,它运行内存不足;如果我将 T 设为稀疏矩阵,则 T+Oi 会失败,因为形状不一致)。

目前,我正在将训练向量平铺成 500,000x65536 稀疏矩阵,这一步骤效率极低:

training = sp.csr_matrix(training.astype(np.float32))
tindptr = np.arange(0, len(training.indices)*observations.shape[0]+1, len(training.indices), dtype=np.int32)
tindices = np.tile(training.indices, observations.shape[0])
tdata = np.tile(training.data, observations.shape[0])
mtraining = sp.csr_matrix((tdata, tindices, tindptr), shape=observations.shape)

但这会占用大量内存(大约 6GB),而它只存储约 1500 个“真实”元素。构建起来也很慢。

我试图通过使用 stride_tricks 使 CSR 矩阵的 indptr 和数据成员不会在重复数据上使用额外的内存来变得聪明。

training = sp.csr_matrix(training)
mtraining = sp.csr_matrix(observations.shape,dtype=np.int32)
tdata = training.data
vdata = np.lib.stride_tricks.as_strided(tdata, (mtraining.shape[0], tdata.size), (0, tdata.itemsize))
indices = training.indices
vindices = np.lib.stride_tricks.as_strided(indices, (mtraining.shape[0], indices.size), (0, indices.itemsize))
mtraining.indptr = np.arange(0, len(indices)*mtraining.shape[0]+1, len(indices), dtype=np.int32)
mtraining.data = vdata
mtraining.indices = vindices

但这不起作用,因为跨步视图 mtraining.data 和 mtraining.indices 是错误的形状(根据this answer,没有办法使它成为正确的形状)。尝试使用 .flat 迭代器使它们看起来平坦失败,因为它看起来不够像数组(例如,它没有 dtype 成员),并且使用 flatten() 方法最终会制作副本。

有什么办法可以解决吗?

【问题讨论】:

  • 如果你想一次生成所有的总和,那么无论如何你都需要 6GB 的存储空间,所以延迟它真的没什么可取的。只需确保使用+= 就地求和!顺便说一句,您的平铺实现非常智能和高效,我认为您没有比这更好的了:我尝试给csr_matrix 提供一个用as_strided 重新整形的矢量视图以获得500000 行,并且它比您的方法花费的时间要长得多,我认为内部数组正在被复制,打破了魔力。正如您所指出的,您的第二种方法不能用 numpy 完成。
  • CSR 矩阵不能就地修改,不幸的是(+= 引发 NotImplemented)。所以我想我坚持使用(理论上)需要的 3 倍内存,这很痛苦,因为我接近我(慷慨的)32GB 的极限。

标签: python matrix numpy scipy sparse-matrix


【解决方案1】:

您的另一个选择,我什至没有考虑过,是自己实现稀疏格式的总和,这样您就可以充分利用数组的周期性。如果您滥用 scipy 稀疏矩阵的这种特殊行为,这很容易做到:

>>> a = sps.csr_matrix([1,2,3,4])
>>> a.data
array([1, 2, 3, 4])
>>> a.indices
array([0, 1, 2, 3])
>>> a.indptr
array([0, 4])

>>> b = sps.csr_matrix((np.array([1, 2, 3, 4, 5]),
...                     np.array([0, 1, 2, 3, 0]),
...                     np.array([0, 5])), shape=(1, 4))
>>> b
<1x4 sparse matrix of type '<type 'numpy.int32'>'
    with 5 stored elements in Compressed Sparse Row format>
>>> b.todense()
matrix([[6, 2, 3, 4]])

因此,您甚至不必寻找训练向量与观察矩阵的每一行之间的重合来将它们相加:只需将所有数据与正确的指针填入其中,以及需要求和的数据,数据被访问时会被求和。

编辑

鉴于第一个代码的速度很慢,您可以用内存换取速度,如下所示:

def csr_add_sparse_vec(sps_mat, sps_vec) :
    """Adds a sparse vector to every row of a sparse matrix"""
    # No checks done, but both arguments should be sparse matrices in CSR
    # format, both should have the same number of columns, and the vector
    # should be a vector and have only one row.

    rows, cols = sps_mat.shape
    nnz_vec = len(sps_vec.data)
    nnz_per_row = np.diff(sps_mat.indptr)
    longest_row = np.max(nnz_per_row)

    old_data = np.zeros((rows * longest_row,), dtype=sps_mat.data.dtype)
    old_cols = np.zeros((rows * longest_row,), dtype=sps_mat.indices.dtype)

    data_idx = np.arange(longest_row) < nnz_per_row[:, None]
    data_idx = data_idx.reshape(-1)
    old_data[data_idx] = sps_mat.data
    old_cols[data_idx] = sps_mat.indices
    old_data = old_data.reshape(rows, -1)
    old_cols = old_cols.reshape(rows, -1)

    new_data = np.zeros((rows, longest_row + nnz_vec,),
                        dtype=sps_mat.data.dtype)
    new_data[:, :longest_row] = old_data
    del old_data
    new_cols = np.zeros((rows, longest_row + nnz_vec,),
                        dtype=sps_mat.indices.dtype)
    new_cols[:, :longest_row] = old_cols
    del old_cols
    new_data[:, longest_row:] = sps_vec.data
    new_cols[:, longest_row:] = sps_vec.indices
    new_data = new_data.reshape(-1)
    new_cols = new_cols.reshape(-1)
    new_pointer = np.arange(0, (rows + 1) * (longest_row + nnz_vec),
                            longest_row + nnz_vec)

    ret = sps.csr_matrix((new_data, new_cols, new_pointer),
                         shape=sps_mat.shape)
    ret.eliminate_zeros()

    return ret

没有以前那么快了,但是1s左右可以做10000行:

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

In [3]: b
Out[3]: 
<1x65536 sparse matrix of type '<type 'numpy.float64'>'
    with 1500 stored elements in Compressed Sparse Row format>

In [4]: csr_add_sparse_vec(a, b)
Out[4]: 
<10000x65536 sparse matrix of type '<type 'numpy.float64'>'
    with 30000000 stored elements in Compressed Sparse Row format>

In [5]: %timeit csr_add_sparse_vec(a, b)
1 loops, best of 3: 956 ms per loop

编辑这段代码非常非常慢

def csr_add_sparse_vec(sps_mat, sps_vec) :
    """Adds a sparse vector to every row of a sparse matrix"""
    # No checks done, but both arguments should be sparse matrices in CSR
    # format, both should have the same number of columns, and the vector
    # should be a vector and have only one row.

    rows, cols = sps_mat.shape

    new_data = sps_mat.data
    new_pointer = sps_mat.indptr.copy()
    new_cols = sps_mat.indices

    aux_idx = np.arange(rows + 1)

    for value, col in itertools.izip(sps_vec.data, sps_vec.indices) :
        new_data = np.insert(new_data, new_pointer[1:], [value] * rows)
        new_cols = np.insert(new_cols, new_pointer[1:], [col] * rows)
        new_pointer += aux_idx

    return sps.csr_matrix((new_data, new_cols, new_pointer),
                          shape=sps_mat.shape)

【讨论】:

  • 不幸的是,我认为你的意思是“密度 = 1500/65536.0”——否则你会得到密度 = 0,这确实非常快 :) 一旦我修复了这个问题,我发现 csr_add_sparse_vec 非常慢,它需要大约 100 行,只有 100 行。
  • @BrendanDolan-Gavitt 我总是用from __future__ import division 开始我的python 脚本来避免这种情况,显然我这次没有......我已经用快x10,000 倍的版本编辑了我的答案,这仍然有点慢,你正在看大约 1 分钟。将向量添加到整个矩阵中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-26
  • 2014-12-21
  • 1970-01-01
  • 2017-07-20
  • 1970-01-01
  • 2012-02-20
相关资源
最近更新 更多