《动手学深度学习》(d2l-zh)线性代数基础:标量、向量、矩阵、张量与范数的数学符号和代码对照指南

原创2026-10-01 14:10:001,760 阅读
文章标签:教程文档人工智能深度学习机器学习

《动手学深度学习》(d2l-zh)线性代数基础:标量、向量、矩阵、张量与范数的数学符号和代码对照指南

线性代数是为深度学习模型提供数学"骨架"的基础语言。本文以《动手学深度学习》中文版(d2l-zh)预备知识章节中线性代数为核心,系统讲解标量、向量、矩阵、张量四种基本数学对象,以及规约、点积、矩阵乘法、范数等核心运算,并给出它们在 MXNet、PyTorch、TensorFlow 与 PaddlePaddle 四套框架下的对应实现。读完本文,你将能够用张量 API 完成深度学习中最常用的线性代数操作(求和、求均值、内积、矩阵乘法、范数计算),并理解它们在后文线性回归、多层感知机等模型中的实际调用方式。

为什么深度学习必须先学线性代数

在预备知识章节中,ndarray 一节解决了"如何存储和操作数据"的问题,而本文要解决的则是"如何用数学语言描述数据和模型"的问题。机器学习中的数据通常表现为表格:行对应样本、列对应属性,而线性代数正是处理这类表格化数据最自然的一套数学工具。深度学习模型(例如后文的线性回归从零实现)本质上就是反复地对张量做线性运算,因此本节介绍的每一种对象和运算,都会在后续章节的模型代码中反复出现。

本文约定:数学符号与代码实现一一对照。你在公式中见到的每个对象,都可以在代码中找到对应的张量写法。

标量:只有一个数值的数学对象

如果你曾经在餐厅结账,就已经掌握了最基础的线性代数——数字间的相加与相乘。严格来说,仅包含一个数值的量被称为标量(scalar)。例如,北京的温度为 52∘F52^{\circ}F(华氏度),要转换为摄氏度可以计算 c=59(f−32)c=\frac{5}{9}(f-32),并将 ff 赋为 5252。在这个式子中,55、99、3232 都是标量值,而 cc 和 ff 是变量(variable),代表未知的标量值。

在数学表示法中,标量变量用普通小写字母表示(如 xx、yy、zz),全体(连续)实值标量构成的空间记作 R\mathbb{R}。表达式 x∈Rx \in \mathbb{R} 是"xx 是一个实值标量"的正式写法,其中符号 ∈\in 读作"属于",表示集合成员关系;例如 x,y∈{0,1}x, y \in \{0, 1\} 表示 xx 和 yy 只能取 00 或 11。

标量在代码中由一个只含单个元素的张量表示。下面的代码实例化两个标量,并执行加法、乘法、除法和指数运算:

# MXNet
from mxnet import np, npx
npx.set_np()

x = np.array(3.0)
y = np.array(2.0)

x + y, x * y, x / y, x ** y
# PyTorch
import torch

x = torch.tensor([3.0])
y = torch.tensor([2.0])

x + y, x * y, x / y, x**y
# TensorFlow
import tensorflow as tf

x = tf.constant([3.0])
y = tf.constant([2.0])

x + y, x * y, x / y, x**y
# PaddlePaddle
import warnings
warnings.filterwarnings(action='ignore')
import paddle

x = paddle.to_tensor([3.0])
y = paddle.to_tensor([2.0])

x + y, x * y, x / y, x**y

需要注意,各框架标量张量的构造方式略有差异:MXNet 与 TensorFlow 使用 np.array(3.0) / tf.constant(3.0),PyTorch 的 torch.tensor([3.0]) 和 PaddlePaddle 的 paddle.to_tensor([3.0]) 通常保留了一个长度为 1 的轴。这不影响运算结果,但会影响 shape 的输出形态。

向量:标量值组成的列表

向量(vector)可以被视为标量值组成的列表,这些标量值被称为向量的元素(element)、条目(entry)或分量(component)。当向量表示数据集中的样本时,分量具有现实意义:例如预测贷款违约风险时,可以将每个申请人与一个向量关联,其分量为收入、工作年限、过往违约次数等因素;研究心脏病风险时,可以用向量表示每位患者的最近生命体征、胆固醇水平、每天运动时长等。数学上,向量通常记为粗体小写字母(如 x\mathbf{x}、y\mathbf{y}、z\mathbf{z})。

