R中的3D矩阵乘法

时间:2019-05-06 16:50:05

标签: r matrix tensor

我有一个简单的问题。我想在不使用for循环的情况下,将R中的3D数组乘以另一个3D数组。

说明

假设我有一个1x3矩阵A:

[A1, A2, A3] 

我有一个3x3矩阵B:

[B1, B2, B3 \\
 B4, B5, B6 \\
 B7, B8, B9]

我的主要操作是A %*% B,产生1x3矩阵。

但是,现在我想重复该过程10,000次,每次重复使用相同尺寸的A和B。我可以使用for-loop

for (i in 1:10000) {
     A[i] %*% B[i]
}

然后我可以存储10,000个值。

但是不使用for循环,有什么方法可以实现相同的目的。我正在考虑可能的3D数组乘法。但是我不确定如何在R中执行此操作。

Matrix A: 1 x 3 x 10000

[A1, A2, A3] 

Matrix B: 3 x 3 x 10000

[B1, B2, B3
 B4, B5, B6
 B7, B8, B9]

此外,矢量化会有所帮助吗?

你们能帮忙吗?谢谢!

3 个答案:

答案 0 :(得分:1)

如果您的ABlist,则可以使用mapply()

> nn <- 1e1
> set.seed(1)
> A <- replicate(nn,matrix(rnorm(3),nrow=1),simplify=FALSE)
> B <- replicate(nn,matrix(rnorm(9),nrow=3),simplify=FALSE)
> head(mapply("%*%",A,B,SIMPLIFY=FALSE),3)
[[1]]
          [,1]      [,2]       [,3]
[1,] -1.193976 0.1275999 -0.6831007

[[2]]
         [,1]     [,2]      [,3]
[1,] 1.371143 1.860379 -1.639078

[[3]]
          [,1]       [,2]     [,3]
[1,] 0.8250047 -0.6967286 1.949236

答案 1 :(得分:1)

有多种方法可以通过数组乘法来实现这一目标。您要付出的代价是将矩阵重新格式化为具有多个零的更大张量。从定义上讲,它们很少,因此主要成本是转换的间接费用。当您要乘以10,000个数组时,它实际上比循环好。

n等于(A,B)对的数量,k = 3维。

最流畅的解决方案似乎是将n的{​​{1}}行({{1} by A矩阵)重组为{{1} }} nk块的块对角矩阵。块n*kn*k = 1 .. k的顶行包含k的行i,否则为零。将此值(在右侧)乘以i(由n乘以i矩阵组成,该矩阵由尺寸为A的{​​{1}}个块的“堆栈”乘以B)计算所有单个乘积,并将它们存放在结果的第1,k + 1、2k + 1,...行中,以便在其中进行挑选。

k*n

如您所见,数组操作是基本的:创建稀疏矩阵,转置数组(使用kn)并相乘。它以k×k数组(如果需要,可以转置)返回结果,每列一个结果向量。

作为测试,这是使用相同数组数据结构的暴力循环。

f3 <- function(a, b) {
  require(RcppArmadillo) # sparseMatrix package
  n <- dim(b)[3]
  k <- dim(b)[2]
  i0 <- (1:n-1)*k+1
  i <- rep(i0, each=k)
  j <- 1:(k*n)
  aa <- sparseMatrix(i, j, x=c(t(a)), dims=c(n*k, n*k))
  bb <- matrix(aperm(b, c(1,3,2)), nrow=n*k)
  t((aa %*% bb)[i0, ])
}

我们可以将这些解决方案应用于相同的输入并比较结果:

aperm

结果并不完全相等,但它们的均方差小于10 ^ -32,表明直到浮点舍入误差为止,它们都可以视为相同。

