d2l-en 深度学习数学基础:从标量、矩阵到乘法的线性代数实战指南

原创2026-10-02 16:29:401,453 阅读
文章标签:文档教程人工智能深度学习NLP计算机视觉强化学习

d2l-en 深度学习数学基础:从标量、矩阵到乘法的线性代数实战指南

本篇技术指南以《Dive into Deep Learning》(d2l-en)预备知识章节中的线性代数一节为骨架,系统讲解深度学习中必备的线性代数核心对象——标量、向量、矩阵与高阶张量——以及它们在实际框架中的实现与运算。读者将掌握在 MXNet、PyTorch、TensorFlow、JAX 四种主流框架下进行元素级运算、归约(sum/mean)、点积、矩阵–向量积、矩阵–矩阵乘法与各类范数计算的能力,并理解这些运算如何支撑神经网络前向传播与损失函数设计。

在进入具体内容前,需要先安装并导入相应框架。本文所有代码示例均可在 d2l-en 仓库对应的章节源码中复现,按 d2l/__init__.py 的说明,可用以下任一种方式引入 d2l 包:

from d2l import mxnet as d2l      # 使用 MXNet 作为后端
from d2l import torch as d2l      # 使用 PyTorch 作为后端
from d2l import tensorflow as d2l # 使用 TensorFlow 作为后端
from d2l import jax as d2l        # 使用 JAX 作为后端

各框架在本节的初始化导入代码如下:

%%tab mxnet
from mxnet import np, npx
npx.set_np()
%%tab pytorch
import torch
%%tab tensorflow
import tensorflow as tf
%%tab jax
from jax import numpy as jnp

标量(Scalars):只有一个元素的张量

日常数学大多是对单个数字进行运算,这些值在形式上被称为标量(scalar)。例如帕洛阿尔托的气温是 72 华氏度,换算成摄氏度需要求值表达式 c=59(f−32)c = \frac{5}{9}(f - 32),其中 55、99、3232 是常量标量,变量 cc 和 ff 一般代表未知标量。本书用普通小写字母(如 xx、yy、zz)表示标量,用 R\mathbb{R} 表示全体(连续)实值标量构成的空间;表达式 x∈Rx \in \mathbb{R} 是"xx 是一个实值标量"的正式写法,符号 ∈\in(读作"in")表示集合成员关系,例如 x,y∈{0,1}x, y \in \{0, 1\} 表示 xx、yy 只能取值 00 或 11。

在代码层面,标量实现为只包含一个元素的张量。四个框架分别用 np.array、torch.tensor、tf.constant、jnp.array 创建标量,并支持熟悉的加法、乘法、除法与幂运算:

%%tab mxnet
x = np.array(3.0)
y = np.array(2.0)
x + y, x * y, x / y, x ** y
%%tab pytorch
x = torch.tensor(3.0)
y = torch.tensor(2.0)
x + y, x * y, x / y, x**y
%%tab tensorflow
x = tf.constant(3.0)
y = tf.constant(2.0)
x + y, x * y, x / y, x**y
%%tab jax
x = jnp.array(3.0)
y = jnp.array(2.0)
x + y, x * y, x / y, x**y

输出依次为 5.0、6.0、1.5、9.0。注意四个框架均使用 ** 表示幂运算,且标量张量参与运算后仍是标量。

向量(Vectors):一阶张量与零基索引

向量可以看作一个定长的标量数组,其中的标量称为向量的元素(element)(同义词有 entry、component)。当向量表示真实数据集中的样本时,其取值承载实际意义:例如预测贷款违约风险时,每个申请人对应用户画像向量(收入、在职时长、历史违约次数等);研究心脏病风险时,每个向量对应一位病人(生命体征、胆固醇水平、每日运动分钟数等)。本书用粗体小写字母(如 x\mathbf{x}、y\mathbf{y}、z\mathbf{z})表示向量。

向量在代码中实现为一阶张量,长度受内存限制。需要特别警惕索引起点差异:Python 与多数编程语言一样采用零基索引(下标从 00 开始),而线性代数教科书中的下标从 11 开始。创建长度为 3 的向量:

%%tab mxnet
x = np.arange(3)
x
%%tab pytorch
x = torch.arange(3)
x
%%tab tensorflow
x = tf.range(3)
x
%%tab jax
x = jnp.arange(3)
x

输出均为 [0, 1, 2]。可以用下标引用向量的某个元素(如 x2x_2 表示第二个元素,因其是标量故不加粗),默认按垂直堆叠方式展示向量:

x=[x1⋮xn].\mathbf{x} =\begin{bmatrix}x_{1} \\ \vdots \\x_{n}\end{bmatrix}.

这里的 x1,…,xnx_1, \ldots, x_n 是向量的元素。后续会区分这种列向量与元素水平堆叠的行向量。通过索引访问张量元素:

