我有一个简单的问题。我想在不使用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]
此外,矢量化会有所帮助吗?
你们能帮忙吗?谢谢!
答案 0 :(得分:1)
如果您的A
和B
是list
,则可以使用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} }} n
乘k
块的块对角矩阵。块n*k
,n*k
= 1 .. k
的顶行包含k
的行i
,否则为零。将此值(在右侧)乘以i
(由n
乘以i
矩阵组成,该矩阵由尺寸为A
的{{1}}个块的“堆栈”乘以B
)计算所有单个乘积,并将它们存放在结果的第1,k + 1、2k + 1,...行中,以便在其中进行挑选。
k*n
如您所见,数组操作是基本的:创建稀疏矩阵,转置数组(使用k
和n
)并相乘。它以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=1e8
和f1
都用光了并且失败了。
摘要
直接循环方法比f3
更有效率。
对于sapply
= 10,000矩阵乘法的问题,运行for循环既简单又高效,只需不到0.02秒。相比之下,仅使用稀疏矩阵函数加载程序包大约需要2/3秒。
对于n
(介于1到1000万之间),whuber的稀疏矩阵解决方案开始表现出色,尤其是在已经加载Matrix软件包的情况下。
for循环使用三种方法中最少的RAM。对于我的具有32Gb RAM的PC上的n
而言,如果价格为1亿美元,则仅适用于循环方法。