跳转至

P2-3.6 用 NumPy 确认线性代数

Section ID: P2-3.6 Version: v2026.07.20

在 P2-3.1 里,我们看了标量(scalar)、向量(vector)、矩阵(matrix)的形状。在 P2-3.2 里,我们看了向量空间(vector space)与位置(position)的直觉;在 P2-3.3 里,我们把矩阵乘法(matrix multiplication)读成了加权求和(weighted sum)的复用;在 P2-3.5 里,我们又把 Python 运行环境分成了 Google Colab 与本地 PC。

现在开始用一小段一小段代码去检查同样的内容。这里我们使用 NumPy。NumPy 是 Python 里最常见的数组(array)创建与计算库之一。

  1. 先把它看成公式。
  2. 再把它做成数组。
  3. 然后检查 shape
  4. 最后检查计算结果。

这里不要求背下很多 NumPy 语法,而是更关注:线性代数记号在代码里到底会呈现成什么样子。

这里重新整理 NumPy数组(array)shape逐位置乘法(element-wise multiplication)矩阵乘法(matrix multiplication)。如果 3.1 是在读数据形状,3.3 是在读矩阵乘法结构,那么这里就是整理“这些计算在代码和输出里长什么样”。

这里并不是要学完 NumPy 的所有功能,而是重新通过代码和 shape 去确认公式里看到的线性代数结构。只要先通过真实输出把向量、矩阵、*@、batch 计算确认下来,后面在阅读 Pandas 数据和模型输入数组时,就更容易看出公式和代码是在说同一种结构。

从读者的角度来说,很容易出现一种感觉:代码能跑,但这一节到底在确认前面哪一个概念? 所以这里会把每个例子一起连回:它是在重新确认 P2-3.1 的形状(shape)、P2-3.2 的向量比较,还是 P2-3.3 的矩阵乘法结构。

这一节现在先抓住什么 紧接着会连到的问题 后面再次出现的位置
公式和 NumPy 数组是对应的 后面的章节里会继续看表格数据和模型输入数组如何连接。 在 Part 3 里按 shape 读取 Xy、train/test 分割结果时会再次出现。
*@ 连同 shape 一起区分的习惯 P2-12.1P2-12.3P2-13.1 的数据处理和可视化练习里,也要一起确认输出形状。 在 Part 4 的线性层,以及 Part 5 的 embedding/logit 计算里会再次出现。
把 batch 计算读成矩阵层面的计算 它会继续连到 P2-4.1P2-5.1 的变化率与概率说明,再一路连到后面的数组变换流程。 在跟随机器学习输入矩阵和 mini-batch 解释时会成为基础。

先看的概念对应表

这一节里的例子,会按下面这种方式重新确认前面的小节概念。

代码场景 回头确认的前文概念 读者第一步先看什么
创建向量并检查 shape P2-3.1 的标量/向量/矩阵形状 是不是先看到了 shape,而不是先盯着数值
比较向量 ab P2-3.2 的位置与接近直觉 这些向量长度是否相同
a + b2 * a P2-3.2 的向量空间最小运算 加法和标量乘法在代码里怎么出现
区分 *@ P2-3.3 的矩阵乘法与加权求和 这是同位置计算,还是乘并相加的计算
batch 矩阵计算 P2-3.3 的“一次处理多个向量”视角 输入矩阵和输出矩阵的 shape 怎样变化

只要先抓住这张表,每个代码块就会更清楚地变成 重新确认前文概念的地方,而不只是语法示例。

核心判断标准:用 NumPy 确认线性代数

  • 能用 NumPy 数组(array)创建向量和矩阵。
  • 能用 .shape 检查数据形状。
  • 能在代码里确认向量加法与标量乘法。
  • 能说明 *@ 的区别。
  • 能读懂把多个样本打包成矩阵,并应用同一个权重矩阵的计算。
  • 能建立“公式、shape、代码结果一起读”的习惯。

三个基准

基准 为什么重要 本节所需的理解水平
NumPy 把公式里的数组结构搬进代码 因为只有真的创建并打印向量与矩阵,才能看清 shape 与运算差异。 把 NumPy 理解成数组计算工具。
先看 shape,再看值 因为这是检查计算是否成立、输入输出结构是什么的最快方法。 理解要先用 .shape 读长度、行数、列数。
必须分清 *@ 因为一旦把逐位置乘法与矩阵乘法混淆,公式与代码都会一起读错。 理解 * 是逐位置计算,@ 是矩阵乘法。

运行环境

这一节的代码可以在任何安装了 NumPy 的 Python 环境里运行。