%%tab all
x[2]

输出为 2。若向量含 nn 个元素,记作 x∈Rn\mathbf{x} \in \mathbb{R}^n,其中 nn 称为向量的维数(dimensionality),对应代码中张量的长度,可用 Python 内置 len 函数获取:

%%tab all
len(x)

也可以通过 shape 属性获取长度。shape 是一个元组,表示张量沿每个轴的长度;只有一个轴的张量其 shape 只有一个元素:

%%tab all
x.shape

输出为 (3,)。原文档特别提醒:"dimension"一词常被过度使用,既指轴的数量也指某个轴的长度。为避免混淆,本书用 order(阶) 表示轴的数量,用 dimensionality(维数) 专指分量个数。例如向量是 1 阶张量,其维数即元素个数。

矩阵(Matrices):二阶张量与转置

正如标量是零阶张量、向量是一阶张量,矩阵是二阶张量。本书用粗体大写字母(如 X\mathbf{X}、Y\mathbf{Y}、Z\mathbf{Z})表示矩阵,在代码中由具有两个轴的张量表示。表达式 A∈Rm×n\mathbf{A} \in \mathbb{R}^{m \times n} 表示矩阵 A\mathbf{A} 包含 m×nm \times n 个实值标量,按 mm 行 nn 列排列;当 m=nm = n 时称矩阵为方阵(square)。引用单个元素时同时下标行与列,如 aija_{ij} 表示第 ii 行第 jj 列的值:

