Neo4j:字面、语义、词汇表:三类"敏感"各要一种检索来治

1 阅读7分钟

一句话价值:检索效果不好时,大多数人的第一反应是"换个更强的 embedding"。但真正的问题是——你没分清是哪一类东西没被治住。这篇把"检索要找什么"拆成三类敏感:字面敏感、词义敏感、词汇表敏感,各自对应一种检索。分清之后,选型不再靠感觉。


一个被问反了的问题

经常听到这样的讨论:

"我们是不是该上 Elasticsearch 了?它对词义比较敏感。"

这句话本身就问反了。Elasticsearch 恰恰是三种检索里最不"懂词义"的那个。

它擅长的是字面命中——你查"退款流程",它保证含这四个字的文档一定被捞出来。至于"如何把钱退回来"这种换个说法的问法,它基本无能为力。

问题的根源在于,"敏感"这个词被混用了。检索里其实是三种完全不同的敏感,需要三种完全不同的工具:

敏感类型关心的是谁的主场谁无能无力
字面敏感这个词必须原样命中倒排索引 / BM25向量
词义敏感换个说法也要懂向量关键词检索
词汇表敏感得先对齐到同一个实体别名 / 实体链接两种都不行

下面用一个贯穿案例把它们逐层剥开。


案例:那个查不到的"台式奶茶"

我们建了一张奶茶图谱。节点叫 台湾奶茶,用户问的是:

台式奶茶的饮品都有哪些配料?

("台式奶茶"是台湾奶茶的常见俗称,两者指同一个东西。)

先看纯关键词检索这条路——把"台式奶茶"丢进倒排索引,会发生什么?

查不到。

因为倒排索引里压根没有"台式"这个词元。索引里存的是节点名"台湾奶茶"、描述文本里出现的"台湾"。用户说的"台式",系统从没见过。

所以这里的关键结论是:

这不是字面敏感问题,也不是词义敏感问题。这是词汇表敏感问题——用户用的词和你存的词是两个词。

用 ES 裸查,同样漏。指望向量蒙,实测只把 台湾奶茶 排到第 7 位(相似度 0.8031),进不了候选。

这类问题,两种检索都救不了。 必须显式告诉系统:"台式"和"台湾"指同一个东西。


第一类:字面敏感——倒排索引的主场

先补上 Elasticsearch 的真实价值。它不可替代的场景是精确命中:

  • 专有名词、编号、型号 订单号 SO20260901-8842、iPhone 15 Pro Max、药品通用名、SKU 编码。 这类 token 是低频的,embedding 模型在训练语料里几乎没见过——在向量空间里它基本是个随机点。BM25 反而稳如老狗。

  • 长尾词、新词、造出来的词 同理,模型没见过的词,向量给不出有意义的距离,倒排索引照样一次命中。

  • 精确过滤与排序 时间范围、价格区间、类目、状态。

  • 可解释性 命中了哪个词、为什么命中,一目了然。向量只能给你一个 0.83,说不清为什么。

一句话:ES 的价值是"保证字面不漏",不是"理解语义"。 把它当"更懂词义"的工具就错位了。


第二类:词义敏感——向量的主场

反过来说,向量治的是另一类病。看这个真实提问:

喝了会上瘾、甜甜的那种奶茶,用了什么工艺?

没有任何一个字是图谱里的节点名。"会上瘾""甜甜"这些词,在库里一个都搜不到(字面检索会返回空)。但向量能接住——它把这句话编码后,召回:

冲泡(0.854)  牛奶(0.852)  珍珠奶茶(0.847)  果糖(0.846)  ...

珍珠奶茶 排到了第三。 因为它描述的"口感香甜浓郁、喝了就上瘾"跟这句问话语义最近。

这就是向量的看家本领:模糊表述、口语化提问、换个说法。这是倒排索引完全做不到的。

反过来,向量也有它彻底无能的地方——同形异义。"苹果"是水果还是那家公司?向量把它们编码得很近(因为上下文里它们确实常一起出现),它分不出来。这种歧义消解得靠上下文,不是靠检索。


