如何更有效地存储距离矩阵?

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(最近一次调用最后一次):文件“”,第 1 行,在 文件“/mnt/lib/ python3.7/site-packages/scipy/spatial/kdtree.py", line 989, in distance_matrix result = np.empty((m,n),dtype=float) # FIXME: 找出最好的 dtype MemoryError: Unable to为形状为 (34076, 34076) 且数据类型为 float64 的数组分配 8.65 GiB 只看矩阵的三角形,你几乎可以将这个数字减半...... 【参考方案1】:

您在代码中询问 ~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(当然是在投票和选择之后:))。

以上是关于如何更有效地存储距离矩阵?的主要内容,如果未能解决你的问题,请参考以下文章

如何直接得到成对距离的压缩形式?

将测地线数据类型更改为整数

如何在Oracle中有效地计算坐标之间的距离

在 Pandas 中将字典转换为对称/距离矩阵的最有效方法

如何使用 data.table 有效地计算坐标对之间的距离:=

压缩距离矩阵如何工作? (pdist)