如何更有效地存储距离矩阵?
Posted
技术标签:
【中文标题】如何更有效地存储距离矩阵?【英文标题】:How to store a distance matrix more efficiently? 【发布时间】:2020-09-08 04:01:16 【问题描述】:我有这个 python 代码来计算不同点之间的坐标距离。
IDs,X,Y,Z
0-20,193.722,175.733,0.0998975
0-21,192.895,176.727,0.0998975
7-22,187.065,178.285,0.0998975
0-23,192.296,178.648,0.0998975
7-24,189.421,179.012,0.0998975
8-25,179.755,179.347,0.0998975
8-26,180.436,179.288,0.0998975
7-27,186.453,179.2,0.0998975
8-28,178.899,180.92,0.0998975
代码运行良好,但由于我现在拥有的坐标量非常大(~50000),我需要优化这段代码,否则无法运行。有人可以建议我一种更节省内存的方法吗?感谢您的任何建议。
#!/usr/bin/env python
import pandas as pd
import scipy.spatial as spsp
df_1 =pd.read_csv('Spots.csv', sep=',')
coords = df_1[['X', 'Y', 'Z']].to_numpy()
distances = spsp.distance_matrix(coords, coords)
df_1['dist'] = distances.tolist()
# CREATES columns d0, d1, d2, d3
dist_cols = df_1['IDs']
df_1[dist_cols] = df_1['dist'].apply(pd.Series)
df_1.to_csv("results_Spots.csv")
【问题讨论】:
你能添加一些数据吗,因为read_csv
在这里没有帮助。我们不需要 50K,但足以看到你的代码做了什么
这一步“coords = ... .to_numpy()”很可能可以去掉。 pandas 使用 numpy 数据类型;无需复制。 (样本数据+1)
你从哪里开始遇到内存错误?
紧随其后:distances = spsp.distance_matrix(coords, coords) Traceback(最近一次调用最后一次):文件“您在代码中询问 ~50000 x ~50000 矩阵中的点到点距离。如果您真的喜欢存储它,结果将非常大。矩阵是密集的,因为每个点与其他点之间的距离为正。 我建议重新审视您的业务需求。您真的需要预先计算所有这些点并将它们存储在磁盘上的文件中吗?有时最好即时进行所需的计算; scipy.spacial 速度很快,甚至可能比读取预先计算的值慢很多。
编辑(基于评论): 您可以按阈值过滤计算的距离(此处为说明:5.0),然后在 DataFrame 中查找 ID
import pandas as pd
import scipy.spatial as spsp
df_1 =pd.read_csv('Spots.csv', sep=',')
coords = df_1[['X', 'Y', 'Z']].to_numpy()
distances = spsp.distance_matrix(coords, coords)
adj_5 = np.argwhere(distances[:] < 5.0)
pd.DataFrame(zip(df_1['IDs'][adj_5[:,0]].values,
df_1['IDs'][adj_5[:,1]].values),
columns=['from', 'to'])
【讨论】:
这是评论,不是解决方案。您不应该在回答中问 OP 问题,而且您肯定有足够的声誉发表评论。 我会坚持我的回答;对否决票感到满意。只有否定的答案可能有助于解决用户的问题。 (或者我错了,在那种情况下道歉) 所以,实际上是生物数据,每个坐标属于一个细胞,我试图找到每个细胞的邻居。所以我确实需要计算到每个点的所有距离,然后按特定半径进行过滤。只有这样我才能得到每个单元格的真正邻居。希望这是有道理的。 @Amaranta_Remedios。我已经发布了一个可能的解决方案。有很多方法可以解决这个问题,并且鉴于您的问题并非全新的,“它无法完成”不太可能是一个合理的答案。 编辑无济于事。整个问题是不能分配distances
。您无法在崩溃后对其进行后期处理以减小大小。【参考方案2】:
有几种方法可以节省空间。第一个是仅存储矩阵的上三角形,并确保您的索引始终反映这一点。第二个是仅存储满足阈值的值。这可以通过使用稀疏矩阵共同完成,它支持您可能需要的大部分操作,并且只存储您需要的元素。
要存储一半数据,请在访问矩阵时预处理索引。所以对于你的矩阵,像这样访问索引[i, j]
:
getitem(A, i, j):
if i > j:
i, j = j, i
return dist[i, j]
scipy.sparse
支持多种稀疏矩阵格式:BSR、Coordinate、CSR、CSC、Diagonal、DOK、LIL。根据usage reference,构造矩阵的最简单方法是使用 DOK 或 LIL 格式。为简单起见,我将展示后者,尽管前者可能更有效。一旦展示了基本的功能方法,我将留给读者对不同的选项进行基准测试。做矩阵数学时记得转换成 CSR 或 CSC 格式。
我们将通过一次构建一行来牺牲空间效率:
N = coords.shape[0]
threshold = 2
threshold2 = threshold**2 # minor optimization to save on some square roots
distances = scipy.sparse.lil_matrix((N, N))
for i in range(N):
# Compute square distances
d2 = np.sum(np.square((coords[i + 1:, :] - coords[i])), axis=1)
# Threshold
mask = np.flatnonzero(d2 <= threshold2)
# Apply, only compute square root if necessary
distances[i, mask + i + 1] = np.sqrt(d2[mask])
对于您的玩具示例,我们发现实际上只有四个元素通过阈值,从而使存储非常高效:
>>> distances.nnz
4
>>> distances.toarray()
array([[0. , 1.29304486, 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
[0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
[0. , 0. , 0. , 0. , 0. , 0. , 0. , 1.1008038 , 0. ],
[0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
[0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
[0. , 0. , 0. , 0. , 0. , 0. , 0.68355102, 0. , 1.79082802],
[0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
[0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
[0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ]])
使用来自scipy.spatial.distance_matrix
的结果确认这些数字实际上是准确的。
如果您想填充矩阵(有效地将存储量翻倍,这不应该是禁止的),您可能应该在这样做之前远离 LIL 格式。只需将转置添加到原始矩阵即可填充。
此处显示的方法解决了您的存储问题,但您可以使用空间排序和其他地理空间技术提高整个计算的效率。例如,您可以使用scipy.spatial.KDTree
或类似的scipy.spatial.cKDTree
在特定阈值内直接高效地排列数据集和查询邻居。
例如,以下将用可能更有效的方法替换此处显示的矩阵构造:
tree = scipy.spatial.KDTree(coords)
distances = tree.sparse_distance_matrix(tree, threshold)
【讨论】:
谢谢!我正在尝试您的解决方案。但我收到缩进错误。所以需要先排序。无论如何,非常感谢您为我提供了一个框架。 @Amaranta_Remedios。只有三个缩进行。你在哪里得到错误?另外,我真的建议你忽略我的大部分答案,直接使用KDTree
(当然是在投票和选择之后:))。以上是关于如何更有效地存储距离矩阵?的主要内容,如果未能解决你的问题,请参考以下文章