【问题标题】:how to compare entries in numpy array with each other efficiently?如何有效地比较numpy数组中的条目?
【发布时间】:2017-05-28 15:00:40
【问题描述】:

我有一个长度为 tot_vec 的 numpy 数组 embed_vec,其中每个条目都是一个 3d 向量:

[[ 0.52483319  0.78015841  0.71117216]
 [ 0.53041481  0.79462171  0.67234534]
 [ 0.53645428  0.80896727  0.63119403]
 ..., 
 [ 0.72283509  0.40070804  0.15220522]
 [ 0.71277758  0.38498613  0.16141834]
 [ 0.70221445  0.36918032  0.17370776]]

对于这个数组中的每个元素,我想找出与该条目“接近”的其他条目的数量。接近,我的意思是两个向量之间的距离小于指定值R。为此,我必须将这个数组中所有可能的对相互比较,然后找出数组中每个向量的接近向量的数量。所以我这样做:

p = np.zeros(tot_vec) # This contains the number of close vectors
for i in range(tot_vec-1):
    for j in range(i+1, tot_vec):
        if np.linalg.norm(embed_vec[i]-embed_vec[j]) < R:
            p[i] += 1

但是,这是非常低效的,因为我有两个嵌套的 python 循环并且对于更大的数组大小,这需要很长时间。如果这是在 C++ 或 Fortran 中,那将不是一个大问题。我的问题是,可以使用某种矢量化方法有效地使用 numpy 实现相同的目标吗?作为旁注,我也不介意使用 Pandas 的解决方案。

【问题讨论】:

  • 在您的实际用例中embed_vec 的形状是什么?
  • @Divakar:是(60000, 3)
  • @Peaceful 我删除了评论,因为您使用的是多维距离。尽管我可能会尝试使用其中的一些逻辑,但这是一个截然不同的问题
  • 你可以使用scipy的pdist得到一个距离矩阵。如果tot_vec 很大,可能会遇到内存问题。
  • 真的应该有人为此实现一些东西。它必须是关于被问到最多的 numpy 问题。

标签: python arrays pandas numpy vectorization


【解决方案1】:

方法#1:矢量化方法 -

def vectorized_app(embed_vec, R):  
    tot_vec = embed_vec.shape[0]          
    r,c = np.triu_indices(tot_vec,1)
    subs = embed_vec[r] - embed_vec[c]
    dists = np.einsum('ij,ij->i',subs,subs)
    return np.bincount(r,dists<R**2,minlength=tot_vec)

方法#2:循环复杂度较低(对于非常大的数组)-

def loopy_less_app(embed_vec, R):  
    tot_vec = embed_vec.shape[0]
    Rsq = R**2
    out = np.zeros(tot_vec,dtype=int)
    for i in range(tot_vec):
        subs = embed_vec[i] - embed_vec[i+1:tot_vec]
        dists = np.einsum('ij,ij->i',subs,subs)
        out[i] = np.count_nonzero(dists < Rsq)
    return out

基准测试

原始方法-

def loopy_app(embed_vec, R):
    tot_vec = embed_vec.shape[0]
    p = np.zeros(tot_vec) # This contains the number of close vectors
    for i in range(tot_vec-1):
        for j in range(i+1, tot_vec):
            if np.linalg.norm(embed_vec[i]-embed_vec[j]) < R:
                p[i] += 1
    return p                

时间安排 -

In [76]: # Sample random array
    ...: embed_vec = np.random.rand(3000,3)
    ...: R = 0.5
    ...: 

In [77]: %timeit loopy_app(embed_vec, R)
1 loops, best of 3: 50.5 s per loop

In [78]: %timeit loopy_less_app(embed_vec, R)
10 loops, best of 3: 143 ms per loop

350x+ 加速!

建议使用更大的数组 loopy_less_app -

In [81]: # Sample random array
    ...: embed_vec = np.random.rand(20000,3)
    ...: R = 0.5
    ...: 

In [82]: %timeit loopy_less_app(embed_vec, R)
1 loops, best of 3: 4.47 s per loop

【讨论】:

  • 这给了我:ValueError: array is too big; arr.size * arr.dtype.itemsize` 大于最大可能大小。 `
  • @Peaceful 查看方法 #2 ?希望这对您的 (60000,3) 数组有好处!
  • 这确实令人印象深刻。你能解释一下如何修改一维向量吗?因为在这种情况下它会抛出错误:ValueError: einstein sum subscripts string contains too many subscripts for operand 0
  • @Peaceful 对于一维数组,跳过einsum 步骤,在最后一步执行:out[i] = np.count_nonzero(np.abs(subs) &lt; R) for loopy_less_app 方法。
  • 如果输入数组既是一维数组又是有序数组,如何进一步加快速度?
【解决方案2】:

先广播区别:

disp_vecs=tot_vec[:,None,:]-tot_vec[None,:,:]

现在,根据您的数据集有多大,您可能希望在不进行所有数学运算的情况下进行拳头传递。如果距离小于r,则所有组件都应小于r

first_mask=np.max(disp_vec, axis=-1)&lt;r

然后进行实际计算

disps=np.linlg.norm(disp_vec[first_mask],axis=-1)
second_mask=disps<r

现在重新分配

disps=disps[second_mask]
first_mask[first_mask]=second_mask

disps 现在是好的值,first_mask 是它们去向的布尔掩码。您可以从那里处理。

【讨论】:

  • 您的意思是写embed_vec 代替tot_vec
【解决方案3】:

我对这个问题很感兴趣,并尝试使用 scipy 的cKDTree 有效地解决它。但是,这种方法可能会耗尽内存,因为在内部维护了距离

import numpy as np
from scipy.spatial import cKDTree as KDTree

tot_vec = 60000
embed_vec = np.random.randn(tot_vec, 3)
R = 0.1

tree = KDTree(embed_vec, leafsize=100)
p = np.zeros(tot_vec)
for pair in tree.query_pairs(R):
    p[pair[0]] += 1
    p[pair[1]] += 1

如果内存是个问题,可以通过一些努力将query_pairs 重写为 Python 中的生成器函数,但会牺牲 C 的性能。

【讨论】:

  • 我正在研究 kmeans,但没有运气。很高兴看到它用于解决这个问题! count_neighbors呢?
  • @Divakar 我猜count_neighbors 效率较低,因为它旨在对两棵树进行操作,因此可能会遍历这棵树两次。不过没试过。
  • 我明白了。不太熟悉 kmeans 工具 :) 我猜想看看一些时间安排会很有趣。
猜你喜欢
  • 1970-01-01
  • 2021-06-20
  • 1970-01-01
  • 1970-01-01
  • 2015-11-01
  • 2018-12-03
  • 1970-01-01
  • 2011-05-10
  • 1970-01-01
相关资源
最近更新 更多