我有一个布尔矩阵,其中有 1.5E6
行和 20E3
列,类似于以下示例:
M = [[ True, True, False, True, ...],
[False, True, True, True, ...],
[False, False, False, False, ...],
[False, True, False, False, ...],
...
[ True, True, False, False, ...]
]
此外,我还有另一个矩阵 N
(1.5E6
行,1
列):
N = [[ True],
[False],
[ True],
[ True],
...
[ True]
]
我需要做的是通过M
运算符组合的矩阵AND
(1&1、1&2、1&3、1&N,2&1、2&2等)中的每一列对进行计算,并计算结果与矩阵N
之间存在许多重叠。
我的Python / Numpy代码如下:
for i in range(M.shape[1]):
for j in range(M.shape[1]):
result = M[:,i] & M[:,j] # Combine the columns with AND operator
count = np.sum(result & N.ravel()) # Counts the True occurrences
... # Save the count for the i and j pair
问题在于,通过两个{for循环进行20E3 x 20E3
组合在计算上是昂贵的( 需要5到10天的时间进行计算 )。我尝试过的一个更好的选择是将每一列与整个矩阵M进行比较:
for i in range(M.shape[1]):
result = M[:,i]*M.shape[1] & M # np.tile or np.repeat is used to horizontally repeat the column
counts = np.sum(result & N*M.shape[1], axis=0)
... # Save the counts
这可以将开销和计算时间减少到10%左右,但是 仍需要花费 1天左右时间来进行计算。
我的问题将是:
进行这些计算(基本上只是AND
和SUM
的最快方法(也许不是Python?)是什么?
我当时正在考虑使用低级语言,GPU处理,量子计算等。但是我对这些都不了解,因此对方向的任何建议都是值得的!
其他想法: 当前正在考虑是否可以使用点积(如Davikar提出的那样)快速计算组合的三胞胎:
def compute(M, N):
out = np.zeros((M.shape[1], M.shape[1], M.shape[1]), np.int32)
for i in range(M.shape[1]):
for j in range(M.shape[1]):
for k in range(M.shape[1]):
result = M[:, i] & M[:, j] & M[:, k]
out[i, j, k] = np.sum(result & N.ravel())
return out
答案 0 :(得分:6)
只需使用np.einsum
即可获取所有计数-
np.einsum('ij,ik,i->jk',M,M.astype(int),N.ravel())
随意使用带有optimize
的{{1}}标志。另外,还可以随意使用不同的dtypes转换。
要利用GPU,我们可以使用也支持einsum
的np.einsum
软件包。
使用tensorflow
的更快速选择:
np.dot
时间-
(M&N).T.dot(M.astype(int))
(M&N).T.dot(M.astype(np.float32))
对于两个布尔数组,通过float32转换使它更进一步-
In [110]: np.random.seed(0)
...: M = np.random.rand(500,300)>0.5
...: N = np.random.rand(500,1)>0.5
In [111]: %timeit np.einsum('ij,ik,i->jk',M,M.astype(int),N.ravel())
...: %timeit (M&N).T.dot(M.astype(int))
...: %timeit (M&N).T.dot(M.astype(np.float32))
227 ms ± 191 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
66.8 ms ± 198 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
3.26 ms ± 753 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
答案 1 :(得分:5)
编辑:要修复下面的代码以适应已解决的问题,在compute
中只需进行几处小改动:
def compute(m, n):
m = np.asarray(m)
n = np.asarray(n)
# Apply mask N in advance
m2 = m & n
# Pack booleans into uint8 for more efficient bitwise operations
# Also transpose for better caching (maybe?)
mb = np.packbits(m2.T, axis=1)
# Table with number of ones in each uint8
num_bits = (np.arange(256)[:, np.newaxis] & (1 << np.arange(8))).astype(bool).sum(1)
# Allocate output array
out = np.zeros((m2.shape[1], m2.shape[1]), np.int32)
# Do the counting with Numba
_compute_nb(mb, num_bits, out)
# Make output symmetric
out = out + out.T
# Add values in diagonal
out[np.diag_indices_from(out)] = m2.sum(0)
# Scale by number of ones in n
return out
我将使用一些技巧与Numba一起使用。首先,您只能执行按列操作的一半,因为另一半是重复的。其次,您可以将布尔值打包为字节,因此对于每个&
,您将操作八个值而不是一个。第三,您可以使用多重处理对其进行并行化。总的来说,您可以这样做:
import numpy as np
import numba as nb
def compute(m, n):
m = np.asarray(m)
n = np.asarray(n)
# Pack booleans into uint8 for more efficient bitwise operations
# Also transpose for better caching (maybe?)
mb = np.packbits(m.T, axis=1)
# Table with number of ones in each uint8
num_bits = (np.arange(256)[:, np.newaxis] & (1 << np.arange(8))).astype(bool).sum(1)
# Allocate output array
out = np.zeros((m.shape[1], m.shape[1]), np.int32)
# Do the counting with Numba
_compute_nb(mb, num_bits, out)
# Make output symmetric
out = out + out.T
# Add values in diagonal
out[np.diag_indices_from(out)] = m.sum(0)
# Scale by number of ones in n
out *= n.sum()
return out
@nb.njit(parallel=True)
def _compute_nb(mb, num_bits, out):
# Go through each pair of columns without repetitions
for i in nb.prange(mb.shape[0] - 1):
for j in nb.prange(1, mb.shape[0]):
# Count common bits
v = 0
for k in range(mb.shape[1]):
v += num_bits[mb[i, k] & mb[j, k]]
out[i, j] = v
# Test
m = np.array([[ True, True, False, True],
[False, True, True, True],
[False, False, False, False],
[False, True, False, False],
[ True, True, False, False]])
n = np.array([[ True],
[False],
[ True],
[ True],
[ True]])
out = compute(m, n)
print(out)
# [[ 8 8 0 4]
# [ 8 16 4 8]
# [ 0 4 4 4]
# [ 4 8 4 8]]
作为一个快速的比较,这是一个针对原始循环和仅NumPy的方法的小型基准测试(我很确定Divakar的建议是从NumPy可获得的最佳建议):
import numpy as np
# Original loop
def compute_loop(m, n):
out = np.zeros((m.shape[1], m.shape[1]), np.int32)
for i in range(m.shape[1]):
for j in range(m.shape[1]):
result = m[:, i] & m[:, j]
out[i, j] = np.sum(result & n)
return out
# Divakar methods
def compute2(m, n):
return np.einsum('ij,ik,lm->jk', m, m.astype(int), n)
def compute3(m, n):
return np.einsum('ij,ik->jk',m, m.astype(int)) * n.sum()
def compute4(m, n):
return np.tensordot(m, m.astype(int),axes=((0,0))) * n.sum()
def compute5(m, n):
return m.T.dot(m.astype(int))*n.sum()
# Make random data
np.random.seed(0)
m = np.random.rand(1000, 100) > .5
n = np.random.rand(1000, 1) > .5
print(compute(m, n).shape)
# (100, 100)
%timeit compute(m, n)
# 768 µs ± 17.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit compute_loop(m, n)
# 11 s ± 1.23 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit compute2(m, n)
# 7.65 s ± 1.06 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit compute3(m, n)
# 23.5 ms ± 1.53 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit compute4(m, n)
# 8.96 ms ± 194 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit compute5(m, n)
# 8.35 ms ± 266 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
答案 2 :(得分:0)
我建议尝试不使用Python:将列转换为位字段,将N也转换为位字段,&
每个三元组在一起,然后使用(bin(num) .count('1'))(如果numpy有一个,则为适当的popcnt)。