我主要在MATLAB中工作,但我认为答案应该不难从一种语言移植到另一种语言。
我有一个维度为df %>%
group_by(ID) %>%
mutate(V3 = as.numeric(any(V2 == 1)))
的多维数组X
。
我想计算以下多维数组。
[n, p, 3]
总和是长度{T = zeros(p, p, p)
for i = 1:p
for j = 1:p
for k = 1:p
T(i, j, k) = sum(X(:, i, 1) .* X(:, j, 2) .* X(:, k, 3));
end
end
end
向量的元素。任何帮助表示赞赏!
答案 0 :(得分:5)
您只需要对维度进行置换和单例扩展的乘积:
T = sum(bsxfun(@times, bsxfun(@times, permute(X(:,:,1), [2 4 5 3 1]), permute(X(:,:,2), [4 2 5 3 1])), permute(X(:,:,3), [4 5 2 3 1])), 5);
从R2016b开始,可以更容易地将其编写为
T = sum(permute(X(:,:,1), [2 4 5 3 1]) .* permute(X(:,:,2), [4 2 5 3 1]) .* permute(X(:,:,3), [4 5 2 3 1]), 5);
答案 1 :(得分:5)
正如我在a comment中提到的那样,矢量化不再总是一个巨大的优势。因此,有矢量化方法会使代码变慢而不是加快代码速度。您必须始终安排解决方案的时间。向量化通常涉及创建大型临时数组或复制大量数据,而在循环代码中则避免了这种情况。如果这样的解决方案要更快,则取决于体系结构,输入的大小和许多其他因素。
尽管如此,在这种情况下,矢量化方法似乎可以大大提高速度。
关于原始代码的第一件事要注意的是,X(:, i, 1) .* X(:, j, 2)
在内部循环中被重新计算,尽管它在那里是一个常数。重写内部循环,因为这样可以节省时间:
Y = X(:, i, 1) .* X(:, j, 2);
for k = 1:p
T(i, j, k) = sum(Y .* X(:, k, 3));
end
现在我们注意到内循环是一个点积,可以这样写:
Y = X(:, i, 1) .* X(:, j, 2);
T(i, j, :) = Y.' * X(:, :, 3);
.'
是向量,Y
上的Y
转置不会复制数据。接下来,我们注意到X(:, :, 3)
被重复索引。让我们将其移出外部循环。现在,我剩下了以下代码:
T = zeros(p, p, p);
X1 = X(:, :, 1);
X2 = X(:, :, 2);
X3 = X(:, :, 3);
for i = 1:p
for j = 1:p
Y = X1(:, i) .* X2(:, j);
T(i, j, :) = Y.' * X3;
end
end
删除j
上的循环可能同样容易,这将在i
上留下一个循环。但这是我停下来的地方。
这是我看到的时间(R2017a,具有4核的3年旧iMac)。对于n=10, p=20
:
original: 0.0206
moving Y out the inner loop: 0.0100
removing inner loop: 0.0016
moving indexing out of loops: 7.6294e-04
Luis' answer: 1.9196e-04
对于带有n=50, p=100
的更大数组:
original: 2.9107
moving Y out the inner loop: 1.3488
removing inner loop: 0.0910
moving indexing out of loops: 0.0361
Luis' answer: 0.1417
“路易斯的答案”是this one。到目前为止,对于小型阵列而言,它是最快的,但是对于大型阵列,它显示了排列的成本。将第一个乘积的计算移出内部循环可节省一半以上的计算成本。但是删除内部循环会极大地降低成本(我没想到,我想单矩阵产品可以比许多小型元素产品更好地使用并行性)。然后,通过减少循环中的索引操作量,可以进一步减少时间。
这是时间代码:
function so()
n = 10; p = 20;
%n = 50; p = 100;
X = randn(n,p,3);
T1 = method1(X);
T2 = method2(X);
T3 = method3(X);
T4 = method4(X);
T5 = method5(X);
assert(max(abs(T1(:)-T2(:)))<1e-13)
assert(max(abs(T1(:)-T3(:)))<1e-13)
assert(max(abs(T1(:)-T4(:)))<1e-13)
assert(max(abs(T1(:)-T5(:)))<1e-13)
timeit(@()method1(X))
timeit(@()method2(X))
timeit(@()method3(X))
timeit(@()method4(X))
timeit(@()method5(X))
function T = method1(X)
p = size(X,2);
T = zeros(p, p, p);
for i = 1:p
for j = 1:p
for k = 1:p
T(i, j, k) = sum(X(:, i, 1) .* X(:, j, 2) .* X(:, k, 3));
end
end
end
function T = method2(X)
p = size(X,2);
T = zeros(p, p, p);
for i = 1:p
for j = 1:p
Y = X(:, i, 1) .* X(:, j, 2);
for k = 1:p
T(i, j, k) = sum(Y .* X(:, k, 3));
end
end
end
function T = method3(X)
p = size(X,2);
T = zeros(p, p, p);
for i = 1:p
for j = 1:p
Y = X(:, i, 1) .* X(:, j, 2);
T(i, j, :) = Y.' * X(:, :, 3);
end
end
function T = method4(X)
p = size(X,2);
T = zeros(p, p, p);
X1 = X(:, :, 1);
X2 = X(:, :, 2);
X3 = X(:, :, 3);
for i = 1:p
for j = 1:p
Y = X1(:, i) .* X2(:, j);
T(i, j, :) = Y.' * X3;
end
end
function T = method5(X)
T = sum(permute(X(:,:,1), [2 4 5 3 1]) .* permute(X(:,:,2), [4 2 5 3 1]) .* permute(X(:,:,3), [4 5 2 3 1]), 5);
答案 2 :(得分:4)
您已经提到过您可以使用其他语言,并且NumPy的语法与MATLAB非常接近,因此我们将尝试在此基础上提供基于NumPy的解决方案。
现在,这些与张量相关的和约简,特别是矩阵乘法,很容易表示为einstein-notation
,幸运的是NumPy具有与np.einsum
相同的功能。在后台,它是在C
中实现的,非常有效。最近,已对其进行了进一步优化,以利用基于BLAS的矩阵乘法实现。
因此,请记住,将声明的代码转换为NumPy区域是遵循从0开始的索引,并且轴的可视化与使用MATLAB的尺寸不同-
import numpy as np
# X is a NumPy array of shape : (n,p,3). So, a random one could be
# generated with : `X = np.random.rand(n,p,3)`.
T = np.zeros((p, p, p))
for i in range(p):
for j in range(p):
for k in range(p):
T[i, j, k] = np.sum(X[:, i, 0] * X[:, j, 1] * X[:, k, 2])
解决问题的einsum
方法是-
np.einsum('ia,ib,ic->abc',X[...,0],X[...,1],X[...,2])
要利用matrix-multiplication
,请使用optimize
标志-
np.einsum('ia,ib,ic->abc',X[...,0],X[...,1],X[...,2],optimize=True)
时间(大尺寸)
In [27]: n,p = 100,100
...: X = np.random.rand(n,p,3)
In [28]: %%timeit
...: T = np.zeros((p, p, p))
...: for i in range(p):
...: for j in range(p):
...: for k in range(p):
...: T[i, j, k] = np.sum(X[:, i, 0] * X[:, j, 1] * X[:, k, 2])
1 loop, best of 3: 6.23 s per loop
In [29]: %timeit np.einsum('ia,ib,ic->abc',X[...,0],X[...,1],X[...,2])
1 loop, best of 3: 353 ms per loop
In [31]: %timeit np.einsum('ia,ib,ic->abc',X[...,0],X[...,1],X[...,2],optimize=True)
100 loops, best of 3: 10.5 ms per loop
In [32]: 6230.0/10.5
Out[32]: 593.3333333333334
在 600x
附近加速!