最近在做一个人事政策问答的RAG项目,文档大概两千多份,都是制度文件。一开始用bge-m3的稠密向量,top5召回还行,但很多专有名词(比如“年休假折算”)匹配不到,就加了BM25做混合检索,权重调成0.5/0.5。结果测试集上F1反而掉了4个点,尤其长文档片段被错误地排到前面。想问问大家,混合检索的权重一般怎么调?有没有必要先做查询改写再走两路召回?还是说直接换多向量模型更省事?刚入坑RAG,感觉每个环节都是坑,求指点。
RAG检索用稠密向量还是稀疏向量?混用效果反而变差了
全部回复
共 32 条说实话你这个情况我太熟了,bge-m3配BM25看着是标配,但真跑起来经常互相拖后腿。我个人感觉问题不一定出在权重上,而是两路召回的分数分布本身就不在一个量纲,0.5/0.5硬融合反而把稠密向量那边的高质量结果给稀释了,尤其长文档片段在BM25里天然容易拿高分。你可以试试先不看权重,单独把两路召回结果分别看top20,观察一下是不是BM25把那些带专有名词的短query拉偏了。要我说,对人事政策这种术语密集的场景,不如先做轻量级查询改写,比如把“年休假折算”拆成“年休假”和“折算”再分别走两路,比直接调权重更有效。多向量模型我也试过,像colbert那类,效果确实稳,但2000多份文档的索引开销和维护成本你得掂量下。还有个土办法,你可以在融合前对BM25的得分做min-max归一化,或者直接砍掉BM25里过长片段的结果,很多坑是数据分布造成的,不是算法问题。我刚入坑时也总想着调参,后来发现先清洗query和文档分块策略比换模型优先级高多了。
试试对查询里的专有名词做加权,别直接五五开,长文档片段得加位置惩罚或者截断。
这问题我太有同感了,之前做合同审核RAG也踩过一模一样的坑。混合检索不是简单加权就能1+1>2的,你F1掉4个点很可能不是权重问题,而是两路召回结果在融合时没有做去重和位置重排,长文档片段天然容易被BM25的高词频带上来。权重这块我建议先单独测两路的上限,比如稠密向量top10里有多少是对的,稀疏top10里有多少是对的,再根据你测试集里专有名词和长文档的比例去反推权重,别直接拍0.5。查询改写我个人觉得是必须的,尤其是“年休假折算”这种词,不改写的话两路都容易把它拆碎。但注意改写别用大模型生成太口语化的query,最好做个同义词扩展或者NER识别,把术语原文保留下来。多向量模型像ColBERT确实能缓解术语匹配问题,但部署和调参成本也不低,你才两千文档,不如先试试把BM25的k1和b参数调一调,默认值对长文档很不友好。最后想问下,你混用时有没有做score归一化?我见过好多人直接拿bm25原始分和余弦相似度相加,那量纲都不一致,效果铁定崩。
bge-m3本身是支持稀疏向量的,你可以试试直接用它的混合向量,而不是硬叠BM25,这样语义和词汇层面能对齐。权重0.5/0.5确实容易让长文档因为词频高占便宜,我一般从0.3/0.7或者0.7/0.3开始调,看具体badcase再动。查询改写对专有名词挺有用,但别搞太复杂,简单做个同义词扩展就行。你那个“年休假折算”匹配不到,会不会是文档里压根没写全称,先查查索引里有没有这个词。
你这情况我也踩过,bge-m3配BM25权重0.5/0.5确实容易翻车,尤其长文档片段会被BM25的关键词命中带偏。我后来是把稠密向量权重提到0.7,BM25只用来兜底专有名词,效果才稳回来。查询改写我觉得值得试,把“年休假折算”扩成“年休假未休天数补偿计算”这种,两路召回质量都会上去。多向量模型像ColBERT倒是省心,但你这文档量级不算大,先调权重和改写性价比更高。
同款坑踩过,bge-m3在专有名词上确实容易哑火。权重0.5/0.5太粗暴了,我后来把BM25压到0.3,稠密保持0.7,效果就回来了,长文档乱序大概率是稀疏向量被长度带偏了。
查询改写挺值得试,但别搞太复杂,我把政策名称和术语先拆出来强制走BM25,其他走稠密,F1涨了差不多3个点。多向量模型成本高,你两千文档量级没必要。
建议先查一下你是不是直接拼了原始分数,没做归一化,这坑我见过好多次。混合检索的关键是让两路结果在不同语义层级上互补,而不是简单加权。
建议先做查询改写,把“年休假折算”这类词扩写一下再走双路,权重往稠密那边偏点试试。
说实话你这问题我也踩过,bm25和稠密向量混用不是简单加权就行,两者分数分布不在一个量级上,直接0.5/0.5容易让长文档靠词频刷上去。建议先各自召回top20再按RRF融合,或者用min-max归一化后再加权,权重从0.3/0.7开始试。
至于专有名词,我觉得问题不在向量类型,而是bge-m3对这类组合词切分不敏感,你试试在索引前做个实体词典替换,把“年休假折算”先映射成标准词,可能比换模型更直接。查询改写对长尾query有效,但政策类问法本身就挺规范,收益未必大。
多向量模型得看你预算,训练和推理成本高不少,如果文档量不大,先把预处理和融合策略调明白更划算。
看到你这个情况我其实挺有同感的,bge-m3在专有名词上确实容易犯迷糊,但BM25一进来长文本干扰的问题我这边也踩过。你F1掉4个点我猜不是权重的问题,而是两路召回在score层面直接相加太粗暴了——稠密和稀疏的分布压根不在一个量级上,0.5/0.5这个比例其实没什么物理意义。我后来是先把两路结果各自做min-max归一化,再按0.6/0.4去融合,效果就稳了不少,你可以先试试这个。至于查询改写,我觉得对你这场景反而是必须的,因为“年休假折算”这种词,你直接拆成“年休假”和“折算”做BM25都会好很多,但前提是改写别把原意带偏了。多向量模型我倒觉得不是万能的,尤其你文档里长制度文件多,它照样会面临片段切分和语义重叠的问题,不如先检查下你的chunk大小是不是太大,导致一段里混了多个不相关的小知识点。还有个小坑,你测试集评估的是长文档片段,那排序的时候是不是该加个位置惩罚,或者限定只从标题命中的段落里找答案?反正别急着换模型,先把两路召回的score对齐和chunk粒度调一调,大概率能救回来。
试试把权重调到0.3/0.7再跑几组,BM25那路加个长度惩罚,长文档截断再进重排。
权重0.5/0.5本来就容易翻车,稠密和稀疏的分数分布根本不在一个量级,直接线性加权会让BM25那路把长文档顶上来。建议先分别做归一化再用RRF融合,比拍脑袋调权重靠谱得多。BM25对“年休假折算”这种词确实强,但长文档命中次数多会虚高,可以试试加个文档长度惩罚或者用BM25的b参数压一压。查询改写我觉得优先级更高,先把口语化问题转成制度里的标准表述,两路召回质量都会上去。
权重0.5/0.5确实容易出问题,稀疏那路对长文档特别敏感,BM25分数会被文档长度带偏,排到前面很正常。我一般先用RRF融合看基线,再按业务调,专有名词多就把稀疏压到0.3左右试试。查询改写挺值得做的,尤其人事这种缩写和口语混着来的场景,改完再走两路召回命中率能提不少。多向量模型不一定省事,部署和延迟都是成本,先把融合策略和改写跑通更划算。