【问题标题】:Search for the nearest array in a huge array of arrays在大量数组中搜索最近的数组
【发布时间】:2018-09-22 17:03:24
【问题描述】:

我需要找到最接近的句子。 我有一个句子数组和一个用户句子,我需要找到最接近用户句子的数组元素。

我使用 word2vec 以向量的形式呈现每个句子:

def get_avg_vector(word_list, model_w2v, size=500):
    sum_vec = np.zeros(shape = (1, size))
    count = 0

    for w in word_list:
        if w in model_w2v and w != '':
            sum_vec += model_w2v[w]
            count +=1
    if count == 0:
        return sum_vec
    else:
        return sum_vec / count + 1

因此,数组元素如下所示:

array([[ 0.93162371,  0.95618944,  0.98519795,  0.98580566,  0.96563747,
         0.97070891,  0.99079191,  1.01572807,  1.00631016,  1.07349398,
         1.02079309,  1.0064849 ,  0.99179418,  1.02865136,  1.02610303,
         1.02909719,  0.99350413,  0.97481178,  0.97980362,  0.98068508,
         1.05657591,  0.97224562,  0.99778703,  0.97888296,  1.01650529,
         1.0421448 ,  0.98731804,  0.98349052,  0.93752996,  0.98205837,
         1.05691232,  0.99914532,  1.02040555,  0.99427229,  1.01193818,
         0.94922226,  0.9818139 ,  1.03955   ,  1.01252615,  1.01402485,
         ...
         0.98990598,  0.99576604,  1.0903802 ,  1.02493086,  0.97395976,
         0.95563786,  1.00538653,  1.0036294 ,  0.97220088,  1.04822631,
         1.02806122,  0.95402776,  1.0048053 ,  0.97677222,  0.97830801]])

我也将用户的句子表示为一个向量,我计算最接近它的元素是这样的:

%%cython
from scipy.spatial.distance import euclidean

def compute_dist(v, list_sentences):
    dist_dict = {}

    for key, val in list_sentences.items():
        dist_dict[key] = euclidean(v, val)

    return sorted(dist_dict.items(), key=lambda x: x[1])[0][0]

上述方法中的list_sentences是一个字典,其中键是句子的文本表示,值是向量。

这需要很长时间,因为我有超过 6000 万个句子。 我怎样才能加快、优化这个过程?

如果有任何建议,我将不胜感激。

【问题讨论】:

标签: python arrays performance numpy word2vec


【解决方案1】:

至少如果您对多个句子执行此过程,您可以尝试使用scipy.spatial.cKDTree(我不知道它是否在单个查询中为自己付费。另外500 相当高,我似乎记得KDTrees 在没有那么多维度的情况下工作得更好。你必须进行实验)。

假设您已将所有向量(dict 值)放入一个大型 numpy 数组中:

>>> import numpy as np
>>> from scipy.spatial import cKDTree as KDTree
>>>
# 100,000 vectors (that's all my RAM can take)
>>> a = np.random.random((100000, 500))
>>>
>>> t = KDTree(a)
# create one new vector and find distance and index of closest
>>> t.query(np.random.random(500))
(8.20910072933986, 83407)

【讨论】:

    【解决方案2】:

    我可以考虑 2 种可能的方法来优化这个过程。

    首先,如果您的目标只是获取最接近的向量(或句子),您可以去掉list_sentences 变量,只将您找到的最接近的句子保留在内存中。这样,您就不需要在最后对完整(并且可能非常大)列表进行排序,而只返回最接近的列表。

    def compute_dist(v, list_sentences):
        min_dist = 0
    
        for key, val in list_sentences.items():
            dist = euclidean(v, val)
            if dist < min_dist:
                closest_sentence = key
                min_dist = dist
    
        return closest_sentence
    

    第二个可能有点不健全。您可以尝试重新实现euclidean 方法,方法是为其提供第三个参数,该参数将是您目前找到的最近向量与用户向量之间的当前最小距离min_dist。我不知道 scipy euclidean 方法是如何实现的,但我想它接近于对所有向量维度的平方差求和。你想要的是在总和高于min_dist时停止的方法(距离无论如何都会高于min_dist,你不会保留它)。

    【讨论】:

      【解决方案3】:

      6000 万个句子向量的初始计算本质上是您只需支付一次的固定成本。我假设您主要关心每个后续查找的时间,对于单个用户提供的查询语句。

      使用 numpy 本机数组操作可以加快距离计算,而不是在 Python 循环中进行自己的单独计算。 (它能够使用优化的代码批量处理。)

      但首先您需要将list_sentences 替换为真正的 numpy 数组,该数组只能通过数组索引访问。 (如果您有其他需要与每个插槽关联的键/文本,您可以在其他地方使用一些 dict 或列表来执行此操作。)

      假设您已经这样做了,以任何对您的数据来说自然的方式,现在有array_sentences,一个 6000 万乘 500 维的 numpy 数组,每行一个句子的平均向量。

      然后,获得一个充满距离的数组的 1-liner 方法是作为 6000 万个候选者和第 1 个查询(给出 6000 万个条目)之间差异的向量长度(“规范”)回答每个差异):

      dists = np.linalg.norm(array_sentences - v)  
      

      另一种 1-liner 方法是使用 numpy 实用函数 cdist() 计算每对两个输入集合之间的距离。在这里,您的第一个集合只是一个查询向量 v(但如果您要同时处理多个批次,一次提供多个查询可能会稍微加快速度):

      dists = np.linalg.cdists(array[v], array_sentences)
      

      (请注意,此类向量比较通常使用余弦距离/余弦相似度而不是欧几里得距离。如果您切换到那个,您可能正在执行其他规范/点积而不是上面的第一个选项,或者使用metric='cosine' 选项为cdist()。)

      一旦你在一个 numpy 数组中获得了所有距离,使用 numpy-native 排序选项可能比使用 Python sorted() 更快。例如,numpy 的间接排序 argsort(),它只返回排序后的索引(从而避免移动所有矢量坐标),因为您只想知道 哪些 项是最佳匹配(es )。例如:

      sorted_indexes = argsort(dists)
      best_index = sorted_indexes[0]
      

      如果您需要将该 int 索引转换回您的其他键/文本,您将使用自己的 dict/list 来记住插槽到键的关系。

      通过与所有候选者进行比较,所有这些仍然给出了完全正确的结果,这(即使做得最好)仍然很耗时。

      有一些方法可以更快地获得结果,基于对完整候选集的预构建索引 - 但此类索引在高维空间(如 500 维空间)中变得非常棘手。他们经常以完全准确的结果换取更快的结果。 (也就是说,它们返回的 'closest 1' 或 'closest N' 会有一些错误,但通常不会相差太多。)有关此类库的示例,请参阅 Spotify's ANNOYFacebook's FAISS

      【讨论】:

        猜你喜欢
        • 2014-03-24
        • 2012-03-19
        • 2020-12-14
        • 2015-09-14
        • 1970-01-01
        • 2018-03-14
        • 1970-01-01
        • 2018-12-28
        • 1970-01-01
        相关资源
        最近更新 更多