从一个问题开始
在文本检索或推荐系统中,我们如何判断两篇文章或两个用户的兴趣是否相似?在计算机视觉中,我们如何衡量两张图像提炼出的特征向量是否接近?
答案并不只是“把对应的数值相乘”,而是通过向量、**范数(Norm)与余弦相似度(Cosine Similarity)**来定量刻画方向与距离。这一章我们将从最基础的数表结构出发,理清标量、向量、矩阵与高维张量的区别,手推范数与余弦相似度,并用 NumPy 验证向量化计算的巨大威力。
在数学推导中,默认向量是列向量 x∈Rn×1;在 NumPy / PyTorch 编程中,1 维数组的 shape 是 (n,)。批量处理 m 个样本时,习惯把每行作为一个样本,形成 X∈Rm×n 的矩阵。先看 shape 再算内积是防错的第一法则。
1. 标量、向量、矩阵与张量
在 AI 和数据科学中,所有数据最终都表现为多维数值阵列(Array):
- 标量(Scalar, 0D 张量):单个数值,如温度 t=36.5,
shape = ()。
- 向量(Vector, 1D 张量):一排有序排列的数,表示空间中的一个点或方向,如 x=[3,4]T,
shape = (n,)。
- 矩阵(Matrix, 2D 张量):二维表格,由行和列组成,如样本表 X∈Rm×n,
shape = (m, n)。
- 张量(Tensor, ≥3D 张量):高维数据阵列。例如彩色图像批量 X∈RB×C×H×W(批次、通道、高度、宽度)。
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.
2. 范数体系:如何衡量向量的大小?
在日常生活中,我们量两点之间的距离通常用直尺。但在高维数据和机器学习中,“距离”有多种不同的衡量方法。
向量的**范数(Norm)**就是把一个向量映射为一个非负实数的“测距仪”,用于衡量向量的“长度”或“规模”。通用的 Lp 范数公式为:
∥x∥p=(∑i=1n∣xi∣p)p1
2.1 零基础必懂的三种直观距离
- L1 范数(曼哈顿/出租车距离):p=1,∥x∥1=∑∣xi∣。
- 生活比喻:就像出租车在横平竖直的城市街区(如纽约曼哈顿)里行驶,不能穿过建筑物直飞,必须沿着水平和垂直街道走完的总路程。在 AI 中常用于 L1 正则化,能产生“稀疏解”(自动把不重要的特征系数清零)。
- L2 范数(欧氏/鸟飞距离):p=2,∥x∥2=∑xi2。
- 生活比喻:就像小鸟在空中直线飞行的距离,也是我们在中学学过的勾股定理两点间距离。
- L∞ 范数(切比雪夫/国际象棋王距离):p→∞,∥x∥∞=maxi∣xi∣。
- ♔ 生活比喻:就像国际象棋中的“王(King)”,一步可以向任意相邻方格移动(包括斜行),走完两点所需的最少步数取决于横向或纵向最大的差值。
初学者直觉:为什么 $L_1$ 范数能让权重变 0?
在二维平面上,把所有范数等于 1 的点画出来:
- L1 单位球是个尖角朝向坐标轴的菱形。当优化目标找到这个菱形时,最先接触到的点往往是在坐标轴的“尖角”上,此时另一个坐标刚好是 0!
- L2 单位球是个光滑的正圆形,没有尖角,所以变量倾向于变小但不会正好为 0。
点击放大查看图:L1, L2, Linf 范数单位球几何形状对比
手算例子:计算向量的各阶范数
给定向量 x=[3,−4]T:
- L1 范数(出租车走法):∥x∥1=∣3∣+∣−4∣=3+4=7
- L2 范数(鸟飞直线):∥x∥2=32+(−4)2=9+16=5
- L∞ 范数(最大的单维跨度):∥x∥∞=max(∣3∣,∣−4∣)=4
3. 点积与余弦相似度
3.1 为什么有了距离,还需要“余弦相似度”?
假设我们在做一个新闻推荐系统,统计两篇文章中关键词出现的次数:
- 文章 A(短新闻):《苹果 科技》(词频向量:
[1, 1])
- 文章 B(长长评):《苹果 苹果 苹果 科技 科技 科技》(词频向量:
[3, 3])
- 文章 C(美食文):《做饭 菜谱》(词频向量:
[0, 0],在科技词上均为 0)
文章 A 和文章 B 都在讨论科技,主题完全一致!但是如果计算 L2 欧氏距离,因为文章 B 很长,点 [3,3] 距离 [1,1] 很远。
而余弦相似度排除了文章长短(向量模长)的干扰,只关注它们在空间里的夹角方向!
点击放大查看图:余弦相似度夹角与欧氏距离几何对比
3.2 余弦相似度 (Cosine Similarity) 公式与含义
对于两个同维向量 a,b∈Rn,点积按对应分量相乘再求和:
aTb=i=1∑naibi=∥a∥2∥b∥2cos(θ)
消去长度影响后,得出余弦相似度公式:
cos(θ)=∥a∥2∥b∥2aTb=∑ai2∑bi2∑aibi
- 值在 [−1,1] 之间:
- +1:方向完全一致(θ=0∘)
- 0:相互正交(θ=90∘)
- −1:方向完全相反(θ=180∘)
4. 手算演练
手算练习 1:向量线性组合与点积
给定二维向量 a=[23] 与 b=[−14]:
- 计算线性组合 c=3a−2b:
3a=3[23]=[69],2b=2[−14]=[−28]
c=[69]−[−28]=[6−(−2)9−8]=[81]
- 计算点积 aTb:
aTb=(2×−1)+(3×4)=−2+12=10
手算练习 2:余弦相似度完整计算
给定向量 u=[11] 与 v=[02]:
- 第一步:算点积 uTv:
uTv=(1×0)+(1×2)=2
- 第二步:算各自的 L2 模长:
∥u∥2=12+12=2
∥v∥2=02+22=4=2
- 第三步:代入公式求 cos(θ):
cos(θ)=∥u∥2∥v∥2uTv=2×22=21=22
因为 cos(45∘)=22,所以两向量在空间中的夹角为 45∘!
5. 动手实战:NumPy 余弦相似度与向量化性能 benchmark
在 Python 中,绝不要用 for 循环对向量逐分量相乘!NumPy 的底层 C 语言向量化(SIMD)指令可以带来几十上百倍的性能提升。
余弦相似度实现与循环 vs 向量化 Benchmark import numpy as np
import time
# 1. 定义余弦相似度函数(带 shape 校验断言)
def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
assert a.shape == b.shape, f"Shape 不匹配: {a.shape} vs {b.shape}"
norm_a = np.linalg.norm(a)
norm_b = np.linalg.norm(b)
if norm_a == 0 or norm_b == 0:
return 0.0
return float(np.dot(a, b) / (norm_a * norm_b))
# 测试样例
vec1 = np.array([3.0, 4.0, 0.0])
vec2 = np.array([6.0, 8.0, 0.0])
vec3 = np.array([-4.0, 3.0, 0.0])
print("vec1 与 vec2 (同向) 相似度:", cosine_similarity(vec1, vec2)) # 应接近 1.0
print("vec1 与 vec3 (正交) 相似度:", cosine_similarity(vec1, vec3)) # 应为 0.0
# 2. 向量化 vs Python 循环 性能对比
N = 1_000_000
v_a = np.random.randn(N)
v_b = np.random.randn(N)
# 循环方式
t0 = time.time()
dot_loop = sum(x * y for x, y in zip(v_a, v_b))
t_loop = time.time() - t0
# 向量化方式
t0 = time.time()
dot_vec = np.dot(v_a, v_b)
t_vec = time.time() - t0
print(f"\n100万维点积 - 循环耗时: {t_loop*1000:.2f} ms")
print(f"100万维点积 - 向量化耗时: {t_vec*1000:.2f} ms")
print(f"加速比: {t_loop / t_vec:.1f} 倍")
5. 常见错误与避坑指南
|
ValueError: shapes (3,) and (4,) not aligned | 两向量维度是否相同 | 在计算点积或余弦前加上 assert a.shape == b.shape |
ZeroDivisionError 或返回 NaN | 是否传入了全零向量 | 检查向量的 L2 范数是否为 0,防止除零 |
误把 a * b 当成点积 | 是否需要标量内积 | a * b 在 NumPy 中是逐元素相乘(Hadamard 积),点积需用 np.dot(a, b) 或 a @ b |
检查清单 (Checklist)
自测题
- 给定向量 x=[1,−2,2]T,计算其 L1 范数、 L2 范数与 L∞ 范数。
- 若向量 a=[1,0]T,b=[1,1]T,手算它们的夹角余弦值 cos(θ),夹角是多少度?
- 解释:为什么在文本 Embeddings 检索中,通常更倾向于使用余弦相似度而非欧氏距离?
- 【代码阅读题】如果在 NumPy 中执行
a = np.array([1, 2]); b = np.array([3, 4]); c = a * b,c 的结果是什么?与 a @ b 有何区别?
点击查看参考答案
- ∥x∥1=1+2+2=5;∥x∥2=12+(−2)2+22=9=3;∥x∥∞=max(1,2,2)=2。
- aTb=1×1+0×1=1;∥a∥2=1,∥b∥2=2。余弦值 cos(θ)=21=22,对应的夹角为 45∘。
- 因为文本长度(字数)会导致词频向量长度变大,但文本的主题方向可能一致。余弦相似度排除了长度干扰,只比较方向;而欧氏距离易受文本长度影响。
c 结果为 [3, 8](逐元素相乘 Hadamard 积);而 a @ b 结果为标量 11(向量点积)。
下一步
进入下一个专项 ,解锁矩阵乘法、矩阵的秩与迹、逆矩阵以及二维旋转、缩放与剪切变换的动画与代码可视化。
参考资料
- :第 2 章 向量空间与范数定义。
- :NumPy 线性代数官方指南。
- :向量与线性组合的本质几何直觉。