给定两个 2D numpy 数组 A 和 B,如何有效地将采用两个 1D 数组的函数应用于 A 和 B 行的每个组合?

Posted

技术标签:

【中文标题】给定两个 2D numpy 数组 A 和 B,如何有效地将采用两个 1D 数组的函数应用于 A 和 B 行的每个组合?【英文标题】:Given two 2D numpy arrays A and B, how to efficiently apply a function that takes two 1D arrays to each combination of rows of A and B? 【发布时间】:2022-01-20 16:27:24 【问题描述】:

要清楚,以下是我想要做的。问题是,我如何更改函数oper_AB(),而不是嵌套的for循环,而是利用numpy中的矢量化/广播并更快地到达ret_list

def oper(a_1D, b_1D):
    return np.dot(a_1D, b_1D) / np.dot(b_1D, b_1D)

def oper_AB(A_2D, B_2D):
    ret_list = []
    for a_1D in A_2D:
        for b_1D in B_2D:
            ret_list.append(oper(a_1D, b_1D))
    return ret_list

【问题讨论】:

旁注:数量oper(a, b)是什么意思? ab 上的标量投影会不同 (a @ b / np.linalg.norm(b))。当除以b @ b 时,您正在除以范数平方。 这是向量投影中的系数。 en.wikipedia.org/wiki/Vector_projection 标量投影为dot(a, b) / norm(b),具有几何意义(向量间夹角的余弦)。 ab 的矢量投影为dot(a,b) / dot(b,b) * b,具有明显的几何解释。您计算的系数本身没有意义(没有乘以b)。如果你以后要乘以b,那很好。否则,我不明白它的含义。仅供参考。 这其实是有含义的。它只是投影向量的归一化版本。这是一个分数,表明a_1Db_1Db_1D 的方向上有多相似。如果为1,则表示a_1Db_1D上的投影等于b_1D 【参考方案1】:

这应该可以。

result = (np.matmul(A_2D, B_2D.transpose())/np.sum(B_2D*B_2D,axis=1)).flatten()

但是由于缓存利用率,第二个实现会更快。

def oper_AB(A_2D, B_2D):
    b_squared = np.sum(B_2D*B_2D,axis=1).reshape([-1,1])
    b_normalized = B_2D/b_squared
    del b_squared
    returned_val = np.matmul(A_2D,b_normalized.transpose())
    return returned_val.flatten()

del 只是在 B_2D 分配的内存太大时才会出现,(或者只是我习惯于使用多个 GB 数组)

编辑:根据 A_1D - B_1D 的要求

def oper2_AB(A_2D, B_2D):
    output = np.zeros([A_2D.shape[0]*B_2D.shape[0],A_2D.shape[1]],dtype=A_2D.dtype)
    for i in range(len(A_2D)):
        output[i*len(B_2D):(i+1)*len(B_2D)] = A_2D[i]-B_2D
    return output

【讨论】:

谢谢,这很快!当oper(a_1D,b_1D) 返回a_1D-b_1D 而不是np.dot(a_1D,b_1D)/np.dot(b_1D,b_1D) 时,我该如何做类似的事情?我们可以安全地假设向量 a_1Db_1D 的大小是相同的。 刚刚用你的额外问题编辑了我的答案,如果解决方案有效,请务必投票。 好的,谢谢,我预计没有循环就没有办法做减法,看起来像这样。 有一种使用 repmat 的方法,但是它会占用更多的内存并且比循环版本执行得慢,所以我不写它。 (它让我的电脑崩溃了)【参考方案2】:

严格解决这个问题(保留我怀疑 OP 想要规范,而不是规范平方,如下面的除数):

r = a @ b.T / np.linalg.norm(b, axis=1)**2

例子:

np.random.seed(0)
a = np.random.randint(0, 10, size=(2,2))
b = np.random.randint(0, 10, size=(2,2))

然后:

>>> a
array([[5, 0],
       [3, 3]])

>>> b
array([[7, 9],
       [3, 5]])

>>> oper_AB(a, b)
[0.2692307692307692,
 0.4411764705882353,
 0.36923076923076925,
 0.7058823529411765]

>>> a @ b.T / np.linalg.norm(b, axis=1)**2
array([[0.26923077, 0.44117647],
       [0.36923077, 0.70588235]])

>>> np.ravel(a @ b.T / np.linalg.norm(b, axis=1)**2)
array([0.26923077, 0.44117647, 0.36923077, 0.70588235])

速度:

n, m = 1000, 100
a = np.random.uniform(size=(n, m))
b = np.random.uniform(size=(n, m))

orig = %timeit -o oper_AB(a, b)
# 2.73 s ± 11 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

new = %timeit -o np.ravel(a @ b.T / np.linalg.norm(b, axis=1)**2)
# 2.22 ms ± 33.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

orig.average / new.average
# 1228.78 (speedup)

我们的解决方案比原来的解决方案快 1200 倍。

正确性:

>>> np.allclose(np.ravel(a @ b.T / np.linalg.norm(b, axis=1)**2), oper_AB(a, b))
True

大型阵列的速度,与@Ahmed AEK 的解决方案相比:

n, m = 2000, 2000
a = np.random.uniform(size=(n, m))
b = np.random.uniform(size=(n, m))

new = %timeit -o np.ravel(a @ b.T / np.linalg.norm(b, axis=1)**2)
# 86.5 ms ± 484 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
other = %timeit -o AEK(a, b)  # Ahmed AEK's answer
# 102 ms ± 379 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

我们的解决方案速度提高了 15% :-)

【讨论】:

两种实现在我的机器上运行 40 次时实际上具有相同的平均运行时间,我认为减少 15% 的时间是靠运气或 CPU 上的一些硬件加速,你也不应该 永远永远当你的意思是 a*a 时使用 a**2 ,因为这个幂比向量乘法慢很多,它实际上比线性缩放慢。

以上是关于给定两个 2D numpy 数组 A 和 B,如何有效地将采用两个 1D 数组的函数应用于 A 和 B 行的每个组合?的主要内容,如果未能解决你的问题,请参考以下文章

从给定的 2D numpy 数组中删除集群

Numpy:找到两个 3-D 数组之间的欧几里得距离

在 python swig 中读取 c++ 2d 数组

将 2D NumPy 数组元素相乘并求和

numpy with python:将3d数组转换为2d

给定一个 numpy 数组视图中项目的索引,在基本数组中找到它的索引