先按照 P2-3.5 的 先把运行环境分开 来确认执行位置。如果还没有安装 Python,可以在 Google Colab 代码单元里运行;如果使用本地 PC,也可以在自己的终端里运行。

在 Colab 代码单元里,可以这样准备 NumPy。

问题场景:在 Colab 中运行本节 NumPy 示例之前,先准备好包。 输入(input):笔记本代码单元里的 %pip install numpy 命令。 期望输出(output):NumPy 被安装到当前 Colab 内核。 要确认的概念:Colab 代码单元里的安装命令,与 Python 代码的执行,是两个不同阶段。

# 这条命令是在 Colab/Jupyter 代码单元里安装 NumPy。
%pip install numpy

在本地 PC 终端里,则使用下面的命令。

python -m pip install numpy

然后,在 Python 代码里,用下面这种方式导入 NumPy。

问题场景:准备好包之后,在 Python 代码里真正导入 NumPy。 输入(input):import numpy as np 这一句。 期望输出(output):虽然不会打印结果,但后面例子里就可以使用 np 这个名字。 要确认的概念:安装命令与 import 语句的执行位置和作用不同。

# 这一行把已安装的 NumPy 用 np 这个短名字导入 Python 代码。
import numpy as np

这里的 np 是对 NumPy 的惯例性简写别名(alias)。

本节完整的示例代码也可以通过下面这个文件获得。

如果从项目根目录运行,可以在个人电脑终端里使用下面的命令。

python docs/assets/part-02/chapter-03/p2_3_6_numpy_linear_algebra.py

这个文件会把向量加法、标量乘法、逐位置乘法、矩阵乘法、batch 计算一起打印出来。

创建向量与矩阵

向量(vector)可以作为值的列表来创建。

这个例子是在代码里确认 P2-3.1 提到的 向量可以读成一维数组

问题场景:把公式里看到的向量做成 NumPy 一维数组,并检查它的形状。 输入(input):含有两个值的数组 x。 期望输出(output):打印向量值与 shape (2,)。 要确认的概念:向量在 NumPy 中表现为一维数组,可以通过 shape 检查长度。

# 这个例子导入 NumPy,为线性代数例子中的向量和矩阵计算做准备。
import numpy as np

# x 是一个有两个成分的输入向量。
x = np.array([2, 3])

print(x)

# shape 用来确认这个向量是包含两个成分的一维数组。
print(x.shape)

输出可以读成:

[2 3]
(2,)

(2,) 表示“一个含有 2 个值的一维数组”。对应到公式里就是:

\[ \mathbf{x} = [2,\ 3] \]

矩阵(matrix)则可以创建成带有行(row)和列(column)的二维数组。

这个例子是在重新确认 P2-3.1 中“矩阵是收集多个向量的二维结构”这一说法。

问题场景:把公式中看到的权重矩阵做成 NumPy 二维数组,并检查它的形状。 输入(input):2 行 2 列 的数组 W。 期望输出(output):打印矩阵的值与 shape (2, 2)。 要确认的概念:矩阵在 NumPy 中表现为二维数组,行数和列数通过 shape 来读取。

# W 是把输入向量变成另一个输出的 2x2 权重矩阵。
W = np.array([
    [4, 1],
    [5, 2],
])

print(W)

# W 的 shape 是判断矩阵乘法维度是否匹配的依据。
print(W.shape)

输出可以读成:

1
2
3
[[4 1]
 [5 2]]
(2, 2)

(2, 2) 表示 2 行 2 列。

\[ W = \begin{bmatrix} 4 & 1 \\ 5 & 2 \end{bmatrix} \]

先检查 shape

当 AI 代码里的计算不工作时,往往应该先看 shape,再看数值。

这个习惯就是把 P2-3.2 中“只能在同一个空间里比较”的标准,搬到代码层面来。只要长度和形状不同,比较或乘法本身就可能不成立。

问题场景:先检查输入向量与权重矩阵是否具备可计算的形状。 输入(input):向量 x 和矩阵 W。 期望输出(output):打印 xWshape。 要确认的概念:把线性代数计算搬进代码时,先看 shape 再看值。

# x 是输入向量,W 是要与它相乘的权重矩阵。
x = np.array([2, 3])
W = np.array([
    [4, 1],
    [5, 2],
])

# 把两个 shape 并排输出,可以先判断 x @ W 是否可行。
print("x shape:", x.shape)
print("W shape:", W.shape)

输出会是:x shape: (2,)W shape: (2, 2)

这些信息能帮助我们回答:x 有多少个值W 接收多少输入并产生多少输出它们两者能不能相乘

