Python 求两个阵列重叠的算法

Python 求两个阵列重叠的算法,python,algorithm,numpy,Python,Algorithm,Numpy,我的代码中有一个瓶颈,我正在努力解决 取一个数组A,大小为nxm,只包含1和0。我需要一个算法,该算法需要两行A的所有组合,并计算它们之间的重叠 更具体地说,我需要一个比以下算法更快的替代方案: for i in range(A.shape[0]): for j in range(A.shape[0]): a=b=c=d=0 for k in range(A.shape[1]): if A[i][k]==1 and A[j][k]==1: a+=1

我的代码中有一个瓶颈,我正在努力解决

取一个数组A,大小为nxm,只包含1和0。我需要一个算法,该算法需要两行A的所有组合,并计算它们之间的重叠

更具体地说,我需要一个比以下算法更快的替代方案:

for i in range(A.shape[0]):
  for j in range(A.shape[0]):
    a=b=c=d=0
    for k in range(A.shape[1]):
     if A[i][k]==1 and A[j][k]==1:
       a+=1
     if A[i][k]==0 and A[j][k]==0:
       b+=1
     if A[i][k]==1 and A[j][k]==0:
       c+=1
     if A[i][k]==0 and A[j][k]==1:
       d+=1
    print(a,b,c,d)

谢谢你的回复

由于
a、b、c、d
都在循环中,因此我假设您希望每个
I、j都有它们。我将用
[I,j]
中的元素为它们创建一个矩阵,该元素是循环
a,b,c,d
的对应值,不带任何循环。例如
a[i,j]
是循环
i,j
a
的值:

A_c = 1-A
a = np.dot(A, A.T)
b = np.dot(A_c, A.T)
c = np.dot(A, A_c.T)
d = np.dot(A_c, A_c.T)

如果您更关心速度,您可以分解和缩短/重用上面等式中的一些计算

由于
a、b、c、d
都在循环中,因此我假设您希望每个
I、j都有它们。我将用
[I,j]
中的元素为它们创建一个矩阵,该元素是循环
a,b,c,d
的对应值,不带任何循环。例如
a[i,j]
是循环
i,j
a
的值:

A_c = 1-A
a = np.dot(A, A.T)
b = np.dot(A_c, A.T)
c = np.dot(A, A_c.T)
d = np.dot(A_c, A_c.T)

如果您更关心速度,您可以分解和缩短/重用上面等式中的一些计算

虽然上面的答案绝对正确,但我想继续回答一个更技术性的问题——主要是因为我做了一些与上周你问题中的问题非常相似的事情,并在这一过程中学到了一些很酷的东西

首先,是的,矩阵乘法和向量化是正确的方法。然而,当矩阵变大时,这些可能会变得有点昂贵。让我展示一个N=100和M=100的小基准:

N,M = 100,100
A = np.random.randint(2,size=(N,M))

def type1():
    A_c = 1-A
    a = np.dot(A, A.T)
    b = np.dot(A_c, A.T)
    c = np.dot(A, A_c.T)
    d = np.dot(A_c, A_c.T)
    return a,b,c,d

%timeit -n 100 type1()

>>>3.76 ms ± 48.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
一个简单的加速可以通过以下事实来实现:
a+b+c+d=M
。我们实际上不需要找到d;因此,我们可以减少一个昂贵的点产品在这里

def type2():
    A_c = 1-A
    a = np.dot(A, A.T)
    b = np.dot(A_c, A.T)
    c = np.dot(A, A_c.T)
    return a,b,c,M-(a+b+c)

%timeit -n 100 type2()
>>>2.81 ms ± 15.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
这几乎缩短了一毫秒,但我们可以做得更好。Numpy数组有两种顺序:
C-连续
F-连续
。您可以通过打印
A.flags
来检查这一点;默认情况下,A是C连续数组。然而,它的转置A.T表示为一个F-连续数组,当我们将它们传递给dot时,会为A.T创建一个内部副本,因为顺序不匹配

绕过这个问题的一种方法是转到scipy并将我们的程序与BLAS()连接起来,特别是通用矩阵乘法
gemm
例程

from scipy.linalg import blas as B

def type3():
    A_c = 1-A
    a = B.dgemm(alpha=1.0, a=A, b=A, trans_b=True)
    b = B.dgemm(alpha=1.0, a=A_c, b=A, trans_b=True)
    c = B.dgemm(alpha=1.0, a=A, b=A_c, trans_b=True)
    return a,b,c,M-(a+b+c)

%timeit -n 100 type3()
>>>449 µs ± 27 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

时间从毫秒直接下降到了微秒,这真是太棒了。

