矩阵乘法是线性代数中的基本运算,它采用一对矩阵并产生另一个矩阵。
用数学术语来说,给定两个矩阵 ( A ) 和 ( B ),乘积 ( AB ) 是通过 ( A ) 的行与 ( B ) 的列的点积来计算的。
本教程旨在深入了解矩阵乘法NumPy,
逐元素与矩阵乘法
逐元素乘法,也称为哈达玛积,将两个形状相同的矩阵的相应元素相乘。
Example:
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
result = A * B
print(result)
Output:
[[ 5, 12],
[21, 32]]
结果矩阵中的每个元素都是矩阵 ( A ) 和 ( B ) 中相应元素的乘积:
- ( 1 X 5 = 5 )
- ( 2X 6 = 12 )
- ( 3 X 7 = 21 )
- ( 4 X 8 = 32 )
另一方面,矩阵乘法要求第一个矩阵中的列数等于第二个矩阵中的行数。
这是一个更复杂的运算,涉及行和列的点积。
使用相同的矩阵 ( A ) 和 ( B ),我们得到:
result = np.dot(A, B)
print(result)
Output:
[[19, 22],
[43, 50]]
使用 np.dot 进行乘法
The np.dot
NumPy 中的函数是执行矩阵乘法的主要方法之一。它处理二维数组(矩阵)和一维数组(向量)。
矩阵相乘
这是一个使用的示例np.dot
两个矩阵相乘的函数:
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
result = np.dot(A, B)
print(result)
Output:
[[19, 22],
[43, 50]]
输出是通过执行标准矩阵乘法获得的,其中第一个矩阵的行与第二个矩阵的相应列相乘。
向量相乘
np.dot
也可用于计算两个向量的点积:
v1 = np.array([3, 4])
v2 = np.array([5, 6])
result = np.dot(v1, v2)
print(result)
Output:
39
输出是向量 ( v1 ) 和 ( v2 ) 的点积,计算公式为 ( 3X 5 + 4X 6 = 39 )。
np.matmul 函数
Unlike np.dot
, np.matmul
专门为矩阵乘法而设计,并提供了更明确的方法来执行此操作。
使用方法如下np.matmul
两个矩阵相乘:
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
result = np.matmul(A, B)
print(result)
Output:
[[19, 22],
[43, 50]]
输出与获得的结果相同np.dot
, as np.matmul
还执行标准矩阵乘法。
多维数组乘法
np.matmul
还处理多维数组并提供数组广播的特定行为。
Example:
A = np.random.rand(2, 3, 4)
B = np.random.rand(2, 4, 3)
result = np.matmul(A, B)
print(result.shape)
Output:
(2, 3, 3)
在这种情况下,np.matmul
对输入数组的最后两个维度执行矩阵乘法,并在其余维度上进行广播。最终的形状是(2, 3, 3)
.
运营商
在Python 3.5及更高版本中,您可以使用@
运算符作为矩阵乘法的简写。该运算符提供了一种干净简洁的矩阵相乘方法。
这是如何使用的示例@
两个矩阵相乘的运算符:
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
result = A @ B
print(result)
Output:
[[19, 22],
[43, 50]]
结果与使用获得的结果相同np.dot
or np.matmul
. The @
运算符简化了代码,使矩阵乘法运算直观清晰。
The @
运算符也可以与高维数组一起使用,就像np.matmul
,遵循相同的广播规则。
比较 np.dot、np.matmul 和 @
了解之间的差异和相似之处np.dot
, np.matmul
,以及@
操作员可以帮助您选择适合您特定需求的工具。
np.dot
- 可用于矩阵乘法和向量点积。
- 将二维数组作为一般矩阵乘法处理。
- 对于高维数组,其行为可能不同于
np.matmul
.
np 矩阵相乘
- 专为矩阵乘法而设计。
- 在不同维度上提供一致的行为。
- 处理多维数组最后两个维度的广播。
@ 操作员
- 适用于 Python 3.5 及以上版本。
- 为矩阵乘法提供干净简洁的表示法。
与标量相乘
除了矩阵相乘之外,您还可以将矩阵乘以标量(单个数值)。此运算将矩阵的每个元素乘以标量。
import numpy as np
A = np.array([[1, 2], [3, 4]])
scalar = 2
result = A * scalar
print(result)
Output:
[[2, 4],
[6, 8]]
将矩阵 ( A ) 中的每个元素乘以标量值 2 即可获得输出:
- ( 1X2 = 2 )
- ( 2X 2 = 4 )
- ( 3 X 2 = 6 )
- ( 4 X 2 = 8 )
与标量的就地乘法
与标量的就地乘法意味着通过将原始矩阵与标量相乘来修改原始矩阵,而不创建矩阵的新副本。
这对于提高内存效率非常有用,尤其是在处理大型矩阵时。
您可以使用*=
运算符将矩阵与标量相乘。
import numpy as np
A = np.array([[1, 2], [3, 4]])
scalar = 2
A *= scalar
print(A)
Output:
[[2 4]
[6 8]]
矩阵A
就地乘以2,意味着原始矩阵A
无需创建新矩阵即可进行修改。
您还可以使用numpy.multiply
函数与out
论证实现就地乘法。
A = np.array([[1, 2], [3, 4]])
scalar = 2
np.multiply(A, scalar, out=A) # In-place multiplication
print(A)
Output:
[[2 4]
[6 8]]
The np.乘法函数与out
指向原始矩阵的参数A
确保乘法就地执行。
空间复杂度比较
我们可以使用memory_profiler
包来分析标量乘法的内存使用情况。
from memory_profiler import memory_usage
def inplace_multiplication(numbers, scalar):
for i in range(len(numbers)):
numbers[i] *= scalar
def multiplication_with_scalar(numbers, scalar):
result = [num * scalar for num in numbers]
def main():
numbers = [1, 2, 3, 4, 5] * 1000000
scalar = 2
mem_usage_inplace = memory_usage((inplace_multiplication, (numbers, scalar)))
print(f'In-place multiplication memory used: {max(mem_usage_inplace) - min(mem_usage_inplace)} MiB')
mem_usage_with_scalar = memory_usage((multiplication_with_scalar, (numbers, scalar)))
print(f'Multiplication with scalar memory used: {max(mem_usage_with_scalar) - min(mem_usage_with_scalar)} MiB')
if __name__ == '__main__':
main()
Output:
In-place multiplication memory used: 0.2265625 MiB
Multiplication with scalar memory used: 37.125 MiB
就地乘法的内存使用量显着降低。
np.dot、np.matmul 和 @ 的时间复杂度
让我们使用以下方法测量每个方法的执行时间time
module.
import numpy as np
import time
A = np.random.rand(5000, 5000)
B = np.random.rand(5000, 5000)
# Measuring time for np.dot
start_time = time.time()
result_dot = np.dot(A, B)
end_time = time.time()
print(f"np.dot execution time: {(end_time - start_time) :.2f} seconds")
# Measuring time for np.matmul
start_time = time.time()
result_matmul = np.matmul(A, B)
end_time = time.time()
print(f"np.matmul execution time: {(end_time - start_time) :.2f} seconds")
# Measuring time for @ operator
start_time = time.time()
result_at_operator = A @ B
end_time = time.time()
print(f"@ operator execution time: {(end_time - start_time) :.2f} seconds")
输出(示例时间;实际时间可能有所不同):
np.dot execution time: 1.13 seconds
np.matmul execution time: 1.18 seconds
@ operator execution time: 1.17 seconds
执行时间为np.dot
, np.matmul
,以及@
操作员反映了这些方法的实际性能几乎相同。
并行处理和 GPU 加速
并行处理和 GPU(图形处理单元)加速可以显着加快矩阵乘法,尤其是大型矩阵。
NumPy 本身不提供直接 GPU 加速,但可以利用提供直接 GPU 加速的库。
CuPy 等库为 GPU 加速计算提供了与 NumPy 兼容的接口。
首先,安装与您的 CUDA 版本相对应的 CuPy。然后,您可以执行 GPU 加速的矩阵乘法,如下所示:
import cupy as cp
A_gpu = cp.random.rand(1000, 1000)
B_gpu = cp.random.rand(1000, 1000)
# Matrix multiplication on the GPU
result_gpu = A_gpu @ B_gpu
只需将 NumPy 替换为 CuPy,您就可以在 GPU 上执行相同的代码。
这可以显着提高大规模矩阵乘法的性能。
注意事项
- GPU 加速对于可以充分利用 GPU 并行性的大型矩阵最为有效。
- CPU 和 GPU 之间的数据传输可能会带来开销,尤其是对于小型矩阵。
- GPU 加速库并不支持所有 NumPy 函数,因此应检查兼容性。
类型错误
在矩阵乘法中,了解所涉及矩阵的数据类型至关重要。
混合整数和浮点矩阵可能会导致意外结果或类型错误。以下是处理它们的方法:
隐式类型转换
将整数矩阵与浮点矩阵相乘时,NumPy 会执行隐式类型转换,将整数矩阵转换为浮点矩阵。
import numpy as np
A = np.array([[1, 2], [3, 4]], dtype=int)
B = np.array([[1.5, 2.5], [3.5, 4.5]], dtype=float)
result = A @ B
print(result.dtype)
Output:
float64
这里,整数矩阵A
在乘法之前隐式转换为 float,结果是 float 矩阵。
显式类型转换
将矩阵显式转换为通用数据类型可以防止意外行为。
Example:
A = A.astype(float) # Explicitly converting A to float
result = A @ B
通过显式转换A
要浮动,您需要确保乘法按预期进行,不会出现与数据类型相关的意外情况。
使用矩阵乘法的真实示例
缩放计算机图形学中的 2D 对象
假设您有一个由顶点定义的 2D 三角形,并且您想要缩放它。
1. 定义对象
首先将三角形顶点的坐标定义为矩阵。
import numpy as np
# Vertices of the triangle
triangle_vertices = np.array([
[0, 0],
[4, 0],
[2, 3]
])
在这里,顶点增加了一个附加维度,以使用齐次坐标处理平移。
2. 定义缩放矩阵
创建一个缩放矩阵,在 x 方向缩放 2,在 y 方向缩放 3。
scale_x = 2
scale_y = 3
scaling_matrix = np.array([
[scale_x, 0],
[0, scale_y]
])
3. 执行缩放
将顶点乘以缩放矩阵以缩放三角形。
scaled_triangle_vertices = triangle_vertices.dot(scaling_matrix)
print(scaled_triangle_vertices)
Output:
array([[0, 0],
[8, 0],
[4, 9]]))
该三角形在 x 方向上缩放了 2 倍,在 y 方向上缩放了 3 倍。
让我们绘制缩放后的坐标,在matplotlib中绘制缩放后的对象:
import matplotlib.pyplot as plt
def plot_triangle(vertices, color, label):
plt.plot(*vertices.T, color=color, label=label) # Plot the edges
plt.scatter(*vertices.T, color=color) # Plot the vertices
plt.fill(vertices[:, 0], vertices[:, 1], alpha=0.1, color=color) # Fill the triangle
# Plot the original triangle
plot_triangle(triangle_vertices, color='blue', label='Original Triangle')
# Plot the scaled triangle
plot_triangle(scaled_triangle_vertices, color='red', label='Scaled Triangle')
# Add labels and legend
plt.xlabel('X')
plt.ylabel('Y')
plt.legend()
plt.grid(True)
plt.axis('equal') # Equal scaling ensures that the plot is not distorted
plt.title('Scaling Transformation of a 2D Triangle')
plt.show()
Output:
图像旋转
首先,图像以颜色加载,并且由于 OpenCV 默认以 BGR 读取图像,因此颜色方案从 BGR 转换为 RGB。还导入了必要的库。
import numpy as np
from scipy.ndimage import affine_transform
import cv2
import matplotlib.pyplot as plt
image = cv2.imread("image.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # Convert from BGR to RGB
使用矩阵乘法组合变换
然后我们定义旋转角度并创建相应的旋转矩阵。
此外,我们计算变换矩阵以将旋转中心移动到图像的中心。
然后使用矩阵乘法将这些变换组合成full_transform
matrix.
angle_degrees = 60
angle_radians = np.radians(angle_degrees)
rotation_matrix = np.array([[np.cos(angle_radians), -np.sin(angle_radians), 0],
[np.sin(angle_radians), np.cos(angle_radians), 0],
[0, 0, 1]])
height, width, _ = image.shape
center_x, center_y = width // 2, height // 2
transform_matrix = np.array([[1, 0, -center_x],
[0, 1, -center_y],
[0, 0, 1]])
# Combine the shift and rotation using matrix multiplication
full_transform = np.dot(np.linalg.inv(transform_matrix), rotation_matrix)
full_transform = np.dot(full_transform, transform_matrix)
在最后一步中,使用以下方法将旋转应用于图像affine_transform
来自 SciPy 库的函数。
这是针对每个颜色通道单独完成的,以处理图像的 3D 特性。最后,使用 Matplotlib 显示旋转后的图像。
rotated_image = np.zeros_like(image)
for channel in range(image.shape[2]):
rotated_image[..., channel] = affine_transform(image[..., channel], full_transform[:2, :2], offset=(full_transform[0, 2], full_transform[1, 2]))
plt.imshow(rotated_image)
plt.title("Rotated Image")
plt.axis("off")
plt.show()
Output:
资源
https://numpy.org/doc/stable/reference/ generated/numpy.dot.html
https://numpy.org/doc/stable/reference/ generated/numpy.matmul.html