3710 字
19 分钟
01 向量与余弦相似度:AI 如何判断"意思相近"
复习文档 · ADHD友好 · 从零开始
🔧 准确术语速查
| 术语 | 准确含义 | 本章对应 |
|---|---|---|
| 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 = 多维勾股定理 = 量箭的长度
2维向量:norm = √(分量1² + 分量2²)
vec_a = [3, 2] norm = √(3² + 2²) = √(9+4) = √13 ≈ 3.61
横着走了3步,竖着走了2步 箭的长度 = 3.61步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 方向其实一样!
→ 点积混了"长度"信息,必须削掉七、余弦相似度 = 削掉长度比方向 = 最终答案
完整流程三步:
余弦相似度 = 点积 / (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) - 余弦相似度
核心思想:只要标准一致,不管尺度大小十四、代码 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 模型跑一遍,亲眼看”代码质量”和”天气真好”的相似度到底差多少!
01 向量与余弦相似度:AI 如何判断"意思相近"
https://enkiud.com/posts/math-01/