如果按 P2-3.3 的视角来读,x 是一个含有 2 个输入值的向量,而 W 是一个接收 2 个输入并产生 2 个输出的权重矩阵(weight matrix)。

确认向量加法与标量乘法

向量加法(vector addition)会把相同位置上的值相加。

这个例子是在代码里重新确认 P2-3.2 的向量空间最小运算。也就是说,向量不只是被比较的对象,也是可以被相加、缩放、从而做出新表示的计算对象。

问题场景:确认两个向量按相同位置相加的基础计算。 输入(input):长度为 3 的向量 ab。 期望输出(output):打印逐位置相加结果 [5 7 9]。 要确认的概念:向量加法就是把相同位置的值相加。

1
2
3
4
5
6
# a 和 b 是两个 shape 相同的向量。
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 这里确认相同位置的成分会相加。
print(a + b)

输出是:

[5 7 9]

对应到公式里:

\[ [1,\ 2,\ 3] + [4,\ 5,\ 6] = [5,\ 7,\ 9] \]

标量乘法(scalar multiplication)则是把同一个数字乘到数组的每个位置上。

问题场景:确认把一个标量乘到整个向量上的计算。 输入(input):向量 a 与标量 2。 期望输出(output):打印 [2 4 6]。 要确认的概念:标量乘法会把同一个数字应用到向量的每个位置。

# 这里把前面创建的向量 a 的每个成分都乘以同一个标量 2。
print(2 * a)

输出是:

[2 4 6]

对应到公式里:

\[ 2[1,\ 2,\ 3] = [2,\ 4,\ 6] \]

这两个计算,都直接连回了 P2-3.2 所说的向量空间最小计算。

* 表示逐位置乘法

在 NumPy 里,数组之间的 * 通常表示逐位置乘法(element-wise multiplication)。

这个例子也会继续连到 P2-3.4 里重新出现的比较问题。现在先熟悉“相同位置相乘”这种运算长什么样,之后再把它和点积(dot product)、相似度计算区分开来。

问题场景:确认 * 运算符不是矩阵乘法,而是逐位置乘法。 输入(input):长度为 3 的向量 ab。 期望输出(output):打印逐位置乘积 [ 4 10 18]。 要确认的概念:NumPy 的 * 表示同位置之间的 element-wise 运算。

1
2
3
4
5
6
# a 和 b 是用来比较逐元素乘法的两个向量。
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# * 会把相同位置的成分相乘。
print(a * b)

输出是:

[ 4 10 18]

对应到公式里:

\[ [1,\ 2,\ 3] \odot [4,\ 5,\ 6] = [4,\ 10,\ 18] \]

这里最重要的是:* 不是矩阵乘法。它只是把相同位置的值相乘。换句话说,* 是逐位置乘法,@ 才是矩阵乘法。

@ 表示矩阵乘法

在 NumPy 里,@ 用来做矩阵乘法(matrix multiplication)。

这个例子是在代码里恢复 P2-3.3 的核心。@ 并不是相同位置相乘,而是一种“乘并相加”从而生成新向量的加权求和结构。

问题场景:用输入向量和权重矩阵计算基于加权求和的矩阵乘法。 输入(input):长度为 2 的向量 x2 x 2 的矩阵 W。 期望输出(output):打印输出向量 y 及其 shape。 要确认的概念:@ 不是逐位置乘法,而是通过乘并相加生成新向量的矩阵乘法。

# x 是输入向量,W 是生成输出成分的权重矩阵。
x = np.array([2, 3])
W = np.array([
    [4, 1],
    [5, 2],
])

# y 是 x 和 W 做矩阵乘法后得到的输出向量。
y = x @ W

print(y)
print(y.shape)

输出是:

[23  8]
(2,)

这个计算正是 P2-3.3 里看过的加权求和结构。

\[ [2,\ 3] \begin{bmatrix} 4 & 1 \\ 5 & 2 \end{bmatrix} = [23,\ 8] \]

第一个输出是:

\[ 2 \times 4 + 3 \times 5 = 23 \]

第二个输出是:

\[ 2 \times 1 + 3 \times 2 = 8 \]

所以,@ 就是 乘并相加、做出新向量的计算

一次计算多个样本

如果把多个输入样本(sample)收集成一个矩阵,就可以把同一个权重矩阵一次性应用上去。

问题场景:确认把多个样本收集成矩阵,并一次性应用同一个权重矩阵的 batch 计算。 输入(input):2 x 2 的输入矩阵 X2 x 2 的权重矩阵 W。 期望输出(output):打印 XWY 的 shape,以及 batch 计算结果矩阵。 要确认的概念:即使是同时计算多个样本,核心仍然是矩阵形状是否兼容。

