我正在解决一个光度学立体问题,其中我有“ n”个光源,每个光源有3个通道(红色,绿色,蓝色)。
因此,光阵列的形状为nx3:lights.shape = nx3
我有与每种照明条件相对应的图像。图片尺寸为hxw(高度x宽度),images.shape = nxhxw
我想将图像中的每个像素矩阵化为3 x n形状的矩阵,并获得另一个3xhxw形状的数组,这些将成为图像上每个像素的法线向量。
形状:
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)
答案 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
,第一维和第二维分别命名为t
和n
,并类似地命名为n
, h
的{{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.inner
; inner
沿最后一个轴减小并保留所有其他轴;因此,这取决于移调完美匹配:
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_ims
,j = 3
,k = h
和l = w
由于这是一次收缩,所以您也可以使用np.tensordot()
b = np.tensordot(S_pinv.T, images, axes = 1)
或
b = np.tensordot(S_pinv, images, axes = ([0], [0]))