手动设置 scipy 稀疏矩阵形状的含义

Implications of manually setting scipy sparse matrix shape

我需要对 TF-IDF 模型进行在线训练。我发现 scipy 的 TfidfVectorizer 不支持在线时尚培训,所以我正在实施自己的 CountVectorizer 来支持在线培训,然后使用 scipy 的 TfidfTransformer 来更新 tf-idf将预定义数量的文档输入语料库后的值。

我发现 here 您不应该向 numpy 数组添加行或列,因为所有数据都需要复制,以便将其存储在连续的内存块中。

不过后来我也发现其实用scipy稀疏矩阵可以manually change the matrix's shape.

Numpy reshape docs 说:

It is not always possible to change the shape of an array without copying the data. If you want an error to be raised when the data is copied, you should assign the new shape to the shape attribute of the array

由于稀疏矩阵的 "reshaping" 是通过分配新的形状来完成的,可以说数据没有被复制吗?这样做有什么影响?有效率吗?

代码示例:

matrix = sparse.random(5, 5, .2, 'csr') # Create (5,5) sparse matrix
matrix._shape = (6, 6) # Change shape to (6, 6)
# Modify data on new empty row

我还想扩展我的问题,询问有关 vstack 等允许一个到 append arrays to one another 的方法(与添加一行相同)。 vstack 是否正在复制整个数据,以便将其存储为我的第一个 link 中所述的连续内存块? hstack 呢?


编辑: 因此,在 之后,我实现了一种方法来更改稀疏矩阵中一行的值。

现在,将添加新空行的想法与修改现有值的想法结合起来,我想到了以下内容:

matrix = sparse.random(5, 3, .2, 'csr')
matrix._shape = (6, 3)
# Update indptr to let it know we added a row with nothing in it.
matrix.indptr = np.hstack((matrix.indptr, matrix.indptr[-1]))

# New elements on data, indices format
new_elements = [1, 1]
elements_indices = [0, 2] 

# Set elements for new empty row
set_row_csr_unbounded(matrix, 5, new_elements, elements_indices)

我运行在同一执行过程中多次执行上述代码并且没有出现错误。但是,一旦我尝试添加一个新列(那么就不需要更改 indptr),当我尝试更改值时就会出现错误。关于为什么会发生这种情况的任何线索?

嗯,因为 set_row_csr_unbounded 在下面使用 numpy.r_,我想我最好使用 lil_matrix。即使所有的元素,一旦添加就无法修改。我说得对吗?

我认为 lil_matrix 会更好,因为我假设 numpy.r_ 正在复制数据。

numpy中,reshape表示改变shape以保持相同数量的元素。所以形状项的乘积不能改变。

最简单的例子是

np.arange(12).reshape(3,4)

赋值方法为:

x = np.arange(12)
x.shape = (3,4)

method(或np.reshape(...))returns一个新数组。 shape 作业就地工作。

文档指出,您引用的内容在执行类似

的操作时会发挥作用
x = np.arange(12).reshape(3,4).T
x.reshape(3,4)   # ok, but copy
x.shape = (3,4)  # raises error

为了更好地理解这里发生了什么,打印不同阶段的数组,并查看原始 0,1,2,... 连续性如何变化。 (这留作 reader 的练习,因为它不是更大问题的核心。)

有一个 resize 函数和方法,但用得不多,而且它在视图和副本方面的行为很棘手。

np.concatenate(以及 np.stacknp.vstack 等变体)创建新数组,并从输入中复制所有数据。

列表(和对象 dtype 数组)包含指向元素(可能是数组)的指针,因此不需要复制数据。

稀疏矩阵将其数据(和 row/col 索引)存储在格式不同的各种属性中。 coocsrcsc 有 3 个一维数组。 lil 有 2 个包含列表的对象数组。 dok 是一个字典子类。

lil_matrix 实现了 reshape 方法。其他格式没有。与 np.reshape 一样,尺寸的乘积不能改变。

理论上,稀疏矩阵可以 'embedded' 在一个更大的矩阵中,数据复制最少,因为所有新值都将是默认值 0,并且不占用任何 space。但尚未针对任何格式制定该操作的详细信息。

sparse.hstacksparse.vstack(不要在稀疏矩阵上使用 numpy 版本)通过组合输入的 coo 属性(通过 sparse.bmat).所以是的,他们制作了新数组 (datarowcol)。

制作更大稀疏矩阵的最小示例:

In [110]: M = sparse.random(5,5,.2,'coo')
In [111]: M
Out[111]: 
<5x5 sparse matrix of type '<class 'numpy.float64'>'
    with 5 stored elements in COOrdinate format>
In [112]: M.A
Out[112]: 
array([[0.        , 0.80957797, 0.        , 0.        , 0.        ],
       [0.        , 0.        , 0.        , 0.        , 0.        ],
       [0.        , 0.23618044, 0.        , 0.91625967, 0.8791744 ],
       [0.        , 0.        , 0.        , 0.        , 0.        ],
       [0.        , 0.        , 0.        , 0.7928235 , 0.        ]])
In [113]: M1 = sparse.coo_matrix((M.data, (M.row, M.col)),shape=(7,5))
In [114]: M1
Out[114]: 
<7x5 sparse matrix of type '<class 'numpy.float64'>'
    with 5 stored elements in COOrdinate format>
