【问题标题】:Implications of manually setting scipy sparse matrix shape手动设置 scipy 稀疏矩阵形状的含义
【发布时间】:2018-10-31 15:17:30
【问题描述】:

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

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

但是后来我也发现其实用scipy sparse matrix可以manually change the matrix's shape.

Numpy reshape docs 说:

在不复制数据的情况下改变数组的形状并不总是可能的。如果您希望在复制数据时引发错误,则应将新形状分配给数组的形状属性

由于稀疏矩阵的“重塑”是通过分配一个新形状来完成的,因此可以肯定地说数据没有被复制吗?这样做有什么影响?效率高吗?

代码示例:

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 是否正在复制整个数据,以便按照我的第一个链接中所述将其存储为连续的内存块? hstack呢?


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

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

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_ 正在复制数据。

【问题讨论】:

    标签: python numpy scipy sparse-matrix reshape


    【解决方案1】:

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

    最简单的例子是这样的

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

    赋值方法是:

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

    method(或np.reshape(...))返回一个新数组。 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,... 连续性如何变化。 (这留给读者作为练习,因为它不是更大问题的核心。)

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

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

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

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

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

    理论上,稀疏矩阵可以“嵌入”到更大的矩阵中,而数据复制最少,因为所有新值都将是默认值 0,并且不占用任何空间。但该操作的细节尚未针对任何格式制定。

    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),您可以有利地创建一个具有大形状的“空”矩阵,然后迭代地设置值。你不想用csr 这样做。并且不能直接设置coo 值。

    创建稀疏矩阵的规范方法是构建 datarowcol 数组或从各个部分迭代地列出 - 使用列表追加/扩展或数组连接,并创建一个 coo (或csr) 格式数组。所以你甚至在创建矩阵之前就完成了所有的“增长”。

    改变_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)
    

    矩阵之间的链接断开:

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

    【讨论】:

    • 谢谢你的回答,我有一个问题,比如说我创建了一个csr_matrix,形状为(3,5),然后我做matrix._shape = (3,6),因为所有新元素都是0不占用任何额外空间,但是一旦我将这些 0 修改为另一个值,它将占用更多空间,对吗?如果是,这样做时会不会复制所有数据?
    • 你会得到一个效率警告,
    • 问题是,它没有,我已经尝试过here 并没有收到任何警告
    • 更改私有属性_shape 不会发出警告,但添加新值会发出警告。
    • 当您开始直接修改矩阵属性时,您需要了解它们。许多 SO 答案建议直接使用 indptr 对行进行快速迭代,但这是针对不改变稀疏性的计算。当您开始直接更改稀疏度(添加或删除非零值)时,您就是在冒险。我自己并没有尝试这样做,尽管我可能可以调试代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-26
    • 2017-03-31
    • 1970-01-01
    • 2023-04-10
    • 2017-07-21
    • 2011-11-28
    • 2017-07-02
    相关资源
    最近更新 更多