在代码中,向量用一维张量表示,其长度受机器内存限制。用 arange(4) 创建包含 4 个连续整数的向量:

# MXNet
x = np.arange(4)
x
# PyTorch
x = torch.arange(4)
x
# TensorFlow
x = tf.range(4)
x
# PaddlePaddle
x = paddle.arange(4)
x

用下标访问元素

可以用下标引用向量的任一元素:xix_i 表示向量 x\mathbf{x} 的第 ii 个元素。注意元素 xix_i 本身是标量,因此引用它时不加粗。大量文献(以及本书)默认向量的方向为列向量,数学上写作:

x=[x1x2⋮xn],\mathbf{x} =\begin{bmatrix}x_{1} \\x_{2} \\ \vdots \\x_{n}\end{bmatrix},

其中 x1,…,xnx_1, \ldots, x_n 是向量的元素。在代码中,通过张量索引访问任一元素:

# 以 PyTorch 为例,其余框架写法相同
x[3]

长度、维度与形状

向量是一个数字数组,就像每个数组都有长度一样,每个向量也有长度。若向量 x\mathbf{x} 由 nn 个实值标量组成,记作 x∈Rn\mathbf{x} \in \mathbb{R}^n;向量的长度通常称为向量的维度(dimension)。

与普通 Python 数组一样,可以调用内置 len() 函数访问张量长度;当张量表示只有一个轴的向量时,还可以通过 .shape 属性访问长度。shape 是一个元素组(tuple),列出张量沿每个轴的长度:

# 以 PyTorch 为例
len(x)
x.shape

这里有一个常被混淆的概念需要澄清:向量或轴的维度指其长度(元素数量),而张量的维度指张量拥有的轴数。因此"某个轴的维数"其实就是这个轴的长度——例如形状为 (2, 3, 4) 的张量有 3 个轴,其中轴 0 的维度是 2。

矩阵:把向量推广到二阶

正如向量把标量从零阶推广到一阶,矩阵(matrix)把向量从一阶推广到二阶。矩阵通常用粗体大写字母表示(如 X\mathbf{X}、Y\mathbf{Y}、Z\mathbf{Z}),代码中对应具有两个轴的张量。

数学上,A∈Rm×n\mathbf{A} \in \mathbb{R}^{m \times n} 表示由 mm 行 nn 列实值标量组成的矩阵,元素 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\mathbf{A} 的 shape 为 (m,n)(m, n) 或 m×nm \times n;当行数与列数相等时即为方阵(square matrix)。创建 m×nm \times n 矩阵时,只需在实例化张量的函数中指定包含两个分量的形状:

# MXNet
A = np.arange(20).reshape(5, 4)
A
# PyTorch
A = torch.arange(20).reshape(5, 4)
A
# TensorFlow
A = tf.reshape(tf.range(20), (5, 4))
A
# PaddlePaddle
A = paddle.arange(20).reshape([5, 4])
A

矩阵元素 aija_{ij} 通过行下标 ii 和列下标 jj 访问,记作 [A]ij[\mathbf{A}]_{ij};当需要区分下标时用逗号隔开,例如 a2,3ja_{2, 3j}、[A]2i−1,3[\mathbf{A}]_{2i-1, 3}。

转置与对称矩阵

交换矩阵的行和列得到转置(transpose)。若 B=A⊤\mathbf{B} = \mathbf{A}^\top,则 bij=ajib_{ij} = a_{ji};因此 A∈Rm×n\mathbf{A} \in \mathbb{R}^{m \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}.

代码中访问矩阵的转置:

# MXNet / PyTorch
A.T
# TensorFlow
tf.transpose(A)
# PaddlePaddle
paddle.transpose(A, [1, 0])

方阵有一种特殊类型:对称矩阵(symmetric matrix)满足 A=A⊤\mathbf{A} = \mathbf{A}^\top。下面的矩阵 B 沿主对角线对称:

