[英]Efficiently slice triangular sparse matrix
我有一個稀疏的三角矩陣(例如距離矩陣)。 實際上,這將是一個具有高稀疏性的 > 1M x 1M 距離矩陣。
from scipy.sparse import csr_matrix
X = csr_matrix([
[1, 2, 3, 3, 1],
[0, 1, 3, 3, 2],
[0, 0, 1, 1, 3],
[0, 0, 0, 1, 3],
[0, 0, 0, 0, 1],
])
我想將此矩陣子集化為另一個三角距離矩陣。 索引的排序可能不同和/或重復。
idx = np.matrix([1,2,4,2])
X2 = X[idx.T, idx]
這可能導致生成的矩陣不是三角形的,上三角形中缺少一些值,而下三角形中的一些值重復。
>>> X2.toarray()
array([[1, 3, 2, 3],
[0, 1, 3, 1],
[0, 0, 1, 0],
[0, 1, 3, 1]])
如何盡可能有效地獲得正確的上三角矩陣? 目前,我在子集之前鏡像矩陣,然后將其子集到三角形,但這感覺不是特別有效,因為它至少需要復制所有條目。
# use transpose method, see https://stackoverflow.com/a/58806735/2340703
X = X + X.T - scipy.sparse.diags(X.diagonal())
X2 = X[idx.T, idx]
X2 = scipy.sparse.triu(X2, k=0, format="csr")
>>> X2.toarray()
array([[1., 3., 2., 3.],
[0., 1., 3., 1.],
[0., 0., 1., 3.],
[0., 0., 0., 1.]])
這是一種不涉及鏡像數據的方法,而是對稀疏索引進行操作以達到所需的結果:
import scipy.sparse as sp
X2 = X[idx.T, idx]
# Extract indices and data (this is essentially COO format)
i, j, data = sp.find(X2)
# Generate indices with elements moved to upper triangle
ij = np.vstack([
np.where(i > j, j, i),
np.where(i > j, i, j)
])
# Remove duplicate elements
ij, ind = np.unique(ij, axis=1, return_index=True)
# Re-build the matrix
X2 = sp.coo_matrix((data[ind], ij)).tocsr()
好吧,我不能讓它triu
,但這應該更快:
idx = np.array([1,2,4,2])
i = np.stack(np.meshgrid(idx, idx))
X2 = X[i.min(0), i.max(0)]
array([[1, 3, 2, 3],
[3, 1, 3, 1],
[2, 3, 1, 3],
[3, 1, 3, 1]])
所以整個過程是:
idx = np.array([1,2,4,2])
i = np.stack(np.meshgrid(idx, idx))
X2 = scipy.sparse.triu(X[i.min(0), i.max(0)], k=0, format="csr")
但我無法擺脫這種感覺,必須有一種更優化的方式。
這不是一個改進的工作答案,而是對稀疏索引和triu
作用的探索。 它可能會為您提供進行更直接計算的想法。 您從 tri 開始並期望 tri 的事實意味着這不是一項簡單的任務,即使是密集的 arrays (索引速度要快得多)也不是。
sparse.csr
索引使用矩陣乘法。 我將用密集的 arrays 來說明這一點:
In [304]: X = np.array([
...: [1, 2, 3, 3, 1],
...: [0, 1, 3, 3, 2],
...: [0, 0, 1, 1, 3],
...: [0, 0, 0, 1, 3],
...: [0, 0, 0, 0, 1],
...: ])
In [305]: idx = np.array([1,2,4,2])
In [306]: X[idx[:,None],idx]
Out[306]:
array([[1, 3, 2, 3],
[0, 1, 3, 1],
[0, 0, 1, 0],
[0, 1, 3, 1]])
In [307]: m = np.array([[0,1,0,0,0],[0,0,1,0,0],[0,0,0,0,1],[0,0,1,0,0]])
In [308]: m@X@m.T
Out[308]:
array([[1, 3, 2, 3],
[0, 1, 3, 1],
[0, 0, 1, 0],
[0, 1, 3, 1]])
並使用全距離陣列:
In [309]: X2 = X+X.T-np.diag(np.diag(X))
In [311]: X2[idx[:,None],idx]
Out[311]:
array([[1, 3, 2, 3],
[3, 1, 3, 1],
[2, 3, 1, 3],
[3, 1, 3, 1]])
In [312]: m@X2@m.T
Out[312]:
array([[1, 3, 2, 3],
[3, 1, 3, 1],
[2, 3, 1, 3],
[3, 1, 3, 1]])
我不知道是否可以直接從X
(或X2
)構造一個提供所需結果的m
,上 tri 或不
In [316]: sparse.triu(Out[312])
Out[316]:
<4x4 sparse matrix of type '<class 'numpy.int64'>'
with 10 stored elements in COOrdinate format>
In [317]: _.A
Out[317]:
array([[1, 3, 2, 3],
[0, 1, 3, 1],
[0, 0, 1, 3],
[0, 0, 0, 1]])
sparse.triu
會:
In [331]: A = sparse.coo_matrix(_312)
...: mask = A.row <= A.col
In [332]: A
Out[332]:
<4x4 sparse matrix of type '<class 'numpy.int64'>'
with 16 stored elements in COOrdinate format>
In [333]: mask
Out[333]:
array([ True, True, True, True, False, True, True, True, False,
False, True, True, False, False, False, True])
這個mask
數組是 16 項, A.nnz
。
然后它使用從A
屬性中選擇的 data/row/col arrays 創建一個新的coo
矩陣:
In [334]: d=A.data[mask]
In [335]: r=A.row[mask]
In [336]: c=A.col[mask]
In [337]: d
Out[337]: array([1, 3, 2, 3, 1, 3, 1, 1, 3, 1])
In [338]: sparse.coo_matrix((d, (r,c)))
Out[338]:
<4x4 sparse matrix of type '<class 'numpy.int64'>'
with 10 stored elements in COOrdinate format>
In [339]: _.A
Out[339]:
array([[1, 3, 2, 3],
[0, 1, 3, 1],
[0, 0, 1, 3],
[0, 0, 0, 1]])
np.triu
使用如下mask
:
In [349]: np.tri(4,4,-1)
Out[349]:
array([[0., 0., 0., 0.],
[1., 0., 0., 0.],
[1., 1., 0., 0.],
[1., 1., 1., 0.]])
總結所有出色的貢獻,這個問題的簡短答案是:
不要使用三角矩陣。 與使用方陣相比,在速度或 memory 方面沒有任何好處。
@hpaulj 的回答中解釋了其原因:
triu
是一項昂貴的操作,因為它實現了密集掩碼。將@jakevdp 的解決方案與僅使用方陣進行比較時,這一點變得很明顯。 使用方形更快,使用更少的 memory。
該測試使用具有高稀疏度 (%nnz << 1%) 的稀疏三角形 800k x 800k 距離矩陣。 數據和代碼可在此處獲得。
# Running benchmark: Converting to square matrix
./benchmark.py squarify 6.29s user 1.59s system 80% cpu 9.738 total
max memory: 4409 MB
# Running benchmark: @jakevdp's solution
./benchmark.py sparse_triangular 67.03s user 3.01s system 99% cpu 1:10.15 total
max memory: 5209 MB
如果一個人迫切希望在使用方陣之外對其進行優化, @CJR 的評論是一個很好的起點:
我會考慮將其實現為與pdist相同的樣式的壓縮距離矩陣,但作為 1xN CSR 矩陣,然后在需要獲取特定值時使用坐標數學重新索引它。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.