19.向量化的计算
文章目录
1 | # pip install langchain langchain_community |
这段代码的核心目的是:用数学方法计算“皇帝”和“皇后”这两个词在语义上有多接近。它先把文字转成数字(向量),然后用余弦相似度公式算出它们的相似度分数。
下面我把代码拆成三部分,重点把余弦相似度给你掰开揉碎讲明白:
1. 代码在做什么(整体流程)
- 第1步:调用
DashScopeEmbeddings()(阿里云通义千问的向量模型),把文字“皇帝”变成一串1536维的数字列表(向量),同样把“皇后”也变成向量。 - 第2步:计算这两个向量之间的余弦相似度,得出一个0~1之间的分数(越接近1表示语义越相似)。
2. 什么是“余弦相似度”(核心重点)
抛开复杂的数学符号,用最直白的话说:
余弦相似度,就是看两根“箭头”(向量)在空间里指向的方向是不是一致的。它不管箭头有多长(不管文字长短),只管箭头指向哪里(只管语义方向)。
- 如果两个向量指向完全相同的方向(夹角为0度),相似度 = 1(比如“皇帝”和“皇上”)。
- 如果两个向量指向完全相反的方向(夹角180度),相似度 = -1(极少出现)。
- 如果两个向量方向垂直(夹角90度),相似度 = 0(完全不相关,比如“皇帝”和“苹果”)。
对于文本语义向量,由于向量各维度通常都是非负值,所以相似度基本落在 0 ~ 1 之间。
公式拆解(对应代码中的手搓部分):
余弦相似度 = (向量1 · 向量2) ÷ (|向量1| × |向量2|)
- 分子(top):
向量1 @ 向量2,即点积。把两个向量对应位置的数字相乘,再全部加起来。这个值代表“两个箭头在方向上重合的力度”。 - 分母(bottom):
|向量1| × |向量2|,即两个向量的“模”相乘。模就是向量的长度(从原点(0,0)到箭头尖端的直线距离)。代码里通过sqrt(所有维度的平方和)来算这个长度。 - 为什么除以分母:为了消除文本长度(向量长度)的影响。比如“皇帝”和“中国历史上伟大的皇帝”,后者向量会很长(模很大),如果不除以长度,点积会虚高。除以长度后,只看方向,不看长短,结果才公平。
3. 对照代码,逐行看计算过程
第一步:向量化(把字变成坐标)
1 | model = embeddings.DashScopeEmbeddings() |
这相当于把“皇帝”和“皇后”两个词,映射到1536维空间里的两个坐标点。
第二步:手搓余弦相似度(代码里注释掉的部分,但逻辑最清晰)
- 算分子(top):
1
top = np.array(e1) @ np.array(e2) # 对应位置相乘再相加
- 算分母(bottom):
- 先算向量1的模(长度):
distance1 = sqrt(0.12² + (-0.05)² + 0.89² + ...),代码里用for循环累加平方和再开根。 - 同样算向量2的模
distance2。 bottom = distance1 * distance2
- 先算向量1的模(长度):
- 算最终相似度:
1
cos = top / bottom
第三步:API快捷方式(一行搞定)
1 | cos = np.dot(e1,e2) / (np.linalg.norm(e1) * np.linalg.norm(e2)) |
np.dot(e1,e2)= 点积(分子)。np.linalg.norm(e1)= 直接算向量的模(长度),完全等价于上面手搓的那一堆for循环。
🎯 最终总结(一句话记忆法)
余弦相似度 = 两个向量的“方向一致性”。它通过“点积 ÷ 长度乘积”,把文字的语义浓缩成一个0~1的小数。在这段代码里,因为“皇帝”和“皇后”语义相近,所以算出来的相似度会很高(比如0.85以上),这就证明向量模型成功捕捉到了这两个词的内在关联。这就是AI做语义搜索、RAG检索时的底层数学原理。
你的代码里总共用了 2种 方法来实现余弦相似度的计算。
这两种方法的数学原理完全一样,区别只在于:第一种是你自己一步步手算(手搓),第二种是偷懒直接调用现成的计算工具(调包)。
我用买菜算账的大白话给你捋一遍:
方法1:手搓算法(纯手工计算)
也就是代码中被注释掉的那一大段(# 手搓 的部分)。这就像你不用计算器,拿着笔在草稿纸上一步步算:
- 第一步(算分子):你把两个向量对应的数字挨个相乘,然后把所有的乘积加起来(
np.array(e1)@np.array(e2))。这相当于算“两个箭头的重合力度”。 - 第二步(算分母):你先分别算出两个向量的“长度”(模)。怎么算呢?就是把向量里每个数字都平方一下,全部加起来,最后再开个根号(
np.sqrt)。这相当于分别量出两根箭头的物理长短。 - 第三步(做除法):用刚才算出来的“重合力度”除以“两根箭头的长度相乘”,得出的结果就是相似度。
特点:步骤特别琐碎,甚至为了算“长度”还专门写了
for循环。但好处是每一步逻辑都摆在你眼前,看得一清二楚。
方法2:调用现成的数学工具包(API快捷计算)
也就是代码里最后那一行没被注释掉的(cos = np.dot(e1,e2) / (np.linalg.norm(e1) * np.linalg.norm(e2)))。这就像你直接掏出一个功能强大的多功能计算器按两下:
- 你不需要自己写循环去累加相乘了,直接用
np.dot()这个现成函数,它就把“分子”(点积)算好了。 - 你也不需要去写平方和开根号了,直接用
np.linalg.norm()这个现成函数,它就直接把向量的“长度”(模)吐出来了。 - 最后你再把它们除一下,结果就出来了。
特点:代码极其简洁,只需要一行。因为
numpy这个库底层是用更快的语言(C语言)写的,所以这种方法不仅省事,而且计算速度比你自己写for循环要快得多。
总结一句话
代码里共 2种 实现:
- 手搓法:自己写循环算点积、算长度、算除法(看得懂原理,但啰嗦)。
- 调包法:直接拿现成的
dot(点积函数)和norm(求模函数)来用(简洁高效,推荐实际使用)。
特别注意:代码里最上面的 model.embed_query() 是“向量化”(把文字变成数字)的过程,这是准备工作,不算在“计算相似度”的方法里。真正的“计算方法”,就上面这俩。😊