费曼学习法11 - NumPy 的 “线性代数” 之力:矩阵运算与应用 (应用篇)

第六篇:NumPy 的 "线性代数" 之力:矩阵运算与应用 (应用篇)

开篇提问:

考虑一个实际问题:图像的旋转 。 当你使用图像编辑软件旋转照片时,背后是什么在驱动图像像素的精确移动? 答案是 线性代数 。 图像可以表示为 数值矩阵 ,而旋转、缩放、剪切等图像变换,都可以通过 矩阵运算 来实现。 线性代数不仅是图像处理的基石,也在 机器学习、物理模拟、工程计算 等众多领域扮演着核心角色。 它提供了一套强大的数学工具,用于 描述和解决多维空间中的问题

NumPy,作为 Python 中科学计算的核心库,提供了 完善的线性代数运算功能 。 它不仅能高效地表示 向量和矩阵 ,还能进行各种 矩阵运算、求解线性方程组、计算特征值和特征向量 等。 今天,我们将深入探索 NumPy 的 "线性代数" 之力,掌握矩阵运算的基本技巧,并了解其在实际应用中的价值。

核心概念讲解 (费曼式解释):

  1. NumPy 中的矩阵表示: ndarray 是主力

    在线性代数中,矩阵 (Matrix) 是一个 按行和列排列的矩形数组 。 NumPy 主要使用 多维数组 ndarray 来表示矩阵。 虽然 NumPy 历史上也提供了一个 matrix 类,但 ndarray 更加通用和灵活,并且是推荐的矩阵表示方式 。 我们可以使用 np.array() 创建二维 ndarray 来表示矩阵。

    python 复制代码
    import numpy as np
    
    # 使用 np.array 创建二维数组,表示矩阵
    matrix_a = np.array([[1, 2], [3, 4]])
    print("矩阵 A (ndarray):\n", matrix_a)
    print("矩阵 A 的形状:", matrix_a.shape) # (2, 2) - 2 行 2 列
    
    matrix_b = np.array([[5, 6], [7, 8]])
    print("\n矩阵 B (ndarray):\n", matrix_b)
    print("矩阵 B 的形状:", matrix_b.shape) # (2, 2) - 2 行 2 列

    代码解释:

    • 二维 ndarray 表示矩阵: NumPy 中,我们使用 二维 ndarray 来表示矩阵。 np.array([[...], [...], ...]) 创建的二维数组天然就符合矩阵的行列结构。
    • 形状 shape 属性: 二维数组的 shape 属性返回一个元组 (rows, columns),表示矩阵的 行数和列数
  2. NumPy 矩阵运算: 线性代数的核心操作

    NumPy 提供了丰富的函数和运算符,用于执行各种线性代数中的 矩阵运算。 掌握这些运算是利用 NumPy 进行线性代数计算的基础。

    • 矩阵乘法 (Matrix Multiplication): np.dot(), @ 运算符

      矩阵乘法是线性代数中 最重要的运算之一 。 NumPy 提供了 np.dot(a, b) 函数以及 @ 运算符 (Python 3.5+) 来进行矩阵乘法。 注意: NumPy 的 * 运算符执行的是元素级乘法,而不是矩阵乘法。 矩阵乘法要求 第一个矩阵的列数必须等于第二个矩阵的行数

      python 复制代码
      import numpy as np
      
      matrix_a = np.array([[1, 2], [3, 4]]) # 2x2 矩阵
      matrix_b = np.array([[5, 6], [7, 8]]) # 2x2 矩阵
      matrix_c = np.array([[1, 2, 3], [4, 5, 6]]) # 2x3 矩阵
      
      # 1. 使用 np.dot() 函数进行矩阵乘法
      matrix_multiply_dot = np.dot(matrix_a, matrix_b) # A 乘以 B
      print("矩阵乘法 (np.dot(A, B)):\n", matrix_multiply_dot) # 结果是 2x2 矩阵
      
      matrix_multiply_dot_ac = np.dot(matrix_a, matrix_c) # A 乘以 C (2x2 乘以 2x3,结果是 2x3)
      print("\n矩阵乘法 (np.dot(A, C)):\n", matrix_multiply_dot_ac) # 结果是 2x3 矩阵
      
      # 2. 使用 @ 运算符进行矩阵乘法 (更简洁,推荐使用)
      matrix_multiply_at = matrix_a @ matrix_b # A @ B,等价于 np.dot(A, B)
      print("\n矩阵乘法 (A @ B):\n", matrix_multiply_at) # 结果与 np.dot(A, B) 相同
      
      matrix_multiply_at_ac = matrix_a @ matrix_c # A @ C,等价于 np.dot(A, C)
      print("\n矩阵乘法 (A @ C):\n", matrix_multiply_at_ac) # 结果与 np.dot(A, C) 相同
      
      # 尝试不符合矩阵乘法规则的形状 (例如 2x2 乘以 2x2 的元素级乘法)
      matrix_element_multiply = matrix_a * matrix_b # 元素级乘法 (对应位置元素相乘)
      print("\n元素级乘法 (A * B):\n", matrix_element_multiply) # 注意:这不是矩阵乘法!
    • 矩阵转置 (Matrix Transpose): .T 属性

      矩阵转置是 交换矩阵的行和列 的操作。 NumPy 中可以使用 .T 属性 快速获取矩阵的转置。

      python 复制代码
      import numpy as np
      
      matrix_a = np.array([[1, 2, 3], [4, 5, 6]]) # 2x3 矩阵
      print("原始矩阵 A:\n", matrix_a)
      
      matrix_transpose_a = matrix_a.T # 矩阵 A 的转置
      print("\n矩阵 A 的转置 (A.T):\n", matrix_transpose_a) # 结果是 3x2 矩阵,行和列互换
      print("转置后矩阵的形状:", matrix_transpose_a.shape) # (3, 2)
    • 矩阵求逆 (Matrix Inverse): np.linalg.inv()

      矩阵求逆是线性代数中一个重要的运算, 只有方阵 (行数和列数相等的矩阵) 才可能存在逆矩阵 。 并非所有方阵都可逆,只有行列式不为 0 的方阵 (非奇异矩阵) 才是可逆的 。 NumPy 提供了 np.linalg.inv(a) 函数来 计算方阵 a 的逆矩阵

      python 复制代码
      import numpy as np
      
      matrix_a = np.array([[1, 2], [3, 4]]) # 2x2 方阵
      print("原始矩阵 A:\n", matrix_a)
      
      # 计算矩阵 A 的逆矩阵
      try:
          matrix_inverse_a = np.linalg.inv(matrix_a)
          print("\n矩阵 A 的逆矩阵 (np.linalg.inv(A)):\n", matrix_inverse_a)
      
          # 验证逆矩阵的性质: A * A_inverse = 单位矩阵 (近似)
          identity_matrix_check = matrix_a @ matrix_inverse_a # 矩阵乘法
          print("\n验证 A * A_inverse (应为单位矩阵):\n", identity_matrix_check) # 接近单位矩阵 (对角线为 1,其余为 0)
      
      except np.linalg.LinAlgError:
          print("\n矩阵 A 不可逆 (奇异矩阵)") # 如果矩阵不可逆,np.linalg.inv() 会抛出 LinAlgError 异常
      
      # 对于奇异矩阵 (行列式为 0 的矩阵),求逆会报错
      matrix_singular = np.array([[1, 2], [2, 4]]) # 奇异矩阵,行列式为 0
      print("\n奇异矩阵:\n", matrix_singular)
      try:
          matrix_inverse_singular = np.linalg.inv(matrix_singular) # 会抛出 LinAlgError 异常
          print("\n奇异矩阵的逆矩阵:\n", matrix_inverse_singular) # 不会执行到这里
      except np.linalg.LinAlgError:
          print("\n奇异矩阵不可逆 (np.linalg.inv() 抛出 LinAlgError 异常)")
    • 矩阵行列式 (Matrix Determinant): np.linalg.det()

      矩阵行列式是一个 标量值 ,用于 描述方阵的某些性质 ,例如 是否可逆、矩阵变换的缩放比例 等。 NumPy 提供了 np.linalg.det(a) 函数来 计算方阵 a 的行列式方阵可逆的条件是其行列式不为 0。

      python 复制代码
      import numpy as np
      
      matrix_a = np.array([[1, 2], [3, 4]]) # 2x2 方阵
      print("矩阵 A:\n", matrix_a)
      
      # 计算矩阵 A 的行列式
      determinant_a = np.linalg.det(matrix_a)
      print("\n矩阵 A 的行列式 (np.linalg.det(A)):\n", determinant_a) # -2.0  不为 0,矩阵 A 可逆
      
      matrix_singular = np.array([[1, 2], [2, 4]]) # 奇异矩阵
      print("\n奇异矩阵:\n", matrix_singular)
      
      determinant_singular = np.linalg.det(matrix_singular)
      print("\n奇异矩阵的行列式 (np.linalg.det(奇异矩阵)):\n", determinant_singular) # 0.0  为 0,奇异矩阵不可逆
    • 矩阵特征值和特征向量 (Eigenvalues and Eigenvectors): np.linalg.eig()

      特征值和特征向量是线性代数中 非常重要的概念 ,它们描述了 线性变换的本质特征 。 对于方阵 A特征向量 v 是指 经过 A 变换后,方向保持不变,只发生缩放的向量缩放比例就是特征值 λ 。 数学表示为: Av = λv。 NumPy 提供了 np.linalg.eig(a) 函数来 计算方阵 a 的特征值和特征向量

      python 复制代码
      import numpy as np
      
      matrix_a = np.array([[1, -2], [2, -3]]) # 2x2 方阵
      print("矩阵 A:\n", matrix_a)
      
      # 计算矩阵 A 的特征值和特征向量
      eigenvalues, eigenvectors = np.linalg.eig(matrix_a) # 返回两个数组:特征值和特征向量
      
      print("\n矩阵 A 的特征值 (eigenvalues):\n", eigenvalues) # 特征值数组
      print("\n矩阵 A 的特征向量 (eigenvectors):\n", eigenvectors) # 特征向量数组 (按列排列,每一列是一个特征向量)
      
      # 验证特征值和特征向量的性质: A @ v = λ * v (近似)
      #  取第一个特征值和第一个特征向量进行验证
      eigenvalue_1 = eigenvalues[0] # 第一个特征值
      eigenvector_1 = eigenvectors[:, 0] # 第一个特征向量 (注意 eigenvectors 是按列排列的)
      print("\n验证第一个特征值和特征向量:")
      print("特征值 λ1:", eigenvalue_1)
      print("特征向量 v1:", eigenvector_1)
      
      av = matrix_a @ eigenvector_1 # A 乘以 v1
      lambda_v = eigenvalue_1 * eigenvector_1 # λ1 乘以 v1
      print("\nA @ v1:\n", av)
      print("\nλ1 * v1:\n", lambda_v) #  A @ v1 和 λ1 * v1  应该近似相等 (由于浮点数精度问题,可能 не完全相等)
      #  可以看到 A @ v1 和 λ1 * v1  在数值上非常接近,验证了特征值和特征向量的性质
  3. 求解线性方程组 (Solving Linear Equations): np.linalg.solve()

    线性方程组是线性代数中的重要应用。 NumPy 提供了 np.linalg.solve(a, b) 函数来 求解线性方程组 Ax = b ,其中 A系数矩阵b常数向量x未知数向量np.linalg.solve() 可以 直接解出未知数向量 x方程组要有唯一解,系数矩阵 A 必须是方阵且可逆 (非奇异矩阵)。

    python 复制代码
    import numpy as np
    
    # 求解线性方程组:
    #   x + 2y = 5
    #   3x + 4y = 13
    
    # 系数矩阵 A
    matrix_a = np.array([[1, 2], [3, 4]])
    print("系数矩阵 A:\n", matrix_a)
    
    # 常数向量 b
    vector_b = np.array([5, 13])
    print("\n常数向量 b:\n", vector_b)
    
    # 使用 np.linalg.solve(A, b) 求解线性方程组 Ax = b
    solution_x = np.linalg.solve(matrix_a, vector_b) # 求解 x
    print("\n线性方程组的解 x (np.linalg.solve(A, b)):\n", solution_x) # [3. 1.]  解为 x=3, y=1
    
    # 验证解是否正确: A @ x 是否等于 b (近似)
    b_check = matrix_a @ solution_x
    print("\n验证 A @ x 是否等于 b:\n", b_check) # [ 5. 13.]  与向量 b 近似相等,解正确

    代码解释:

    • 线性方程组 Ax = b 的矩阵表示: 线性方程组可以表示为矩阵形式 Ax = b,其中 A 是系数矩阵,x 是未知数向量,b 是常数向量。
    • np.linalg.solve(a, b): a系数矩阵 Ab常数向量 b , 函数返回 解向量 x
  4. 案例应用: 图像旋转 (使用矩阵乘法)

    我们回到文章开篇提到的 图像旋转 案例,演示如何使用 NumPy 的矩阵运算,实现图像的 旋转变换 。 这里我们以 灰度图像 为例,演示 逆时针旋转图像 45 度

    python 复制代码
    import numpy as np
    from PIL import Image
    
    # 1. 读取灰度图像并转换为 NumPy 数组
    image_path = "your_image.jpg" # 替换成你的图像文件路径 (建议使用正方形灰度图像,旋转效果更佳)
    img = Image.open(image_path).convert('L') # 打开图像并转换为灰度模式
    image_array = np.array(img) # 转换为 NumPy 数组 (二维数组)
    print("原始图像数组的形状:", image_array.shape) # (height, width)
    
    # 2. 定义旋转角度 (逆时针 45 度,转换为弧度)
    angle_degrees = 45
    angle_radians = np.deg2rad(angle_degrees) # 角度转弧度
    
    # 3. 构建 2D 旋转矩阵
    rotation_matrix = np.array([
        [np.cos(angle_radians), -np.sin(angle_radians)],
        [np.sin(angle_radians), np.cos(angle_radians)]
    ])
    print("\n旋转矩阵 (2D, 逆时针 45 度):\n", rotation_matrix)
    
    # 4. 获取图像中心坐标 (作为旋转中心)
    image_height, image_width = image_array.shape
    center_x, center_y = image_width // 2, image_height // 2 # 图像中心坐标 (整数)
    
    # 5. 创建新的旋转后图像数组 (初始化为黑色,与原始图像形状相同)
    rotated_image_array = np.zeros_like(image_array) # 创建与原始图像形状和数据类型相同的全零数组
    
    # 6. 遍历原始图像的每个像素,计算旋转后的坐标,并赋值到新的图像数组中
    for y in range(image_height):
        for x in range(image_width):
            # 将像素坐标转换为相对于图像中心的坐标
            offset_x = x - center_x
            offset_y = y - center_y
    
            # 应用旋转矩阵进行坐标变换 (矩阵乘法)
            rotated_offset_coords = rotation_matrix @ np.array([offset_x, offset_y]) # 矩阵乘法
            rotated_x_offset, rotated_y_offset = rotated_offset_coords
    
            # 将相对于中心偏移的坐标转换回图像像素坐标
            rotated_x = int(rotated_x_offset + center_x + 0.5) # 加 0.5 并取整,四舍五入
            rotated_y = int(rotated_y_offset + center_y + 0.5) # 加 0.5 并取整,四舍五入
    
            # 检查旋转后的坐标是否在图像边界内
            if 0 <= rotated_x < image_width and 0 <= rotated_y < image_height:
                rotated_image_array[rotated_y, rotated_x] = image_array[y, x] # 将原始像素值赋值给旋转后的图像对应位置
    
    # 7. 将旋转后的 NumPy 数组转换回 PIL 图像对象
    rotated_img = Image.fromarray(rotated_image_array)
    
    # 8. 保存并显示旋转后的图像
    output_path = "rotated_image_45.jpg"
    rotated_img.save(output_path)
    rotated_img.show()
    print(f"\n旋转 {angle_degrees} 度后的图像已保存到: {output_path}")

    代码解释:

    • 读取灰度图像并转换为 NumPy 数组: 与之前案例相同,将图像转换为灰度模式并加载为 NumPy 数组。
    • 定义旋转角度和构建旋转矩阵: 定义旋转角度 (度数),并使用 np.deg2rad() 将角度转换为弧度。 根据旋转角度,构建 2D 逆时针旋转矩阵。 旋转矩阵是线性代数中用于描述旋转变换的矩阵。
    • 获取图像中心坐标: 计算图像的中心像素坐标,作为旋转中心。
    • 创建新的旋转后图像数组: 创建一个与原始图像形状相同、数据类型相同的 全零数组,用于存储旋转后的图像像素值。
    • 遍历像素并应用旋转变换: 遍历原始图像的每个像素 ,对于每个像素:
      • 坐标偏移: 将像素坐标转换为 相对于图像中心的坐标偏移量
      • 矩阵乘法: 使用 旋转矩阵与坐标偏移量向量进行矩阵乘法,计算旋转后的坐标偏移量。
      • 坐标反偏移: 将旋转后的坐标偏移量转换回 图像像素坐标
      • 边界检查: 检查旋转后的坐标是否仍然在图像边界内。 如果超出边界,则忽略该像素。
      • 像素赋值: 如果旋转后的坐标在边界内,则将 原始像素值赋值给旋转后的图像数组的对应位置
    • NumPy 数组转换回 PIL 图像对象、保存和显示: 与之前案例相同,将 NumPy 数组转换回 PIL 图像对象,并保存和显示旋转后的图像。

    这个案例演示了如何使用 NumPy 的矩阵运算 (矩阵乘法) 来实现图像的旋转变换。 图像旋转的核心数学原理就是 坐标的线性变换 ,而线性变换可以用 矩阵乘法 来表示。 通过这个案例,可以体会到线性代数在图像处理等实际应用中的强大威力。

