【问题标题】:looking for fast way to compute pair wise distances of many strings寻找快速计算许多字符串的成对距离的方法
【发布时间】:2013-01-21 01:33:37
【问题描述】:

我有一个包含约 100 万个唯一 16 个字符的字符串的列表(一个称为 VEC 的数组),我想计算 Python 中每个字符串的最小成对汉明距离(一个称为 RES 的数组)。基本上,我一次计算一行完整的成对距离矩阵,但只将每行的最小值存储在 RES 中。

VEC= ['AAAAAAAAAAAAAAAA','AAAAAAAAAAAAAAAT','AAAAGAAAAAATAAAA'...]

所以 dist(VEC[1],VEC[2])=1, dist(VEC[1],VEC[3])=2 等等...并且 RES[1]=1。使用这些页面中的提示和技巧,我想出了:

#METHOD#1:
import Levenshtein
import numpy
RES=99*numpy.ones(len(VEC))
i=0
for a in VEC:
    dist=numpy.array([Levenshtein.hamming(a,b) for b in VEC] ) #array of distances
    RES[i]=numpy.amin(dist[dist>0])  #pick min distance greater than zero
    i+=1

仅 10,000 的缩短 VEC 大约需要 70 秒,但如果我将其推断为整百万,则需要 8 天。我的方法似乎很浪费,因为我正在重新计算距离矩阵的对称部分,所以我尝试计算矩阵的一半,同时更新每一行的 RES:

#METHOD #2:
import Levenshtein
import numpy
RES=99*numpy.ones(len(VEC))
for i in range(len(VEC)-1):
    dist=[Levenshtein.hamming(VEC[i],VEC[j]) for j in range(i+1, len(VEC))]
    RES[i]=min(numpy.amin(dist),RES[i])
    #update RES as you go along:
    k=0
    for j in range(i+1,len(VEC)):
        if dist[k]<RES[j]:
             RES[j]=dist[k]
        k+=1

可能不足为奇,第二种方法几乎需要两倍的时间(117 秒),所以它不是很好。无论如何,任何人都可以推荐改进/更改以使其更快吗?

【问题讨论】:

  • 这可能需要很长时间。您可以尝试减少搜索空间,也许通过计算每个字符串中的字符集,如果它们不相交则不计算距离。你对字符串的结构有什么额外的了解吗?如果这样做,可能会进一步限制搜索。
  • 计算给定行中的第一个成对距离。现在对于同一行中的所有其他距离计算,在距离超过此初始距离后停止它。如果它更短,请更新您当前的最小值并重复。这不会降低时间复杂度,但可能会根据您的字符串的排序方式有很大帮助。此外,这个问题很简单,可以用 C 重新编码并获得另一个不断减少的内容。
  • 另外,您是否使用过scipy.distance?它有一个汉明距离度量。我不知道 Levenshtein 库的优化程度如何,但是通过将数据转换为大型 numpy 数组并使用 scipy 工具,您可能会看到收益。
  • 每个字符串都应该是随机的,但我正在研究一些聚类方法。我希望我至少能够得到这个距离矩阵的简化版本作为比较点。检查其他距离计算是一个好主意。 Levenshtein.hamming() 绝对比我的“for”循环实现更快。在找到第一个最小值后停止计算每一行的想法可能会有所帮助。我按字母顺序对字符串进行排序,因此有时相似的字符串会彼此相邻。谢谢。
  • @jfb 我并不是要在第一个局部最小值处停止,而是我的意思是说你在超过当前最小距离后停止计算一对之间的距离。如果您可以修改数据,以便使用 VP-tree 等数据结构,那么一切都可能变得更快。

标签: python distance


【解决方案1】:

如果您只需要每个二进制的最近邻居(忽略自身),并且您可以侥幸获得一个近似最近邻居,您可以考虑实施“位采样" 汉明距离的局部敏感散列。简而言之,创建三个哈希表。从每个 128 位输入中,使用这 16 位样本作为密钥,对 16 位进行 3 次采样。您的哈希表的值应该是具有该采样键的所有 128 位输入的列表。将所有数百万个输入放入 LSH 索引后,只需:

  • 迭代百万点
  • 对于每个输入,执行上述 3 次采样
  • 在三个列表(距离 > 0)的每一个中找到最近的邻居,保持整体最佳

加载和测试都快得离谱。我可能会推荐优秀的 bitarray 库来支持这一点。

【讨论】:

    【解决方案2】:

    我尝试使用 numpy.这是我的代码:

    #!/usr/bin/env python
    
    import numpy as np
    import time
    
    def gen_data(n):
        arr = np.empty(shape=(n, 16))
        for i in range(n):
            arr[i] = np.random.randint(ord('A'), ord('Z')+1, 16)
        return arr
    
    def distance_from_array(i, arr):
        r = arr[i] != arr
        r[i,:] = True
        min_i = np.argmin(np.sum(r, axis=1))
        return min_i
    
    data = gen_data(1000000)
    distances = []
    start = time.time()
    for i in range(200):
        distances.append(distance_from_array(i, data))
    end = time.time()
    print end - start
    

    您可以将字符串列表转换为数字数组。然后您可以使用 numpy 函数来处理数组,例如 sum 和 argmin。如果一个字符串可能出现两次,我认为你不想只找到大于 1 的距离。

    我在我的电脑上测试了一下,处理 200 个字符串大约需要 10 秒。对于每一个,您必须遍历所有 1 000 000 个其他字符串,因此我们可以相当容易地计算出处理所有这些字符串所需的时间。应该是13小时左右。但是,不要忘记我们目前只使用一个核心。如果您拆分索引并使用http://docs.python.org/2/library/multiprocessing.html#module-multiprocessing.pool,您可以很快得到结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-05-18
      • 2012-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-26
      相关资源
      最近更新 更多