资讯动态

071、Reranker模型与精排

发布时间:2026/9/17 2:18:23 来源:尧图企业网站定制
071、Reranker模型与精排老规矩先说个真事儿。上个月调一个问答系统的线上badcase用户问“苹果售后怎么预约”日志里召回阶段明明把“苹果官网预约服务”、“Apple Store天才吧预约”这些文档都拉出来了可排在第一位的是“苹果公司2023年财报分析”。当时第一反应是embedding模型没调好结果把召回top20挨个看发现召回没问题问题出在排序——我们当时压根没接精排直接用向量相似度排序结果“财报”这种词向量上跟“苹果”相关度太高把真正的售后意图给压下去了。后来我把同一组query和候选文档喂给一个cross-encoder重排第一位立刻变成了“iPhone电池维修预约攻略”问题当场解决。这就是Reranker也就是精排存在的意义。先说清楚召回和精排的分工。召回阶段为了快通常用双塔模型把query和doc分别编码成向量拿余弦相似度捞topK。双塔是个“非交互”结构query和doc在最后算相似度之前谁都见不到谁所以它必须把语义压缩成一个固定向量天然会丢失细节。而Reranker不一样它把query和doc拼接成一对输入里面的self-attention会让两者充分交互能捕捉到“苹果”出现在“售后”语境下该是什么角色。代价就是慢没法对全量文档跑只能在召回的几百条里重新排。Reranker的典型模型叫cross-encoder名字很直白就是让两个文本在编码器里交叉。跟双塔的sentence-bert那种bi-encoder相对。用起来倒是不复杂HuggingFace上随便拉一个模型就行。我这里写个用sentence-transformers的CrossEncoder的代码注意看注释都是我踩过的坑。fromsentence_transformersimportCrossEncoder# 别用太小的模型比如mini那种效果跟双塔差不多白费劲# 我常用的是 bge-reranker-base或者 mmarco-mMiniLMv2中文场景看情况modelCrossEncoder(BAAI/bge-reranker-base,max_length512)defrerank(query,docs,top_k10):# 这里把query和doc拼成pair别用字典列表就用元组列表pairs[(query,doc)fordocindocs]# 千万别一次把几千条都塞进去GPU显存爆了没人给你报销# 我一般分batch128或者256看你的卡scoresmodel.predict(pairs,batch_size128,show_progress_barFalse)# scores是float数组值越大越相关但别直接拿它当概率# 有的模型输出是0~1的sigmoid有的是logits别想当然resultslist(zip(docs,scores))results.sort(keylambdax:x[1],reverseTrue)return[docfordoc,_inresults[:top_k]]这段代码看起来简单实际调的时候有几个地方能让你怀疑人生。第一个是max_length你以为设个512就完事了我是做营销文案检索的有用户把广告词写成长图里的文字堆起来一个doc超过2000token。设置512后后面内容直接被截断导致精排结果跟召回差得离谱。后来我改成动态长度或者干脆预处理切段再聚合。第二个是score的分布不同训练好的模型输出范围完全不一样有的偏好大数有的偏小数。你想把精排分数跟业务分数合并时不归一化就是灾难。我现在的做法是先用softmax把当前batch的精排分数转成概率分布再跟CTR预估分数加权不然两个量纲打架。回归到这个场景精排不是简单调个接口。它在整个推荐/搜索流程里承担的角色是“最后一公里的纠偏”。召回管广精排管准。但精排不只有Reranker模型真正的工业级流程里精排层通常是“逻辑回归/GBDT 深度排序模型 业务规则”的混合体。Reranker模型只是其中提供语义信号的一路输入还得把价格、销量、时效、用户画像这些特征拼进去。之前我天真地以为上了cross-encoder就能解决所有排序问题结果测试集AUC涨了0.03线上点击率反而跌了。为啥因为那些业务特征在传统的精排模型里已经排序了你再加一个语义分等于是让模型捡了芝麻丢西瓜。所以现在我的习惯是先用Reranker模型在召回结果上做一个独立打分然后把打分结果作为特征喂给下游的LGBM或者深度模型。这个方案在技术叫“feeding the ranker”好处是后续模型能学到“语义相关度”跟业务指标的非线性关系而不是粗暴加和。调试Reranker还有一个隐藏坑负样本的构造。cross-encoder训练时正样本好找通常是点击过的query-doc对负样本呢你要是随机采样模型很快就能学会“只要不相干就是负样本”但它学不会“两个都相关但一个更相关”。这种困难负样本得从召回里挑那些分数高但没被点击的样本或者用另一个模型排出来的topK里人为标记错误项。我有次偷懒用BM25的top结果当负样本训出来的模型把“苹果”全当成了水果线上查“苹果手机壳”差点翻车。再聊一下延迟问题。cross-encoder慢是绕不开的尤其公司没有GPU推理集群的时候。CPU上跑一个base模型平均一个pair大概20毫秒看起来不高但一次检索可能召回500条那就是10秒直接超时。抠门老板又不肯买卡怎么办几种土办法第一个是缩小召回数top100以内因为精排提升主要靠前几十位的判别力太靠后本来就不该被看到。第二个是模型蒸馏拿大模型给一个小模型打标签让student学teacher的输出改成一个小cross-encoder速度能快3倍但性能保留七八成。第三个是跟粗排配合先用双塔或者更轻量的模型排两百条再让Reranker只精排前五十。这个过程千万别省我见过最惨的一次直接对全量一万条跑Reranker结果线上QPS掉到了个位数监控拉红。还有个值得记一记的技巧精排分数不要全局归一化要按query归一化。同一个query下分数高低才有比较意义跨query比较没道理。我一开始直接对全库文档做softmax等于把不同分布的高分凑一块效果反而乱。后来改成每个query的topK内做z-score再跟业务分数相乘整体指标稳定多了。写到这里想起上周同事问我“既然Reranker这么好能不能直接用Reranker来做召回”我劝他别动这个心思除非你把底层的乘积量化、ANN、IVF那些全扔掉否则Reranker做召回的成本能让你老板看到电费单当场心梗。召回要的是快召回10万个候选花50毫秒Reranker重排100个花30毫秒这个比例才是健康的。至于选什么模型我跟几个朋友交流下来小数据集上bge-reranker-v2-m3表现挺稳中文任务尤其好英文场景的ms-marco系列跑不了太远的领域。领域数据多的话强烈建议在开源模型基础上继续预训练因为通用模型对垂直行业的术语和用户口语化表达往往力不从心。比如“耳机掉水里了”这种话“防水”和“进水保修”在通用语义里可能相距十万八千里但在你的产品场景里就是强相关。你不好好喂数据Reranker就只是个花瓶。最后说个个人经验写下来也是提醒自己。别拿到一个新模型就直奔调参。先找20个线上badcase看召回对不对、精排对不对、业务规则到底卡在哪。我那次“苹果售后”的问题如果早做这一步也许根本不用上Reranker直接在业务层加上“意图分类”规则就能解决。但另一方面规则只能处理已知问题而Reranker能兜住那些你没想到的语义变形。我的建议是每一层都用最简单的方式解决那个层该解决的问题召回保召回精排保精排别跨越层级微操。精排模型上线前务必做线上A/B测试别信离线AUC离线指标提升并不等于线上点击率一定涨。这是无数人用头发换来的教训。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价