面向数组的过程t最初比循环过程k慢,但到n为10,000时才赶上。之后,速度大约是以前的两倍或更好(在此计算机上为YMMV)。两种算法都应以f1 <- function(a, b) sapply(1:nrow(a), function(i) a[i,] %*% b[,,i]) 线性缩放(并且时间表明它们确实可以,至少达到# # Create random matrices for testing. # k <- 3 n <- 1e6 # Number of (a,B) pairs a <- matrix(runif(k*n), ncol=k) b <- array(runif(k^2*n), dim=c(k,k,n)) system.time(c1 <- f1(a,b)) # 4+ seconds system.time(c3 <- f3(a,b)) # 2/3 second mean((c1-c3)^2) # Want around 10^-32 or less = 10,000,000)。

答案 2 :(得分:1)

for循环比您想象的要有效

在通常意义上,将n(A,B)对相乘的问题并不等同于张量乘法,尽管whuber提供了一种非常巧妙的方法,通过将Bs堆叠为块将其转换为矩阵乘法在稀疏矩阵中。

您已经说过要避免for循环,但是for循环方法实际上在有效编程时非常有竞争力,我建议您重新考虑。

我将使用与whuber相同的符号,例如A的尺寸为n x k,而B的尺寸为k x k x n,例如:

n <- 1e4
k <- 3
A <- array(rnorm(k*n),c(n,k))
B <- array(rnorm(k*k*n),c(k,k,n))

一个简单而有效的for循环解决方案就是这样

justAForLoop <- function(A,B) {
  n <- nrow(A)
  for (i in 1:n) A[i,] <- A[i,] %*% B[,,i]
  A
}

产生一个n x k结果矩阵。

我已经修改了whuber的f3函数以加载Matrix程序包,否则sparseMatrix函数不可用。我的f3版本比原始版本快很多,因为在返回结果之前,我已经消除了最后一个矩阵转置。 通过此修改,它会将相同的数值结果返回到justAForLoop

f3 <- function(a, b) {
  require(Matrix)
  n <- dim(b)[3]
  k <- dim(b)[2]
  i0 <- (1:n-1)*k+1
  i <- rep(i0, each=k)
  j <- 1:(k*n)
  aa <- sparseMatrix(i, j, x=c(t(a)), dims=c(n*k, n*k))
  bb <- matrix(aperm(b, c(1,3,2)), nrow=n*k)
  (aa %*% bb)[i0, ]
}

现在,我在全新的R会话中重新进行Whuber的模拟:

> k <- 3
> n <- 1e6
> a <- matrix(runif(k*n), ncol=k)
> b <- array(runif(k^2*n), dim=c(k,k,n))
> 
> system.time(c1 <- f1(a,b))
   user  system elapsed 
   3.40    0.09    3.50 
> system.time(c3 <- f3(a,b))
Loading required package: Matrix
   user  system elapsed 
   1.06    0.24    1.30 
> system.time(c4 <- justAForLoop(a,b))
   user  system elapsed 
   1.27    0.00    1.26 

实际上,for-loop方法是最快的。它比依靠f1的{​​{1}}快得多。 (我的机器是Windows 10 PC,运行R 3.6.0的32Gb RAM)。

如果第二次运行所有这三种方法,那么sapply会变得最快,因为这一次Matrix包已经在搜索路径中,而不必重新加载:

f3

但是> system.time(c1 <- f1(a,b)) user system elapsed 3.23 0.04 3.26 > system.time(c3 <- f3(a,b)) user system elapsed 0.33 0.20 0.53 > system.time(c4 <- justAForLoop(a,b)) user system elapsed 1.28 0.01 1.30 比for循环使用更多的RAM。在我的PC上,我可以通过f3成功运行justAForLoop,而n=1e8f1都用光了并且失败了。

摘要

直接循环方法比f3更有效率。

对于sapply = 10,000矩阵乘法的问题,运行for循环既简单又高效,只需不到0.02秒。相比之下,仅使用稀疏矩阵函数加载程序包大约需要2/3秒。

对于n(介于1到1000万之间),whuber的稀疏矩阵解决方案开始表现出色,尤其是在已经加载Matrix软件包的情况下。

for循环使用三种方法中最少的RAM。对于我的具有32Gb RAM的PC上的n而言,如果价格为1亿美元,则仅适用于循环方法。