NumPy 基础内容及代码示例


前言

Python 提供了原生的数据结构,如: 列表 List 和 字典 Dictionary,它们分别用于存储有序和键值对数据。然而,当处理大规模、多维数据时,原生数据结构往往不够高效。
因此,引入科学计算第三方库-NumPy 。

NumPy

NumPy(Numerical Python)是高性能科学计算和数据分析的基础包。它极大的简化了多维数组的操纵和处理,大部分数据处理软件包都依赖于 NumPy,例如 Pandas、Matplotlib、Scikit-learn 等。

NumPy 的一些特点

  • NumPy 提供了对数组和矩阵进行快速运算的标准数学函数;
  • NumPy 提供了向量化(数组表达式)计算方法比按顺序逐元素用循环实现运算速度要快很多;
  • NumPy 开放源代码,由许多协作者共同维护开发。
  • ……

NumPy 库的核心是表示 同质的多维数据 —— 每个元素占用相同大小的内存块, 并且所有块都以完全相同的方式解释。

NumPy 支持比 Python 更多种类的数字类型,有5种基本数字类型:

  • 布尔值(bool)
  • 整数(int)
  • 无符号整数(uint)
  • 浮点(float)
  • 复数(complex)

NumPy 的一些应用

1、人工智能中的基础数学工具

人工智能大量依赖 线性代数、概率统计、矩阵运算。NumPy 提供了高效的矩阵运算功能,例如:

  • 矩阵乘法
  • 向量点积
  • 线性代数运算

2、深度学习框架的底层依赖

像 TensorFlow、PyTorch、scikit-learn 等框架在底层都大量使用了 NumPy:

  • 数据存储和批处理
  • 数值计算
  • 提供类似 API 接口,便于与 NumPy 交互
    学 NumPy 是理解这些框架的桥梁。

3、数据预处理与特征工程

人工智能不仅是建模,更重要的是数据准备。NumPy 常用于:

  • 数据清洗(缺失值填充、异常值处理)
  • 标准化/归一化(让特征有合适的范围,利于模型收敛)
  • 数组切片与索引(高效选择特征、样本)
>>> import numpy as np    # 本篇以下代码块部分不再重复导入
>>> data = np.array([10,20,30,40])  
>>> normalized =  (data - np.mean(data)) / np.std(data)  # 可直接计算平均数 np.mean(数组)、标准差 np.std(数组)
>>> print(normalized)
[-1.34164079 -0.4472136   0.4472136   1.34164079]

4、加速运算(向量化优势)

NumPy 的底层是 C 语言实现,比纯 Python 循环快几十倍。 在人工智能里,训练数据通常是百万级甚至更大规模,如果用 Python for 循环会非常慢,NumPy 可以直接实现批量运算。
示例:

>>> x = list(range(1000000))
>>> # 在 Jupyter Notebook 中 可使用 %time 显示任务运行所花费时间
>>> %time x = [i**2 for i in x]          # Python 列表推导式
CPU times: total: 78.1 ms
Wall time: 56.8 ms

>>> x = np.arange(1000000)
>>> %time x = x ** 2                     # NumPy 向量化运算
CPU times: total: 0 ns
Wall time: 1.71 ms

NumPy 的创建 - Ndarray

创建数组常见方法:

  • 从其他 Python 结构(例如:列表,元组)转换
  • NumPy 原生数组的创建(例如:arange、ones、zeros等)
  • 从磁盘读取数组
  • 通过使用字符串创建数组
  • 使用特殊库函数(例如:random)

NumPy 最重要、最常用的是其 N 维数组对象 ndarray,用于存放同类型元素,ndarray 中的每个元素在内存中所占区域大小一致。

示例:

1、创建全是0的一维数组

>>> arr = np.zeros(10)
>>> arr
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

2、创建全是1的一维数组

>>> arr = np.ones(10)
>>> arr
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])

3、创建没有初始值的一维数组

>>> arr = np.empty(10)
>>> arr # 这样输出的是未初始化的内存值(看起来像乱码/极小数)
array([2.12199579e-314, 2.12199579e-314, 0.00000000e+000,
       0.00000000e+000, 2.12199579e-314, 2.12199579e-314,
       0.00000000e+000, 0.00000000e+000, 2.12199579e-314,
       2.12199579e-314])

