找到两个巨大的 CSR 矩阵的行之间的欧氏距离

Find euclidean distance between rows of two huge CSR matrices

我有两个稀疏矩阵,A 和 B。A 是 120000*5000,B 是 30000*5000。我需要找到 B 中每一行与 A 中所有行之间的欧氏距离,然后找到 A 中与 B 中所选行的距离最短的 5 行。因为这是一个非常大的数据,我正在使用 CSR,否则我得到内存错误。很明显,对于 A 中的每一行,它计算 (x_b - x_a)^2 5000 次并对它们求和,然后得到一个 sqrt。这个过程需要非常非常长的时间,比如 11 天!有什么办法可以更有效地做到这一点?我只需要与 B 中每一行的距离最短的 5 行。

我正在实施 K 最近邻,A 是我的训练集,B 是我的测试集。

好吧 - 我不知道您是否可以 'vectorize' 那个代码,这样它就会 运行 在本机代码中而不是 Python。 speed-up numpy 和 scipy 的诀窍总是得到它。

如果您可以 运行 在 1GHz CPU 的本机代码中编写该代码,并使用 1 个用于时钟 cicle 的 FP 指令,您将在不到 10 小时的时间内完成。 (5000 * 2 * 30000 * 120000) / 1024 ** 3

将其提高到 1.5Ghz x 2 CPU 物理内核 x 4 路 SIMD 指令与乘法 + 累加(Intel AVX 扩展,在大多数 CPUs 中可用),你可以得到这个数字 c 运行在普通的核心 i5 机器上以 2 x 100% 的速度缩短到一小时。但这需要在本机代码中进行完整的 SIMD 优化——远非一项微不足道的任务(尽管,如果你决定走这条路,关于 S.O 的进一步问题可能会得到人们的帮助,或者在 SIMD 编码中弄湿他们的手: -) ) - 使用 cython 在 C 中将这段代码与 Scipy 连接并不难,例如(你只需要那部分就可以达到上面的 10 小时数字)

现在...至于算法优化,并保留东西Python :-)
事实是,您不需要完全计算 A 中行的 all 距离 - 您只需要保留 5 个较低行的排序列表 - 并且任何时候总和的累积的正方形变得大于第 5 个最近的行(到目前为止),您只需中止该行的计算。

您可以为此使用 Python' heapq 操作:

import heapq
import math

def get_closer_rows(b_row, a):
    result = [(float("+inf"), None)  * 5]
    for i, a_row in enumerate(a):
        distance_sq = 0
        count = 0
        for element_a, element_b in zip(a_row, b_row):
            distance_sq += element_a * element_b
            if not count % 64 and distance_sq > result[4][0]:
                break
            count += 1
        else:
            heapq.heappush(result, (distance, i))
            result[:] = result[:5]
    return [math.sqrt(r) for r in result]

closer_rows_to_b = []
for row in b:
    closer_rows_to_b.append(get_closer_rows(row, a))

请注意辅助 "count" 以避免所有乘法的值的昂贵检索和比较。 现在,如果您可以 运行 使用 pypy 而不是常规 Python 这段代码,我相信它可以充分利用 JITting,如果您 运行 在纯 Python 中使用代码(即:非 numpy/scipy 向量化代码)。