A=[a11a12⋯a1na21a22⋯a2n⋮⋮⋱⋮am1am2⋯amn].\mathbf{A}=\begin{bmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & \cdots & a_{mn} \\ \end{bmatrix}.

代码中,A∈Rm×n\mathbf{A} \in \mathbb{R}^{m \times n} 对应 shape 为 (m,n)(m, n) 的二阶张量。可以把任意尺寸合适的 m×nm \times n 张量通过 reshape 变成 m×nm \times n 矩阵——例如用 arange(6) 生成 0~5 共 6 个数后重塑为 3 行 2 列:

%%tab mxnet
A = np.arange(6).reshape(3, 2)
A
%%tab pytorch
A = torch.arange(6).reshape(3, 2)
A
%%tab tensorflow
A = tf.reshape(tf.range(6), (3, 2))
A
%%tab jax
A = jnp.arange(6).reshape(3, 2)
A

输出为 [[0, 1], [2, 3], [4, 5]]。注意 TensorFlow 的 API 是 tf.reshape(tensor, shape),而其余三个框架采用 obj.reshape(shape) 的方法调用形式。

转置与对称矩阵

当交换矩阵的行与列时,结果称为矩阵的转置(transpose)。矩阵 A\mathbf{A} 的转置记为 A⊤\mathbf{A}^\top;若 B=A⊤\mathbf{B} = \mathbf{A}^\top,则对所有 ii、jj 有 bij=ajib_{ij} = a_{ji}。因此 m×nm \times n 矩阵的转置是 n×mn \times m 矩阵:

A⊤=[a11a21…am1a12a22…am2⋮⋮⋱⋮a1na2n…amn].\mathbf{A}^\top = \begin{bmatrix} a_{11} & a_{21} & \dots & a_{m1} \\ a_{12} & a_{22} & \dots & a_{m2} \\ \vdots & \vdots & \ddots & \vdots \\ a_{1n} & a_{2n} & \dots & a_{mn} \end{bmatrix}.

代码中访问矩阵转置:

%%tab mxnet, pytorch, jax
A.T
%%tab tensorflow
tf.transpose(A)

对称矩阵(symmetric matrix) 是等于其自身转置的方阵子集:A=A⊤\mathbf{A} = \mathbf{A}^\top。下面的 3×3 矩阵即是对称的,可用 A == A.T 逐元素比较验证:

%%tab mxnet
A = np.array([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
A == A.T
%%tab pytorch
A = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
A == A.T
%%tab tensorflow
A = tf.constant([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
A == tf.transpose(A)
%%tab jax
A = jnp.array([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
A == A.T

输出为全 True 的 3×3 布尔矩阵。矩阵常用于表示数据集:通常行对应个体记录,列对应不同属性——这正是后续章节中设计矩阵 X∈Rn×d\mathbf{X} \in \mathbb{R}^{n \times d}(nn 个样本、dd 个特征)的由来,如线性回归与分类章节所述。

张量(Tensors):任意阶数组与图像数据

仅靠标量、向量和矩阵虽能走很远,但最终需要处理高阶张量。张量给出了描述 nn 阶数组扩展的通用方式,代码中称之为"tensor"的软件对象正因为可以拥有任意数量的轴而得名。数学上张量用特殊字体的大写字母表示(如 X\mathsf{X}、Y\mathsf{Y}、Z\mathsf{Z}),其索引机制(如 xijkx_{ijk} 和 [X]1,2i−1,3[\mathsf{X}]_{1, 2i-1, 3})从矩阵自然推广而来。

张量在处理图像时尤为重要:每张图片作为三阶张量,其轴对应高度、宽度与通道(channel);在每个空间位置,红、绿、蓝三种颜色的强度沿通道轴堆叠。而一批图片在代码中由四阶张量表示,不同图片沿第一个轴索引。构造高阶张量只需像向量、矩阵那样不断增加 shape 分量,例如把 0~23 共 24 个数重塑为 (2, 3, 4) 的三阶张量:

%%tab mxnet
np.arange(24).reshape(2, 3, 4)
%%tab pytorch
torch.arange(24).reshape(2, 3, 4)
%%tab tensorflow
tf.reshape(tf.range(24), (2, 3, 4))
%%tab jax
jnp.arange(24).reshape(2, 3, 4)

输出是两个 3×4 的"块",分别是 0~11 与 12~23。对张量的预处理、切片、索引与 shape 语义的完整介绍见同一预备章节的数据操作一节。

张量算术的基本性质:逐元素运算与 Hadamard 积

标量、向量、矩阵和高阶张量都具备一些实用性质。首先,逐元素(elementwise)运算的输出与操作数形状相同。注意不同框架在复制张量语义上的差异:MXNet 用 A.copy()、PyTorch 用 A.clone() 显式分配新内存复制 A,而 TensorFlow 与 JAX 直接令 B = A(不额外分配内存)。

%%tab mxnet
A = np.arange(6).reshape(2, 3)
B = A.copy()  # 通过分配新内存将 A 的副本赋给 B
A, A + B
%%tab pytorch
A = torch.arange(6, dtype=torch.float32).reshape(2, 3)
B = A.clone()  # 通过分配新内存将 A 的副本赋给 B
A, A + B
%%tab tensorflow
A = tf.reshape(tf.range(6, dtype=tf.float32), (2, 3))
B = A  # 不分配新内存,B 即 A
A, A + B
%%tab jax
A = jnp.arange(6, dtype=jnp.float32).reshape(2, 3)
B = A
A, A + B

两个矩阵的逐元素乘积称为 Hadamard 积(Hadamard product),记为 ⊙\odot。对 A,B∈Rm×n\mathbf{A}, \mathbf{B} \in \mathbb{R}^{m \times n}:

A⊙B=[a11b11a12b12…a1nb1na21b21a22b22…a2nb2n⋮⋮⋱⋮am1bm1am2bm2…amnbmn].\mathbf{A} \odot \mathbf{B} = \begin{bmatrix} a_{11} b_{11} & a_{12} b_{12} & \dots & a_{1n} b_{1n} \\ a_{21} b_{21} & a_{22} b_{22} & \dots & a_{2n} b_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} b_{m1} & a_{m2} b_{m2} & \dots & a_{mn} b_{mn} \end{bmatrix}.

在代码中,A * B 即计算 Hadamard 积(四个框架一致,输出 shape 仍为 (2, 3)):

%%tab all
A * B

此外,标量与张量相加或相乘的结果与原始张量形状相同——每个元素都加上(或乘以)该标量。例如标量 a = 2 与 shape 为 (2, 3, 4) 的张量 X 运算:

%%tab mxnet
a = 2
X = np.arange(24).reshape(2, 3, 4)
a + X, (a * X).shape
%%tab pytorch
a = 2
X = torch.arange(24).reshape(2, 3, 4)
a + X, (a * X).shape
%%tab tensorflow
a = 2
X = tf.reshape(tf.range(24), (2, 3, 4))
a + X, (a * X).shape
%%tab jax
a = 2
X = jnp.arange(24).reshape(2, 3, 4)
a + X, (a * X).shape

输出 a + X 中每个元素比 X 大 2,(a * X).shape 仍为 (2, 3, 4)。

归约(Reduction):sum、mean 与 axis 语义

求和与均值

常需要计算张量元素之和。对长度 nn 的向量 x\mathbf{x},其元素和写作 ∑i=1nxi\sum_{i=1}^n x_i,对应函数:

%%tab mxnet
x = np.arange(3)
x, x.sum()
%%tab pytorch
x = torch.arange(3, dtype=torch.float32)
x, x.sum()
%%tab tensorflow
x = tf.range(3, dtype=tf.float32)
x, tf.reduce_sum(x)
%%tab jax
x = jnp.arange(3, dtype=jnp.float32)
x, x.sum()

输出 ([0, 1, 2], 3)。注意 PyTorch 与 TensorFlow 的示例中显式指定了 dtype=torch.float32 / dtype=tf.float32,以避免整型张量求和带来的类型问题。

对任意形状张量,求和只需沿其所有轴进行;m×nm \times n 矩阵的元素和可写作 ∑i=1m∑j=1naij\sum_{i=1}^{m} \sum_{j=1}^{n} a_{ij}:

%%tab mxnet, pytorch, jax
A.shape, A.sum()
%%tab tensorflow
A.shape, tf.reduce_sum(A)

默认情况下,求和函数会沿所有轴归约张量,最终产生标量。框架也允许指定归约的轴:sum(axis=0) 沿行(轴 0)求和,此时输入矩阵沿轴 0 归约生成输出向量,故输出 shape 中缺失该轴:

%%tab mxnet, pytorch, jax
A.shape, A.sum(axis=0).shape
%%tab tensorflow
A.shape, tf.reduce_sum(A, axis=0).shape

输出 ((2, 3), (3,))。同理,axis=1 沿列维度(轴 1)归约:

%%tab mxnet, pytorch, jax
A.shape, A.sum(axis=1).shape
%%tab tensorflow
A.shape, tf.reduce_sum(A, axis=1).shape

输出 ((2, 3), (2,))。同时沿行与列归约等价于对矩阵全部元素求和:

%%tab mxnet, pytorch, jax
A.sum(axis=[0, 1]) == A.sum()  # 与 A.sum() 相同
%%tab tensorflow
tf.reduce_sum(A, axis=[0, 1]), tf.reduce_sum(A)  # 与 tf.reduce_sum(A) 相同

均值(mean,又称 average) 是密切相关的量:用总和除以元素总数得到。由于均值计算极为常见,框架提供了与 sum 类似的专用函数:

%%tab mxnet, jax
A.mean(), A.sum() / A.size
%%tab pytorch
A.mean(), A.sum() / A.numel()
%%tab tensorflow
tf.reduce_mean(A), tf.reduce_sum(A) / tf.size(A).numpy()

计算均值同样可以沿特定轴归约:

%%tab mxnet, pytorch, jax
A.mean(axis=0), A.sum(axis=0) / A.shape[0]
%%tab tensorflow
tf.reduce_mean(A, axis=0), tf.reduce_sum(A, axis=0) / A.shape[0]

这里 axis=0 是样本/批次维——在神经网络训练中,对一批样本的损失取均值正是这一操作的典型应用,可参见 d2l 封装代码中 squared_loss 与 linreg(d2l/torch.py)以及 d2l.jax 中通过 d2l.reduce_mean 计算损失的用法(d2l/jax.py)。

非归约求和:keepdims 与广播

有时在求和或求均值时希望保持轴的数量不变,这在要使用广播(broadcast)机制时至关重要:

%%tab mxnet, pytorch, jax
sum_A = A.sum(axis=1, keepdims=True)
sum_A, sum_A.shape
%%tab tensorflow
sum_A = tf.reduce_sum(A, axis=1, keepdims=True)
sum_A, sum_A.shape

sum_A 在按行求和后仍保留两个轴(shape 为 (2, 1)),因此可以用广播将 A 除以 sum_A,得到每一行之和均为 1 的矩阵——这是将行归一化为概率分布的常用技巧(softmax 的核心思想雏形):

%%tab all
A / sum_A

如果沿某个轴(例如 axis=0,逐行)计算 A 元素的累积和,可调用 cumsum 函数;该函数按设计不会沿任何轴归约输入张量:

%%tab mxnet, pytorch, jax
A.cumsum(axis=0)
%%tab tensorflow
tf.cumsum(A, axis=0)

点积(Dot Products):加权求和与余弦相似度

若只能做逐元素运算、求和与均值,线性代数就不配拥有独立章节。最基础的非逐元素运算是点积(dot product):给定两个向量 x,y∈Rd\mathbf{x}, \mathbf{y} \in \mathbb{R}^d,其点积 x⊤y\mathbf{x}^\top \mathbf{y}(又称内积,记 ⟨x,y⟩\langle \mathbf{x}, \mathbf{y} \rangle)是对同一位置元素乘积的求和:x⊤y=∑i=1dxiyi\mathbf{x}^\top \mathbf{y} = \sum_{i=1}^{d} x_i y_i。

%%tab mxnet
y = np.ones(3)
x, y, np.dot(x, y)
%%tab pytorch
y = torch.ones(3, dtype = torch.float32)
x, y, torch.dot(x, y)
%%tab tensorflow
y = tf.ones(3, dtype=tf.float32)
x, y, tf.tensordot(x, y, axes=1)
%%tab jax
y = jnp.ones(3, dtype = jnp.float32)
x, y, jnp.dot(x, y)

等价地,点积可通过先逐元素相乘再求和得到:

%%tab mxnet
np.sum(x * y)
%%tab pytorch
torch.sum(x * y)
%%tab tensorflow
tf.reduce_sum(x * y)
%%tab jax
jnp.sum(x * y)

两者输出均为 3.0。注意各框架点积 API 命名不同:MXNet/JAX 用 dot,PyTorch 用 torch.dot,TensorFlow 用 tf.tensordot(x, y, axes=1)。

点积的应用极为广泛:给定一组值(向量 x∈Rn\mathbf{x} \in \mathbb{R}^n)与一组权重(w∈Rn\mathbf{w} \in \mathbb{R}^n),按权重对 x\mathbf{x} 加权求和可表示为点积 x⊤w\mathbf{x}^\top \mathbf{w};当权重非负且和为 1(即 ∑i=1nwi=1\sum_{i=1}^{n} {w_i} = 1)时,点积表达的是加权平均;将两个向量归一化为单位长度后,点积表示二者夹角的余弦——这正是注意力机制中查询与键相似度度量的数学基础(参见注意力打分函数)。

矩阵–向量积(Matrix–Vector Products):从 Rn\mathbb{R}^n 到 Rm\mathbb{R}^m 的变换

理解点积后,m×nm \times n 矩阵 A\mathbf{A} 与 nn 维向量 x\mathbf{x} 的乘积就水到渠成。先把矩阵按行向量可视化:

A=[a1⊤a2⊤⋮am⊤],\mathbf{A}= \begin{bmatrix} \mathbf{a}^\top_{1} \\ \mathbf{a}^\top_{2} \\ \vdots \\ \mathbf{a}^\top_m \\ \end{bmatrix},

其中每个 ai⊤∈Rn\mathbf{a}^\top_{i} \in \mathbb{R}^n 是矩阵 A\mathbf{A} 第 ii 行的行向量。矩阵–向量积 Ax\mathbf{A}\mathbf{x} 就是一个长度为 mm 的列向量,其第 ii 个元素是点积 ai⊤x\mathbf{a}^\top_i \mathbf{x}:

Ax=[a1⊤a2⊤⋮am⊤]x=[a1⊤xa2⊤x⋮am⊤x].\mathbf{A}\mathbf{x} = \begin{bmatrix} \mathbf{a}^\top_{1} \\ \mathbf{a}^\top_{2} \\ \vdots \\ \mathbf{a}^\top_m \\ \end{bmatrix}\mathbf{x} = \begin{bmatrix} \mathbf{a}^\top_{1} \mathbf{x} \\ \mathbf{a}^\top_{2} \mathbf{x} \\ \vdots\\ \mathbf{a}^\top_{m} \mathbf{x}\\ \end{bmatrix}.

可以把与矩阵 A∈Rm×n\mathbf{A}\in \mathbb{R}^{m \times n} 相乘理解为把向量从 Rn\mathbb{R}^{n} 投影到 Rm\mathbb{R}^{m} 的变换。这些变换用途广泛:旋转可用特定方阵的乘法表示;矩阵–向量积还描述了神经网络中每一层根据上一层输出计算本层输出的核心计算——这正是全连接层 y = W @ x + b 的数学本质,可在 d2l 封装的线性回归实现(d2l/torch.py 的 forward)与 JAX 版 d2l.matmul(X, self.w) + self.b(d2l/jax.py)中印证。

代码中四个框架的 API 与注意点各不相同:

  • MXNet:复用 dot 函数,运算类型由参数类型推断;A 的列维(轴 1 长度)必须等于 x 的维数。
  • PyTorch:使用 mv 函数;另有便捷运算符 @,可根据参数同时执行矩阵–向量与矩阵–矩阵乘积,故可写 A@x。
  • TensorFlow:使用 matvec 函数;列维约束同上。
  • JAX:使用 jnp.matmul(A, x)。
%%tab mxnet
A.shape, x.shape, np.dot(A, x)
%%tab pytorch
A.shape, x.shape, torch.mv(A, x), A@x
%%tab tensorflow
A.shape, x.shape, tf.linalg.matvec(A, x)
%%tab jax
A.shape, x.shape, jnp.matmul(A, x)

以 A(shape (3, 2))与 x(shape (2,))为例,输出为形状 (3,) 的向量。维度不匹配(A 的列数不等于 x 的长度)时会抛出形状错误。

矩阵–矩阵乘法(Matrix–Matrix Multiplication):点积的拼接

掌握了点积与矩阵–向量积后,矩阵–矩阵乘法顺理成章。设有 A∈Rn×k\mathbf{A} \in \mathbb{R}^{n \times k} 与 B∈Rk×m\mathbf{B} \in \mathbb{R}^{k \times m}:

A=[a11a12⋯a1ka21a22⋯a2k⋮⋮⋱⋮an1an2⋯ank],B=[b11b12⋯b1mb21b22⋯b2m⋮⋮⋱⋮bk1bk2⋯bkm].\mathbf{A}=\begin{bmatrix} a_{11} & a_{12} & \cdots & a_{1k} \\ a_{21} & a_{22} & \cdots & a_{2k} \\ \vdots & \vdots & \ddots & \vdots \\ a_{n1} & a_{n2} & \cdots & a_{nk} \\ \end{bmatrix},\quad \mathbf{B}=\begin{bmatrix} b_{11} & b_{12} & \cdots & b_{1m} \\ b_{21} & b_{22} & \cdots & b_{2m} \\ \vdots & \vdots & \ddots & \vdots \\ b_{k1} & b_{k2} & \cdots & b_{km} \\ \end{bmatrix}.

设 ai⊤∈Rk\mathbf{a}^\top_{i} \in \mathbb{R}^k 为 A\mathbf{A} 第 ii 行的行向量,bj∈Rk\mathbf{b}_{j} \in \mathbb{R}^k 为 B\mathbf{B} 第 jj 列的列向量。乘积 C∈Rn×m\mathbf{C} \in \mathbb{R}^{n \times m} 的每个元素 cijc_{ij} 是 A\mathbf{A} 第 ii 行与 B\mathbf{B} 第 jj 列的点积 ai⊤bj\mathbf{a}^\top_i \mathbf{b}_j:

C=AB=[a1⊤a2⊤⋮an⊤][b1b2⋯bm]=[a1⊤b1a1⊤b2⋯a1⊤bma2⊤b1a2⊤b2⋯a2⊤bm⋮⋮⋱⋮an⊤b1an⊤b2⋯an⊤bm].\mathbf{C} = \mathbf{AB} = \begin{bmatrix} \mathbf{a}^\top_{1} \\ \mathbf{a}^\top_{2} \\ \vdots \\ \mathbf{a}^\top_n \\ \end{bmatrix} \begin{bmatrix} \mathbf{b}_{1} & \mathbf{b}_{2} & \cdots & \mathbf{b}_{m} \\ \end{bmatrix} = \begin{bmatrix} \mathbf{a}^\top_{1} \mathbf{b}_1 & \mathbf{a}^\top_{1}\mathbf{b}_2& \cdots & \mathbf{a}^\top_{1} \mathbf{b}_m \\ \mathbf{a}^\top_{2}\mathbf{b}_1 & \mathbf{a}^\top_{2} \mathbf{b}_2 & \cdots & \mathbf{a}^\top_{2} \mathbf{b}_m \\ \vdots & \vdots & \ddots &\vdots\\ \mathbf{a}^\top_{n} \mathbf{b}_1 & \mathbf{a}^\top_{n}\mathbf{b}_2& \cdots& \mathbf{a}^\top_{n} \mathbf{b}_m \end{bmatrix}.

可以把矩阵–矩阵乘法 AB\mathbf{AB} 看作执行 mm 次矩阵–向量积(或 m×nm \times n 次点积)并把结果拼接成 n×mn \times m 矩阵。 下面的示例中 A 是 2 行 3 列矩阵,B 是 3 行 4 列矩阵,相乘得到 2 行 4 列矩阵:

%%tab mxnet
B = np.ones(shape=(3, 4))
np.dot(A, B)
%%tab pytorch
B = torch.ones(3, 4)
torch.mm(A, B), A@B
%%tab tensorflow
B = tf.ones((3, 4), tf.float32)
tf.matmul(A, B)
%%tab jax
B = jnp.ones((3, 4))
jnp.matmul(A, B)

输出为 2×4 的矩阵。"矩阵–矩阵乘法"常简称为"矩阵乘法",切勿与 Hadamard 积混淆:后者是逐元素运算、输出形状与操作数一致;而点积、矩阵–向量积与矩阵–矩阵积均非逐元素运算,一般返回与操作数形状不同的对象,且矩阵乘法计算量显著更大(立方级而非平方级时间)。在 JAX 版本中 jnp.matmul 被 d2l 封装并广泛用于全连接层前向计算(d2l/jax.py、d2l/jax.py),PyTorch 中 A@B 与 torch.mm 等价。

范数(Norms):度量向量与矩阵的大小

线性代数中最有用的算子之一是范数(norm)。非正式地说,向量的范数告诉我们它"有多大"——例如 ℓ2\ell_2 范数度量向量(欧几里得)长度,这里讨论的"大小"关乎向量分量的幅度(而非维数)。

范数是把向量映射为标量的函数 ∥⋅∥\| \cdot \|,满足以下三条性质:

  1. 齐次性:给定任意向量 x\mathbf{x},若用标量 α∈R\alpha \in \mathbb{R} 缩放向量的所有元素,其范数相应缩放:

∥αx∥=∣α∣∥x∥.\|\alpha \mathbf{x}\| = |\alpha| \|\mathbf{x}\|.

  1. 三角不等式:对任意向量 x\mathbf{x} 与 y\mathbf{y}:

∥x+y∥≤∥x∥+∥y∥.\|\mathbf{x} + \mathbf{y}\| \leq \|\mathbf{x}\| + \|\mathbf{y}\|.

  1. 正定性:向量范数非负,且仅当向量为零时为零:

∥x∥>0 for all x≠0.\|\mathbf{x}\| > 0 \textrm{ for all } \mathbf{x} \neq 0.

ℓ2\ell_2 范数与 ℓ1\ell_1 范数

小学几何中计算直角三角形斜边时的欧几里得长度,即向量元素平方和的平方根,形式上是 ℓ2\ell_2 范数:

∥x∥2=∑i=1nxi2.\|\mathbf{x}\|_2 = \sqrt{\sum_{i=1}^n x_i^2}.

norm 方法计算 ℓ2\ell_2 范数:

%%tab mxnet
u = np.array([3, -4])
np.linalg.norm(u)
%%tab pytorch
u = torch.tensor([3.0, -4.0])
torch.norm(u)
%%tab tensorflow
u = tf.constant([3.0, -4.0])
tf.norm(u)
%%tab jax
u = jnp.array([3.0, -4.0])
jnp.linalg.norm(u)

对 [3,−4][3, -4] 输出均为 55(勾股数 3-4-5)。ℓ1\ell_1 范数同样常见,其度量称为曼哈顿距离,定义为向量元素绝对值之和:

∥x∥1=∑i=1n∣xi∣.\|\mathbf{x}\|_1 = \sum_{i=1}^n \left|x_i \right|.

与 ℓ2\ell_2 范数相比,ℓ1\ell_1 范数对离群值更不敏感。计算时把绝对值与求和组合即可:

%%tab mxnet
np.abs(u).sum()
%%tab pytorch
torch.abs(u).sum()
%%tab tensorflow
tf.reduce_sum(tf.abs(u))
%%tab jax
jnp.linalg.norm(u, ord=1) # 与 jnp.abs(u).sum() 相同

ℓ2\ell_2 与 ℓ1\ell_1 范数都是更一般的 ℓp\ell_p 范数的特例:

∥x∥p=(∑i=1n∣xi∣p)1/p.\|\mathbf{x}\|_p = \left(\sum_{i=1}^n \left|x_i \right|^p \right)^{1/p}.

Frobenius 范数与谱范数

矩阵的情况更复杂:矩阵既可看作单个元素的集合,又可看作作用于向量并将其变换为其他向量的对象。例如可以问矩阵–向量积 Xv\mathbf{X} \mathbf{v} 相对 v\mathbf{v} 最多变长多少——这一思路引出谱范数(spectral norm)。本节先引入计算容易得多的 Frobenius 范数,定义为矩阵元素平方和的平方根:

∥X∥F=∑i=1m∑j=1nxij2.\|\mathbf{X}\|_\textrm{F} = \sqrt{\sum_{i=1}^m \sum_{j=1}^n x_{ij}^2}.

Frobenius 范数的行为如同把矩阵展平成向量后的 ℓ2\ell_2 范数。下面计算 4×9 全 1 矩阵的 Frobenius 范数:

%%tab mxnet
np.linalg.norm(np.ones((4, 9)))
%%tab pytorch
torch.norm(torch.ones((4, 9)))
%%tab tensorflow
tf.norm(tf.ones((4, 9)))
%%tab jax
jnp.linalg.norm(jnp.ones((4, 9)))

输出均为 66(36\sqrt{36})。

范数在深度学习中的角色

虽然不想过度超前,但可以建立一些直觉:深度学习中我们常求解优化问题——最大化观测数据被分配的概率;最大化推荐模型的收益;最小化预测与真实观测之间的距离;最小化同一个人不同照片表征之间的距离、同时最大化不同人照片表征之间的距离。这些构成深度学习算法目标的距离,常常用范数表达。例如权重衰减正则化即通过向损失中添加权重矩阵的 ℓ2\ell_2 范数平方实现,见权重衰减章节;梯度裁剪则依赖梯度的范数(如 d2l/jax.py 中 norm = jnp.sqrt(sum(jnp.vdot(x, x) ...)) 的用法)。

延伸讨论与思考题

本节覆盖了理解现代深度学习相当一部分内容所需的全部线性代数。当然线性代数远不止于此,例如矩阵可分解为因子,分解能揭示真实数据集的低维结构——机器学习的整个子领域都在利用矩阵分解及其高阶张量推广来发现数据结构。但本书聚焦深度学习,后续章节会根据需要适时引入更多数学工具。想深入了解线性代数的读者可参考更进阶的速成资料(如 Strang 的线性代数、Kolter 的凸优化速成课以及 Petersen 等人的矩阵手册,参见仓库参考文献)。

原文档给出以下要点小结:

  • 标量、向量、矩阵和张量是线性代数的基本数学对象,分别具有零、一、二和任意数量的轴。
  • 张量可通过索引沿指定轴切片,或通过 sum、mean 等操作沿指定轴归约。
  • 逐元素乘积称为 Hadamard 积;相比之下,点积、矩阵–向量积与矩阵–矩阵积不是逐元素运算,一般返回与操作数形状不同的对象。
  • 相比 Hadamard 积,矩阵–矩阵乘法耗时显著更长(立方级而非平方级时间)。
  • 范数刻画向量(或矩阵)的多种"大小"概念,常作用于两个向量之差以度量它们之间的距离。
  • 常见向量范数包括 ℓ1\ell_1、ℓ2\ell_2 范数;常见矩阵范数包括谱范数与 Frobenius 范数。

配套思考题(原文档练习,可自行验证):

  1. 证明矩阵转置的转置是矩阵自身:(A⊤)⊤=A(\mathbf{A}^\top)^\top = \mathbf{A}。
  2. 对任意两个矩阵 A\mathbf{A}、B\mathbf{B},证明求和与转置可交换:A⊤+B⊤=(A+B)⊤\mathbf{A}^\top + \mathbf{B}^\top = (\mathbf{A} + \mathbf{B})^\top。
  3. 对任意方阵 A\mathbf{A},A+A⊤\mathbf{A} + \mathbf{A}^\top 是否总是对称的?能否仅用前两题的结论证明?
  4. 本节定义了 shape 为 (2, 3, 4) 的张量 X,len(X) 输出什么?先用推理回答,再用代码验证。
  5. 对任意形状的张量 X,len(X) 是否总对应 X 某个轴的长度?是哪个轴?
  6. 运行 A / A.sum(axis=1) 观察发生了什么,能否分析结果(提示:广播机制与 keepdims 的差异)。
  7. 在曼哈顿市中心两点之间出行,用街道坐标(大道与街)表示需要覆盖的距离是什么?能否对角线穿行(提示:ℓ1\ell_1 范数与欧几里得距离的区别)?
  8. 对 shape 为 (2, 3, 4) 的张量,沿轴 0、1、2 求和输出的 shape 分别是什么?
  9. 给 linalg.norm 传入三轴及以上张量并观察输出,它对任意形状张量计算的是什么(提示:Frobenius 范数)。
  10. 三个大矩阵 A∈R210×216\mathbf{A} \in \mathbb{R}^{2^{10} \times 2^{16}}、B∈R216×25\mathbf{B} \in \mathbb{R}^{2^{16} \times 2^{5}}、C∈R25×214\mathbf{C} \in \mathbb{R}^{2^{5} \times 2^{14}}(高斯随机初始化),计算 ABC\mathbf{A} \mathbf{B} \mathbf{C} 时,按 (AB)C(\mathbf{A} \mathbf{B}) \mathbf{C} 与 A(BC)\mathbf{A} (\mathbf{B} \mathbf{C}) 两种结合顺序在内存占用与速度上是否有差异?为什么(提示:中间结果的形状与乘法复杂度 O(nmk)O(nmk))?
  11. 若 A∈R210×216\mathbf{A} \in \mathbb{R}^{2^{10} \times 2^{16}}、B∈R216×25\mathbf{B} \in \mathbb{R}^{2^{16} \times 2^{5}}、C∈R25×216\mathbf{C} \in \mathbb{R}^{2^{5} \times 2^{16}},计算 AB\mathbf{A} \mathbf{B} 与 AC⊤\mathbf{A} \mathbf{C}^\top 是否有速度差异?为什么?若令 C=B⊤\mathbf{C} = \mathbf{B}^\top(不克隆内存)会有什么变化(提示:矩阵布局与内存连续性)?
  12. 三个矩阵 A,B,C∈R100×200\mathbf{A}, \mathbf{B}, \mathbf{C} \in \mathbb{R}^{100 \times 200},通过堆叠 [A,B,C][\mathbf{A}, \mathbf{B}, \mathbf{C}] 构造三轴张量,其维数是多少?切出第三轴第二个坐标恢复 B\mathbf{B},并验证结果正确。

上述练习可在本地以对应框架交互式运行验证;建议结合自动求导与数据操作两节继续构建完整的前置知识体系,为后续线性回归、多层感知机等章节的向量化实现打下基础。

登录后查看全文
d2l-en