做算法工程和科学计算的人,大概率都在代码里见过这几个词:1-范数、2-范数、∞-范数、F-范数,以及它们背后那对双竖线。它们正是向量范数与矩阵范数的具体实例,也是很多论文公式里最早出现的符号之一。可奇怪的是,大部分教材只给定义、给不等式、给证明,就是不告诉你这东西到底拿来干什么。
这几年我实际写过推荐系统、做过稀疏优化、解过病态线性方程组、也被矩阵低秩逼近坑过几回,回头看才意识到:范数不是一个抽象的数学摆设,它是一把尺子,而且是每种场景都有对应刻度的尺子。选错了范数,优化结果不对、误差估计偏差大、条件数评估失真,最后表现出来就是模型不稳定或者代码跑出来结果没法解释。
这篇文章我把1-范数、2-范数、∞-范数、F-范数全部拆开讲,从定义、几何直觉、计算成本,到它们在工程里的实际用途和NumPy里的正确写法。目标是让你读完能明白每个范数在什么场景下选、为什么选、代码里怎么写不容易出错。
1. 范数是什么,以及为什么绕不开它
在没有范数这个概念之前,我们怎么描述"一个向量多大"?最自然的答案是"长度"。二维平面里一个点离原点多远,用勾股定理算;三维空间里也是一样,平方和开根号。可问题在于:长度只是众多度量方式中的一种,而且它并不总是最适合工程场景的那一种。
1.1 用"长度"建立第一直觉
范数本质上就是对"向量大小"的一种度量,它满足三条基本规则:非负性(大小不为负,且只有零向量的范数是零)、齐次性(向量放大两倍,范数也放大两倍)、三角不等式(两个向量相加之后的大小,不超过两个向量大小之和)。
这三条规则听起来像废话,其实就是"长度"该有的基本素养。有了这组规则,我们才能放心地拿范数去比较误差、讨论收敛、估计偏差。而1-范数、2-范数、∞-范数,全都是满足这三条规则的合法"长度",只是它们对"多大"的理解角度不同。
我经常用一个类比帮朋友理解:2-范数是鸟直线飞行的距离;1-范数是只能在方格街道上走的出租车距离;∞-范数则是看你在任何一个坐标方向上偏离了多远,取那个最大的偏差作为衡量标准。同一个向量,换一种度量方式,得到的"尺寸"就完全不同。
1.2 工程里到底哪里在用范数
很多刚接触的朋友会问:既然都是度量大小,我随便用一个不就行了?不是的。范数在工程里的角色远比"度量长度"广,它直接参与了三件重要的事。
第一,误差分析。解线性方程组时,如果右侧数据有一点点扰动,解会变化多少?这个"变化多少"必须靠范数定义。矩阵条件数就是两个范数的比值,它告诉你一个方程组是健康的还是病态的。没有范数,你连"病态"这个概念都无法量化。
第二,优化问题的目标函数。经典最小二乘法的目标函数是残差向量的2-范数平方。岭回归加的是参数向量的2-范数,LASSO加的是1-范数。机器学习里密密麻麻的正则化项,本质上就是在不同的范数之间选尺子。
第三,算法收敛性判断。迭代算法是否收敛、误差是否在减小,需要范数来定义"迭代点与真解之间的距离"。你选哪一种范数去衡量,有时候甚至会改变人们对算法好坏的评价标准。
所以范数不是数学家的玩具,它是工程判断、误差控制、模型设计的基础工具。下面我开始逐个拆解,先从向量范数说起。
2. 向量范数:三种最常用范数的定义与直觉
向量范数是在单个向量上定义的。给定一个 $n$ 维向量 $x = (x_1, x_2, \dots, x_n)^T$,三种最常见的度量方式如下。
2.1 三种范数的定义与几何直觉
1-范数(也叫曼哈顿范数)是所有分量绝对值之和:
$$ |x|1 = \sum{i=1}^{n} |x_i| $$
它衡量的是"沿着坐标轴方向走的总路程"。在二维平面里,$(3,4)$ 这个点的1-范数是 $3+4=7$,而不是直线距离5。这就像在棋盘式街道上,你只能横着走和竖着走,绕的路永远是横竖拼出来的。
2-范数(也叫欧几里得范数)是所有分量平方和的平方根:
$$ |x|2 = \sqrt{\sum{i=1}^{n} x_i^2} $$
这就是我们最熟悉的"直线距离"。向量 $(3,4)$ 的2-范数是5。它有两个非常重要的性质:旋转不变性和对梯度的友好性。你在优化里频繁看到它,跟这两条性质直接相关。
∞-范数(也叫切比雪夫范数)是所有分量绝对值中的最大值:
$$ |x|\infty = \max{i} |x_i| $$
它只看"最出格"的那个分量。一群学生在考试里各自得分,如果按∞-范数评价这个团队的成绩,那就只看考得最差的那个人的分数。谁最差,谁就代表整体水平。
2.2 性质对比:谁更"厚道"、谁更"极端"
三种范数不是随意的变体,它们各自捕捉了不同侧面的特征。下面这张表是我经常用的对比,能快速建立整体印象。
| 范数 | 表达式 | 几何意义 | 计算成本 | 对稀疏性偏好 | 对异常值敏感度 |
|---|---|---|---|---|---|
| 1-范数 | $\sum|x_i|$ | 曼哈顿距离 | 低 | 强 | 中等 |
| 2-范数 | $\sqrt{\sum x_i^2}$ | 直线距离 | 中(需开方) | 弱 | 高 |
| ∞-范数 | $\max|x_i|$ | 最大偏差 | 最低 | 极强 | 极高 |
为什么1-范数对稀疏性有偏好?你去看它在二维平面上的单位球:所有满足 $|x|_1 \le 1$ 的点构成一个菱形。在做LASSO这类带约束优化时,目标函数的等高线去碰这个菱形,最优解更容易落在菱形的尖角上,也就是坐标轴上的点。坐标轴上的点意味着某些分量为0,这就是稀疏解。
2-范数的单位球是个圆,等高线跟圆的接触点往往是圆滑的,解里各个分量都不为0,只会整体缩小。这就是岭回归系数被压缩但不会清零的原因。
∞-范数的单位球是个方形,它格外容易让优化解跑到方形角上,同样是稀疏解的有力制造者。在一些压缩感知的算法里,它甚至能替代1-范数做稀疏度量。
2.3 手算一遍:一个简单的例子
光看公式不过瘾,我们拿一个具体的向量算一遍。设 $x = (1, -2, 3)^T$。注意这里的负号,范数永远取绝对值,方向不参与度量。
1-范数:
$$ |x|_1 = |1| + |-2| + |3| = 1 + 2 + 3 = 6 $$
2-范数:
$$ |x|_2 = \sqrt{1^2 + (-2)^2 + 3^2} = \sqrt{14} \approx 3.74 $$
∞-范数:
$$ |x|_\infty = \max(1, 2, 3) = 3 $$
同一个向量,三种范数给出了6、3.74、3三个不同的"大小"。这个差异在误差分析中非常重要——同样一个变化量,你用1-范数看可能觉得"挺大",用∞-范数看可能觉得"还好"。所以讨论误差时必须先说明用的是哪种范数,否则结论完全没有可比性。
3. 矩阵范数:诱导范数、谱范数与F-范数
向量范数解决的是"一个向量有多大"的问题。但工程里大量计算面对的不只是向量,还有矩阵。矩阵怎么度量大小?把矩阵当成一个拉长的向量,算所有元素的平方和再开方——这是F-范数的思路,直观,但它并不是唯一的选择。
3.1 矩阵不能直接套向量范数
矩阵的范数不能随便定义,因为矩阵还会做乘法。在实际应用中,我们经常需要知道:两次变换连续作用,误差会如何累积?这要求矩阵范数满足一个额外的性质——次乘性:
$$ |AB| \le |A| \cdot |B| $$
这个性质很像"误差放大倍数的乘积不大于各自放大倍数的乘积",它在迭代算法、扰动分析、级数收敛性证明里反复出现。如果只把矩阵拉成向量算范数,这一步就不一定能成立,所以矩阵范数的定义有自己的一套体系。
3.2 诱导范数:一个矩阵能"拉伸"多少
诱导范数是从向量范数"诱导"出来的矩阵范数。它的思想是:把一个矩阵作用到所有可能的非零向量上,看看它最多能把向量的长度放大多少倍。
$$ |A|p = \max{x \neq 0} \frac{|Ax|_p}{|x|_p} $$
这个定义看起来抽象,其实特别直觉:矩阵代表一个线性变换,它把空间中的向量拉伸、旋转、压缩。诱导范数问的是"你最猛的拉伸方向是哪个,放大倍数是多少"。如果把输入约束为单位球上的点,那诱导范数就是"单位球经矩阵变换后最远的点到原点的距离",也就是变形后的椭球最长半径。
不同的 $p$ 值有非常漂亮的计算公式:
- 当 $p=1$ 时,诱导范数等于矩阵所有列绝对值之和的最大值,也就是最大列和;
- 当 $p=\infty$ 时,诱导范数等于矩阵所有行绝对值之和的最大值,也就是最大行和;
- 当 $p=2$ 时,诱导范数等于矩阵的最大奇异值,也就是谱范数。
拿矩阵 $A = \begin{pmatrix} 1 & 2 \ 3 & 4 \end{pmatrix}$ 来说,第一列绝对值和为 $1+3=4$,第二列绝对值和为 $2+4=6$,所以 $|A|1 = 6$;第一行绝对值和为 $1+2=3$,第二行绝对值和为 $3+4=7$,所以 $|A|\infty = 7$。而 $|A|_2$ 需要用奇异值分解,算出来的最大奇异值约等于5.46。这三个数值差别很大,也反映了不同诱导范数着眼的重点不一样。
3.3 F-范数:最简单直观的矩阵范数
F-范数(弗罗贝尼乌斯范数)大概是矩阵范数里最好算的一个。它是矩阵所有元素平方和的平方根:
$$ |A|F = \sqrt{\sum{i=1}^{m} \sum_{j=1}^{n} a_{ij}^2} $$
本质上是把矩阵拉直成一个 $mn$ 维的长向量,然后计算这个长向量的2-范数。它的优势是廉价、直观、可导性好。
在最小二乘问题里,目标函数通常写成 $\min_X |AX - B|_F^2$。为什么不用谱范数?因为谱范数需要奇异值分解,求导过程牵扯到特征值,非常麻烦;而F-范数的平方对所有元素分别求导,梯度就是 $2(AX-B)A^T$ 或者类似形式,计算起来干净利落。矩阵分解类问题,比如推荐系统里的SVD、低秩矩阵补全,绝大多数用F-范数做误差度量,就是因为这个便利性。
需要特别提醒一句:F-范数满足次乘性 $|AB|_F \le |A|_F |B|_F$,但它并不是由某个向量范数诱导出来的。换句话说,它不是任何诱导范数。这两类概念经常被混为一谈,实际差别很大:诱导范数有明确的几何含义,F-范数则更接近"矩阵元素规模总量"的一种度量。
3.4 谱范数与F-范数的关系
既然谱范数 $|A|_2$ 是最大奇异值,F-范数是所有奇异值的平方和再开方,它们之间就有如下关系:
$$ |A|_2 \le |A|_F \le \sqrt{r},|A|_2 $$
其中 $r$ 是矩阵的秩。左边这个不等式是因为最大奇异值只是所有奇异值的一部分;右边则是因为F-范数把所有奇异值的能量都叠加起来了,叠加的上限由秩决定。
这个不等式用在哪?最典型的是矩阵截断误差分析。你用奇异值分解取前 $k$ 个奇异值重构矩阵 $A_k$ 时,截断误差 $|A - A_k|_F$ 等于被丢弃的奇异值的平方和开方,而 $|A - A_k|_2$ 等于被丢弃的最大奇异值。两者之间就能通过秩的关系互相估算。我实际做数据压缩时,经常用这个不等式判断"用F-范数评估的误差和用谱范数评估的误差会不会差太多"。
4. 范数等价性与实战选型的关键考量
理论上每种范数都能量化大小,但工程里选哪个,背后是几何偏好、计算成本、优化难度三者的权衡。这一节把这个权衡讲透。
4.1 范数等价性:为什么换来换去也没关系
数学上有一个重要结论:在有限维空间里,所有范数都是等价的。也就是说,对任意两个范数 $|\cdot|_a$ 和 $|\cdot|_b$,存在正常数 $c_1, c_2$,使得对所有向量 $x$ 都有:
$$ c_1 |x|_a \le |x|_b \le c_2 |x|_a $$
这意味着在一个范数下趋于零的向量,在另一个范数下也一样趋于零。很多收敛性证明之所以敢"挑软柿子捏",选择最容易分析的范数去证,最后再靠等价性推广到别的范数,靠的就是这个性质。
实际中常被用到的等价关系有:
- $|x|_2 \le |x|_1 \le \sqrt{n},|x|_2$
- $|x|_\infty \le |x|2 \le \sqrt{n},|x|\infty$
- $|x|_\infty \le |x|1 \le n,|x|\infty$
注意,这些不等式的系数里带了维度 $n$,说明随着维度升高,不同范数之间的差异可能会被放大。做高维数据时,不能想当然认为"反正等价,随便用",在具体数值上它们还是可以差出好几个数量级的。
4.2 选型场景:L1、L2、L∞到底怎么挑
我自己的经验是,选范数先看你要解决什么问题,再看成本和几何偏好。下面这几种场景是我在真实项目里反复遇到的:
做稀疏解:选1-范数。LASSO、压缩感知、图像去噪里的稀疏约束都是这个思路。1-范数在约束区域形成菱形/多面体,优化解容易落在坐标轴上,天然产生零分量。
做能量型度量:选2-范数。信号的能量、最小二乘误差、物理系统的势能,这些概念天然和2-范数契合,而且2-范数有旋转不变性,不会因为坐标系旋转而改变结果。这在很多物理和几何相关的计算里是刚需。
做鲁棒度量:选∞-范数或1-范数。∞-范数只看最大偏差,适合"每个点都不能太离谱"的场景,比如均匀逼近问题;1-范数对个别异常值的敏感度比2-范数低,因为平方操作会把离群点的影响放得很大,而绝对值没有这个效果。如果数据里有明显的异常值,用2-范数做损失函数容易被少数点带偏,改用1-范数能让模型更稳。
做矩阵误差:选F-范数。它是矩阵版本的能量型度量,计算快、求导方便,适合迭代算法里的损失函数和矩阵分解误差。
这些偏好不是凭感觉来的,而是不同范数的单位球形状决定的。优化问题里,惩罚项和约束项的图像是单位球的某个缩放版本,目标函数的等高线跟这个形状相切,切点落在什么位置,直接在数学上决定了你的解是什么形态。
4.3 优化与梯度计算中的细节
从计算成本角度看,范数选择对迭代算法的速度影响巨大。
F-范数的平方,求导是最干净的。$\frac{\partial}{\partial A}|A|_F^2 = 2A$,这个结果直接到离谱。你用梯度下降、交替最小二乘、牛顿法,都能轻松拿到梯度。
2-范数虽然没有平方时也还能求导(归一化操作经常用到),但放在矩阵上就是谱范数,求导要经过奇异值分解。奇异值分解的代价从 $O(mn^2)$ 起步,矩阵稍微大一点就非常吃力。所以矩阵优化问题里几乎没人拿谱范数当目标函数项。
1-范数的次梯度也简单,就是符号函数 $\text{sign}(x)$,很多一阶算法都能处理。∞-范数的次梯度稍微复杂一些,因为它只在最大值分量上有贡献,写代码时要特殊处理"最大值出现在多个分量"的边界情况。
还有一点值得注意:正则化里加平方项还是绝对值项,收敛速度也会不一样。L2正则化对应的梯度是一个平滑函数,迭代过程普遍稳定,一般不需要额外做太多处理;L1正则化在零点附近不光滑,需要用到近端梯度、软阈值这类专门算法。这不是数学偏好问题,而是实打实的工程实现差异。
5. 代码实操:用 NumPy 计算范数与常见"翻车"现场
定义讲透了,最终还是要落到代码里。NumPy 的linalg.norm封装了大部分范数计算,但封装越方便,越容易让人忽略背后的语义。我见过不少人在这一步栽跟头。
5.1 向量范数的 NumPy 实操
先看向量范数。假设有一个包含负数的向量,我们要分别算1-范数、2-范数、∞-范数:
import numpy as np x = np.array([1, -2, 3]) norm_1 = np.linalg.norm(x, ord=1) # 6.0 norm_2 = np.linalg.norm(x, ord=2) # 3.741657... (sqrt(14)) norm_inf = np.linalg.norm(x, ord=np.inf) # 3.0这里最容易错的是ord参数。向量情况下:
ord=1返回绝对值之和;ord=2返回平方和开方;ord=np.inf返回绝对值最大值。
这三个行为都是符合定义的,代码写起来也直接。真正容易出问题的是矩阵情况。
5.2 矩阵范数的 NumPy 实操
矩阵范数才是翻车重灾区。看下面的例子:
A = np.array([[1, 2], [3, 4]]) print(np.linalg.norm(A, ord='fro')) # F-范数: 5.477 (sqrt(30)) print(np.linalg.norm(A, ord=2)) # 谱范数: 5.464 (最大奇异值) print(np.linalg.norm(A, ord=1)) # 最大列和: 6.0 print(np.linalg.norm(A, ord=np.inf)) # 最大行和: 7.0注意看,对矩阵A使用ord=2,得到的是谱范数,也就是最大奇异值,不是把矩阵拉直后求的2-范数!如果想对矩阵所有元素做平方和开方,要明确写ord='fro'。这个细节在实际项目里一旦搞错,误差评估、条件数计算、正则化实现全都会偏。
如果要按行或按列计算范数,需要用axis参数:
# 按列求2-范数,结果是一个向量,长度等于列数 col_norms = np.linalg.norm(A, ord=2, axis=0) # 按行求2-范数,结果是一个向量,长度等于行数 row_norms = np.linalg.norm(A, ord=2, axis=1)我在实际项目里经常用谱范数估条件数。np.linalg.cond(A)默认就用2-范数,它等价于最大奇异值除以最小奇异值:
cond_A = np.linalg.cond(A) # 等价于 np.linalg.norm(A, 2) * np.linalg.norm(np.linalg.inv(A), 2)条件数有多大,直接决定了求解线性方程组的数值稳定性。条件数超过 $10^{12}$ 基本可以判定病态,求解结果会非常依赖舍入误差。
5.3 常见误用与一张速查表
把我在代码审查和实际调试里见到的典型问题整理一下,这些都是真实踩过的坑。
| 误用方式 | 实际后果 | 正确做法 |
|---|---|---|
np.linalg.norm(A, ord=2)当成矩阵逐元素2-范数 | 得到的是谱范数,不是平方和开方,数值可能就差很多 | 逐元素用ord='fro' |
忘了传axis,对二维矩阵整体求范数 | 结果只有一个数,不是按行/按列 | 明确写axis=0或axis=1 |
想算"非零元素个数"(0-范数)用ord=0 | np.linalg.norm的ord=0在向量上会报错,语义不支持 | 手动用np.count_nonzero(x) |
| 对复数矩阵没考虑模 | 范数计算会出现复数参与排序或求和,结果是复数或错乱 | 先取模再算,或确认np.linalg.norm内部处理 |
| 矩阵条件数直接用1-范数而不说明 | 条件数数值不同,与论文里用的标准不一致,复现对不上 | 统一用2-范数,或明确注明用哪个范数 |
还有一个容易被忽略的问题:np.linalg.norm在维度很高时会先让矩阵参与运算,如果矩阵本身是稀疏存储的,它会先把它转成稠密矩阵再计算,导致内存爆炸。此时要用scipy.sparse里专门的稀疏范数计算,或者手工用稀疏矩阵的乘法、切片方式算。
另外,如果你的数据维度很大,算F-范数时直接用np.sum(A**2)的方式可能会有精度问题。数据里存在量级差异很大的元素时,平方和容易溢出或损失精度,更稳妥的办法是用scipy.linalg.norm里经过数值封装的实现,内部会对大数做缩放处理。
最后再分享一个我自己的习惯:所有涉及范数的代码,我都会在旁边写注释,标明用的是哪种范数、为什么用这种范数。因为半年后回来看代码,真的会忘记ord=2在矩阵上到底是谱范数还是逐元素2-范数。常见误用里列的那些坑,我自己基本都踩过一遍,写注释是最笨也最有效的防错方式。