费曼回顾 (知识巩固):

现在,请你用自己的话,总结一下今天我们学习的 NumPy 线性代数运算的知识,包括:

  • NumPy 中如何表示矩阵? 推荐使用 matrix 类还是 ndarray? 为什么?
  • 我们学习了哪些常用的 NumPy 矩阵运算? 矩阵乘法、矩阵转置、矩阵求逆、矩阵行列式、矩阵特征值和特征向量,它们分别有什么作用和应用? 如何使用 NumPy 函数实现这些运算?
  • 什么是线性方程组? 如何使用 NumPy 求解线性方程组? np.linalg.solve() 函数有什么作用?
  • 在图像旋转的案例中,我们是如何运用 NumPy 的矩阵运算来实现图像变换的? 旋转矩阵是什么? 矩阵乘法在图像旋转中起什么作用?

像给你的同学讲解一样,用清晰简洁的语言解释这些概念,并结合图像旋转的案例,帮助他们理解 NumPy 线性代数运算的强大功能和应用价值。

课后思考 (拓展延伸):

  1. 尝试修改图像旋转案例的代码,例如:
    • 调整旋转角度,看看不同的旋转效果?
    • 实现 图像缩放、剪切 等其他图像变换 (提示: 查找 缩放矩阵、剪切矩阵 的公式,并修改代码中的旋转矩阵部分)?
    • 尝试处理 彩色图像 的旋转 (提示: 彩色图像是三维数组,可以分别对每个颜色通道应用旋转变换)?
  2. 思考一下,除了图像处理,NumPy 的线性代数功能还可以应用在哪些科学计算和数据分析场景中? 例如,机器学习 (例如线性回归、PCA 降维)、物理模拟 (例如力学分析、电路分析)、金融建模等等。 你有什么新的应用场景想法吗?
  3. 尝试查阅 NumPy 官方文档或其他线性代数教程,了解更多关于 NumPy 线性代数运算的高级特性,例如:
    • 奇异值分解 (SVD): np.linalg.svd()
    • 特征值分解 (EVD): np.linalg.eig() 的更深入应用
    • 各种矩阵分解 (decomposition) 方法: 例如 QR 分解、LU 分解等
    • 线性代数在机器学习和深度学习中的应用