4、给定数组形状创建二维或三维数组

  • 二维:
>>> arr2d = np.zeros( (3,6) )
>>> arr2d
array([[0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.]])
  • 三维:
>>> arr3d = np.zeros( (2,2,2) )
>>> arr3d
array([[[0., 0.],
        [0., 0.]],

       [[0., 0.],
        [0., 0.]]])

5、创建指定值构成的特定形状数组

>>> arr = np.full(10,3)
>>> arr
array([3, 3, 3, 3, 3, 3, 3, 3, 3, 3])
>>> arr2d = np.full( (2,3) ,3)
>>> arr2d
array([[3, 3, 3],
       [3, 3, 3]])
>>> arr3d = np.full( (2,2,2) ,3)
>>> arr3d
array([[[3, 3],
        [3, 3]],

       [[3, 3],
        [3, 3]]])

6、给定形状和数据类型创建数组

>>> arr = np.full(10,3)  
>>> arr = np.ones_like(arr,'float64')  
>>> arr
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])

Ndarray 的数据类型

类型 类型 代码 说明
int8, uint8 i1, u1 有符号和无符号的8位(1个字节)整型
int16,uint16 i2, u2 有符号和无符号的16位(2个字节)整型
int32,uint32 i4, u4 有符号和无符号的32位(4个字节)整型
int64,uint64 i8, u8 有符号和无符号的64位(8个字节)整型
float16 f2 半精度浮点数
float32 f4或f 标准的单精度浮点数
float64 f8或d 标准的双精度浮点数
bool ? 存储 True 和 False 值的布尔类型
object O 任意 python 对象【注意是字母(大写),不是数字 零。**】
bytes_ S 固定长度的ASCII字符串类型(每个字符1个字节)。 例如,要创建一个长度为10的字符串,应使用'S10'【注意大写】
unicode_ U 固定长度的 Unicode 类型(字节数由平台决定);与字符串的指定语法一样(如‘U10’ (Unicode 占用空间主要依赖于操作系统,Windows、Linux 不一样的)【注意大写】

Ndarray 的索引与切片

Ndarray 对象的索引和切片操作,与序列的索引和切片操作类似。

索引支持:

  • 正向索引(从左到右,下标从 0 开始不断增大);
  • 反向索引(从右到左,下标从-1 开始不断减小)。

切片操作:

  • 可通过 slice 函数,设置 start、stop 和 step 参数进行;
  • 也可以通过冒号分隔切片参数 start:stop:step 进行。

对于多维数组来说,可以分别对每一个维度进行索引和切片,多个维度的索引和切片之间用逗号隔开。

但也存在不同之处:

  • 序列进行切片操作后,会生成一个新的序列,相当于是将相应的元素复制出来组成了一个新的序列;
  • Ndarray 切片结果并不会单独生成一个新的 Ndarray,访问的仍然是原始的 Ndarray 中的数据,因此对切片结果的修改会影响到原始数据。

此外,NumPy 中还提供了一些特殊的索引方式,例如:

  1. 整数数组索引
    主要用于同时访问多个无规律的元素,用整数数组作为索引,一个整数数组表示一个维度,有多少个维度就需要多少个整数数组,整数数组中用于存放各元素在这一维度的索引。

  2. 布尔索引
    通过布尔运算来获取符合一定条件的元素,对一个多维数组使用布尔运算时,会对数组中的每一个元素执行布尔运算,最终得到一个元素为 True 或 False的多维数组,然后将这个多维布尔数组作为索引,此时会取出 True 对应位置上的数据。

  3. 花式索引
    根据索引数组的值作为目标数组的某个轴的下标来取值。
    对于使用一维整型数组作为索引,如果目标是一维数组,那么索引的结果就是对应位置的元素;
    如果目标是二维数组,那么就是对应下标的行。

NumPy 的运算

1、简单运算 + - * / **

>>> arr = np.array([[1,2,3],[4,5,6]])  
>>> print(arr)
[[1 2 3]
 [4 5 6]]
  • 加:
>>> print(arr + arr)
[[ 2  4  6]
 [ 8 10 12]]
  • 减:
>>> print(arr - arr)
[[0 0 0]
 [0 0 0]]
  • 乘:
>>> print(arr * arr)
[[ 1  4  9]
 [16 25 36]]
  • 除:
    数组与标量做运算时,会将标量传播至数组的每一个元素
>>> print(1 / arr)
[[1.         0.5        0.33333333]
 [0.25       0.2        0.16666667]]
  • 幂运算:
>>> print(arr ** 2)
[[ 1  4  9]
 [16 25 36]]

2、比较运算 > < == !=

  • 大小相同的数组之间的比较会生成 布尔数组
>>> print(arr > arr)
[[False False False]
 [False False False]]
  • 不同大小数组之间的运算称为 广播
>>> print(arr > 5)
[[False False False]
 [False False  True]]

数组形状操作

1、reszie() / reshape()

将数组从一个形状改变为另一个形状

reszie() vs reshape():

  • resize 生成指定形状的数组的数据是原数组的副本
  • reshape 生成指定形状的数组的数据是原数组的视图

reszie()

>>> arr = np.arange(12)  

>>> b = np.resize(arr, (3, 4))

reshape()

>>> c = np.reshape(arr, (3, 4))  
>>> c2 = arr.reshape((3, 4))
>>> print(np.shares_memory(arr,b)) 
False

>>> print(np.shares_memory(arr,c)) 
True

>>> print(np.shares_memory(arr,c2))
True

2、flatten() / ravel()

  • flatten():将多维数组展平为一维数组(返回副本)
  • ravel():将多维数组展平为一维数组(返回视图,与 flatten 不同)

flatten()

>>> b = np.array([[0, 1], [2, 3], [4, 5]])
>>> c = b.flatten()
>>> print(c) 
[0 1 2 3 4 5]

>>> print(b)     # 原始数组不会产生变化
[[0 1]
 [2 3]
 [4 5]]

ravel()

>>> a = np.arange(10).reshape(2,5)  
>>> print(a) 
[[0 1 2 3 4]  
 [5 6 7 8 9]]  

>>> print(a.ravel(order='F'))      # Fortran 语言:数组存储方式-列优先(Column-major)
[0 5 1 6 2 7 3 8 4 9]

>>> print(a.ravel(order='C'))      # C 语言:数组存储方式-行优先(Row-major)
[0 1 2 3 4 5 6 7 8 9]

3、NumPy 的转置 T

转置前:\(A\)[m, n] (m 行 n 列)
转置后:\(A^T\)[n, m] (n 行 m 列)

  • 示例1:

转置前:

>>> arr = np.arange(8)                     
>>> print(arr)
[0 1 2 3 4 5 6 7]

转置后:

>>> print(arr.T) # 没有效果,转置结果为自身
[0 1 2 3 4 5 6 7]
  • 示例2:

转置前:

>>> arr = np.arange(8).reshape((2,4))  
>>> print(arr)
[[0 1 2 3]
 [4 5 6 7]]

转置后:

>>> print(arr.T) # 将行变成列,列变成行
[[0 4]
 [1 5]
 [2 6]
 [3 7]]

对于多维数组,转置的本质是轴的重新排列。
如三维数组转置:

  • 轴顺序: (0,1,2) → (2,1,0)

  • 索引变换:arr[i, j, k] = arr.T[k, j, i]

  • 示例3:

转置前:

>>> arr = np.arange(8).reshape((2,2,2))
>>> print(arr)
[[[0 1]
  [2 3]]

 [[4 5]
  [6 7]]]

转置后:

>>> print(arr.T)
[[[0 4]
  [2 6]]

 [[1 5]
  [3 7]]]

常用统计函数、数组排序与条件筛选

1、常用统计函数

函数 作用 示例
np.mean(a) 算术平均值 [1,2,3,4] → 2.5
np.median(a) 中位数(排序后取中间值) [1,2,3,4] → 2.5
np.average(a) 加权平均数 [1,2,3], w=[0.1,0.2,0.7] → 2.6
np.std(a) 标准差(总体标准差) [1,2,3,4] → 1.118...
np.var(a) 方差 [1,2,3,4] → 1.25
np.min(a) / np.max(a) 最小值 / 最大值 [1,2,3,4] → 最小值 1 / 最大值 4
np.argmin(a) / np.argmax(a) 最小值 / 最大值的索引位置 [1,2,3,4] → 最小值索引 0 / 最大值索引 3
np.percentile(a, q) 百分位数(q 可取 0-100) [1,2,3,4], q=50 → 2.5
np.quantile(a, q) 分位数(q 可取 0-1) [1,2,3,4], q=0.5 → 2.5
np.cumsum(a) 累加和 [1,2,3] → [1,3,6]
np.cumprod(a) 累积 [1,2,3] → [1,2,6]
np.ptp(a) 极值(最大值 - 最小值) [1,2,3] → 2

2、常用数组排序

数组排序有很多种方法包括但不限于:

  • 使用 np.sort() 并指定 order 参数按特定字段排序
  • 使用切片 [::-1] 实现降序排序
  • 也可以使用 np.argsort() 获取排序索引

3、常用条件筛选

示例:

>>> # 模拟客户年龄数据
>>> customer_age = np.array([20, 26, 17, 22, 30, 19])

>>> # 生成布尔数组
>>> age_mask = (customer_age >= 18) & (customer_age <= 25)
>>> print("布尔数组:", age_mask) 
布尔数组: [ True False False  True False  True]

>>> # 求和(计数)
>>> count = np.sum(age_mask)
>>> print("18-25岁客户数:", count)
18-25岁客户数: 3

np.sum() 对布尔数组的求和操作,本质是统计 True 的数量,因此在这种场景下表现为“计数”功能。这是 NumPy 对布尔类型的内置处理机制,非常适合用于条件筛选后的计数需求。

NumPy 的广播机制

多个数组之间的算术运算都是对应位置的元素之间的运算。
两个形状相同的数组间执行运算容易理解,两个形状不同的数组之间有时也能执行算术运算,此时会对数组进行扩展,使其形状相同,然后再执行算术运算,这种机制叫做广播。
⚠️ 但并不是所有的数组之间都能执行算术运算。

广播的过程如图所示:
image
image

示例 1:形状相同的数组间运算

>>> a = np.random.randint(1, 10, (2, 3))
>>> b = np.random.randint(2, 8, (2, 3))
>>> print(a)
[[5 8 7]
 [7 2 4]]
>>> print(b)
[[4 7 5]
 [4 7 2]]

>>> print(a + b) # 逐元素相加
[[ 9 15 12]
 [11  9  6]]

>>> print(a - b) # 逐元素相减
[[ 1  1  2]
 [ 3 -5  2]]

>>> print(a * b) # 逐元素相乘
[[20 56 35]
 [28 14  8]]

示例 2:形状不同的数组间运算(广播)

>>> a = np.random.randint(1, 10, (2, 3)) # 形状 (2, 3)
>>> b = np.random.randint(2, 8, (2, 1)) # 形状 (2, 1)
>>> print(a)
[[4 1 6]
 [3 7 2]]
>>> print(b)
[[7]
 [5]]

>>> print(a + b) # b 广播到 (2, 3)
[[11  8 13]
 [ 8 12  7]]

>>> print(a - b) # b 广播到 (2, 3)
[[-3 -6 -1]
 [-2  2 -3]]

>>> print(a * b) # b 广播到 (2, 3)
[[28  7 42]
 [15 35 10]]

对比

1、矩阵加法

>>> A = np.array([[1, 2], [3, 4]])
>>> B = np.array([[5, 6], [7, 8]])
>>> A + B
array([[ 6,  8],
       [10, 12]])
# 矩阵加法要求形状相同,逐元素相加

2、矩阵乘法

- 矩阵×矩阵 (m,n) × (n,p) → (m,p)

>>> A = np.array([[1, 2], [3, 4]])
>>> B = np.array([[5, 6], [7, 8]])
>>> A @ B
array([[19, 22],
       [43, 50]])
# 三种写法等价
>>> np.dot(A, B)
array([[19, 22],
       [43, 50]])
>>> np.matmul(A, B)
array([[19, 22],
       [43, 50]])
>>> A @ B
array([[19, 22],
       [43, 50]])

# ⚠️ 注意区分:* 是逐元素相乘,@ 是矩阵乘法
>>> A * B
array([[ 5, 12],
       [21, 32]])

- 矩阵×向量 (m,n) × (n,) → (m,)

>>> A = np.array([[1, 2], [3, 4]])  # (2, 2)
>>> v = np.array([1, 2])            # (2,) ← 注意:这是一维向量,不是 (2,1)
>>> A @ v                           # (2, 2) × (2,) → (2,)
array([ 5, 11])

3、矩阵广播

# 矩阵 + 向量(广播)
>>> A = np.array([[1, 2, 3],    # (2, 3)
...               [4, 5, 6]])
>>> v = np.array([10, 20, 30])  # (3,)
>>> A + v
# v 被广播为 [[10, 20, 30],
#             [10, 20, 30]],然后逐元素相加
array([[11, 22, 33],
       [14, 25, 36]])

# 矩阵 + 列向量(广播)
>>> A = np.array([[1, 2, 3],    # (2, 3)
...               [4, 5, 6]])
>>> c = np.array([[10], [20]])  # (2, 1)
>>> A + c
# c 被广播为 [[10, 10, 10],
#             [20, 20, 20]]
array([[11, 12, 13],
       [24, 25, 26]])

对比分析图示

880f803f30c1e55b4b30bc20e4b89632

绘图代码如下:

点击查看代码
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec

# ========== 中文显示 ==========
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'PingFang SC']
plt.rcParams['axes.unicode_minus'] = False

