这道题其实是在问一个很本质的取舍:检索系统到底是要精准的关键词匹配,还是要语义上的泛化理解。我理解,[[稀疏检索]]和[[稠密检索]]分别代表了这两个极端,而真正落地的时候,我们往往需要把两者结合起来。
先说稀疏检索,典型代表就是 [[BM25]] 和 [[TF-IDF]]。它的思路很简单,把文本拆成词,每个词作为一个维度,整个词表可能有几十万维,但大部分是0,所以叫稀疏。相似度度量就是看词有没有匹配上,再结合词频算个加权分数。它的优势是 精确匹配能力极强,比如搜一个订单号、错误码、或者像“麦当劳”这种专有名词,它一定能命中。而且它不需要训练,上线就能用,可解释性也好,出了问题好排查。
但它的短板也很明显:对语义改写、同义词、口语化表达不敏感。比如用户说“好吃的汉堡”,它可能就匹配不上“美味汉堡”这种同义表述,因为字面不一样。
再来看稠密检索,它走的是另一条路。通过 [[Bi-Encoder]] 这类双塔模型,把文本压缩成一个低维的稠密向量,比如768维,然后用 余弦相似度 或者点积来衡量语义距离。它的优势是 语义泛化能力强,能理解“好吃的汉堡”和“美味汉堡”其实是一个意思,甚至跨语言也能做。
但这里有个坑:稠密检索对长尾、低频词覆盖不好。比如某个很冷门的专业术语,训练数据里出现的次数少,向量表达就不够准确。另外,它对精确标识符、ID这类东西天生不敏感,比如搜“订单号12345”,它可能把12345当成语义的一部分,反而匹配到不相关的内容。
所以你看,两者正好互补。稀疏检索擅长精确匹配和长尾词,稠密检索擅长语义泛化和同义改写。那在实际系统里,比如美团做POI搜索,用户既会搜“麦当劳”这种精确品牌,也会搜“附近好吃的汉堡”这种语义意图,单一检索肯定搞不定。所以 [[混合检索]] 就成了工业界的标配做法。
具体落地,我一般会分成两阶段:第一阶段并行召回,BM25 和 向量检索 各取Top-K,然后做融合排序。融合的方式可以是线性加权,比如 score = α·BM25_score + β·dense_score,这个α和β需要根据业务调优,或者用 LTR 模型学一个更复杂的融合。第二阶段再用 [[Cross-Encoder]] 做精排,把混合召回的候选集重新打分。
这里有一个风险点:混合检索的前提是两边的分数要可比。如果BM25分数和向量分数不在同一个量级,直接加权会出问题。常见的失败场景是,向量检索的分数普遍偏低,结果融合后BM25完全主导,混合就失去了意义。所以上线前我会特别关注分数归一化,比如用min-max归一化或者分位数归一化,让两边分数对齐。
另外,还有一个有意思的方向是 [[ColBERT]] 这种延迟交互模型,它不把文本压缩成一个向量,而是保留每个token的向量,最后算交互分数。这个思路其实介于稀疏和稠密之间,既保留了词级别的精确匹配,又有语义泛化能力,但代价是计算量更大。
所以我会把稀疏检索和稠密检索看成是互补的伙伴,而不是对手。我更倾向用混合检索加两阶段架构,在精确和泛化之间取一个平衡。具体到权重怎么调、用什么归一化,那就要看业务数据的特点了。