Scipy稀疏矩阵copy参数不符合Numpy复制/视图设计理念的问询
Scipy稀疏矩阵类型转换中copy参数与data.base属性的疑问
我发现Scipy中稀疏矩阵的类型转换方法并未正确使用方法提供的copy参数。多数情况下实际已经完成了数据复制,但生成的data数组始终被设置了base属性,在代码中表现为view,可实际上复制操作已经完成。请问这是有意设计的行为吗?
示例验证
以下是csr和csc数组的测试示例,可见无论是否指定copy=True,它们的data都带有base属性:
In [1]: import numpy as np ...: from scipy import sparse ...: ...: a = np.arange(20).reshape(4, 5) ...: csr = sparse.csr_array(a, copy=True) ...: print('csr.data.base', id(csr.data.base) if csr.data.base is not None else None) ...: ...: csr_copy = csr.copy() ...: print('csr_copy.data.base', id(csr_copy.data.base) if csr_copy.data.base is not None else None) ...: ...: csc_copy = csr.tocsc(copy=True) ...: print('csc_copy.data.base', id(csc_copy.data.base) if csc_copy.data.base is not None else None) ...: ...: csc_copy_2 = csr.tocsc() ...: print('csc_copy_2.data.base', id(csc_copy_2.data.base) if csc_copy_2.data.base is not None else None)
输出结果:
csr.data.base 4392865488 csr_copy.data.base 4392866448 csc_copy.data.base 4392866640 csc_copy_2.data.base 4392867120
csr_copy.data与csr.data拥有相同base是合理的,但其他对象的data也被设置base属性的行为无法理解。
对用户操作的阻碍
这种行为会阻碍用户直接操作数组的data和indices参数,例如无法通过原地resize方法扩展csr矩阵的行数:
In [2]: old_nnz = csr.nnz ...: row = [1, 2, 3, 4, 5] # 给csr追加一行5个元素 ...: ...: csr.resize(5, 5) ...: ...: print(id(csr.data)) ...: print(csr.data) ...: ...: print(id(csr.data.base)) ...: print(csr.data.base) ...: ...: csr.data.resize((old_nnz + len(row),), refcheck=True)
输出结果:
4757413808 [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19] 4757413520 [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19] Traceback (most recent call last): File "/opt/homebrew/Caskroom/miniforge/base/envs/dev/lib/python3.10/site-packages/IPython/core/interactiveshell.py", line 3433, in run_code exec(code_obj, self.user_global_ns, self.user_ns) File "<ipython-input-34-c52e3457494e>", line 12, in <module> csr.data.resize((old_nnz + len(row),), refcheck=True) ValueError: cannot resize this array: it does not own its data
尝试使用np.resize,但不确定其原地性:
In [3]: old_nnz = csr.nnz ...: row = [1, 2, 3, 4, 5] # 给csr追加一行5个元素 ...: ...: csr.resize(5, 5) ...: ...: print('Data') ...: print(id(csr.data)) ...: print(csr.data) ...: ...: print("Data's Base") ...: print(id(csr.data.base)) ...: print(csr.data.base) ...: ...: print('New Data') ...: new_data = np.resize(csr.data, (old_nnz + len(row),)) ...: print(id(new_data)) ...: print(new_data) ...: ...: print("New Data's Base") ...: print(id(new_data.base)) ...: print(new_data.base) ...: ...: new_indices = np.resize(csr.indices, (old_nnz + len(row),))
输出结果:
Data 5256251600 [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19] Data's Base 5256250736 [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19] New Data 5256250928 [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1 2 3 4 5] New Data's Base 5256253040 [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19]
源码中的问题
查阅相关函数源码后发现,部分函数甚至未使用copy参数,例如tocsc方法:
def tocsc(self, copy=False): idx_dtype = get_index_dtype((self.indptr, self.indices), maxval=max(self.nnz, self.shape[0])) indptr = np.empty(self.shape[1] + 1, dtype=idx_dtype) indices = np.empty(self.nnz, dtype=idx_dtype) data = np.empty(self.nnz, dtype=upcast(self.dtype)) csr_tocsc(self.shape[0], self.shape[1], self.indptr.astype(idx_dtype), self.indices.astype(idx_dtype), self.data, indptr, indices, data) A = self._csc_container((data, indices, indptr), shape=self.shape) A.has_sorted_indices = True return A
虽然代码中创建了新的data数组,但后续流程中(可能在C/Python接口之间)它被设置了base属性。
内容的提问来源于stack exchange,提问作者Kirill Shumilov
相关产品推荐
相关产品推荐