def add_matrix_table(ax, data, bbox, title=None, cmap='Blues', text_color='black'):
    """在子图绝对坐标 (left, bottom, width, height) 处绘制矩阵表格"""
    table = ax.table(cellText=data.astype(str),
                     cellLoc='center',
                     loc='center',
                     bbox=bbox)
    cmap_obj = plt.get_cmap(cmap)
    max_val = data.max() if data.max() > 0 else 1
    for i in range(data.shape[0]):
        for j in range(data.shape[1]):
            cell = table[(i, j)]
            color_val = 0.2 + 0.6 * data[i, j] / max_val
            cell.set_facecolor(cmap_obj(color_val))
            cell.set_edgecolor('black')
            cell.set_linewidth(1)
    table.auto_set_font_size(False)
    table.set_fontsize(10)
    table.scale(1, 1.5)
    if title:
        ax.text(bbox[0] + bbox[2]/2, bbox[1] + bbox[3] + 0.02,
                title, ha='center', va='bottom', fontsize=9, style='italic')
    return table

# 创建画布,调整整体边距
fig = plt.figure(figsize=(20, 14))
fig.suptitle('NumPy 矩阵运算对比图\n', fontsize=22, y=0.98)

# 使用 GridSpec 划分 2 行 3 列,并调节子图间距
gs = GridSpec(2, 3, figure=fig, hspace=0.35, wspace=0.3, top=0.92, bottom=0.12)

