我有一大组N d维向量(位于一个矩阵中),这些向量通过取自身外积(即每个向量乘以自身)来进行提升。对于每个矢量,这将生成一个具有(d + 1)的对称矩阵,并选择2个唯一条目。对于整个数据,这是一个N xd x d张量。我只想计算每个张量切片的较低对角线中唯一的(d + 1)选择2个条目并将它们存储在向量中。我想以最少的内存占用量并在Python中尽可能快地做到这一点-包括使用C绑定。
如果使用标准的numpy方法执行此操作,则它将分配每个矩阵的整体。这大约是实际所需内存复杂度的两倍。
在此处具有规模感,请考虑N = 20k和d = 20k的情况。然后,N * d ^ 2 *每个元素〜8个字节=(2 * 10 ^ 4)^ 3 * 8个字节= 64 TB。
如果仅计算编码唯一项的向量,则(20001选择2)* 20k * 8 = 200010000 * 20000 * 8字节= 32 TB。
是否有一种快速的方法来执行此操作而无需求助于慢速的方法(例如,用python编写我自己的外部产品)?
编辑:我会注意到在Create array of outer products in numpy
中提出了类似的问题我已经知道如何使用einsum进行计算(如上述问题所示)。但是,如果没有额外的(d选择2)计算和分配,则无法解决此问题
编辑2: 该线程How to exploit symmetry in outer product in Numpy (or other Python solutions)?提出了一个相关问题,但没有解决内存复杂性问题。最高答案仍将为每个外部乘积分配一个d x d数组。
该线程Numpy Performance - Outer Product of a vector with its transpose还解决了自外部乘积的计算问题,但没有达到内存有效的解决方案。
编辑3:
如果要分配整个数组然后提取元素,np.tril_indices
或scipy.spatial.distance.squareform
可以解决问题。
答案 0 :(得分:0)
不确定确切要如何输出,但是始终可以使用Numba:
import numpy as np
import numba as nb
# Computes unique pairwise products
@nb.njit(parallel=True)
def self_outer_unique(a):
n, d = a.shape
out = np.empty((n, (d * d + d) // 2), dtype=a.dtype)
for i in nb.prange(n):
for j1 in range(d):
for j2 in range(j1, d):
idx = j1 * (2 * d - j1 + 1) // 2 + j2 - j1
out[i, idx] = a[i, j1] * a[i, j2]
return out
这将为您提供一个在每一行上具有所有唯一乘积的数组(即完整输出的扁平上三角)。
import numpy as np
a = np.arange(12).reshape(4, 3)
print(a)
# [[ 0 1 2]
# [ 3 4 5]
# [ 6 7 8]
# [ 9 10 11]]
print(self_outer_unique(a))
# [[ 0 0 0 1 2 4]
# [ 9 12 15 16 20 25]
# [ 36 42 48 49 56 64]
# [ 81 90 99 100 110 121]]
从性能角度来看,它比使用NumPy计算完整外部乘积要快,尽管从中重新创建完整数组需要更长的时间。
import numpy as np
def np_self_outer(a):
return a[:, :, np.newaxis] * a[:, np.newaxis, :]
def my_self_outer(a):
b = self_outer_unique(a)
n, d = a.shape
b_full = np.zeros((n, d, d), dtype=a.dtype)
idx0 = np.arange(n)[:, np.newaxis]
idx1, idx2 = np.triu_indices(d)
b_full[idx0, idx1, idx2] = b
b_full += np.triu(b_full, 1).transpose(0, 2, 1)
return b_full
n, d = 1000, 100
a = np.arange(n * d).reshape(n, d)
print(np.all(np_self_outer(a) == my_self_outer(a)))
# True
%timeit np_self_outer(a)
# 24.6 ms ± 248 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit self_outer_unique(a)
# 6.32 ms ± 69.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit my_self_outer(a)
# 124 ms ± 770 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)