第三类:词汇表敏感——别名表的主场

回到"台式/台湾"。这类问题的本质是:用户嘴里的词,和你库里的词,是两个词。

用户说图谱里登记的名字
台式奶茶台湾奶茶
波霸奶茶珍珠奶茶
粉圆珍珠

解法只有一个方向:显式登记这份对应关系——也就是上一篇讲的别名表:

台湾奶茶  →  aliases: ["台式奶茶", "台茶"]
珍珠奶茶  →  aliases: ["波霸奶茶", "珍奶"]

然后把别名一起索引进全文索引,用户说"台式奶茶",倒排索引里就有"台式"这个词,直接命中 台湾奶茶 节点。

这和 ES 的 synonym 词典是同一件事

如果你用过 Elasticsearch,这个思路你一定见过——analyzer 里配同义词:

"台式,台湾"  →  归一化到同一个 term

别名词典和 synonym 词典,本质是同一个东西:一份人工维护、可控、可审计的词表,用来做实体对齐。

为什么必须靠词表,而不能靠模型:

  • 词表是确定性的。配了 台式→台湾,它就一定生效,可测试、可回归。
  • 词表是可审计、可运维的。对齐错了,加一条、删一条就行。
  • 模型对齐是概率性的。这次蒙对了你没底,蒙错了你没法跟人解释"为什么这台机器昨天答对了今天答错了"。

在真实业务里,这种确定性本身就是价值。


三路合起来:一张分工表

把三类敏感和三种检索对上:

检索路治什么在"台式奶茶"上的表现在"喝了会上瘾"上的表现
倒排 / 关键词字面命中裸查也漏(词元不存在)漏(没有字面词)
向量语义相近排第 7,没进候选✅ 命中 珍珠奶茶
别名 / 全文+词表词汇表对齐✅ 台式 → 台湾一般
图多跳关系——多跳本身没问题,卡在入口

看出来了吗——没有哪一路是"更强的检索",它们是三个不同的入口。

真实架构里它们叠着用:多路召回 → 融合(RRF)→ 重排(rerank)→ 再交给 LLM。这不是堆料,是因为每路捞到的东西本来就不一样。

而"词汇表"这一路最容易被忽略,因为它不像向量那样有"AI 感"——它就是一张手写的表。但没有它,"台式奶茶"这类问题永远答不对。


收个口:一个自检清单

下次检索效果不好,先别急着换模型。按这个顺序问:

  1. 用户说的词,我库里存的是同一个词吗? 不是 → 别名词表问题。加别名,或配同义词。
  2. 问题是"换个说法",还是"精确某个词"? 换个说法 → 向量。精确某个词(编号、型号)→ 倒排。
  3. 答案需要沿着关系走两步以上吗? 需要 → 图。不需要 → 向量 + 关键词就够。

大多数"检索不准"的抱怨,卡在第 1 条上。而它恰恰是最不需要 AI 的那一条。


本篇小结

  1. "检索敏感"其实分三类:字面敏感、词义敏感、词汇表敏感,各要一种工具。
  2. 倒排索引治字面:编号、型号、专有名词、长尾词——模型没见过的词,它最稳。它不理解词义。
  3. 向量治语义:口语化、模糊表述、换个说法。但它对同形异义(苹果/Apple)无能无力。
  4. 别名表治词汇表:用户说的词和你存的词对不上时。这和 ES 的 synonym 词典是同一件事——确定、可控、可审计,不靠模型猜。
  5. 三路不是"谁更强",是三个不同入口,真实架构里叠着用。

系列预告

下一篇落到工程:既然要三路,是不是得上一套 Milvus + 一套 Elasticsearch?不一定。 一个 Neo4j 就能把向量索引、全文索引和多跳遍历全装下——而且因为向量召回直接返回节点,它反而能把"召回"和"遍历"接在同一个查询里。