# ========== 子图1:矩阵加法 ==========
ax1 = fig.add_subplot(gs[0, 0])
ax1.axis('off')
ax1.set_title('[1] 矩阵加法 (A + B)', fontsize=13, pad=15)
A_add = np.array([[1, 2], [3, 4]])
B_add = np.array([[5, 6], [7, 8]])
C_add = A_add + B_add
add_matrix_table(ax1, A_add, [0.05, 0.55, 0.25, 0.35], title='A (2x2)')
ax1.text(0.32, 0.72, '+', ha='center', va='center', fontsize=20)
add_matrix_table(ax1, B_add, [0.38, 0.55, 0.25, 0.35], title='B (2x2)')
ax1.text(0.65, 0.72, '=', ha='center', va='center', fontsize=20)
add_matrix_table(ax1, C_add, [0.71, 0.55, 0.25, 0.35], title='A+B (2x2)')
ax1.text(0.05, 0.10, '例如:C[0,0] = 1+5 = 6', fontsize=9, color='black')
ax1.text(0.05, 0.02, '逐元素相加,形状必须相同', fontsize=10, color='gray')

# ========== 子图2:矩阵乘法 ==========
ax2 = fig.add_subplot(gs[0, 1])
ax2.axis('off')
ax2.set_title('[2] 矩阵乘法 (A @ B)', fontsize=13, pad=15)
A_mat = np.array([[1, 2], [3, 4]])
B_mat = np.array([[5, 6], [7, 8]])
C_mat = A_mat @ B_mat
add_matrix_table(ax2, A_mat, [0.05, 0.55, 0.25, 0.35], title='A (2x2)')
ax2.text(0.32, 0.72, '@', ha='center', va='center', fontsize=20)
add_matrix_table(ax2, B_mat, [0.38, 0.55, 0.25, 0.35], title='B (2x2)')
ax2.text(0.65, 0.72, '=', ha='center', va='center', fontsize=20)
add_matrix_table(ax2, C_mat, [0.71, 0.55, 0.25, 0.35], title='A@B (2x2)')
calc_text = "计算过程 (点积):\nC[0,0]=1x5+2x7=19\nC[0,1]=1x6+2x8=22\nC[1,0]=3x5+4x7=43\nC[1,1]=3x6+4x8=50"
ax2.text(0.5, 0.35, calc_text, ha='center', va='top', fontsize=9,
         bbox=dict(boxstyle='round', facecolor='lightyellow', alpha=0.9))
