在 NumPy 中将矩阵与向量数组相乘

Multiplying a matrix with array of vectors in NumPy

我正在尝试在 NumPy 中对向量数组进行几何旋转。首先我生成网格的坐标向量。

width = 128
height = 128

x_axis = np.linspace(-1, 1, width)
y_axis = np.linspace(-1, 1, height)

x, y = np.meshgrid(x_axis, y_axis)
z = np.full((width, height), 0)
vectors = np.stack((x, y, z), axis=2)

所以 'vectors' 的形状是 (128, 128, 3)

我已经准备好旋转矩阵,其中a、b、c作为沿轴的旋转角度。

rotation_matrix = np.array([
    [np.cos(b) * np.cos(c),
     - np.cos(b) * np.sin(c),
     np.sin(b)],

    [np.sin(a) * np.sin(b) * np.cos(c) + np.cos(a) * np.sin(c),
     - np.sin(a) * np.sin(b) * np.sin(c) + np.cos(a) * np.cos(c),
     - np.sin(a) * np.cos(b)],

    [- np.cos(a) * np.sin(b) * np.cos(c) + np.sin(a) * np.sin(c),
     np.cos(a) * np.sin(b) * np.sin(c) + np.sin(a) * np.cos(c),
     np.cos(a) * np.cos(b)]
])

现在我希望数组的每个向量都乘以 'rotation_matrix' 的矩阵,例如

vector_rotated = rotation_matrix @ vector

所以结果数组的形状也应该是 (128, 128, 3)。我在处理这个 3 维数组时遇到了一些问题。 Matmul 只能处理二维数组。对于这个用例,NumPy 中有什么优雅的方法吗?还是我必须使用 for 循环来解决这个问题?

非常感谢,祝你有愉快的一天!

有几种不同的方法可以解决这个问题。

选项 1:

最直接的方法是重塑数组 vectors 使其具有形状 (3, 128 * 128),然后调用内置 np.dot 函数,并将结果重塑回您想要的形状。

(请注意,数组形状的 (128, 128) 部分与旋转并不真正相关;这是一种解释,您可能想让您的问题更清楚,但对您想要的线性变换没有影响应用。换句话说,你正在旋转 3 个向量。它们有 128 * 128 == 16384 个,它们恰好被组织成像上面那样的 3D 数组。)

这种方法看起来像:

>>> v = vectors.reshape(-1, 3).T
>>> np.dot(rotation_matrix, v).shape
(3, 16384)
>>> rotated = np.dot(rotation_matrix, v).T.reshape(vectors.shape)
>>> rotated.shape == vectors.shape
True

选项 2:

另一种不涉及任何整形的方法是使用 NumPy 的 Einstein summation。爱因斯坦求和非常灵活,需要一段时间才能理解,但它的强大证明了它的复杂性。在最简单的形式中,您 "label" 您想要相乘的轴。省略的轴是 "contracted",这意味着计算该轴上的总和。对于您的情况,它将是:

>>> np.einsum('ij,klj->kli', rotation_matrix, vectors).shape
(128, 128, 3)
>>> np.allclose(rotated, np.einsum('ij,klj->kli', rotation_matrix_vectors))
True

这里是索引的简要说明。我们正在标记旋转矩阵的轴 ij,以及向量的轴 klj。重复的 j 表示这些轴相乘。这相当于将上面的整形数组右乘旋转矩阵(即,它是一个旋转)。

输出轴标记为 kli。这意味着我们要保留向量的 kl 轴。由于 j 不在输出标签中,因此在该轴上有一个总和。相反,我们有轴 i,因此最终形状为 (128, 128, 3)。你可以在上面看到点积方法和einsum方法一致。

爱因斯坦求和可能需要一段时间才能理解,但它非常棒而且非常强大。我强烈建议您更多地了解它,尤其是如果您经常遇到这种线性代数问题。