scipy `SparseEfficiencyWarning` 在 csr_matrix 的行上除法时
scipy `SparseEfficiencyWarning` when division on rows of csr_matrix
假设我已经有一个 csr_matrix
:
import numpy as np
from scipy.sparse import csr_matrix
indptr = np.array([0, 2, 3, 6])
indices = np.array([0, 2, 2, 0, 1, 2])
data = np.array([1., 2., 3., 4., 5., 6.])
mat = csr_matrix((data, indices, indptr), shape=(3, 3))
print(mat.A)
[[1. 0. 2.]
[0. 0. 3.]
[4. 5. 6.]]
如果我想把这个分成一行就很简单csr_matrix:
mat[0] /= 2
print(mat.A)
[[0.5 0. 1. ]
[0. 0. 3. ]
[4. 5. 6. ]]
但是,如果我想更改多行,它会抛出警告:
mat[np.array([0,1])]/=np.array([[1],[2]])
print(mat.A)
[[1. 0. 2. ]
[0. 0. 1.5]
[4. 5. 6. ]]
SparseEfficiencyWarning: Changing the sparsity structure of a csr_matrix is expensive. lil_matrix is more efficient.
self._set_arrayXarray(i, j, x)
多行除法如何改变其稀疏性?它建议我更改为lil_matrix,但是当我检查def tolil()
的代码时:
def tolil(self, copy=False):
lil = self._lil_container(self.shape, dtype=self.dtype)
self.sum_duplicates()
ptr,ind,dat = self.indptr,self.indices,self.data
rows, data = lil.rows, lil.data
for n in range(self.shape[0]):
start = ptr[n]
end = ptr[n+1]
rows[n] = ind[start:end].tolist()
data[n] = dat[start:end].tolist()
return lil
基本上循环所有行,我认为在我的情况下没有必要。如果我只想划分几行csr_matrix
,正确的方法可能是什么?谢谢!
看来问题出在访问矩阵数据的方式上
mat[np.array([0,1])]/=np.array([[1],[2]])
直接访问行似乎是问题所在。我没有找到任何可靠的数据来说明为什么会这样,但我认为这会强制构建具有 CSR 格式通常会跳过的所有 0
值的行。
缓解该问题的一种方法是首先将数据 np.array([[1],[2]])
写入 1/x
以获得 [[1],[0.5]]
然后从中创建对角线 csr_matrix
以便我们有
mat_x = csr_matrix(([1,0.5,1], [0,1,2], [0,1,2,3]), shape=(3, 3))
如果我们然后从 left 乘以这个矩阵来影响 rows,我们会得到想要的输出而没有任何警告
mat = mat_x * mat
你的矩阵:
In [208]: indptr = np.array([0, 2, 3, 6])
...: indices = np.array([0, 2, 2, 0, 1, 2])
...: data = np.array([1., 2., 3., 4., 5., 6.])
...: mat = sparse.csr_matrix((data, indices, indptr), shape=(3, 3))
In [209]: mat
Out[209]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 6 stored elements in Compressed Sparse Row format>
In [210]: mat.A
Out[210]:
array([[1., 0., 2.],
[0., 0., 3.],
[4., 5., 6.]])
简单除法只是改变 mat.data
值,in-place:
In [211]: mat/= 3
In [212]: mat
Out[212]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 6 stored elements in Compressed Sparse Row format>
In [213]: mat *= 3
In [214]: mat.A
Out[214]:
array([[1., 0., 2.],
[0., 0., 3.],
[4., 5., 6.]])
您案例的 RHS 生成一个 np.matrix
对象:
In [215]: mat[np.array([0,1])]/np.array([[1],[2]])
Out[215]:
matrix([[1. , 0. , 2. ],
[0. , 0. , 1.5]])
将其分配给 mat
子集会产生警告:
In [216]: mat[np.array([0,1])] = _
/usr/local/lib/python3.8/dist-packages/scipy/sparse/_index.py:146: SparseEfficiencyWarning: Changing the sparsity structure of a csr_matrix is expensive. lil_matrix is more efficient.
self._set_arrayXarray(i, j, x)
你的警告和我的警告发生在设置步骤中:
self._set_arrayXarray(i, j, x)
如果我再次划分我不会收到警告:
In [217]: mat[np.array([0,1])]/=np.array([[1],[2]])
In [218]: mat
Out[218]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 9 stored elements in Compressed Sparse Row format>
为什么?因为在第一次赋值之后,mat
有 9 个 non-zero 项,而不是原来的六个。所以 [217] 不会改变稀疏性。
将 mat
转换回 6 个零,我们再次收到警告:
In [219]: mat.eliminate_zeros()
In [220]: mat
Out[220]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 6 stored elements in Compressed Sparse Row format>
In [221]: mat[np.array([0,1])]/=np.array([[1],[2]])
/usr/local/lib/python3.8/dist-packages/scipy/sparse/_index.py:146: SparseEfficiencyWarning: Changing the sparsity structure of a csr_matrix is expensive. lil_matrix is more efficient.
self._set_arrayXarray(i, j, x)
以及稀疏度的变化:
In [222]: mat
Out[222]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 9 stored elements in Compressed Sparse Row format>
分配 [215] 的稀疏矩阵不会触发警告:
In [223]: mat.eliminate_zeros()
In [224]: m1=sparse.csr_matrix(mat[np.array([0,1])]/np.array([[1],[2]]))
In [225]: m1
Out[225]:
<2x3 sparse matrix of type '<class 'numpy.float64'>'
with 3 stored elements in Compressed Sparse Row format>
In [226]: mat[np.array([0,1])]=m1
In [227]: mat
Out[227]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 6 stored elements in Compressed Sparse Row format>
===
最好将 [215] 划分视为一个 ndarray
行动,而不是一个稀疏的行动:
In [232]: mat[np.array([0,1])]/np.array([[1],[2]])
Out[232]:
matrix([[1. , 0. , 2. ],
[0. , 0. , 0.09375]])
In [233]: mat[np.array([0,1])].todense()/np.array([[1],[2]])
Out[233]:
matrix([[1. , 0. , 2. ],
[0. , 0. , 0.09375]])
此除法的详细信息可在 sparse._base.py
、mat._divide
中找到,根据 other
是标量、密集数组还是稀疏矩阵,具有不同的操作。稀疏矩阵除法不实现broadcasting
.
一般来说,矩阵乘法是最有效的稀疏计算。事实上,像行或列总和这样的操作是用它来实现的。某些形式的索引也是如此。如果可以将 Element-wise 计算应用于 M.data
数组而不考虑行或列索引(例如平方、幂、标量乘法),则计算是可以的。 M.multiply
是 element-wise,但没有密集数组的全部广播能力。稀疏划分就更有限了。
编辑
sklearn
有一些实用程序可以执行某些类型的 sparse
它需要的操作,例如缩放和规范化。
In [274]: from sklearn.utils import sparsefuncs
https://scikit-learn.org/stable/modules/classes.html#module-sklearn.utils
与示例mat
:
In [275]: mat
Out[275]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 6 stored elements in Compressed Sparse Row format>
In [276]: mat.A
Out[276]:
array([[1. , 0. , 2. ],
[0. , 0. , 0.1875],
[4. , 5. , 6. ]])
应用行缩放:
In [277]: sparsefuncs.inplace_row_scale(mat,np.array([10,20,1]))
In [278]: mat
Out[278]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 6 stored elements in Compressed Sparse Row format>
In [279]: mat.A
Out[279]:
array([[10. , 0. , 20. ],
[ 0. , 0. , 3.75],
[ 4. , 5. , 6. ]])
缩放数组的长度必须匹配。在您的情况下,您需要取 [[1],[2]]
的倒数,并用 1 填充它以作用于所有行。
查看源代码,我发现它使用 sparsefuncs.inplace_csr_row_scale
。反过来:
X.data *= np.repeat(scale, np.diff(X.indptr))
此操作的详细信息是:
In [283]: mat.indptr
Out[283]: array([0, 2, 3, 6], dtype=int32)
In [284]: np.diff(mat.indptr)
Out[284]: array([2, 1, 3], dtype=int32)
In [285]: np.repeat(np.array([10,20,1]), _)
Out[285]: array([10, 10, 20, 1, 1, 1])
In [286]: mat.data
Out[286]: array([100., 200., 75., 4., 5., 6.])
所以它将 scale
数组转换为与 data
形状匹配的数组。那么就地 *=
数组乘法就很容易了。
假设我已经有一个 csr_matrix
:
import numpy as np
from scipy.sparse import csr_matrix
indptr = np.array([0, 2, 3, 6])
indices = np.array([0, 2, 2, 0, 1, 2])
data = np.array([1., 2., 3., 4., 5., 6.])
mat = csr_matrix((data, indices, indptr), shape=(3, 3))
print(mat.A)
[[1. 0. 2.]
[0. 0. 3.]
[4. 5. 6.]]
如果我想把这个分成一行就很简单csr_matrix:
mat[0] /= 2
print(mat.A)
[[0.5 0. 1. ]
[0. 0. 3. ]
[4. 5. 6. ]]
但是,如果我想更改多行,它会抛出警告:
mat[np.array([0,1])]/=np.array([[1],[2]])
print(mat.A)
[[1. 0. 2. ]
[0. 0. 1.5]
[4. 5. 6. ]]
SparseEfficiencyWarning: Changing the sparsity structure of a csr_matrix is expensive. lil_matrix is more efficient.
self._set_arrayXarray(i, j, x)
多行除法如何改变其稀疏性?它建议我更改为lil_matrix,但是当我检查def tolil()
的代码时:
def tolil(self, copy=False):
lil = self._lil_container(self.shape, dtype=self.dtype)
self.sum_duplicates()
ptr,ind,dat = self.indptr,self.indices,self.data
rows, data = lil.rows, lil.data
for n in range(self.shape[0]):
start = ptr[n]
end = ptr[n+1]
rows[n] = ind[start:end].tolist()
data[n] = dat[start:end].tolist()
return lil
基本上循环所有行,我认为在我的情况下没有必要。如果我只想划分几行csr_matrix
,正确的方法可能是什么?谢谢!
看来问题出在访问矩阵数据的方式上
mat[np.array([0,1])]/=np.array([[1],[2]])
直接访问行似乎是问题所在。我没有找到任何可靠的数据来说明为什么会这样,但我认为这会强制构建具有 CSR 格式通常会跳过的所有 0
值的行。
缓解该问题的一种方法是首先将数据 np.array([[1],[2]])
写入 1/x
以获得 [[1],[0.5]]
然后从中创建对角线 csr_matrix
以便我们有
mat_x = csr_matrix(([1,0.5,1], [0,1,2], [0,1,2,3]), shape=(3, 3))
如果我们然后从 left 乘以这个矩阵来影响 rows,我们会得到想要的输出而没有任何警告
mat = mat_x * mat
你的矩阵:
In [208]: indptr = np.array([0, 2, 3, 6])
...: indices = np.array([0, 2, 2, 0, 1, 2])
...: data = np.array([1., 2., 3., 4., 5., 6.])
...: mat = sparse.csr_matrix((data, indices, indptr), shape=(3, 3))
In [209]: mat
Out[209]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 6 stored elements in Compressed Sparse Row format>
In [210]: mat.A
Out[210]:
array([[1., 0., 2.],
[0., 0., 3.],
[4., 5., 6.]])
简单除法只是改变 mat.data
值,in-place:
In [211]: mat/= 3
In [212]: mat
Out[212]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 6 stored elements in Compressed Sparse Row format>
In [213]: mat *= 3
In [214]: mat.A
Out[214]:
array([[1., 0., 2.],
[0., 0., 3.],
[4., 5., 6.]])
您案例的 RHS 生成一个 np.matrix
对象:
In [215]: mat[np.array([0,1])]/np.array([[1],[2]])
Out[215]:
matrix([[1. , 0. , 2. ],
[0. , 0. , 1.5]])
将其分配给 mat
子集会产生警告:
In [216]: mat[np.array([0,1])] = _
/usr/local/lib/python3.8/dist-packages/scipy/sparse/_index.py:146: SparseEfficiencyWarning: Changing the sparsity structure of a csr_matrix is expensive. lil_matrix is more efficient.
self._set_arrayXarray(i, j, x)
你的警告和我的警告发生在设置步骤中:
self._set_arrayXarray(i, j, x)
如果我再次划分我不会收到警告:
In [217]: mat[np.array([0,1])]/=np.array([[1],[2]])
In [218]: mat
Out[218]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 9 stored elements in Compressed Sparse Row format>
为什么?因为在第一次赋值之后,mat
有 9 个 non-zero 项,而不是原来的六个。所以 [217] 不会改变稀疏性。
将 mat
转换回 6 个零,我们再次收到警告:
In [219]: mat.eliminate_zeros()
In [220]: mat
Out[220]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 6 stored elements in Compressed Sparse Row format>
In [221]: mat[np.array([0,1])]/=np.array([[1],[2]])
/usr/local/lib/python3.8/dist-packages/scipy/sparse/_index.py:146: SparseEfficiencyWarning: Changing the sparsity structure of a csr_matrix is expensive. lil_matrix is more efficient.
self._set_arrayXarray(i, j, x)
以及稀疏度的变化:
In [222]: mat
Out[222]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 9 stored elements in Compressed Sparse Row format>
分配 [215] 的稀疏矩阵不会触发警告:
In [223]: mat.eliminate_zeros()
In [224]: m1=sparse.csr_matrix(mat[np.array([0,1])]/np.array([[1],[2]]))
In [225]: m1
Out[225]:
<2x3 sparse matrix of type '<class 'numpy.float64'>'
with 3 stored elements in Compressed Sparse Row format>
In [226]: mat[np.array([0,1])]=m1
In [227]: mat
Out[227]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 6 stored elements in Compressed Sparse Row format>
===
最好将 [215] 划分视为一个 ndarray
行动,而不是一个稀疏的行动:
In [232]: mat[np.array([0,1])]/np.array([[1],[2]])
Out[232]:
matrix([[1. , 0. , 2. ],
[0. , 0. , 0.09375]])
In [233]: mat[np.array([0,1])].todense()/np.array([[1],[2]])
Out[233]:
matrix([[1. , 0. , 2. ],
[0. , 0. , 0.09375]])
此除法的详细信息可在 sparse._base.py
、mat._divide
中找到,根据 other
是标量、密集数组还是稀疏矩阵,具有不同的操作。稀疏矩阵除法不实现broadcasting
.
一般来说,矩阵乘法是最有效的稀疏计算。事实上,像行或列总和这样的操作是用它来实现的。某些形式的索引也是如此。如果可以将 Element-wise 计算应用于 M.data
数组而不考虑行或列索引(例如平方、幂、标量乘法),则计算是可以的。 M.multiply
是 element-wise,但没有密集数组的全部广播能力。稀疏划分就更有限了。
编辑
sklearn
有一些实用程序可以执行某些类型的 sparse
它需要的操作,例如缩放和规范化。
In [274]: from sklearn.utils import sparsefuncs
https://scikit-learn.org/stable/modules/classes.html#module-sklearn.utils
与示例mat
:
In [275]: mat
Out[275]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 6 stored elements in Compressed Sparse Row format>
In [276]: mat.A
Out[276]:
array([[1. , 0. , 2. ],
[0. , 0. , 0.1875],
[4. , 5. , 6. ]])
应用行缩放:
In [277]: sparsefuncs.inplace_row_scale(mat,np.array([10,20,1]))
In [278]: mat
Out[278]:
<3x3 sparse matrix of type '<class 'numpy.float64'>'
with 6 stored elements in Compressed Sparse Row format>
In [279]: mat.A
Out[279]:
array([[10. , 0. , 20. ],
[ 0. , 0. , 3.75],
[ 4. , 5. , 6. ]])
缩放数组的长度必须匹配。在您的情况下,您需要取 [[1],[2]]
的倒数,并用 1 填充它以作用于所有行。
查看源代码,我发现它使用 sparsefuncs.inplace_csr_row_scale
。反过来:
X.data *= np.repeat(scale, np.diff(X.indptr))
此操作的详细信息是:
In [283]: mat.indptr
Out[283]: array([0, 2, 3, 6], dtype=int32)
In [284]: np.diff(mat.indptr)
Out[284]: array([2, 1, 3], dtype=int32)
In [285]: np.repeat(np.array([10,20,1]), _)
Out[285]: array([10, 10, 20, 1, 1, 1])
In [286]: mat.data
Out[286]: array([100., 200., 75., 4., 5., 6.])
所以它将 scale
数组转换为与 data
形状匹配的数组。那么就地 *=
数组乘法就很容易了。