# PyTorch
B = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
B

用 B == B.T 逐元素比较,可以得到一个全为 True 的布尔张量,从而验证对称性。

矩阵作为表格数据的组织方式

矩阵是极其有用的数据结构,因为它能组织具有不同变化模态的数据:行对应不同的数据样本(如不同的房子),列对应不同的属性(如面积、价格),这与电子表格或 pandas 章节中数据表的组织方式一致。因此,尽管单个向量的默认方向是列向量,但在表示表格数据集的矩阵中,惯例是把每个样本视为矩阵中的行向量。这一约定意义重大——沿张量最外层轴可以访问或枚举小批量数据样本,这正是后文深度学习代码中"按批次遍历数据"的基础。

张量:任意轴数的通用数据结构

正如向量推广标量、矩阵推广向量,张量(tensor,此处指代数对象)给出了描述任意轴数 nn 维数组的通用方式:向量是一阶张量,矩阵是二阶张量。张量用特殊字体的大写字母表示(如 X\mathsf{X}、Y\mathsf{Y}、Z\mathsf{Z}),索引机制与矩阵类似(如 xijkx_{ijk}、[X]1,2i−1,3[\mathsf{X}]_{1, 2i-1, 3})。

张量在处理图像时尤为重要——图像是带有 3 个轴的 nn 维数组,分别对应高度、宽度和通道轴(堆叠红、绿、蓝三个颜色通道)。例如创建一个形状为 (2, 3, 4) 的三阶张量:

# PyTorch
X = torch.arange(24).reshape(2, 3, 4)
X
# TensorFlow
X = tf.reshape(tf.range(24), (2, 3, 4))
X
# PaddlePaddle
X = paddle.arange(24).reshape([2, 3, 4])
X

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

标量、向量、矩阵和任意轴数的张量有一些常用性质。从元素级运算(elementwise operation)的定义可知:

  • 任何元素级一元运算都不会改变操作数的形状;
  • 给定两个形状相同的张量,任何二元元素级运算的结果仍是同形状的张量。

例如,两个同形状矩阵相加就是逐元素相加:

# PyTorch
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
B = A.clone()  # 通过分配新内存将 A 的副本赋给 B
A, A + B
# TensorFlow
A = tf.reshape(tf.range(20, dtype=tf.float32), (5, 4))
B = A  # 没有克隆,B 与 A 共享内存
A, A + B

这里可以注意各框架在"复制张量"上的差异:PyTorch 用 A.clone() 显式分配新内存;TensorFlow 中 B = A 只是让两个名字指向同一张量。MXNet 则使用 A.copy()。在需要避免共享内存的场合(如梯度累积),这个区别很关键。

两个矩阵的元素级乘法被称为 Hadamard 积(Hadamard product),记作 ⊙\odot。对于矩阵 A\mathbf{A} 和 B\mathbf{B}:

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 即可实现。张量与标量相加或相乘也不会改变张量的形状,标量会逐元素作用于操作数:

# PyTorch
a = 2
X = torch.arange(24).reshape(2, 3, 4)
a + X, (a * X).shape

上述性质在深度学习代码中无处不在:例如神经网络某一层对输入做线性变换后再加偏置,本质上就是矩阵乘法加标量广播。

规约:求和与均值

对任意张量,一个常用的操作是计算所有元素之和。数学上用 ∑\sum 符号表达:向量 x\mathbf{x}(长度 dd)的元素和写作 ∑i=1dxi\sum_{i=1}^d x_i;m×nm \times n 矩阵 A\mathbf{A} 的元素和写作 ∑i=1m∑j=1naij\sum_{i=1}^{m} \sum_{j=1}^{n} a_{ij}。

# PyTorch
x = torch.arange(4, dtype=torch.float32)
x, x.sum()
# TensorFlow
x = tf.range(4, dtype=tf.float32)
x, tf.reduce_sum(x)
# PaddlePaddle
x = paddle.arange(4, dtype=paddle.float32)
x, x.sum()

