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 完整流程:从查询到排序

咱们把整个流程串起来:

  1. 预处理:分词、去停用词、提取词干。
  2. 计算全局 IDF:统计每个词在多少篇文档中出现过。
  3. 文档向量化:对每篇文档,计算每个词的 TF-IDF,生成向量。
  4. 查询向量化:对用户输入的查询,同样计算 TF-IDF(用全局 IDF)。
  5. 计算余弦相似度:查询向量与每篇文档向量做点积,除以长度。
  6. 排序输出:按相似度从高到低排序,返回 Top N 结果。

注意: 实际搜索引擎不会真的遍历所有文档。那太慢了。一般先用倒排索引召回候选集,再对候选集做余弦相似度计算。这一步叫「粗排 + 精排」。

4.6 向量空间模型的局限性

说了这么多优点,也得聊聊缺点。

向量空间模型假设词与词之间是独立的。但现实不是这样。「苹果」和「水果」明明相关,模型却把它们当成两个独立的维度。

另外,它无法处理语义相似。比如「苹果手机」和「iPhone」,在向量空间里可能距离很远,但语义上是一回事。

怎么解决?

  • 引入词嵌入(Word Embedding),比如 Word2Vec、GloVe。
  • 或者用 LSI(潜在语义索引),对词-文档矩阵做降维。

不过,这些是后面章节的内容了。咱们先把基础打牢。

我的建议: 如果你刚开始做搜索引擎,先用 TF-IDF + 余弦相似度。它简单、高效、可解释性强。等遇到瓶颈了,再考虑更复杂的模型。别一上来就搞深度学习,容易翻车。

4.7 本章小结

这一章咱们讲了:

  • TF-IDF 怎么算,为什么它能衡量词的重要性。
  • 文档和查询怎么变成向量。
  • 余弦相似度怎么计算,为什么用它。
  • 完整的检索流程。
  • 向量空间模型的优缺点。

下一章,咱们会聊聊怎么优化这个流程,让检索更快、更准。敬请期待。


公众号:蓝海资料掘金营,微信deep3321