Multiplicar una matriz 3D con una matriz 2D

Supongamos que tengo una matriz X AxBxC y una matriz Y BxD .

¿Hay un método sin bucle por el cual puedo multiplicar cada una de las matrices C AxB con Y ?

Puede hacer esto en una línea usando las funciones NUM2CELL para dividir la matriz X en una matriz de celdas y CELLFUN para operar a través de las celdas:

 Z = cellfun(@(x) x*Y,num2cell(X,[1 2]),'UniformOutput',false); 

El resultado Z es una matriz de celdas 1 por C donde cada celda contiene una matriz A por D. Si desea que Z sea ​​una matriz A-by-D-by-C , puede usar la función CAT :

 Z = cat(3,Z{:}); 


NOTA: Mi solución anterior usaba MAT2CELL en lugar de NUM2CELL , que no era tan breve:

 [A,B,C] = size(X); Z = cellfun(@(x) x*Y,mat2cell(X,A,B,ones(1,C)),'UniformOutput',false); 

Como preferencia personal, me gusta que mi código sea lo más breve y legible posible.

Esto es lo que habría hecho, aunque no cumple con su requisito de ‘no-bucles’:

 for m = 1:C Z(:,:,m) = X(:,:,m)*Y; end 

Esto da como resultado una matriz A x D x C Z.

Y, por supuesto, siempre puede preasignar Z para acelerar las cosas usando Z = zeros(A,D,C); .

Aquí hay una solución de una línea (dos si desea dividirla en 3ra dimensión):

 A = 2; B = 3; C = 4; D = 5; X = rand(A,B,C); Y = rand(B,D); %# calculate result in one big matrix Z = reshape(reshape(permute(X, [2 1 3]), [AB*C]), [BA*C])' * Y; %'# split into third dimension Z = permute(reshape(Z',[DAC]),[2 1 3]); 

Por lo tanto, ahora: Z(:,:,i) contiene el resultado de X(:,:,i) * Y


Explicación:

Lo anterior puede parecer confuso, pero la idea es simple. Primero empiezo tomando la tercera dimensión de X y hago una concatenación vertical a lo largo del primer dim:

 XX = cat(1, X(:,:,1), X(:,:,2), ..., X(:,:,C)) 

… la dificultad era que C es una variable, por lo tanto, no se puede generalizar esa expresión usando cat o vertcat . Luego multiplicamos esto por Y :

 ZZ = XX * Y; 

Finalmente lo dividí nuevamente en la tercera dimensión:

 Z(:,:,1) = ZZ(1:2, :); Z(:,:,2) = ZZ(3:4, :); Z(:,:,3) = ZZ(5:6, :); Z(:,:,4) = ZZ(7:8, :); 

Entonces puede ver que solo requiere una multiplicación de matrices, pero debe remodelar la matriz antes y después.

Me estoy acercando exactamente al mismo problema, con un ojo para el método más eficiente. Hay aproximadamente tres enfoques que veo a mi alrededor, salvo el uso de bibliotecas externas (es decir, mtimesx ):

  1. Bucle a través de las rebanadas de la matriz 3D
  2. hechicería repmat-y-permute
  3. multiplicación de cellfun

Recientemente comparé los tres métodos para ver cuál fue el más rápido. Mi intuición fue que (2) sería el ganador. Aquí está el código:

 % generate data A = 20; B = 30; C = 40; D = 50; X = rand(A,B,C); Y = rand(B,D); % ------ Approach 1: Loop (via @Zaid) tic Z1 = zeros(A,D,C); for m = 1:C Z1(:,:,m) = X(:,:,m)*Y; end toc % ------ Approach 2: Reshape+Permute (via @Amro) tic Z2 = reshape(reshape(permute(X, [2 1 3]), [AB*C]), [BA*C])' * Y; Z2 = permute(reshape(Z2',[DAC]),[2 1 3]); toc % ------ Approach 3: cellfun (via @gnovice) tic Z3 = cellfun(@(x) x*Y,num2cell(X,[1 2]),'UniformOutput',false); Z3 = cat(3,Z3{:}); toc 

Los tres enfoques produjeron el mismo resultado (¡uf!), Pero, sorprendentemente, el bucle fue el más rápido:

 Elapsed time is 0.000418 seconds. Elapsed time is 0.000887 seconds. Elapsed time is 0.001841 seconds. 

Tenga en cuenta que los tiempos pueden variar mucho de una prueba a otra, y en ocasiones (2) sale más lento. Estas diferencias se vuelven más dramáticas con datos más grandes. Pero con datos mucho más grandes, (3) latidos (2). El método de bucle sigue siendo el mejor.

 % pretty big data... A = 200; B = 300; C = 400; D = 500; Elapsed time is 0.373831 seconds. Elapsed time is 0.638041 seconds. Elapsed time is 0.724581 seconds. % even bigger.... A = 200; B = 200; C = 400; D = 5000; Elapsed time is 4.314076 seconds. Elapsed time is 11.553289 seconds. Elapsed time is 5.233725 seconds. 

Pero el método de bucle puede ser más lento que (2), si la dimensión del bucle es mucho más grande que las otras.

 A = 2; B = 3; C = 400000; D = 5; Elapsed time is 0.780933 seconds. Elapsed time is 0.073189 seconds. Elapsed time is 2.590697 seconds. 

Entonces (2) gana por un factor importante, en este caso (tal vez extremo). Puede que no haya un enfoque que sea óptimo en todos los casos, pero el ciclo sigue siendo bastante bueno, y el mejor en muchos casos. También es mejor en términos de legibilidad. ¡Bucle lejos!

Nop. Hay varias formas, pero siempre sale en un bucle, directo o indirecto.

Solo para complacer mi curiosidad, ¿por qué querrías eso de todos modos?