在 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
这里是索引的简要说明。我们正在标记旋转矩阵的轴 i
和 j
,以及向量的轴 k
、l
和 j
。重复的 j
表示这些轴相乘。这相当于将上面的整形数组右乘旋转矩阵(即,它是一个旋转)。
输出轴标记为 kli
。这意味着我们要保留向量的 k
和 l
轴。由于 j
不在输出标签中,因此在该轴上有一个总和。相反,我们有轴 i
,因此最终形状为 (128, 128, 3)
。你可以在上面看到点积方法和einsum
方法一致。
爱因斯坦求和可能需要一段时间才能理解,但它非常棒而且非常强大。我强烈建议您更多地了解它,尤其是如果您经常遇到这种线性代数问题。
我正在尝试在 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
这里是索引的简要说明。我们正在标记旋转矩阵的轴 i
和 j
,以及向量的轴 k
、l
和 j
。重复的 j
表示这些轴相乘。这相当于将上面的整形数组右乘旋转矩阵(即,它是一个旋转)。
输出轴标记为 kli
。这意味着我们要保留向量的 k
和 l
轴。由于 j
不在输出标签中,因此在该轴上有一个总和。相反,我们有轴 i
,因此最终形状为 (128, 128, 3)
。你可以在上面看到点积方法和einsum
方法一致。
爱因斯坦求和可能需要一段时间才能理解,但它非常棒而且非常强大。我强烈建议您更多地了解它,尤其是如果您经常遇到这种线性代数问题。