默认情况下,求和函数会沿所有轴把张量规约(reduce)成标量;也可以通过 axis 参数指定沿哪些轴规约。以矩阵为例:

  • A.sum(axis=0) 沿行维(轴 0)求和,即把每一列的所有行元素相加,输出形状中轴 0 的维度消失;
  • A.sum(axis=1) 沿列维(轴 1)求和,输出形状中轴 1 的维度消失;
  • A.sum(axis=[0, 1]) 同时沿两个轴求和,等价于 A.sum()。
# PyTorch
A_sum_axis0 = A.sum(axis=0)
A_sum_axis0, A_sum_axis0.shape

A_sum_axis1 = A.sum(axis=1)
A_sum_axis1, A_sum_axis1.shape

A.sum(axis=[0, 1])  # 与 A.sum() 相同

TensorFlow 中对应 tf.reduce_sum(A, axis=0)、tf.reduce_sum(A, axis=[0, 1]);PaddlePaddle 与 PyTorch 写法一致。

**均值(mean / average)**是另一相关量,由总和除以元素总数得到,等价于 A.sum() / A.numel()(PyTorch 用 numel() 统计元素总数,MXNet 用 A.size,TensorFlow 用 tf.size(A).numpy()):

# PyTorch
A.mean(), A.sum() / A.numel()

# 指定轴
A.mean(axis=0), A.sum(axis=0) / A.shape[0]
# TensorFlow
tf.reduce_mean(A), tf.reduce_sum(A) / tf.size(A).numpy()
tf.reduce_mean(A, axis=0), tf.reduce_sum(A, axis=0) / A.shape[0]

注意:A.shape[0] 是轴 0 的长度(行数),所以 A.sum(axis=0) / A.shape[0] 得到的是每列均值,恰好与 A.mean(axis=0) 一致。

非规约求和:keepdims 与 cumsum

有时我们希望求和/求均值时保持轴数不变,此时使用 keepdims=True:

# PyTorch
sum_A = A.sum(axis=1, keepdims=True)
sum_A

由于 sum_A 在按行求和后仍保留两个轴,可以借助广播(broadcasting)直接做 A / sum_A,得到每一行元素占该行总和的归一化比例——这是数据预处理中常见的"行归一化"操作。

另外,cumsum(累积求和)函数沿指定轴逐步累加,且不会规约掉任何轴。例如 A.cumsum(axis=0) 逐行累积,输出的第 ii 行是原矩阵前 ii 行的元素和:

# PyTorch
A.cumsum(axis=0)
# TensorFlow
tf.cumsum(A, axis=0)

点积:最基础的向量运算

如果线性代数只能做元素级运算、求和与求平均,它就不配拥有独立章节。点积(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.

# MXNet
y = np.ones(4)
x, y, np.dot(x, y)
# PyTorch
y = torch.ones(4, dtype = torch.float32)
x, y, torch.dot(x, y)
# TensorFlow
y = tf.ones(4, dtype=tf.float32)
x, y, tf.tensordot(x, y, axes=1)

点积也可以等价地由"元素级乘法 + 求和"两步实现:x * y 后再 .sum()(TensorFlow 为 tf.reduce_sum(x * y))。这条等价关系在推导中经常被利用。

点积的应用场景非常广泛:

  • 加权和:给定值向量 x∈Rd\mathbf{x} \in \mathbb{R}^d 和权重向量 w∈Rd\mathbf{w} \in \mathbb{R}^d,按权重对 x\mathbf{x} 加权求和可表示为点积 x⊤w\mathbf{x}^\top \mathbf{w};
  • 加权平均:当权重非负且总和为 1(∑i=1dwi=1\sum_{i=1}^{d} w_i = 1)时,点积表达的就是加权平均;
  • 余弦相似度:把两个向量归一化为单位长度后,点积等于它们夹角的余弦值(长度概念将在范数小节正式引入)。

矩阵-向量积:从 Rn\mathbb{R}^n 到 Rm\mathbb{R}^m 的线性变换

有了点积就能理解矩阵-向量积(matrix-vector product)。把矩阵 A∈Rm×n\mathbf{A} \in \mathbb{R}^{m \times n} 按行向量表示:

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 是第 ii 行的行向量。矩阵-向量积 Ax\mathbf{A}\mathbf{x} 就是长度为 mm 的列向量,其第 ii 个元素是点积 ai⊤x\mathbf{a}^\top_i \mathbf{x}:

Ax=[a1⊤xa2⊤x⋮am⊤x].\mathbf{A}\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} 的变换。这类变换极其有用:旋转可以用方阵乘法表示;更重要的是,后文神经网络中每一层最密集的计算(给定上一层输出,计算本层输出)都可以用矩阵-向量积描述。

