簡體   English   中英

高效切片三角稀疏矩陣

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM