4、向量空间模型:TF-IDF权重计算、余弦相似度、文档向量化、查询向量化
好,咱们进入第四章节。这一章可以说是整个相关性计算的基石。
你想想看,搜索引擎怎么知道一篇文档跟你的查询「像不像」?
答案就在向量空间模型里。说白了,就是把文档和查询都变成数学上的向量,然后算一算它们之间的夹角。夹角越小,越相关。
我当年刚入行时,觉得这玩意儿太抽象了。直到亲手实现了一个简易的搜索引擎,才发现——嗯,真香。
4.1 从词频到权重:TF-IDF 的诞生
先问一个问题:一个词在一篇文档里出现很多次,它一定重要吗?
不一定。比如「的」、「了」、「是」这些词,出现一万次也没用。它们叫停用词。
那怎么衡量一个词的重要性呢?
业界最经典的做法就是 TF-IDF。它由两部分组成:
- TF(词频):这个词在文档里出现了多少次。出现越多,越可能重要。
- IDF(逆文档频率):这个词在多少篇文档里出现过。出现范围越广,越没区分度。
公式其实很简单:
TF = 某个词在文档中出现的次数 / 文档总词数
IDF = log(文档总数 / 包含该词的文档数 + 1)
TF-IDF = TF × IDF
我个人习惯把 IDF 加个平滑项,防止分母为零。你想想看,如果一个词在所有文档里都没出现过,那分母就是 0,直接报错。
核心理解: TF-IDF 的本质是「惩罚常见词,奖励稀有词」。一个词在单篇文档里出现多,但在整个文档集里出现少,那它大概率就是这篇文档的主题词。
我在项目中遇到过这样一个坑:直接用原始词频做 TF。结果长文档天然占便宜,因为词多。后来我改用「归一化词频」,也就是除以文档总词数,才公平。
4.2 文档向量化:把文章变成数学
有了 TF-IDF 权重,我们就可以把每篇文档变成一个向量。
怎么做?
假设整个文档集有 10 万个不同的词。那每篇文档就是一个 10 万维的向量。每一维的值,就是这个词在这篇文档里的 TF-IDF 权重。
举个例子:
文档1:「我喜欢苹果」
文档2:「我喜欢香蕉」
假设词典为:["我", "喜欢", "苹果", "香蕉"]
文档1向量:[TF-IDF("我"), TF-IDF("喜欢"), TF-IDF("苹果"), 0]
文档2向量:[TF-IDF("我"), TF-IDF("喜欢"), 0, TF-IDF("香蕉")]
你看,没出现的词,维度就是 0。这就是典型的稀疏向量——大部分维度都是 0。
小技巧: 实际工程中,我们不会真的存一个 10 万维的数组。太浪费内存了。一般用稀疏矩阵或者倒排索引来存。我早期用 Python 的 scipy.sparse,后来改用 C++ 手写,性能提升了一个数量级。
4.3 查询向量化:用户在想什么
文档能向量化,查询当然也可以。
用户输入「苹果好吃吗」,我们把它当成一篇超短的文档。同样计算每个词的 TF-IDF 权重。
注意:查询的 IDF 还是用全局的 IDF,不能重新算。因为查询本身没有文档集。
我见过新手犯这个错:把查询当成独立文档,重新算 IDF。结果查询里出现「苹果」这个词,IDF 变成了 log(1/1) = 0,权重直接归零。这显然不对。
避坑指南: 查询向量化时,TF 可以用原始词频(因为查询很短),但 IDF 必须用预先计算好的全局 IDF。我曾经因为这个 bug,排查了整整两天,最后发现相关性排序全乱了。
4.4 余弦相似度:算一算有多像
现在,文档和查询都变成了向量。怎么比较它们?
最常用的就是余弦相似度。
公式:
cosine_similarity = (A · B) / (||A|| × ||B||)
说白了,就是两个向量的点积,除以它们长度的乘积。
结果在 -1 到 1 之间。1 表示完全一样,0 表示毫不相关,-1 表示完全相反。
为什么用余弦?
- 它只关心方向,不关心长度。一篇长文档和一篇短文档,只要主题一致,余弦值就高。
- 计算简单,适合大规模检索。
我举个例子:
查询向量 Q = [0.5, 0.3, 0, 0.2]
文档向量 D = [0.4, 0.2, 0.1, 0.3]
点积 = 0.5×0.4 + 0.3×0.2 + 0×0.1 + 0.2×0.3 = 0.20 + 0.06 + 0 + 0.06 = 0.32
||Q|| = sqrt(0.5² + 0.3² + 0² + 0.2²) = sqrt(0.25 + 0.09 + 0 + 0.04) = sqrt(0.38) ≈ 0.616
||D|| = sqrt(0.4² + 0.2² + 0.1² + 0.3²) = sqrt(0.16 + 0.04 + 0.01 + 0.09) = sqrt(0.30) ≈ 0.548
余弦相似度 = 0.32 / (0.616 × 0.548) ≈ 0.32 / 0.337 ≈ 0.95
0.95,非常相关。嗯,这个结果符合预期。
4.5 完整流程:从查询到排序
咱们把整个流程串起来:
- 预处理:分词、去停用词、提取词干。
- 计算全局 IDF:统计每个词在多少篇文档中出现过。
- 文档向量化:对每篇文档,计算每个词的 TF-IDF,生成向量。
- 查询向量化:对用户输入的查询,同样计算 TF-IDF(用全局 IDF)。
- 计算余弦相似度:查询向量与每篇文档向量做点积,除以长度。
- 排序输出:按相似度从高到低排序,返回 Top N 结果。
注意: 实际搜索引擎不会真的遍历所有文档。那太慢了。一般先用倒排索引召回候选集,再对候选集做余弦相似度计算。这一步叫「粗排 + 精排」。
4.6 向量空间模型的局限性
说了这么多优点,也得聊聊缺点。
向量空间模型假设词与词之间是独立的。但现实不是这样。「苹果」和「水果」明明相关,模型却把它们当成两个独立的维度。
另外,它无法处理语义相似。比如「苹果手机」和「iPhone」,在向量空间里可能距离很远,但语义上是一回事。
怎么解决?
- 引入词嵌入(Word Embedding),比如 Word2Vec、GloVe。
- 或者用 LSI(潜在语义索引),对词-文档矩阵做降维。
不过,这些是后面章节的内容了。咱们先把基础打牢。
我的建议: 如果你刚开始做搜索引擎,先用 TF-IDF + 余弦相似度。它简单、高效、可解释性强。等遇到瓶颈了,再考虑更复杂的模型。别一上来就搞深度学习,容易翻车。
4.7 本章小结
这一章咱们讲了:
- TF-IDF 怎么算,为什么它能衡量词的重要性。
- 文档和查询怎么变成向量。
- 余弦相似度怎么计算,为什么用它。
- 完整的检索流程。
- 向量空间模型的优缺点。
下一章,咱们会聊聊怎么优化这个流程,让检索更快、更准。敬请期待。
公众号:蓝海资料掘金营,微信deep3321