我正在寻找一种可靠的方法将使用scipy.spatial.distance.pdist
函数生成的精简汉明距离数组转换为相应的2D汉明距离矩阵。我知道scipy.spatial.distance.squareform
功能。但是,我正在计算高达100,000 x 100,000矩阵的汉明距离,这导致Python中的MemoryError
。
我正在寻找一种将压缩矩阵逐行转换为方形的方法。有没有人知道使用NumPy和/或相关软件包的可靠(并且可能是快速)实现?
我需要对每一行执行numpy.sum
次计算,但无法将完整的N x N矩阵存储在内存中。
目前,我正在使用嵌套循环迭代输入矩阵并计算距离"手动"。
identity = 0.7
hamming_sum = numpy.zeros(msa_mat.shape[0], dtype=numpy.float64)
hamming_dist = numpy.zeros(msa_mat.shape[0], dtype=numpy.float64)
for i, row1 in enumerate(msa_mat):
hamming_dist.fill(0)
for j, row2 in enumerate(msa_mat):
if i != j:
hamming_dist[j] = scipy.spatial.distance.hamming(row1, row2)
hamming_sum[i] = numpy.sum(numpy.where(hamming_dist < (1 - identity), 1, 0), axis=0)
我的数据类似于以下矩阵:
>>> a = numpy.array([1, 2, 3, 4, 5, 4, 5, 4, 2, 7, 9, 4, 1, 5, 6, 2, 3, 6], dtype=float).reshape(3, 6)
>>> a
array([[ 1., 2., 3., 4., 5., 4.],
[ 5., 4., 2., 7., 9., 4.],
[ 1., 5., 6., 2., 3., 6.]])
我想计算此矩阵的汉明距离。对于小型矩阵,可以使用SciPy中的cdist
命令轻松完成此操作,如下所示:
>>> cdist(a, a, 'hamming')
array([[ 0. , 0.83333333, 0.83333333],
[ 0.83333333, 0. , 1. ],
[ 0.83333333, 1. , 0. ]])
但是,在具有更大矩阵的情况下,这会在Python中引发MemoryError。
我知道在这种情况下我可以使用pdist
命令计算汉明距离。这将返回1D数组中上三角形的距离。
>>> pdist(a, 'hamming')
array([ 0.83333333, 0.83333333, 1. ])
我的问题涉及这样一个事实:我不知道如何从每行<{1}}结果重建cdist
矩阵。
我知道pdist
函数但是它再次引发了大型矩阵的MemoryErrors。
答案 0 :(得分:3)
这是一种使用基于ID的np.bincount
-
def getdists_v1(a):
n = a.shape[0]
r,c = np.triu_indices(n,1)
vals = pdist(a, 'hamming') < (1 - identity)
return np.bincount(r,vals,minlength=n) + np.bincount(c,vals,minlength=n) + 1
这是另一个使用np.add.reduceat
专注于内存效率的bin-based
-
def getdists_v2(a):
n = a.shape[0]
nr = (n*(n-1))//2
vals = pdist(a, 'hamming') < (1 - identity)
sfidx = n*np.arange(0,n-1) - np.arange(n-1).cumsum()
id_arr = np.ones(nr,dtype=int)
id_arr[sfidx[1:]] = -np.arange(n-3,-1,-1)
c = id_arr.cumsum()
out = np.bincount(c,vals)+1
out[:n-1] += np.add.reduceat(vals,sfidx)
return out
这是另一个循环计算下三角区域行方式求和的方法 -
def getdists_v3(a):
n = a.shape[0]
r_arr = np.arange(n-1)
cr_arr = r_arr.cumsum()
sfidx_c = (n-1)*r_arr - cr_arr
vals = pdist(a, 'hamming') < (1 - identity)
out = np.zeros(n)
for i in range(n-1):
out[i+1] = np.count_nonzero(vals[sfidx_c[:i+1] + i])
out[:n-1] += np.add.reduceat(vals, n*r_arr - cr_arr)
out[:] += 1
return out
答案 1 :(得分:2)
避免内存问题的一种方法是批量使用cdist
:
import numpy as np
from scipy.spatial.distance import cdist
def count_hamming_neighbors(points, radius, batch_size=None):
n = len(points)
if batch_size is None:
batch_size = min(n, 1000)
hamming_sum = np.zeros(n, dtype=int)
num_full_batches, last_batch = divmod(n, batch_size)
batches = [batch_size]*num_full_batches
if last_batch != 0:
batches.append(last_batch)
for k, batch in enumerate(batches):
i = batch_size*k
dists = cdist(points[i:i+batch], points, metric='hamming')
hamming_sum[i:i+batch] = (dists < radius).sum(axis=1)
return hamming_sum
这是对Divakar getdists_v3(a)
的比较,以确保我们得到相同的结果:
In [102]: np.random.seed(12345)
In [103]: a = np.random.randint(0, 4, size=(16, 4))
In [104]: count_hamming_neighbors(a, 0.3)
Out[104]: array([1, 1, 3, 2, 2, 1, 2, 1, 3, 2, 3, 2, 2, 1, 2, 2])
In [105]: identity = 0.7
In [106]: getdists_v3(a)
Out[106]:
array([ 1., 1., 3., 2., 2., 1., 2., 1., 3., 2., 3., 2., 2.,
1., 2., 2.])
比较更大阵列的时间:
In [113]: np.random.seed(12345)
In [114]: a = np.random.randint(0, 4, size=(10000, 4))
In [115]: %timeit hamming_sum = count_hamming_neighbors(a, 0.3)
1 loop, best of 3: 714 ms per loop
In [116]: %timeit v3result = getdists_v3(a)
1 loop, best of 3: 1.05 s per loop
所以它快一点。更改批量大小会影响性能,有时会以令人惊讶的方式:
In [117]: %timeit hamming_sum = count_hamming_neighbors(a, 0.3, batch_size=250)
1 loop, best of 3: 643 ms per loop
In [118]: %timeit hamming_sum = count_hamming_neighbors(a, 0.3, batch_size=2000)
1 loop, best of 3: 875 ms per loop
In [119]: %timeit hamming_sum = count_hamming_neighbors(a, 0.3, batch_size=125)
1 loop, best of 3: 664 ms per loop