如何将2D numpy数组与3D数组矩阵相乘以得到3D数组?

时间:2019-10-28 09:23:21

标签: python numpy numpy-ndarray numpy-broadcasting

我正在解决一个光度学立体问题,其中我有“ n”个光源,每个光源有3个通道(红色,绿色,蓝色)。 因此,光阵列的形状为nx3:lights.shape = nx3 我有与每种照明条件相对应的图像。图片尺寸为hxw(高度x宽度),images.shape = nxhxw

我想将图像中的每个像素矩阵化为3 x n形状的矩阵,并获得另一个3xhxw形状的数组,这些将成为图像上每个像素的法线向量。

形状:

  • 图像:(n_ims,h,w)
  • 灯:(n_ims,3)
S = lights
S_pinv =  np.linalg.inv(S.T@S)@S.T  # pinv is pseudo inverse, S_pinv.shape : (n_ims,3)
b = S_pinv @ images  # I want (3xn @ nxhxw = 3xhxw)

但是我收到此错误:

  

ValueError:matmul:输入操作数1的核心维0不匹配,带有gufunc签名(n?,k),(k,m?)->(n?,m?)(大小100与3)

4 个答案:

答案 0 :(得分:1)

问题是numpy将多维数组视为矩阵的堆栈,并且始终将最后两个维假定为线性空间维。这意味着通过折叠3d数组的 first 维,点积将无法工作。

最简单的方法是将3d数组重塑为2d数组,进行矩阵乘法,然后重塑为3d数组。这还将利用优化的BLAS代码,这是numpy的一大优势。

import numpy as np 

S_pinv = np.random.rand(3, 4)
images = np.random.rand(4, 5, 6)

# error: 
# (S_pinv @ images).shape 
res_shape = S_pinv.shape[:1] + images.shape[1:]  # (3, 5, 6) 
res = (S_pinv @ images.reshape(images.shape[0], -1)).reshape(res_shape)
print(res.shape)  # (3, 5, 6)

因此,我们执行(3,n) x (n,h,w)而不是(3,n) x (n, h*w) -> (3, h*w),我们将其重塑为(3, h, w)。重塑是免费的,因为这并不意味着对内存中的数据进行任何实际的操作(仅是对作为数组基础的单个内存块的重新解释),并且正如我所说的那样,适当的矩阵乘积在numpy中得到了高度优化。


由于您要求使用一种更直观的方式,因此这里是numpy.einsum的替代选择。可能会比较慢,但是如果您对它的表示法有所了解的话,它将非常透明:

res_einsum = np.einsum('tn,nhw -> thw', S_pinv, images)
print(np.array_equal(res, res_einsum))  # True

此符号命名输入数组的每个维:对于S_pinv,第一维和第二维分别命名为tn,并类似地命名为nh的{​​{1}}和w。输出设置为具有尺寸images,这意味着在乘以输入数组之后,将对输出形状中不存在的所有剩余尺寸进行求和。这正是您所需要的。


正如您在评论中指出的那样,您还可以转置数组,以便thw在正确的位置找到正确的尺寸。但这也会很慢,因为这可能会导致内存中的副本,或者至少在数组上产生次佳循环。

我使用以下定义进行了快速时序比较:

np.dot

这是使用IPython内置的def reshaped(S_pinv, images): res_shape = S_pinv.shape[:1] + images.shape[1:] return (S_pinv @ images.reshape(images.shape[0], -1)).reshape(res_shape) def einsummed(S_pinv, images): return np.einsum('tn,nhw -> thw', S_pinv, images) def transposed(S_pinv, images): return (S_pinv @ images.transpose(2, 0, 1)).transpose(1, 2, 0) 魔术和更实际的数组大小进行的时序测试:

%timeit

答案 1 :(得分:0)

答案是np.swapaxes

import numpy as np

q= np.random.random([2, 5,5])
q.shape

w = np.random.random([3,2])
w.shape

w@q

我们有ValueError

import numpy as np

q= np.random.random([5, 2,5])
q.shape

w = np.random.random([3,2])
w.shape

res = (w@q).swapaxes(0,1)
res.shape # =[3, 5, 5]

答案 2 :(得分:0)

一种简单的方法是np.innerinner沿最后一个轴减小并保留所有其他轴;因此,这取决于移调完美匹配:

n,h,w = 10,384,512
images = np.random.randint(1,10,(n,h,w))
S_pinv = np.random.randint(1,10,(n,3))

res_inr = np.inner(images.T,S_pinv.T).T
res_inr.shape
# (3, 384, 512)

类似地,使用转置matmul实际上可以做正确的事情:

res_mml = (images.T@S_pinv).T
assert (res_mml==res_inr).all()

这两个似乎与@AndrasDeak的einsum方法类似,速度差不多。

尤其是它们的速度不如整形的matmul(毫不奇怪,因为单个笔直的matmul必须是目前最优化的操作之一)。为了方便起见,他们正在迅速交易。

答案 3 :(得分:0)

这基本上就是np.einsum的目的。

代替:

b = S_pinv @ images

使用

b = np.einsum('ij, ikl -> jkl', S_pinv, images)

在这种情况下为i = n_imsj = 3k = hl = w

由于这是一次收缩,所以您也可以使用np.tensordot()

b = np.tensordot(S_pinv.T, images, axes = 1)

b = np.tensordot(S_pinv, images, axes = ([0], [0]))