ax2.text(0.05, 0.10, '维度要求:(m,n) x (n,p) -> (m,p)', fontsize=9, color='gray')
ax2.text(0.05, 0.02, '注意:A * B 是逐元素乘,@ 是矩阵乘', fontsize=9, color='red')

# ========== 子图3:逐元素乘法 ==========
ax3 = fig.add_subplot(gs[0, 2])
ax3.axis('off')
ax3.set_title('[3] 逐元素乘法 (A * B)', fontsize=13, pad=15)
A_ele = np.array([[1, 2], [3, 4]])
B_ele = np.array([[5, 6], [7, 8]])
C_ele = A_ele * B_ele
add_matrix_table(ax3, A_ele, [0.05, 0.55, 0.25, 0.35], title='A (2x2)')
ax3.text(0.32, 0.72, '*', ha='center', va='center', fontsize=20)
add_matrix_table(ax3, B_ele, [0.38, 0.55, 0.25, 0.35], title='B (2x2)')
ax3.text(0.65, 0.72, '=', ha='center', va='center', fontsize=20)
add_matrix_table(ax3, C_ele, [0.71, 0.55, 0.25, 0.35], title='A*B (2x2)')
calc_ele = "计算过程 (逐元素乘):\nC[0,0]=1x5=5\nC[0,1]=2x6=12\nC[1,0]=3x7=21\nC[1,1]=4x8=32"
ax3.text(0.5, 0.35, calc_ele, ha='center', va='top', fontsize=9,
         bbox=dict(boxstyle='round', facecolor='lightyellow', alpha=0.9))