虽然上面的答案是绝对正确的,但我想跟进一个更技术性的答案——主要是因为我做了一些与上周你问题中的问题非常相似的事情,一路上还学到了一些很酷的东西

首先,是的,矩阵乘法和向量化是正确的方法。然而,当矩阵变大时,这些可能会变得有点昂贵。让我展示一个N=100和M=100的小基准:

N,M = 100,100
A = np.random.randint(2,size=(N,M))

def type1():
    A_c = 1-A
    a = np.dot(A, A.T)
    b = np.dot(A_c, A.T)
    c = np.dot(A, A_c.T)
    d = np.dot(A_c, A_c.T)
    return a,b,c,d

%timeit -n 100 type1()

>>>3.76 ms ± 48.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
一个简单的加速可以通过以下事实来实现:
a+b+c+d=M
。我们实际上不需要找到d;因此,我们可以减少一个昂贵的点产品在这里

def type2():
    A_c = 1-A
    a = np.dot(A, A.T)
    b = np.dot(A_c, A.T)
    c = np.dot(A, A_c.T)
    return a,b,c,M-(a+b+c)

%timeit -n 100 type2()
>>>2.81 ms ± 15.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
这几乎缩短了一毫秒,但我们可以做得更好。Numpy数组有两种顺序:
C-连续
F-连续
。您可以通过打印
A.flags
来检查这一点;默认情况下,A是C连续数组。然而,它的转置A.T表示为一个F-连续数组,当我们将它们传递给dot时,会为A.T创建一个内部副本,因为顺序不匹配

绕过这个问题的一种方法是转到scipy并将我们的程序与BLAS()连接起来,特别是通用矩阵乘法
gemm
例程

from scipy.linalg import blas as B

def type3():
    A_c = 1-A
    a = B.dgemm(alpha=1.0, a=A, b=A, trans_b=True)
    b = B.dgemm(alpha=1.0, a=A_c, b=A, trans_b=True)
    c = B.dgemm(alpha=1.0, a=A, b=A_c, trans_b=True)
    return a,b,c,M-(a+b+c)

%timeit -n 100 type3()
>>>449 µs ± 27 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

时间从毫秒直接下降到了微秒,这真是太棒了。

仅仅是为了它的运动,这里有一个比当前最快的速度快三倍的方法。我们利用了float上矩阵计算更快的优势,特别是float32。此外,我们只进行一次矩阵乘法,通过更便宜的方法推断其他数字:

def pp():
    A1 = np.count_nonzero(A,1)
    Af = A.astype('f4')
    a = Af@Af.T
    b = A1-a
    c = b.T
    d = M-a-b-c
    return a,b,c,d


[*map(np.array_equal,pp(),type3())]
# [True, True, True, True]

timeit(pp,number=1000)
# 0.14910832402529195
timeit(type3,number=1000)
# 0.4432948770117946

仅就it运动而言,这里有一种比当前最快的速度快三倍的方法。我们利用了float上矩阵计算更快的优势,特别是float32。此外,我们只进行一次矩阵乘法,通过更便宜的方法推断其他数字:

def pp():
    A1 = np.count_nonzero(A,1)
    Af = A.astype('f4')
    a = Af@Af.T
    b = A1-a
    c = b.T
    d = M-a-b-c
    return a,b,c,d


[*map(np.array_equal,pp(),type3())]
# [True, True, True, True]

timeit(pp,number=1000)
# 0.14910832402529195
timeit(type3,number=1000)
# 0.4432948770117946

如前所述,如果
A
是一个列表列表(例如
A.tolist()
),这将更快
A[j][k]
使用数组进行索引的成本很高(与列表相比);尽量避免重复这样做。或者,尝试对A:…`中的行
而不是
范围
,并重复
A[i]
。等等。你能举例说明“重叠”是什么意思吗?代码准确地解释了我的意思。很难用语言来表达。假设[1,0,0,1]和[0,1,0,1]是两行。然后沿着零轴将它们粘在一起,得到[[1,0,0,1],[0,1,0,1]]。然后我想计算不同列的数量,即(1,1)、(0,0)、(1,0)和(0,1)的数量。
numpy
中的速度通常来自“矢量化”,也就是说,将python级别的迭代移动到编译的numpy方法中。这就是尽可能使用整个阵列构建块。在本例中,我将关注
k
循环。我还没有试着弄清楚它在做什么,所以无法直接帮助。但是试着想想测试两行的方法,而不需要迭代。类似于
row1==row2
和布尔测试,如
rows1 | row2
等。如果
A
是一个列表列表(例如
A.tolist()
),那么编写这样的测试会更快
A[j][k]
使用数组进行索引非常昂贵(与列表相比)