代码中,矩阵-向量积复用点积所用的 dot 函数;前提是矩阵 A 的列维(沿轴 1 的长度)必须与向量 x 的维度(长度)相同:

# MXNet
A.shape, x.shape, np.dot(A, x)
# PyTorch
A.shape, x.shape, torch.mv(A, x)
# TensorFlow
A.shape, x.shape, tf.linalg.matvec(A, x)
# PaddlePaddle
A.shape, x.shape, paddle.mv(A, x)

矩阵-矩阵乘法:m 次矩阵-向量积的组合

设 A∈Rn×k\mathbf{A} \in \mathbb{R}^{n \times k} 与 B∈Rk×m\mathbf{B} \in \mathbb{R}^{k \times m},记 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=AB∈Rn×m\mathbf{C} = \mathbf{A}\mathbf{B} \in \mathbb{R}^{n \times m} 的每个元素 cijc_{ij} 都是点积 ai⊤bj\mathbf{a}^\top_i \mathbf{b}_j:

C=AB=[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{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 次矩阵-向量积、再把结果拼接成 n×mn \times m 矩阵。下面用 5×4 的 A 与 4×3 的 B 相乘,得到 5×3 的矩阵:

# MXNet
B = np.ones(shape=(4, 3))
np.dot(A, B)
# PyTorch
B = torch.ones(4, 3)
torch.mm(A, B)
# TensorFlow
B = tf.ones((4, 3), tf.float32)
tf.matmul(A, B)
# PaddlePaddle
B = paddle.ones([4, 3])
paddle.matmul(A, B)

矩阵-矩阵乘法通常直接简称矩阵乘法(matrix multiplication),务必与上文 Hadamard 积(元素级乘法)区分开——前者是点积的组合,后者是逐元素的乘积。

范数:度量向量"大小"的算子

范数(norm)是线性代数中最有用的算子之一。它度量的是一个向量有多"大",这里的"大小"指分量幅值而非维度。向量范数是一个把向量映射为标量的函数 ff,满足四条性质:

  1. 绝对齐次性:缩放所有元素 α\alpha 倍,范数按 ∣α∣|\alpha| 缩放,f(αx)=∣α∣f(x)f(\alpha \mathbf{x}) = |\alpha| f(\mathbf{x});
  2. 三角不等式:f(x+y)≤f(x)+f(y)f(\mathbf{x} + \mathbf{y}) \leq f(\mathbf{x}) + f(\mathbf{y});
  3. 非负性:f(x)≥0f(\mathbf{x}) \geq 0;
  4. 正定性:零向量是唯一范数为 0 的向量,∀i,[x]i=0⇔f(x)=0\forall i, [\mathbf{x}]_i = 0 \Leftrightarrow f(\mathbf{x})=0。

范数与距离度量关系密切。欧几里得距离(勾股定理)就是一种范数——L2L_2 范数。

L2L_2 范数是向量元素平方和的平方根(下标 2 常省略,∥x∥\|\mathbf{x}\| 等价于 ∥x∥2\|\mathbf{x}\|_2):

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

代码中直接调用范数函数:

# MXNet
u = np.array([3, -4])
np.linalg.norm(u)
# PyTorch
u = torch.tensor([3.0, -4.0])
torch.norm(u)
# TensorFlow
u = tf.constant([3.0, -4.0])
tf.norm(u)

例如向量 [3,−4][3, -4] 的 L2L_2 范数为 32+(−4)2=5\sqrt{3^2 + (-4)^2} = 5。深度学习中更常用的是 L2L_2 范数的平方(例如作为正则项加入损失)。

L1L_1 范数是向量元素绝对值之和,相比 L2L_2 范数受离群值影响更小:

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

# MXNet / PyTorch
np.abs(u).sum()
torch.abs(u).sum()
# TensorFlow
tf.reduce_sum(tf.abs(u))

L1L_1 与 L2L_2 范数都是更一般 LpL_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}.

与向量的 L2L_2 范数类似,矩阵的 Frobenius 范数是矩阵元素平方和的平方根:

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

它满足向量范数的全部性质,行为如同把矩阵拉直成向量后的 L2L_2 范数:

# PyTorch
torch.norm(torch.ones((4, 9)))
# TensorFlow
tf.norm(tf.ones((4, 9)))

范数与优化目标

范数概念为何如此重要?因为深度学习的核心是求解优化问题:最大化观测数据被分配的概率,最小化预测与真实观测之间的距离;或者为词、商品、新闻等实体学习向量表示,使相似实体距离最小、不相似实体距离最大。这些目标——深度学习算法除数据外最重要的组成部分——经常以范数的形式表达。理解范数,是理解损失函数与正则化(后文权重衰减等章节)的起点。

源码印证:线性代数运算在 d2l 工具库中的落地

本文介绍的运算并非孤立概念,而是直接构成了 d2l 工具库的核心 API。以 PyTorch 后端 d2l/torch.py 为例,工具库通过别名把框架原生算子统一为 d2l. 前缀接口:

  • d2l.normal = torch.normal(生成服从正态分布的随机张量);
  • d2l.matmul = torch.matmul(矩阵乘法);
  • d2l.reduce_sum = lambda x, *args, **kwargs: x.sum(*args, **kwargs)(带 axis、keepdims 等参数规约求和)。

在线性回归从零实现中,模型前向与损失函数正是基于本文的运算构建的(见 d2l/torch.py):

def linreg(X, w, b):
    """线性回归模型"""
    return d2l.matmul(X, w) + b

def squared_loss(y_hat, y):
    """均方损失"""
    return (y_hat - d2l.reshape(y, y_hat.shape)) ** 2 / 2

其中 d2l.matmul(X, w) 正是矩阵-矩阵乘法:X 是形状为(样本数,特征数)的输入矩阵,w 是形状为(特征数,1)的权重列向量;(y_hat - y) ** 2 / 2 则是对张量做元素级平方运算,再配合规约求均值得到标量损失。可以看到,本节的全部数学对象与运算在真实模型代码中被原样复用——这也是本书"先讲数学、后写模型"编排逻辑的体现。

小结

  • 标量、向量、矩阵和张量是线性代数的基本数学对象,分别有零个、一个、两个和任意数量的轴;
  • 向量推广了标量,矩阵推广了向量;
  • 元素级运算(包括 Hadamard 积 ⊙\odot)与"张量加/乘标量"都不会改变张量的形状;
  • 张量可以通过 sum 和 mean 沿指定轴规约,也可用 keepdims=True 保持轴数,或用 cumsum 做非规约累积求和;
  • 点积是元素乘积之和,矩阵乘法可以看作多次矩阵-向量积的组合,两者都不是元素级运算;
  • 深度学习常用 L1L_1 范数、L2L_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. 本节定义了形状为 (2, 3, 4) 的张量 X,len(X) 的输出是什么?
  5. 对任意形状的张量 X,len(X) 是否总对应 X 某个轴的长度?是哪个轴?
  6. 运行 A / A.sum(axis=1) 观察结果,分析原因(提示:结合广播机制思考形状不匹配时会发生什么)。
  7. 在曼哈顿的两个地点间穿行,需要覆盖的"距离"如何用坐标(即大道与街道数)表达?能否斜向穿行?
  8. 对形状为 (2, 3, 4) 的张量,分别沿轴 0、1、2 求和的输出形状是什么?
  9. 把一个 3 轴及以上的张量喂给 linalg.norm 函数并观察输出,分析它对任意形状张量计算的是什么。

若想进一步探索,可在仓库的预备知识章节中继续阅读ndarray 章节(张量创建与索引基础)、pandas 章节(表格数据预处理),并参考 d2l/torch.py 中 matmul、reduce_sum 等统一接口的别名定义。

登录后查看全文
d2l-zh