In [115]: M1.A
Out[115]: 
array([[0.        , 0.80957797, 0.        , 0.        , 0.        ],
       [0.        , 0.        , 0.        , 0.        , 0.        ],
       [0.        , 0.23618044, 0.        , 0.91625967, 0.8791744 ],
       [0.        , 0.        , 0.        , 0.        , 0.        ],
       [0.        , 0.        , 0.        , 0.7928235 , 0.        ],
       [0.        , 0.        , 0.        , 0.        , 0.        ],
       [0.        , 0.        , 0.        , 0.        , 0.        ]])
In [116]: id(M1.data)
Out[116]: 139883362735488
In [117]: id(M.data)
Out[117]: 139883362735488

MM1 具有相同的 data 属性(相同的数组 ID)。但是对这些矩阵的大多数操作都需要转换为另一种格式(例如 csr 用于数学,或 lil 用于更改值),并且将涉及复制和修改属性。所以这两个矩阵之间的联系就会被打破。

当您使用 coo_matrix 之类的函数创建稀疏矩阵并且不提供 shape 参数时,它会根据提供的坐标推导出形状。如果您提供 shape 它会使用它。该形状必须至少与隐含的形状一样大。使用 lil(和 dok),您可以创建一个具有较大形状的 'empty' 矩阵,然后迭代地设置值。你不想用 csr 来做那件事。而且你不能直接设置 coo 值。

创建稀疏矩阵的规范方法是从各个部分迭代构建 datarowcol 数组或列表 - 使用列表 append/extend 或数组连接,并从中创建一个 coo(或 csr)格式的数组。所以你甚至在创建矩阵之前就完成了所有 'growing'。

改变_shape

制作矩阵:

In [140]: M = (sparse.random(5,3,.4,'csr')*10).astype(int)
In [141]: M
Out[141]: 
<5x3 sparse matrix of type '<class 'numpy.int64'>'
    with 6 stored elements in Compressed Sparse Row format>
In [142]: M.A
Out[142]: 
array([[0, 6, 7],
       [0, 0, 6],
       [1, 0, 5],
       [0, 0, 0],
       [0, 6, 0]])

In [144]: M[1,0] = 10
... SparseEfficiencyWarning)
In [145]: M.A
Out[145]: 
array([[ 0,  6,  7],
       [10,  0,  6],
       [ 1,  0,  5],
       [ 0,  0,  0],
       [ 0,  6,  0]])

你的新形状方法(确保 indptrdtype 没有改变):

In [146]: M._shape = (6,3)
In [147]: newptr = np.hstack((M.indptr,M.indptr[-1]))
In [148]: newptr
Out[148]: array([0, 2, 4, 6, 6, 7, 7], dtype=int32)
In [149]: M.indptr = newptr
In [150]: M
Out[150]: 
<6x3 sparse matrix of type '<class 'numpy.int64'>'
    with 7 stored elements in Compressed Sparse Row format>
In [151]: M.A
Out[151]: 
array([[ 0,  6,  7],
       [10,  0,  6],
       [ 1,  0,  5],
       [ 0,  0,  0],
       [ 0,  6,  0],
       [ 0,  0,  0]])
In [152]: M[5,2]=10
... SparseEfficiencyWarning)
In [153]: M.A
Out[153]: 
array([[ 0,  6,  7],
       [10,  0,  6],
       [ 1,  0,  5],
       [ 0,  0,  0],
       [ 0,  6,  0],
       [ 0,  0, 10]])

添加一列似乎也有效:

In [154]: M._shape = (6,4)
In [155]: M
Out[155]: 
<6x4 sparse matrix of type '<class 'numpy.int64'>'
    with 8 stored elements in Compressed Sparse Row format>
In [156]: M.A
Out[156]: 
array([[ 0,  6,  7,  0],
       [10,  0,  6,  0],
       [ 1,  0,  5,  0],
       [ 0,  0,  0,  0],
       [ 0,  6,  0,  0],
       [ 0,  0, 10,  0]])
In [157]: M[5,0]=10
.... SparseEfficiencyWarning)
In [158]: M[5,3]=10
.... SparseEfficiencyWarning)
In [159]: M
Out[159]: 
<6x4 sparse matrix of type '<class 'numpy.int64'>'
    with 10 stored elements in Compressed Sparse Row format>
In [160]: M.A
Out[160]: 
array([[ 0,  6,  7,  0],
       [10,  0,  6,  0],
       [ 1,  0,  5,  0],
       [ 0,  0,  0,  0],
       [ 0,  6,  0,  0],
       [10,  0, 10, 10]])

属性共享

我可以从现有矩阵创建一个新矩阵:

In [108]: M = (sparse.random(5,3,.4,'csr')*10).astype(int)
In [109]: newptr = np.hstack((M.indptr,6))
In [110]: M1 = sparse.csr_matrix((M.data, M.indices, newptr), shape=(6,3))

data属性是共享的,至少在视图意义上:

In [113]: M[0,1]=14
In [114]: M1[0,1]
Out[114]: 14

但是如果我通过添加一个非零值来修改 M1

In [117]: M1[5,0]=10
...
  SparseEfficiencyWarning)

矩阵之间的link中断:

In [120]: M[0,1]=3
In [121]: M1[0,1]
Out[121]: 14