恭喜你!完成了 NumPy 费曼学习法的第六篇文章学习! 你已经掌握了 NumPy 的 "线性代数" 之力,可以开始探索更高级的科学计算和数据分析应用了! 下一篇文章,也是本系列的 最后一篇文章,我们将一起展望 NumPy 的 "进阶之路",总结 NumPy 的常用技巧和性能优化方法,并探讨 NumPy 在数据科学生态系统中的地位和未来发展方向,为你的 NumPy 学习之旅画上一个圆满的句号! 敬请期待!

相关推荐
我不会编程5554 小时前
Python Cookbook-2.24 在 Mac OSX平台上统计PDF文档的页数
开发语言·python·pdf
胡歌15 小时前
final 关键字在不同上下文中的用法及其名称
开发语言·jvm·python
爱上妖精的尾巴5 小时前
3-5 WPS JS宏 工作表的移动与复制学习笔记
javascript·笔记·学习·wps·js宏·jsa
程序员张小厨5 小时前
【0005】Python变量详解
开发语言·python
Hacker_Oldv6 小时前
Python 爬虫与网络安全有什么关系
爬虫·python·web安全
深蓝海拓7 小时前
PySide(PyQT)重新定义contextMenuEvent()实现鼠标右键弹出菜单
开发语言·python·pyqt
车载诊断技术7 小时前
人工智能AI在汽车设计领域的应用探索
数据库·人工智能·网络协议·架构·汽车·是诊断功能配置的核心
屁股割了还要学7 小时前
【计算机网络入门】初学计算机网络(五)
学习·计算机网络·考研·青少年编程
屁股割了还要学7 小时前
【计算机网络入门】初学计算机网络(七)
网络·学习·计算机网络·考研·青少年编程
AuGuSt_818 小时前
【深度学习】Hopfield网络:模拟联想记忆
人工智能·深度学习