【问题标题】:Python - How to generate the Pairwise Hamming Distance MatrixPython - 如何生成成对汉明距离矩阵
【发布时间】:2017-08-02 19:38:39
【问题描述】:

这里是 Python 初学者。因此,我在尝试仅使用 numpy 库计算输入矩阵的行之间生成的二进制成对汉明顿距离矩阵时遇到了麻烦。我应该避免循环并使用矢量化。例如,如果我有类似的东西:

   [ 1,  0,  0,  1,  1,  0]
   [ 1,  0,  0,  0,  0,  0]
   [ 1,  1,  1,  1,  0,  0]

矩阵应该是这样的:

   [ 0,  2,  3]
   [ 2,  0,  3]
   [ 3,  3,  0]

即如果原始矩阵是A,汉明距离矩阵是B。B[0,1] = 汉明距离(A[0] 和A[1])。在这种情况下,答案是 2,因为它们只有两个不同的元素。

所以我的代码是这样的

def compute_HammingDistance(X):

     hammingDistanceMatrix = np.zeros(shape = (len(X), len(X)))
     hammingDistanceMatrix = np.count_nonzero ((X[:,:,None] != X[:,:,None].T))
     return hammingDistanceMatrix

然而,它似乎只是返回一个标量值而不是预期的矩阵。我知道我可能在数组/向量广播方面做错了,但我不知道如何解决它。我尝试使用 np.sum 而不是 np.count_nonzero 但它们几乎都给了我类似的东西。

【问题讨论】:

    标签: python numpy vectorization hamming-distance


    【解决方案1】:

    尝试这种方法,沿axis = 1 创建一个新轴,然后使用sum 进行广播并计算真或非零:

    (arr[:, None, :] != arr).sum(2)
    
    # array([[0, 2, 3],
    #        [2, 0, 3],
    #        [3, 3, 0]])
    

    def compute_HammingDistance(X):
        return (X[:, None, :] != X).sum(2)
    

    解释

    1) 创建一个形状为 (3,1,6) 的 3d 数组

    arr[:, None, :]
    #array([[[1, 0, 0, 1, 1, 0]],
    #       [[1, 0, 0, 0, 0, 0]],
    #       [[1, 1, 1, 1, 0, 0]]])
    

    2) 这是一个二维数组,形状为 (3, 6)

    arr   
    #array([[1, 0, 0, 1, 1, 0],
    #       [1, 0, 0, 0, 0, 0],
    #       [1, 1, 1, 1, 0, 0]])
    

    3) 这会触发广播,因为它们的形状不匹配,并且 2d 数组 arr 首先沿 3d 数组的 0 轴广播 arr[:, None, :],然后我们对 (3, 6) 广播形状为 (1, 6) 的数组。这两个广播步骤一起对原始数组进行笛卡尔比较。

    arr[:, None, :] != arr 
    #array([[[False, False, False, False, False, False],
    #        [False, False, False,  True,  True, False],
    #        [False,  True,  True, False,  True, False]],
    #       [[False, False, False,  True,  True, False],
    #        [False, False, False, False, False, False],
    #        [False,  True,  True,  True, False, False]],
    #       [[False,  True,  True, False,  True, False],
    #        [False,  True,  True,  True, False, False],
    #        [False, False, False, False, False, False]]], dtype=bool)
    

    4) sum 沿第三个轴计算有多少元素不相等,即给出汉明距离的 true。

    【讨论】:

    • 我用 hammingDistanceMatrix = np.count_nonzero((X[:, None, :] != X).sum(2)) 替换了我在代码中的原件,它似乎仍然给了我相同的结果,单个标量值。
    • 这里不需要 np.count_nonzero 。 sum 为您服务。只需返回hammingDistanceMatrix = (arr[:, None, :] != arr).sum(2) 就可以了。
    • 哦,成功了!非常感谢。如果您能解释一下为什么要像这样 arr[:,None,:] 进行广播,以及为什么要使用 .sum(2),是否有可能?
    【解决方案2】:

    由于我不明白的原因

    (2 * np.inner(a-0.5, 0.5-a) + a.shape[1] / 2)
    

    对于较大的数组,似乎比 @Psidom 快得多:

    a = np.random.randint(0,2,(100,1000))
    timeit(lambda: (a[:, None, :] != a).sum(2), number=100)
    # 2.297890231013298
    timeit(lambda: (2 * np.inner(a-0.5, 0.5-a) + a.shape[1] / 2), number=100)
    # 0.10616962902713567
    

    对于非常小的示例,Psidom 的速度要快一些:

    a
    # array([[1, 0, 0, 1, 1, 0],
    #        [1, 0, 0, 0, 0, 0],
    #        [1, 1, 1, 1, 0, 0]])
    
    timeit(lambda: (a[:, None, :] != a).sum(2), number=100)
    # 0.0004370050155557692
    timeit(lambda: (2 * np.inner(a-0.5, 0.5-a) + a.shape[1] / 2), number=100)
    # 0.00068191799800843
    

    更新

    部分原因似乎是浮点数比其他数据类型更快:

    timeit(lambda: (0.5 * np.inner(2*a-1, 1-2*a) + a.shape[1] / 2), number=100)
    # 0.7315902590053156
    timeit(lambda: (0.5 * np.inner(2.0*a-1, 1-2.0*a) + a.shape[1] / 2), number=100)
    # 0.12021801102673635
    

    【讨论】:

    • 这是一种比较元素并一次性计数的聪明方法。我认为在这种情况下浮点数据类型更快的主要原因是 BLAS 例程仅适用于这些数据类型。所有其他 dtype 都由优化程度较低的 numpy C 代码处理。
    • @user7138814 哇,我不知道 BLAS 有很大的不同!