动态矩阵乘法无循环

时间:2013-10-25 20:18:29

标签: arrays r loops multidimensional-array

我遇到了以下问题:


假设我有三对序列:

{a_1, ..., a_N}, {A_1, ..., A_T}, {b_1, ..., b_N}, {B_1, ..., B_T}, {c_1, ..., c_N}, {C_1, ..., C_T}

我的目标是执行以下操作(不循环!):

for (i in 1:N) {
  for (j in 1:N) {
    for (k in 1:N) {
      ret[i,j,k] <- \sum_{t=1}^T (a_i - A_t) * (b_j - B_t) * (c_k - C_t)
}}}

我不想循环的原因是可能有更多的序列对而不仅仅是那三个。我希望尽可能“有效和灵活”地构建代码。

如果我们只有两对序列,则问题非常简单,因为它简化为简单矩阵乘法((N x T)的{​​{1}}矩阵和{(a_i - A_t)的{​​{1}}矩阵1}}你将第一个与第二个的转置相乘。

但是一旦你有两对以上的序列,我不确定它是否可以在没有循环的情况下完成,因为(N x T)的维数取决于序列对的数量......


------------------------------------------相关问题(11月2013年8月8日)------------------------------------------

由于 @ - mrip ,我成功实施了第一部分。但是,如果我想要以下内容,代码将如何改变:

(b_i - B_t)

output是一些常见的双变量函数。是否有“通用”解决方案,或者您是否需要有关for (i in 1:N) { for (j in 1:N) { for (k in 1:N) { ret[i,j,k] <- \sum_{t=1}^T foo(a_i, a_i - A_t) * foo(b_j, b_j - B_t) * foo(c_k, c_k - C_t) }}}

结构的更多信息?

我尝试使用简单的解决方案并简单地实现循环。当然,这既不灵活(因为我必须事先限制自己可能的对/维数),也不快(因为foo(a, a-A)foo(a, a-A)都可能很大 - 尽管可能是这种情况a只是标量和A一些观察结果。)

我知道我可能会要求。但是很长一段时间以来我一直坚持这个问题......所以任何帮助都非常受欢迎。

1 个答案:

答案 0 :(得分:4)

这实际上根本不需要任何矩阵乘法。它只需要采用外部产品,最终结果可以高效,简洁地利用R矩阵和多维数组的列主要布局。通过将循环中的乘积扩展为单独的加数,可以使计算更有效。这导致以下实现。

 vecout<-function(...)as.vector(outer(...))

 f2<-function(a,b,c,A,B,C){
 N<-length(a)
 t<-length(A)

 ab<-vecout(a,b)
 ret<-array(vecout(ab,c),c(N,N,N))*t

 ret<-ret - ab * sum(C)
 ret<-ret - vecout(a,rep(c,each = N)) * sum(B)
 ret<-ret - rep(vecout(b,c) * sum(A),each=N)
 ret<-ret + a * sum(B*C)
 ret<-ret + rep(b * sum(A*C),each=N)
 ret<-ret + rep(c * sum(A*B),each=N^2)
 ret<-ret - sum(A*B*C)
 ret
 }

我们可以比较运行时间并检查正确性如下。这是天真的实现:

f1<-function(a,b,c,A,B,C){
 N<-length(a)
 ret<-array(0,c(N,N,N))
 for(i in 1:N)
  for(j in 1:N)
   for(k in 1:N)
    ret[i,j,k]<-sum((a[i]-A)*(b[j]-B)*(c[k]-C))
 ret
 }

优化版本的运行速度要快得多,并产生相同的结果,直至达到数值精度误差:

> a<-rnorm(100)
> b<-rnorm(100)
> c<-rnorm(100)
> A<-rnorm(200)
> B<-rnorm(200)
> C<-rnorm(200)
> system.time(r1<-f1(a,b,c,A,B,C))
   user  system elapsed 
  9.006   1.125  10.204 
> system.time(r2<-f2(a,b,c,A,B,C))
   user  system elapsed 
  0.203   0.033   0.242 
> max(abs(r1-r2))
[1] 1.364242e-12

如果每个序列超过三个,那么相同的想法就可以了。编写通用解决方案应该不会太难,事实上,用硬编码的3序列解决方案编写一般代码的代码可能很少,尽管需要花一些时间来考虑所有的索引操作恰到好处。

编辑:好的,无法抗拒。这是一个通用解决方案,具有任意数量的对,作为两个矩阵的列传递:

f3<-function(a,A){
  subsets<-as.matrix(expand.grid(rep(list(c(F,T)),ncol(a))))
  ret<-array(0,rep(nrow(a),ncol(a)))

  for(i in 1:nrow(subsets)){
    sub<-as.logical(subsets[i,])
    temp<-Reduce(outer,as.list(data.frame(a[,sub,drop=F])),init=1)
    temp<-temp*sum(apply(A[,!sub,drop=F],1,prod))
    temp<-aperm(array(temp,dim(ret)),order(c(which(sub),which(!sub))))
    ret<-ret+temp*(-1)^(sum(!sub))
  } 
  ret
}

> system.time(r3<-f3(cbind(a,b,c),cbind(A,B,C)))
   user  system elapsed 
  0.258   0.056   0.303 
> max(abs(r3-r1))
[1] 9.094947e-13

---------------------再次编辑(2013年11月8日)------------------- -

当数组ABC很大时,上面的答案是最有效的。如果ABC远小于abc,那么这里是一个替代,更简洁溶液:

f4<-function(a,A){
  ret<-array(0,rep(nrow(a),ncol(a)))
  for(i in 1:nrow(A)){
    temp<- Reduce(outer,as.list(data.frame(a-rep(A[i,],each=nrow(a)))),init=1)
    ret<-ret + as.vector(temp )
  }
  ret
}

在上述设置中,abc的长度为100AB,{{1长度为C,这比其他解决方案慢:

200

但是,如果> system.time(r3<-f3(cbind(a,b,c),cbind(A,B,C))) user system elapsed 0.704 0.092 0.256 > system.time(r4<-f4(cbind(a,b,c),cbind(A,B,C))) user system elapsed 65.824 19.060 3.553 > max(abs(r3-r4)) [1] 2.728484e-12 AB的长度为C,则会更快:

1