ax3.text(0.05, 0.10, '逐元素相乘,形状相同', fontsize=10, color='gray')
ax3.text(0.05, 0.02, '注意:与 @ 矩阵乘法不同', fontsize=9, color='red')

# ========== 子图4:广播(行向量) ==========
ax4 = fig.add_subplot(gs[1, 0])
ax4.axis('off')
ax4.set_title('[4] 矩阵广播 (矩阵 + 行向量)', fontsize=13, pad=15)
A_b1 = np.array([[1, 2, 3], [4, 5, 6]])
v_row = np.array([10, 20, 30])
C_b1 = A_b1 + v_row

add_matrix_table(ax4, A_b1, [0.02, 0.45, 0.28, 0.35], title='A (2x3)')
ax4.text(0.32, 0.62, '+', ha='center', va='center', fontsize=20)
add_matrix_table(ax4, v_row.reshape(1, -1), [0.38, 0.80, 0.22, 0.16], title='v (3,)', cmap='Blues')
# 广播后中间矩阵(紫色)
v_broad = np.array([[10, 20, 30], [10, 20, 30]])
add_matrix_table(ax4, v_broad, [0.38, 0.45, 0.22, 0.35], title='广播后 (2x3)', cmap='Purples')
ax4.text(0.62, 0.62, '=', ha='center', va='center', fontsize=20)
add_matrix_table(ax4, C_b1, [0.68, 0.45, 0.28, 0.35], title='A+v (2x3)', cmap='Greens')
# 向下复制箭头
ax4.annotate('', xy=(0.49, 0.60), xytext=(0.49, 0.90),
             arrowprops=dict(arrowstyle='->', color='orange', lw=2))
ax4.text(0.52, 0.61, '向下复制为2行', ha='left', va='center', fontsize=9, color='orange')
ax4.text(0.02, 0.10, '广播规则:(2,3) + (3,) -> (2,3) + (2,3) -> (2,3)', fontsize=9, color='gray')

