3D матричное умножение в R - PullRequest
1 голос
/ 06 мая 2019

У меня простая проблема. Я хочу умножить 3D-массив на другой 3D-массив в R без использования цикла for.

Для иллюстрации :

Предположим, у меня есть матрица 1x3 A:

[A1, A2, A3] 

А у меня матрица 3х3 B:

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

Моя основная операция - A %*% B, в результате чего получается матрица 1x3.

Но теперь я хочу повторить процесс 10000 раз, каждый с разными А и В того же размера, что и выше. Я могу использовать цикл for

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

Тогда я могу сохранить 10000 значений.

Но есть ли способ достичь того же самого, не используя цикл 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 ]

1 голос
/ 08 мая 2019

Есть несколько способов сделать это с умножением массива. Цена, которую вы платите, состоит в том, чтобы переформатировать матрицы в намного большие тензоры со многими нулями. По определению, они редки, и поэтому основными затратами являются накладные расходы на конвертацию. На самом деле он превосходит цикл, когда у вас есть 10 000 массивов для умножения.

Пусть n по количеству (A, B) пар и k = 3 размерности.

Самым гладким решением, по-видимому, является реорганизация n строк A (n на k матрица) в n*k на n*k блочно-диагональную матрицу k на k блоков. Блок i, i = 1 .. n содержит строку i из A в верхней строке и в противном случае равен нулю. Умножая это (справа) на B (расположенное в виде матрицы k*n на k, состоящей из "стопки" из n блоков размерности k на k), вычисляем все отдельные продукты, поместив их в строки 1, k + 1, 2k + 1, ..., результата, где их можно выбрать.

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 и t) и умножать. Он возвращает свои результаты в массиве k by n (который вы можете транспонировать, если хотите), один вектор результатов на столбец.

В качестве теста приведем цикл перебора, использующий те же структуры данных массива.

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 ^ -32, показывая, что их можно считать одинаковыми вплоть до ошибки округления с плавающей запятой.

Массив-ориентированная процедура f3 изначально медленнее, чем процедура зацикливания f1, но догоняет, когда n равно 10000 После этого это примерно в два раза быстрее или лучше (на этой машине; YMMV). Оба алгоритма должны линейно масштабироваться в n (и сроки показывают, что они делают, по крайней мере, до n = 10 000 000).

1 голос
/ 08 мая 2019

Цикл for более эффективен, чем вы думаете

Ваша задача умножения пар n (A, B) не эквивалентна тензорному умножению в обычном смысле, хотяwhuber представил очень аккуратный способ превращения его в матричное умножение, складывая Bs как блоки в разреженной матрице.

Вы сказали, что хотите избежать цикла for, но подход for-loopна самом деле очень конкурентоспособен, когда запрограммирован эффективно, и я бы посоветовал вам пересмотреть его.

Я буду использовать те же обозначения, что и whuber, с A измерения nxk и B измерения kxkxn, например:

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

Простое и эффективное решение для цикла будет выглядеть следующим образом:

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

, создающее матрицу результатов nxk.

Я изменил функцию f3 whuber для загрузки матрицыпакет, в противном случае функция 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:

> 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, который опирается на sapply.(Моя машина - ПК с Windows 10 с 32 ГБ ОЗУ под управлением R 3.6.0.)

Если я запускаю все три метода во второй раз, то f3 становится самым быстрым, потому что на этот раз пакет Matrix уже находится впуть поиска и не должен быть перезагружен:

> 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 

Однако f3 использует больше оперативной памяти, чем цикл for.На моем ПК я могу успешно запустить justAForLoop с n=1e8, тогда как f1 и f3 не хватает памяти и произойдет сбой.

Сводка

Подход с прямым циклом гораздо более эффективен, чем sapply.

Для вашей задачи с n = 10 000 умножений матриц выполнение цикла for является простым и эффективным, занимая <0,02 сек.В отличие от этого, простая загрузка пакета с разреженными матричными функциями требует около 2 / 3сек. </p>

Для n между 1-10 миллионами решение для разреженных матриц whuber начинает выигрывать, особенно если пакет Matrix уже загружен.

Цикл for использует наименьшую оперативную память из трех методов.Для n на 100 миллионов на моем ПК с 32 ГБ ОЗУ работает только подход for-loop.

1 голос
/ 06 мая 2019

Если ваши 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
...