一眼看穿相似度:余弦相似度原理详解
前言
在上一篇《手把手实现文本向量数据库》中,我们用余弦相似度来判断两段文字是否相似。但有一个关键问题没讲清楚:
为什么用一个数学公式就能判断"语义是否相似"?
这篇文章,我们专门聊聊余弦相似度背后的原理。
读完这篇,你就明白:
- 余弦相似度是怎么算的
- 为什么它适合判断文本相似度
- 为什么不用正弦或其他方法
一、先说个直观的例子
还记得我们知识库里的两句话吗:
句子A:"阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务" 句子B:"该任务将使用太空发射系统火箭和猎户座飞船"用户问:“月球任务是什么?”
你觉得哪句话更相关?
肯定是句子A对吧?因为A里有"月球"、"任务"这些词。
电脑也是这么想的。但它不会"感觉",它只会"算"。
二、把句子变成数字
我们用词汇表把句子变成一串数字:
词汇表:阿尔忒弥斯、月球、任务、宇航员、火箭、飞船、测试、系统、飞行、轨道句子A出现了哪些词?数一数:
"阿尔忒弥斯2号是...载人月球轨道飞行任务" → 阿尔忒弥斯(1)、月球(1)、任务(1)、飞行(1)、轨道(1) 向量A = [1, 1, 1, 0, 0, 0, 0, 0, 1, 1]句子B出现了哪些词?
"该任务将使用太空发射系统火箭和猎户座飞船" → 任务(1)、火箭(1)、飞船(1)、系统(1) 向量B = [0, 0, 1, 0, 1, 1, 0, 1, 0, 0]现在两句话都变成数字了:
阿尔忒弥斯 月球 任务 宇航员 火箭 飞船 测试 系统 飞行 轨道 句子A: 1 1 1 0 0 0 0 0 1 1 句子B: 0 0 1 0 1 1 0 1 0 0三、怎么算"像不像"?
方法一:直接比差距
把两个向量减一减,看差多少:
差距 = |1-0| + |1-0| + |1-1| + ... = 1+1+0+0+1+1+0+1+1+1 = 7差距是7,看起来不太像?
但问题是:如果一个句子很长,出现很多词,另一个很短,差距就大了。可它们可能说的都是同一件事!
方法二:看"方向"像不像
我们换个思路:不管句子长短,只看"主要讲啥"。
想象一下,站在操场中央:
- 句子A往"月球方向"走
- 句子B往"火箭方向"走
走的方向不一样,所以不像。
但如果:
- 句子A说:“月球任务月球任务”("月球"出现2次,"任务"出现2次)
- 句子B说:“月球任务”
方向是一样的!都是往"月球任务"方向走。
这就是余弦相似度的核心思想:
方向一致 = 内容相似
四、用直角坐标系来理解
还记得学校里学的直角坐标系吗?画个十字,横轴竖轴。
我们把词汇表简化一下,只看两个词:月球、火箭
句子A:"月球任务" → 月球(1),火箭(0) 句子B:"火箭发射" → 月球(0),火箭(1) 句子C:"月球火箭" → 月球(1),火箭(1)画在坐标系里:
Y轴(火箭) ↑ | ● 句子B(月球0,火箭1) | | | ● 句子C(月球1,火箭1) | / | / -----●--------/----→ X轴(月球) | / | / | ● 句子A(月球1,火箭0) |从原点(0,0)到每个点画一条线:
- 句子A往右走(月球方向)
- 句子B往上走(火箭方向)
- 句子C往右上方走(两个都有)
夹角越小 = 方向越像 = 内容越像
五、余弦是啥?为啥能判断像不像?
先记住这个规律
角度 余弦值 意思 0° → 1.0 完全一样 45° → 0.7 很像 60° → 0.5 有点像 90° → 0 完全不同结论:余弦值越接近1,越像!
有人问:为什么不用正弦?
好问题!我们来对比一下:
角度 余弦值 正弦值 哪个更合理? 0° → 1.0 0.0 余弦:完全一样 ✅ 正弦:??? 45° → 0.7 0.7 一样 90° → 0.0 1.0 余弦:完全不同 ✅ 正弦:完全一样?❌ 180° → -1.0 0.0 余弦:完全相反 ✅ 正弦:和0°一样?❌正弦有两个大问题:
问题一:方向相反也算"一样"?
- 两个句子意思完全相反(夹角180°)
- 正弦值 = 0
- 和夹角0°(意思完全相同)的正弦值一样!
- 没法区分"相同"和"相反"
问题二:直觉反了
- 我们希望:越相似 → 数值越大
- 正弦:θ=0°(相同)→ sin=0(最小值)
- 这让人很难理解
余弦刚刚好:
- 相同方向(0°)→ cos=1(最大)→ 相似度最高 ✅
- 垂直方向(90°)→ cos=0 → 完全无关
- 相反方向(180°)→ cos=-1 → 完全相反
六、实际计算演示
用户问:“月球任务是什么?”
先算用户问题的向量:
"月球任务" → 月球(1),任务(1) 问句向量 = [0, 1, 1, 0, 0, 0, 0, 0, 0, 0] (为了简化,只标出非0的位置)然后和知识库里的句子比一比:
和句子A比:
问句向量 = [0, 1, 1, 0, 0, 0, 0, 0, 0, 0] 句子A = [1, 1, 1, 0, 0, 0, 0, 0, 1, 1] ↑ ↑ ↑ ↑ ↑ | | | | | 阿尔忒弥斯 月球 任务 ... 飞行 轨道 第一步:算乘积之和(把对应位置乘起来,再加到一起) = 0×1 + 1×1 + 1×1 + 0×0 + ... = 0 + 1 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 = 2 ← 这个2代表:问句和句子A有2个词是"重叠"的(月球、任务) 第二步:算各自的"长度" 问句长度 = √(0² + 1² + 1² + 0² + ...) = √2 ≈ 1.41 ↑ 这个√2里的2代表:问句有2个词(月球、任务) 句子A长度 = √(1² + 1² + 1² + 0² + 0² + 0² + 0² + 0² + 1² + 1²) = √4 = 2 ↑ 这个2代表:句子A有4个"1",开根号后等于2 第三步:算相似度 2(乘积之和:共同词的数量) 相似度 = ─────────────────────────── = 2 / 2.82 ≈ 0.71 1.41 × 2(两个向量的长度相乘) ↑ ↑ | └── 句子A的长度 └── 问句的长度和句子B比:
问句向量 = [0, 1, 1, 0, 0, 0, 0, 0, 0, 0] 句子B = [0, 0, 1, 0, 1, 1, 0, 1, 0, 0] 乘积之和 = 0×0 + 1×0 + 1×1 + ... = 1 问句长度 = 1.41 句子B长度 = √(0+0+1+0+1+1+0+1+0+0) = √3 ≈ 1.73 相似度 = 1 / (1.41 × 1.73) ≈ 0.41结果对比:
句子A相似度 = 0.71 ← 更像! 句子B相似度 = 0.41对!句子A确实更相关,因为它有"月球"和"任务"两个词都匹配上了。
七、为什么这个方法好?
好处一:不怕句子长短
句子长了,词就多,向量里的数字就大。但没关系!
"月球月球月球" → 向量 [0, 3, 0, ...] "月球" → 向量 [0, 1, 0, ...] 相似度 = 1.0(完全一样!)因为余弦相似度只看"方向",不看"长度"。两个句子都在讲月球,方向一致!
好处二:结果好理解
相似度 意思 0.9+ 几乎一样 0.7-0.9 非常相关 0.5-0.7 比较相关 0.3-0.5 有点关系 0-0.3 基本无关就像考试打分,一眼就看懂。
好处三:能理解"意思相近"的词
知识库里写的是"执行",用户问的是"发射":
知识库:"任务计划在2025年执行" 用户问:"什么时候发射?"虽然词不一样,但"执行"和"发射"都会出现在相似的句子里,所以向量方向会接近,余弦相似度就会高。
这就是语义理解!
八、公式总结
完整公式
余弦相似度 = cos(θ) = (A · B) / (|A| × |B|) 其中: - A · B = a₁×b₁ + a₂×b₂ + ... + aₙ×bₙ (点积:对应位置相乘再相加) - |A| = √(a₁² + a₂² + ... + aₙ²) (向量长度:各元素平方和开根号) - |B| = √(b₁² + b₂² + ... + bₙ²)三个步骤
第一步:算点积(共同词的数量) 第二步:算长度(各自有多少词) 第三步:点积除以长度乘积九、代码实现
// 计算余弦相似度(三个步骤)funccosineSimilarity(a,b[]float64)float64{// 第一步:算乘积之和dotProduct:=0.0fori:=rangea{dotProduct+=a[i]*b[i]}// 第二步:算各自的"长度"lengthA:=0.0lengthB:=0.0fori:=rangea{lengthA+=a[i]*a[i]lengthB+=b[i]*b[i]}lengthA=math.Sqrt(lengthA)lengthB=math.Sqrt(lengthB)// 第三步:相除得到相似度iflengthA==0||lengthB==0{return0// 防止除以0}returndotProduct/(lengthA*lengthB)}对照上面的步骤,一行行看就懂了!
十、总结
余弦相似度 = 看"方向"像不像,不看"长度"差多少
核心思想:
- 把文字变成一串数字(向量)
- 用余弦公式算相似度
- 结果越接近1,内容越像
为什么用余弦不用正弦:
- 余弦:相同→1,不同→0,相反→-1(直观!)
- 正弦:相同→0,不同→1(反了!)
系列文章:
- Go + Ollama 构建tinyRAG应用:Prompt提示工程
- 用阿尔忒弥斯2号讲透文本向量化
- 本文:一眼看穿相似度:余弦相似度原理详解
- 下一篇:手把手实现文本向量数据库(预告)
系列文章:
- Go + Ollama 构建tinyRAG应用:Prompt提示工程
- 用阿尔忒弥斯2号讲透文本向量化
- 本文:一眼看穿相似度:余弦相似度原理详解
- 手把手实现文本向量数据库