# ========== 子图5:广播(列向量) ==========
ax5 = fig.add_subplot(gs[1, 1])
ax5.axis('off')
ax5.set_title('[5] 矩阵广播 (矩阵 + 列向量)', fontsize=13, pad=15)
A_b2 = np.array([[1, 2, 3], [4, 5, 6]])
c_col = np.array([[10], [20]])
C_b2 = A_b2 + c_col

add_matrix_table(ax5, A_b2, [0.02, 0.45, 0.28, 0.35], title='A (2x3)')
ax5.text(0.32, 0.62, '+', ha='center', va='center', fontsize=20)
add_matrix_table(ax5, c_col, [0.38, 0.45, 0.12, 0.35], title='c (2x1)', cmap='Blues')
# 广播后中间矩阵(紫色)
c_broad = np.array([[10, 10, 10], [20, 20, 20]])
add_matrix_table(ax5, c_broad, [0.52, 0.45, 0.22, 0.35], title='广播后 (2x3)', cmap='Purples')
ax5.text(0.76, 0.62, '=', ha='center', va='center', fontsize=20)
add_matrix_table(ax5, C_b2, [0.82, 0.45, 0.16, 0.35], title='A+c (2x3)', cmap='Greens')
# 向右复制箭头
ax5.annotate('', xy=(0.62, 0.62), xytext=(0.45, 0.62),
             arrowprops=dict(arrowstyle='->', color='orange', lw=2))
ax5.text(0.69, 0.68, '向右复制为3列', ha='center', va='bottom', fontsize=9, color='orange')
ax5.text(0.02, 0.10, '广播规则:(2,3) + (2,1) -> (2,3) + (2,3) -> (2,3)', fontsize=9, color='gray')

# ========== 子图6:矩阵乘向量 ==========
ax6 = fig.add_subplot(gs[1, 2])
ax6.axis('off')
ax6.set_title('[6] 矩阵乘向量 (A @ v)', fontsize=13, pad=15)
A_mv = np.array([[1, 2], [3, 4]])
v = np.array([1, 2])
res_mv = A_mv @ v
add_matrix_table(ax6, A_mv, [0.05, 0.55, 0.25, 0.35], title='A (2x2)')
ax6.text(0.32, 0.72, '@', ha='center', va='center', fontsize=20)
v_2d = v.reshape(1, -1)
add_matrix_table(ax6, v_2d, [0.38, 0.72, 0.20, 0.16], title='v (2,)')
ax6.text(0.60, 0.72, '=', ha='center', va='center', fontsize=20)
res_2d = res_mv.reshape(1, -1)
add_matrix_table(ax6, res_2d, [0.66, 0.72, 0.20, 0.16], title='A@v (2,)')
calc_vec = "计算过程:\nA·[1,2]^T = [5,11]^T\n即:5=1x1+2x2\n11=3x1+4x2"
ax6.text(0.5, 0.35, calc_vec, ha='center', va='top', fontsize=9,
         bbox=dict(boxstyle='round', facecolor='lightyellow', alpha=0.9))
ax6.text(0.05, 0.10, '一维向量 (n,) 与矩阵 (m,n) 运算 -> (m,)', fontsize=9, color='gray')
ax6.text(0.05, 0.02, 'numpy 自动处理维度对齐', fontsize=9, color='gray')

# ========== 底部总结 ==========
summary = (
    "运算规则总结\n"
    "• 加法 / 逐元素乘:形状相同,逐元素运算\n"
    "• 矩阵乘法:@ 或 np.matmul 或 np.dot,维度 (m,n)×(n,p)→(m,p)\n"
    "• 广播:从尾部维度对齐,缺失维度自动复制为1后再扩展"
)
fig.text(0.5, 0.04, summary, ha='center', va='bottom', fontsize=10,
         bbox=dict(facecolor='lightyellow', edgecolor='gray', boxstyle='round,pad=0.3'))

plt.savefig('matrix_ops_optimized.png', dpi=200, bbox_inches='tight')
plt.show()

小结

以上简略整理了关于 Numpy 的简介及其常见操作,详细内容可查阅官方文档:
NumPy 官方文档

📢 声明:本文借助AI辅助工具进行资料整理与初稿生成,所有内容均经过作者本人的详细核对、修改与编排,文责自负。

posted @ 2026-04-11 18:10  Lyn_Li  阅读(53)  评论(0)    收藏  举报