复习文档 · ADHD友好 · 从零开始
本章学到哪里,不学到哪里
本章只学习:向量、点积、范数、归一化和余弦相似度之间的关系,并用两个短向量验证公式。
本章暂时不学习:Embedding 模型训练、向量数据库索引、ANN 算法、分块策略和完整 RAG。它们会使用本章的相似度思想,但不是本章的实现目标。
一句话理解
Embedding 把文本变成固定长度的数字向量,余弦相似度比较两个向量的方向,从而作为语义检索的一个相似度信号。
它在 AI 应用中的位置
文本 -> Embedding 模型 -> 向量 -> 余弦相似度 -> 排序相似文本 -> RAG 检索 / 推荐 / 去重 / 聚类余弦相似度只负责“比较两个已经存在的向量”,不负责生成向量,也不负责从数据库取出文档。在 RAG 中,向量模型和向量数据库分别是它的前后环节。
最小验证例子
下面只使用 Python 标准库 math,不需要安装额外依赖:
from math import sqrt
def cosine_similarity(a: list[float], b: list[float]) -> float: if len(a) != len(b): raise ValueError("两个向量的维度必须一致")
dot = sum(x * y for x, y in zip(a, b)) norm_a = sqrt(sum(x * x for x in a)) norm_b = sqrt(sum(y * y for y in b)) if norm_a == 0 or norm_b == 0: raise ValueError("零向量没有可比较的方向") return dot / (norm_a * norm_b)
print(cosine_similarity([3, 2], [6, 4])) # 接近 1:方向相同print(cosine_similarity([1, 0], [0, 1])) # 0:方向垂直验证重点不是背出小数,而是观察:同方向的向量即使长度不同,结果仍接近 1;维度不同或出现零向量时,函数应拒绝计算。
🔧 准确术语速查
| 术语 | 准确含义 | 本章对应 |
|---|---|---|
| Vector | 向量,一组有方向和长度的数字 | [3, 2] |
| Component | 分量,向量里的每个数字 | 3 和 2 |
| Dot product | 点积,逐位相乘再求和 | a1*b1 + a2*b2 |
| Norm | 向量长度,多维勾股定理 | sqrt(x^2 + y^2) |
| Cosine similarity | 余弦相似度,比较方向接近程度 | dot / (norm_a * norm_b) |
| Normalization | 归一化,消除尺度影响 | 除以长度,保留方向 |
一、先记住这张速查表
| 概念 | 一句话 | 类比 |
|---|---|---|
| 向量 | 一根箭,等于一个列表/元组 | 去超市路线:[向东3步, 向北2步] |
| 分量 | 列表里每个数字 | 每个方向各走几步 |
| 平方 | 自己乘自己 | 3² = 3×3 = 9 |
| 开根号 | 平方的反操作 | √9 = 3(因为3×3=9) |
| 勾股定理 | 横²+竖²=对角线² | 房间宽3长4,对角线=5 |
| norm | 多维勾股定理,量箭的长度 | 所有分量平方加起来再开根号 |
| 点积 | 逐位相乘再求和 | 两根箭在同上方向的”重叠量” |
| 余弦相似度 | 点积/(长度×长度),削掉长度比方向 | 只看口味比例,不看吃多少 |
二、从零开始:向量是什么
向量 = 一根箭 = 一个列表
vec_a = [3, 2]
意思是:向右走3步,向上走2步 画出来就是一根从原点出发的箭
2 ↑ │ ● ← 箭头 │╱ 1 │ ─────┼──────3──→ 0 1 2 3类比:
去超市的路线 = [3, 2] 第1个分量(3) = 向东走3条街 第2个分量(2) = 向北走2条街
去医院的路线 = [1, 4] 第1个分量(1) = 向东走1条街 第2个分量(4) = 向北走4条街
两根箭指向不同方向 = 两句话说的是不同内容关键:Embedding模型生成的向量维度永远固定!
模型输出 1024 维 → 不管你输入什么句子,输出永远是1024个数字 模型输出 1536 维 → 永远是1536个数字
维度多 = 描述更精细 = 足以区分不同语义三、平方和开根号
平方 = 自己乘自己
3² = 3 × 3 = 92² = 2 × 2 = 40.9² = 0.9 × 0.9 = 0.81(-3)² = (-3) × (-3) = 9 ← 负数平方也变正数!开根号 = 平方的反操作
√4 = 2 因为 2×2=4√9 = 3 因为 3×3=9√25 = 5 因为 5×5=25√2 ≈ 1.414 因为 1.414×1.414≈2平方 = "放大":3 → 3×3 → 9开根号 = "还原":9 → "谁×自己=9" → 3注意区分!开根号 ≠ 除法
开根号:√9 = 3 (问谁×自己=9)除法: 9÷3 = 3 (把9分成3份)
数字碰巧一样,但操作完全不同!
验证:√15 ≈ 3.87,但 15÷3 = 5→ 3.87 ≠ 5,显然不是一回事四、勾股定理 = 量对角线
一个房间宽3米、长4米从左下角走到右上角的对角线有多远?
● ← 右上角 ╱│ 对角线╱ │4米(竖边) ╱ │ 左下角 ●───┘ 3米(横边)
对角线 = √(横边² + 竖边²) = √(3² + 4²) = √(9 + 16) = √25 = 5米一句话记一辈子:
横² + 竖² = 对角线²五、norm = 多维勾股定理 = 量箭的长度
5.1 先认清”分量”和”分量平方”
向量 A = [3, 2] ↑ ↑ 这两个数字都叫"分量"(Component)
A[0] = 3 ← 第 0 个分量A[1] = 2 ← 第 1 个分量分量平方 = 每个分量自己乘自己:
向量 A = [3, 2]
分量平方: 3² = 3×3 = 9 2² = 2×2 = 4为什么需要分量平方? 因为分量可能是负数(向左走、向下走),但长度不能是负数。平方让负数变正:
向量 [-3, -2] (向左3,向下2)
直接加:-3 + (-2) = -5 ❌ 长度不能是负的
平方后加:(-3)² + (-2)² = 9 + 4 = 13 ✅ 永远是正数5.2 norm = 分量平方之和再开根号
2维向量:norm = √(分量1² + 分量2²)
vec_a = [3, 2] norm = √(3² + 2²) = √(9+4) = √13 ≈ 3.61
横着走了3步,竖着走了2步 箭的长度 = 3.61步完整三步:
第1步:每个分量平方 3² = 9, 2² = 4
第2步:全部加起来 9 + 4 = 13
第3步:开平方根 √13 ≈ 3.61 ← 这就是 norm(A)为什么最后要开根号? 因为前两步(平方+求和)把数字放大了,开根号把它缩回真正的”箭长”。这是勾股定理:横²+竖²=对角线²,所以对角线 = √(横²+竖²)。
5维向量:norm = √(分1² + 分2² + 分3² + 分4² + 分5²)
vec = [0.9, 0.8, 0.7, 0.1, 0.2]
norm = √(0.81 + 0.64 + 0.49 + 0.01 + 0.04) = √(1.99) ≈ 1.41
逻辑完全一样!只是加了5个数字而不是2个1024维也一样:√(分1² + 分2² + ... + 分1024²)
维度再多,本质不变 就是勾股定理的扩展版六、点积 = 逐位乘再求和
A = [3, 2]B = [2.8, 1.9]
点积 = 3 × 2.8 + 2 × 1.9 = 8.4 + 3.8 = 12.2步骤拆解:
第1步:同一位置的两个数字相乘 位置0:3 × 2.8 = 8.4 位置1:2 × 1.9 = 3.8
第2步:所有乘积加起来 8.4 + 3.8 = 12.2 → 点积
乘 = 该方向上两箭的重叠程度(都强→乘积大) 加 = 所有方向的总重叠量5维向量也一样:
A = [0.9, 0.8, 0.7, 0.1, 0.2] B = [0.85, 0.75, 0.65, 0.15, 0.25]
点积 = 0.9×0.85 + 0.8×0.75 + 0.7×0.65 + 0.1×0.15 + 0.2×0.25 = 0.765 + 0.6 + 0.455 + 0.015 + 0.05 = 1.885
逻辑完全一样!只是乘了5次而不是2次注意:点积不能单独判断相似性!
A = [3, 2] 长度 ≈ 3.61B = [2.8, 1.9] 长度 ≈ 3.38 方向一致点积 = 12.2
C = [100, 66.7] 长度 ≈ 121 方向也一致点积 = 433.4
单看点积:12.2 vs 433.4 → 差很多!但A vs B 和 A vs C 方向其实一样!
→ 点积混了"长度"信息,必须削掉七、余弦相似度 = 削掉长度比方向 = 最终答案
7.1 公式(重要:方向别记反!)
余弦相似度 = 点积 ÷ (norm(A) × norm(B)) ↑ ↑ 分子(上面) 分母(下面)
❌ 错误:norm(A) × norm(B) ÷ 点积✅ 正确:点积 ÷ (norm(A) × norm(B))为什么是除以而不是乘? 因为点积里”沾了”长度信息,要除掉它才能还原出”纯方向相似度”。
类比:你称体重,但称上沾了泥(多了 2 kg)真实体重 = 称出来的数 - 2 ← 减掉/除掉多余的部分 不是 + 27.2 完整流程三步
余弦相似度 = 点积 / (norm(A) × norm(B))
= 逐位乘再求和 / (A长度 × B长度)
= 削掉长度,只剩方向走一个完整例子
A = [3, 2] ← "代码质量"B = [2.8, 1.9] ← "程序维护"C = [0.5, 4] ← "天气"
步骤1:算长度(norm = 勾股定理) norm(A) = √(3²+2²) = √13 ≈ 3.61 norm(B) = √(2.8²+1.9²) = √11.45 ≈ 3.38 norm(C) = √(0.5²+4²) = √16.25 ≈ 4.03
步骤2:算重叠(点积 = 逐位乘再求和) dot(A,B) = 3×2.8 + 2×1.9 = 8.4 + 3.8 = 12.2 dot(A,C) = 3×0.5 + 2×4 = 1.5 + 8 = 9.5
步骤3:削掉长度得相似度 A vs B = 12.2 / (3.61 × 3.38) = 12.2 / 12.21 ≈ 1.00 ✅ A vs C = 9.5 / (3.61 × 4.03) = 9.5 / 14.49 ≈ 0.66结果解读
| 相似度 | 含义 | 例子 |
|---|---|---|
| ~1.0 | 方向完全一致 | ”代码质量” vs “程序维护” |
| 0.8-0.9 | 非常相似 | |
| 0.6-0.7 | 比较相似 | ”代码质量” vs “天气”(不同,但有微弱关联) |
| 0.3-0.5 | 有点关系 | |
| <0.3 | 基本无关 | |
| 0 | 完全无关 | 垂直方向(手电筒照墙90度) |
八、三步完整对比图
A向量 B向量 [3,2] [2.8,1.9] │ │ ▼ ▼ ┌──────────┐ ┌──────────────┐ 步骤1 │ 勾股定理 │ │ 勾股定理 │ (量长度) │norm(A)= │ │ norm(B)= │ │√(3²+2²) │ │ √(2.8²+1.9²)│ │≈ 3.61 │ │ ≈ 3.38 │ └──────────┘ └──────────────┘ │ │ └───────┬───────┘ │ ▼ ┌───────────────────────────┐ 步骤2 │ 点积 │ (算重叠) │ dot = 3×2.8 + 2×1.9 │ │ = 12.2 │ └───────────────────────────┘ │ ▼ ┌───────────────────────────┐ 步骤3 │ 余弦相似度 │ (削长度) │ 12.2 / (3.61 × 3.38) │ │ = 12.2 / 12.21 │ │ ≈ 1.00 ✅ 非常相似! │ └───────────────────────────┘九、Python 代码速查
import numpy as np
# 两个向量a = np.array([3, 2])b = np.array([2.8, 1.9])
# 步骤1:算长度(norm)len_a = np.linalg.norm(a) # √(9+4) ≈ 3.61len_b = np.linalg.norm(b) # √(7.84+3.61) ≈ 3.38
# 步骤2:算点积dot = np.dot(a, b) # 3×2.8 + 2×1.9 = 12.2
# 步骤3:余弦相似度cosine = dot / (len_a * len_b) # ≈ 1.0一句代码版
def cosine_similarity(a, b): return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))十、给 Embedding 的类比
"如何提升代码质量" → Embedding模型 → 向量A → 一根箭(朝"编程"方向)"今天天气真好" → Embedding模型 → 向量B → 一根箭(朝"天气"方向)
两根箭方向差很远 → 余弦相似度低 → 语义不同两根箭方向很近 → 余弦相似度高 → 语义相近
这就是 AI 判断"意思相近"的数学原理!✅ 检查清单
- 向量 = 一根箭 = 一个列表/元组
- 分量 = 列表里的每个数字
- 平方 = 自己乘自己;开根号 = 平方的反操作
- 勾股定理 = 横²+竖²=对角线²(2维特殊版)
- norm = 多维勾股定理 = 量箭的长度
- 点积 = 逐位乘再求和 = 两根箭的重叠量
- 余弦相似度 = 点积 ÷ (A长度×B长度) = 削掉长度比方向
- 越接近1 → 越相似;越接近0 → 越无关
- 同一模型输出的向量维度永远一样,不会出现长度不同
十一、深入理解:为什么除以长度能削掉倍数
问题的核心
A = [3, 2]B = [100, 66.7] = 33.3 × A ← B是A放大33.3倍,方向完全相同
问:如何计算相似度,让结果不受"33.3倍"影响?第一步:观察点积里的倍数
np.dot(A, B)= 3×100 + 2×66.7= 3×(33.3×3) + 2×(33.3×2) ← 代入 B = k×A用乘法交换律提取k:
3×(33.3×3)= 3×33.3×3 ← 去括号(结合律)= 33.3×3×3 ← 交换律:3和33.3换位置= 33.3×(3×3)= 33.3×9
2×(33.3×2)= 2×33.3×2= 33.3×2×2= 33.3×(2×2)= 33.3×4提取公因数(分配律):
np.dot(A, B)= 33.3×9 + 33.3×4= 33.3 × (9 + 4) ← 提取公因数33.3= 33.3 × 13= k × np.dot(A, A) ← 点积里混进了倍数k!第二步:观察norm里的倍数
norm(B)= √(100² + 66.7²)= √((33.3×3)² + (33.3×2)²)= √(33.3²×9 + 33.3²×4)= √(33.3² × (9+4))= √(33.3² × 13)= 33.3 × √13= 33.3 × norm(A)= k × norm(A) ← norm里也混进了倍数k!第三步:相除消掉k
余弦相似度 = dot(A,B) / (norm(A) × norm(B))
分子:k × np.dot(A,A) = k × (3×3 + 2×2) = k × 13分母:norm(A) × k × norm(A) = k × norm(A)² = k × (√13)² = k × 13
= (k × 13) / (k × 13) ← k在分子分母都有!= k/k × 13/13= 1 × 1= 1.0 ← k被消掉了!
关键:norm(A)² = dot(A, A),等式两边严格相等! norm(A)² = (√(3²+2²))² = 3²+2² = 9+4 = 13 = dot(A, A) 所以不需要用近似值3.61,用精确的13就能完美消掉!一句话总结
| 位置 | 计算结果 | 倍数k在哪 |
|---|---|---|
| 分子 dot(A,B) | 433.4 | 33.3 × 13 ← 这里! |
| 分母 norm(A) | 3.61 | 没有k |
| 分母 norm(B) | 120.2 | 33.3 × 3.61 ← 这里! |
| 相除结果 | 1.0 | k/k=1,消了! |
433.4 / (3.61 × 120.2)≈ (33.3×13) / (√13 × 33.3×√13) ← 不用3.61,用精确的√13= 33.3/33.3 × 13/(√13×√13) ← k/k 消掉,后面 √13×√13 = 13= 1 × 13/13= 1 × 1≈ 1.0 ← 用精确值算出来就是严格的1核心洞察:倍数k同时污染了分子和分母,一除就没了,结果只反映方向!
十二、数学定律速查
乘法交换律
a × b = b × a
例子: 3 × 5 = 15 5 × 3 = 15 → 相同!
在点积中: 3 × (33.3 × 3) = 33.3 × (3 × 3) 把33.3提到前面乘法结合律
(a × b) × c = a × (b × c)
例子: (2 × 3) × 4 = 6 × 4 = 24 2 × (3 × 4) = 2 × 12 = 24 → 相同!
在norm中: √((33.3×3)² + (33.3×2)²) = √(33.3² × (3² + 2²)) = 33.3 × √(3² + 2²)提取公因数(分配律逆用)
a × b + a × c = a × (b + c)
例子: 5 × 7 + 5 × 3 = 5 × (7 + 3) = 5 × 10 = 50
在点积中: 33.3×9 + 33.3×4 = 33.3 × (9 + 4) = 33.3 × 13十三、归一化的数学思想
什么是归一化
归一化 = 把不同尺度的东西拉到同一标准下再比较
例子1:打分 小明:85/100分 小红:42/50分 归一化:都转成百分制 → 85% vs 84%
例子2:房价 北京:800万/100平 = 8万/平 小城市:80万/100平 = 0.8万/平 归一化:每平米价格,公平比较在向量里
问题:两根箭长度不同,直接比不公平 A = [3, 2],长度3.61 B = [100, 66.7],长度120.2(是A的33.3倍)
但方向完全相同!
解决:除以norm,削成单位长度1 削后A = [3/3.61, 2/3.61] = [0.83, 0.55] 削后B = [100/120.2, 66.7/120.2] = [0.83, 0.55]
→ 削完完全相同!都是[0.83, 0.55]
结果:余弦相似度 = 1.0,正确反映方向相同!一句话
归一化 = 削掉长度的影响,只比方向
在AI中无处不在: - 距离归一化 - 数据标准化 - 概率归一化(softmax) - 余弦相似度
核心思想:只要标准一致,不管尺度大小十三·补、归一化与余弦相似度的关系(为什么归一化后点积就是余弦相似度)
1. 先回顾两个公式
余弦相似度公式: cosine(A, B) = dot(A, B) ÷ (norm(A) × norm(B)) ↑ ↑ 点积(分子) 两个范数相乘(分母)
归一化定义: A' = A ÷ norm(A) B' = B ÷ norm(B)2. 归一化后范数必然是 1(数学证明)
设 A = [a, b],norm(A) = √(a² + b²) = N
归一化后 A' = [a/N, b/N]
norm(A') = √((a/N)² + (b/N)²) = √(a²/N² + b²/N²) ← 平方后 N² 在分母 = √((a² + b²) / N²) ← 合并分子 = √(N² / N²) ← 因为 a² + b² = N²(范数定义) = √1 = 1 ✅
→ 任何向量归一化后,范数必然是 1→ 任何数除以自己都等于 1,所以这是数学必然3. 范数变 1 → 分母变 1 → 余弦相似度 = 点积
归一化后: norm(A') = 1 norm(B') = 1
代入余弦相似度公式: cosine(A', B') = dot(A', B') ÷ (norm(A') × norm(B')) = dot(A', B') ÷ (1 × 1) = dot(A', B') ÷ 1 = dot(A', B') ✅
→ 归一化后,余弦相似度 = 点积→ 因为分母 (1×1=1),除以 1 等于没除4. 关键洞察:除法被”提前”了
不归一化(除法在阶段 2,检索时做): 阶段 1:无 阶段 2:dot(A, B) ÷ (norm(A) × norm(B)) ↑ 除法在这(每次检索都要做)
归一化(除法在阶段 1,灌库时做): 阶段 1:A' = A ÷ norm(A),B' = B ÷ norm(B) ↑ ↑ 除法搬到这了(一次性做完,存起来)
阶段 2:dot(A', B') ↑ 没有除法,只算点积除法没消失,只是从阶段 2 搬到阶段 1。
5. 数学等价性证明
归一化后的点积: dot(A', B') = dot(A÷norm(A), B÷norm(B)) = (A[0]÷norm(A))×(B[0]÷norm(B)) + (A[1]÷norm(A))×(B[1]÷norm(B)) = (A[0]×B[0])÷(norm(A)×norm(B)) + (A[1]×B[1])÷(norm(A)×norm(B)) = [(A[0]×B[0]) + (A[1]×B[1])] ÷ (norm(A)×norm(B)) ← 分母提取出来 = dot(A, B) ÷ (norm(A) × norm(B)) ← 跟原公式一样!
→ 两种做法数学结果完全相同6. 用具体数字验证
原始向量: A = [3, 4] norm(A) = 5 B = [6, 7] norm(B) = √85 ≈ 9.22
做法 1:不归一化(阶段 2 做除法) dot(A, B) = 3×6 + 4×7 = 46 余弦 = 46 ÷ (5 × 9.22) = 46 ÷ 46.1 ≈ 0.998
做法 2:归一化(阶段 1 做除法) A' = [3÷5, 4÷5] = [0.6, 0.8] B' = [6÷9.22, 7÷9.22] ≈ [0.65, 0.76] dot(A', B') = 0.6×0.65 + 0.8×0.76 = 0.998
两种做法结果都是 0.998 ✅7. 为什么要”提前”做除法?(性能优势)
场景:知识库有 1 万个文档,用户问一个问题
不归一化(每次检索都要算): 对每个文档:dot(Q, D) ÷ (norm(Q) × norm(D)) ↑ 1 万次 norm 计算 + 1 万次除法
归一化(提前算好,检索只算点积): 灌库时:D' = D ÷ norm(D) ← 每个文档算 1 次,存起来 查询时:Q' = Q ÷ norm(Q) ← 算 1 次
对每个文档:dot(Q', D') ← 只算点积,不用算 norm,不用除
省掉了:1 万次 norm 计算 + 1 万次除法8. 类比理解
不归一化 = 每次去超市都现量苹果重量,再算总价 顾客1:0.3kg × 10元 = 3元 ← 称重 + 乘法 顾客2:0.5kg × 10元 = 5元 ← 称重 + 乘法 (每次都要称重,慢)
归一化 = 提前把苹果标成"1斤袋",单价提前算好 阶段 1:1斤装 × 10元/斤 = 10元/袋 ← 除法(分装)在这 阶段 2:顾客买 3袋 = 30元 ← 只数袋数,不称重,快
除法(分装)没消失,只是从"每次结账"搬到"提前分装"结账时只算袋数×单价,快很多9. 在项目里的对应
# 阶段 1 在这里发生(模型内部自动归一化)model.encode( "test", convert_to_numpy=True, normalize_embeddings=True, # ← 阶段 1:向量 ÷ norm,长度变 1)
# app/tools/kb_search.py# 阶段 2 在这里发生(ChromaDB 内部算点积)docs = get_vector_store().similarity_search(query, k=safe_limit)# ↑# 阶段 2:对归一化向量算点积 = 余弦相似度10. 关键前提:文档和查询必须一致
文档端:normalize_embeddings=True → 存的向量长度都是 1查询端:normalize_embeddings=True → 查的向量长度也是 1
两边一致 → 点积 = 余弦相似度 ✅
如果一边开一边不开: 文档长度 1,查询长度 3.61 dot(Q, D) ÷ (1 × 3.61) = 点积 / 3.61 ❌ 不是余弦相似度项目里 get_query_embedding 和 get_document_embedding 都用 normalize_embeddings=True,保持一致。
11. 一句话总结
归一化 = 把余弦相似度的除法"提前"到算点积之前
数学等价:dot(A÷n_a, B÷n_b) = dot(A,B) ÷ (n_a × n_b)原因:归一化后范数都是 1,分母 (1×1=1),除以 1 等于没除好处:除法只做一次(灌库时),检索时只算点积,快很多前提:文档和查询必须都开归一化(或都不开),不能一边开一边不开十四、代码 vs 数学原理
| 层面 | 做了什么 | 看到k了吗 |
|---|---|---|
| 代码执行 | 直接乘除 | ❌ 看不到k |
| 数学原理 | 提取公因数、消掉k | ✅ 能看到k被消 |
# 代码:直接算def cosine_similarity(a, b): return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# 数学原理:k被消掉了cosine = (k × dot(A,A)) / (norm(A) × k × norm(A)) = k/k × dot(A,A)/norm(A)² = 1 × dot(A,A)/norm(A)²
# 结果一样,原理解释了"为什么长度不影响结果"结论:代码直接乘除,但背后的数学原理就是提取公因数和消掉倍数k。
十五、完整思维导图
对比两句话相似度 │ ▼调用Embedding模型 → 获得两个向量列表 │ ▼┌─────────────────┐│ A = [3, 2] ││ B = [100, 66.7]│ ← B = k×A,k=33.3└─────────────────┘ │ ├─→ 点积 dot(A,B) = 3×100 + 2×66.7 │ = 3×(k×3) + 2×(k×2) │ = k×(3×3) + k×(2×2) ← 交换律 │ = k × (9 + 4) ← 提取公因数 │ = k × 13 ← 混进k! │ ├─→ norm(A) = √(3²+2²) = 3.61 │ ├─→ norm(B) = √(100²+66.7²) │ = √((k×3)² + (k×2)²) │ = √(k² × (3²+2²)) │ = k × √(3²+2²) │ = k × 3.61 ← 也混进k! │ ▼余弦 = dot / (norm(A) × norm(B)) = (k×13) / (3.61 × k×3.61) = k/k × 13/(3.61×3.61) ← k消掉了! = 1 × 1 = 1.0 ✅ 方向相同!✅ 完整检查清单
基础概念
- 向量 = 一根箭 = 一个列表
- 分量 = 列表里的每个数字
- 平方 = 自己乘自己;开根号 = 平方的反操作
- 勾股定理 = 横²+竖²=对角线²
- norm = 多维勾股定理 = 量箭的长度
- 点积 = 逐位乘再求和
- 余弦相似度 = 点积÷(长度×长度)
数学定律
- 乘法交换律:a×b = b×a,可以换位置
- 乘法结合律:(a×b)×c = a×(b×c),可以换括号
- 提取公因数:a×b + a×c = a×(b+c),把相同提出来
核心原理
- 点积里混进了倍数k(因为B=k×A)
- norm(B)里也混进了倍数k
- 相除时k/k=1,倍数被消掉
- 结果只反映方向,不受长度影响
- 这就是归一化的数学思想
✅ 四条理解标准
- 思想是什么:把文本变成向量后,用方向是否接近来判断语义是否接近。
- 干什么:给 RAG 检索、推荐、聚类等任务提供“相似度”计算方法。
- 为什么这么干:只看点积会被长度影响,余弦相似度通过除以长度消掉倍数。
- 怎么干:能写出
dot / (norm_a * norm_b),并能解释点积、norm、归一化各自的作用。
⚠️ 常见坑
| 坑 | 现象 | 正确做法 |
|---|---|---|
| 把点积当相似度最终答案 | 长向量天然分数更大 | 用余弦相似度消掉长度影响 |
| 以为开根号是除法 | norm 推导卡住 | 开根号是平方的反操作 |
| 忽略分母为 0 | 空向量计算报错或无意义 | 真实代码里先检查 norm 是否为 0 |
| 把数学公式和代码割裂 | 会背公式但不会写函数 | 对照 dot、norm、cosine_similarity 三步实现 |
下一章预告:用真实 Embedding 模型跑一遍,亲眼看”代码质量”和”天气真好”的相似度到底差多少!