二进制numpy数组之间的快速汉明距离计算

ben*_*nbo 8 python arrays numpy cython hamming-distance

我有两个相同长度的numpy数组包含二进制值

import numpy as np
a=np.array([1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0])
b=np.array([1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1])
Run Code Online (Sandbox Code Playgroud)

我想尽可能快地计算它们之间的汉明距离,因为我有数以百万计的这样的距离计算.

一个简单但缓慢的选择(取自维基百科):

%timeit sum(ch1 != ch2 for ch1, ch2 in zip(a, b))
10000 loops, best of 3: 79 us per loop
Run Code Online (Sandbox Code Playgroud)

我已经提出了更快的选项,灵感来自堆栈溢出的一些答案.

%timeit np.sum(np.bitwise_xor(a,b))
100000 loops, best of 3: 6.94 us per loop

%timeit len(np.bitwise_xor(a,b).nonzero()[0])
100000 loops, best of 3: 2.43 us per loop
Run Code Online (Sandbox Code Playgroud)

我想知道是否有更快的方法来计算这个,可能使用cython?

yev*_*niy 15

有一个准备好的numpy功能击败len((a != b).nonzero()[0]);)

np.count_nonzero(a!=b)
Run Code Online (Sandbox Code Playgroud)


小智 7

与我平台上 np.count_nonzero(a!=b) 的 1.07µs 相比,gmpy2.hamdist 在将每个数组转换为 mpz(多精度整数)后降低到大约 143ns:

import numpy as np
from gmpy2 import mpz, hamdist, pack

a = np.array([1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0])
b = np.array([1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1])
Run Code Online (Sandbox Code Playgroud)

根据@casevh 的提示,使用 gmpy2.pack(list(reversed(list(array))),1) 可以合理有效地完成从一维数组的一和零到 gmpy2 mpz 对象的转换。

# gmpy2.pack reverses bit order but that does not affect
# hamdist since both its arguments are reversed
ampz = pack(list(a),1) # takes about 4.29µs
bmpz = pack(list(b),1)

hamdist(ampz,bmpz)
Out[8]: 7

%timeit hamdist(ampz,bmpz)
10000000 loops, best of 3: 143 ns per loop
Run Code Online (Sandbox Code Playgroud)

对于相对比较,在我的平台上:

%timeit np.count_nonzero(a!=b)
1000000 loops, best of 3: 1.07 µs per loop

%timeit len((a != b).nonzero()[0])
1000000 loops, best of 3: 1.55 µs per loop

%timeit len(np.bitwise_xor(a,b).nonzero()[0])
1000000 loops, best of 3: 1.7 µs per loop

%timeit np.sum(np.bitwise_xor(a,b))
100000 loops, best of 3: 5.8 µs per loop   
Run Code Online (Sandbox Code Playgroud)

  • 您可以使用 `gmpy2.pack(list(a),1)` 将 numpy 数组转换为 mpz。它比 `convert2mpz()` 快。如果包括转换时间,它仍然会比 numpy 解决方案慢。 (3认同)
  • 公平地说,您可能应该包括将输入数组转换为 mpz 格式所需的时间。 (2认同)

ser*_*lle 5

在这里使用pythran可以带来额外的好处:

$ cat hamm.py
#pythran export hamm(int[], int[])
from numpy import nonzero
def hamm(a,b):
    return len(nonzero(a != b)[0])
Run Code Online (Sandbox Code Playgroud)

作为参考(没有pythran):

$ python -m timeit -s 'import numpy as np; a = np.random.randint(0,2, 100); b = np.random.randint(0,2, 100); from hamm import hamm' 'hamm(a,b)'
100000 loops, best of 3: 4.66 usec per loop
Run Code Online (Sandbox Code Playgroud)

pythran编译后:

$ python -m pythran.run hamm.py
$ python -m timeit -s 'import numpy as np; a = np.random.randint(0,2, 100); b = np.random.randint(0,2, 100); from hamm import hamm' 'hamm(a,b)'
1000000 loops, best of 3: 0.745 usec per loop
Run Code Online (Sandbox Code Playgroud)

大约是6x通过numpy实现的速度提高了,因为pythran在评估按元素进行比较时会跳过中间数组的创建。

我还测量了:

def hamm(a,b):
    return count_nonzero(a != b)
Run Code Online (Sandbox Code Playgroud)

我得到3.11 usec per loop了Python版本和0.427 usec per loopPythran 版本。

免责声明:我是Pythran开发人员之一。