# X 是把两个样本放在行里的输入矩阵。
X = np.array([
    [2, 3],
    [1, 4],
])

# W 是把每个输入样本变成输出向量的权重矩阵。
W = np.array([
    [4, 1],
    [5, 2],
])

# Y 是把 W 应用于整个输入批次 X 后得到的输出矩阵。
Y = X @ W

print(X.shape)
print(W.shape)
print(Y)
print(Y.shape)

输出是:

1
2
3
4
5
(2, 2)
(2, 2)
[[23  8]
 [24  9]]
(2, 2)

对应到公式里:

\[ X = \begin{bmatrix} 2 & 3 \\ 1 & 4 \end{bmatrix} \]
\[ W = \begin{bmatrix} 4 & 1 \\ 5 & 2 \end{bmatrix} \]
\[ XW = \begin{bmatrix} 23 & 8 \\ 24 & 9 \end{bmatrix} \]

这里,第一行是第一个样本的输出,第二行是第二个样本的输出。也就是说:有 2 个输入样本,每个样本有 2 个值,应用同一个 W 之后,得到 2 个输出样本,而每个输出也有 2 个值。

这就是 batch 计算最小的例子。

故意想一遍 shape 错误

下面这个计算就不能直接对上。

问题场景:确认当输入向量长度和权重矩阵输入侧大小不一致时,会出现什么问题。 输入(input):长度为 3 的向量 bad_x2 x 2 的矩阵 W。 期望输出(output):尝试做矩阵乘法时出现 shape 不匹配错误。 要确认的概念:线性代数代码不是“值差不多就能跑”,而是“shape 对得上才能跑”。

1
2
3
4
5
6
7
8
# bad_x 有 3 个成分,因此和只有 2 行的 W 做矩阵乘法时维度不匹配。
bad_x = np.array([2, 3, 4])
W = np.array([
    [4, 1],
    [5, 2],
])

bad_y = bad_x @ W

bad_x 的 shape 是 (3,),而 W 的 shape 是 (2, 2)。输入有 3 个值,但权重矩阵只接收 2 个输入。也就是:bad_x shape: (3,)W shape: (2, 2)

因此,它无法对齐到底哪些输入值该与哪些权重相乘。在真实的 NumPy 运行中,就会报出 shape 不匹配的错误。

比起死记错误消息,更重要的是问清:左边最后一个大小和右边输入侧大小是否相等、我打算做的是逐位置乘法还是矩阵乘法、输出 shape 理应是什么。

通过案例来看

案例 1. 为什么不先看 shape,看起来差不多的代码也会突然跑不通

很多学习者在跟着写 NumPy 示例时,会把 *@ 混用,或者没有把向量长度和矩阵大小对齐,于是就碰到错误。即使数值看起来好像差不多,NumPy 也会先按 shape 是否匹配来决定计算能不能进行。

例如,长度为 2 的向量 x2 x 2 矩阵 W 可以通过 x @ W 相乘。但如果输入向量长度变成 3,而权重矩阵保持不变,计算就会停下来,因为系统无法再决定哪些值应该和哪些权重组合。

这个案例说明了为什么我们会不断重复那句话:先看 shape,再看值。在线性代数公式里,符号看起来也许很简洁,但到了代码里,必须真正检查每个向量和矩阵到底含有多少个值,才能复现同一个计算。

所以,NumPy 实践的核心,与其说是记语法,不如说是用 shape 再次确认公式结构。* 是不是逐位置乘法、@ 是不是矩阵乘法、输入和输出维度如何连接,这些阅读标准都会直接继续连到后面的模型计算。

检查清单

  • 能用 NumPy 数组(array)创建向量和矩阵吗?
  • 能用 .shape 检查向量与矩阵的形状吗?
  • 能把向量加法和标量乘法连回代码与公式吗?
  • 能说明 NumPy 的 * 是逐位置乘法(element-wise multiplication)吗?
  • 能说明 NumPy 的 @ 是矩阵乘法(matrix multiplication)吗?
  • 能读懂 x @ W 里的输入 shape、权重 shape、输出 shape 吗?
  • 能说明“把多个样本收集成矩阵,再应用同一个权重矩阵”的 batch 计算吗?
  • 能说明在 NumPy 里,比起单独记语法,更重要的是把公式、shape、输出一起阅读的习惯吗?
  • 能区分 *@,并应用“先看 shape、再看值”的标准吗?

来源与参考资料