1. 先说说我为什么动了这个念头
前两天接了个需求:一批业务场景文本,要判定它们是否属于某个风险类型。数据量不死多,几千条,但要求精度高,而且语言混杂,不是那种标准化的模板文本。最要紧的是数据不能出内网,只能本地推理。对方预期很明确:给我一个模型,输出“是/否/不确定”,要能解释得过去,不能是黑盒随便糊弄。
第一反应当然是微调一个大模型。但盘了一下手里的资源,单卡、还要兼顾线上延迟和成本,大模型这条路走起来压力不小。那会儿正好看到几篇用 rerank 思路做判定的文章,讲的是把分类问题转成检索排序问题:给定一个待判定文本,把它当 query,把类别定义和反例文本当候选文档,用 rerank 模型给它打分排序。分数高的就是命中。这个思路我越看越觉得合理,因为 rerank 本质上是 cross-encoder,query 和 doc 之间做深度交互,比双塔模型在相关性建模上强太多了。那用这种强力交互去做“文本属于/不属于某个类别”的判断,理论上也说得通。
加上现在开源小模型生态确实繁荣,bge-reranker-base、miniLM 系列、Qwen 系列的小尺寸版本,随便拉出来一个都能在本地跑。模型小、速度快、部署不挑机器,这几个优点叠加起来,让我当时真的觉得这条路有戏。尤其是 bge-reranker-base 在中文检索任务上的表现,我期待值拉得相当高。
于是接下来的两天,我搭了实验链路,一口气跑了 16 组实验。指标始终没过及格线,最后只能认输。这篇文章就是把这两天的思路、踩坑、排查过程完整写下来。如果你也动过“用 rerank 配合小模型做判定分类”的念头,我建议你先看完我这篇,应该能帮你省下至少两天。
2. 整体方案设计:怎么把分类变成排序
2.1 核心思路拆解
这个方案的逻辑链条是这样的:传统分类模型需要学习一个从文本到类别标签的映射函数,类别是固定的,模型参数量不够的时候,很难学得动这个映射。而 rerank 模型做的是另一件事——计算两段文本之间的相关性分数。它不需要学习“这个类别长什么样”的抽象知识,只需要判断“这段文本和那段文本像不像”。从这个角度看,把类别定义写成一整段描述性文字,然后计算待判定文本和这段描述文字的相关性,似乎就能把分类问题偷换成相似度问题。
整个流程分四步:
- 构造候选文档集。把每个类别的定义、特征、典型表现写成一段自然语言描述。
- 待判定文本作为 query。
- 用 rerank 模型给 query 和每个候选文档打分。
- 设定阈值,分数超过阈值就判为命中,排序第一的类别就是最终判定。
2.2 我当时的技术选型逻辑
模型层面,我首选 bge-reranker-base。原因很简单:第一它是中文场景训练过的 cross-encoder,交互强度高;第二模型体积只有几百 MB,单卡跑起来毫无压力;第三 BGE 系列有大量检索评测背书,业界评价稳定。
为了对照,我还准备了两个备选方案:一个是更轻量的中文 MiniLM 交叉编码器,另一个是 Qwen2.5-1.5B 做分类微调的 baseline。我当时的算力是单张 4090,内存 64GB,跑这些模型完全够。
数据层面,我拿了 800 条已标注样本做验证集,标注分三类:正例、反例、边界样本。其中边界样本 200 条,都是我特意挑出来“看着像但实际不是”的困难样本。我的想法是,如果 rerank 方案真的能行,那最有说服力的表现就是能把这些边界样本和正例分开。
2.3 这个设计的致命假设
现在回头看,这套方案成立的前提是:类别描述文本能作为一个稳定的“文档侧”代表,而这个文档侧与属于该类别的真实文本之间,存在足够强的相关性信号。
这个前提当时被我当成了理所应当的事。但实际跑起来才发现,它恰恰是整个方案最薄弱的地方。类别定义是抽象的概括,是浓缩后的规则集合,而真实业务文本是具体的、口语化的、充满噪声的个体。两者之间的“语义距离”,和两个同类真实文本之间的语义距离,根本不是一回事。
3. 16 个实验的完整记录:参数、过程、结论
3.1 实验基础设施与关键参数
所有实验都在同一套数据集下进行,保证可比性。通用参数如下:
- 验证集:800 条已标注文本,三类分布为 400 正例 / 200 反例 / 200 边界样本
- 评估指标:F1、AUC、以及分数分布图
- max_length:512
- batch_size:32
- 阈值选择:在验证集上搜索最优 F1 对应的分数阈值
我把 16 个实验分成四组:零样本基线组(6 个)、结构设计组(3 个)、训练微调组(4 个)、补救尝试组(3 个)。下面按组展开。
3.2 零样本基线组:一上来就有点不对劲
第一组实验全部使用 bge-reranker-base 原厂权重,不做任何训练,只改 query 和 doc 的写法。
- 实验 01:query 为原始业务文本,doc 为类别定义原文。这是最直接的做法。结果 F1 只有 38.7%,基本等于瞎猜。
- 实验 02:doc 改成“类别名 +:+ 定义 + 典型特征”的结构化描述。F1 提升到 44.2%,有效果,但远远不够。
- 实验 03:加入了 3 条典型正例作为 doc 的一部分。F1 45.6%,提升有限。
- 实验 04:把 query 的无效语气词去除,做了轻量清洗。F1 46.1%,几乎没变。
- 实验 05:doc 中同时包含正例和反例,做成对比描述。F1 43.5%,反而比实验 03 还低。
- 实验 06:用 MiniLM 中文交叉编码器替代 bge-reranker-base,同样设置下 F1 直接跌到 35.1%。小模型在零样本检索场景下确实弱,这个不意外。
这组实验跑完,我确认了两件事:第一,rerank 模型在零样本状态下,对类别描述和业务文本之间的相关性建模能力没有我预期的高;第二,正向的描述优化能带来几个点的提升,但天花板很低。
3.3 结构设计组:改造输入格式能不能救
既然模型本身零样本能力不行,那就从输入结构下手,让模型更容易“理解”任务。
- 实验 07:把分类任务显式改造成“二选一”的对比排序任务。构造两个候选 doc:doc_positive 是类别定义,doc_negative 是“不属于该类别”的通用描述。用两个分数的差值作为判定依据。这个思路灵感来源是对比学习:让模型在正反两个选项之间做相对判断,而不是绝对判断。实测 F1 涨到 48.9%,说明相对判断确实比绝对打分更容易。
- 实验 08:在 07 的基础上,把 doc_negative 从通用描述换成真实反例文本拼接。F1 50.2%。反例信息比无意义的通用描述有用。
- 实验 09:把 07 的差值分数换成正例相似度占比,即 score_positive / (score_positive + score_negative)。F1 反而跌到 47.8%。这个公式变体没有带来增益,说明单纯做归一化不解决根本问题。
这组结束时,F1 勉强过了 50%,我开始意识到问题不只是输入格式,而是模型本身的排序能力在分类场景下就没有被正确激活。
3.4 训练微调组:小模型微调能不能靠谱
既然零样本不行,那就微调。我选了 bge-reranker-base 做继续训练,冻结不了全部参数,全参数微调,用三元组损失函数。候选集从标注样本里构造:anchor 是待判定文本,positive 是同一类别的样本,negative 是不同类别的样本。
- 实验 10:训练 3 个 epoch,学习率 2e-5。验证集 F1 51.7%,提升不明显。
- 实验 11:增加到 5 个 epoch,学习率降到 1e-5。F1 52.3%,瓶颈出现了。
- 实验 12:随机采样 5000 条构造数据(原来是 2000 条)再训练 5 个 epoch。F1 52.9%。
这个结果很让我困惑。照理说有了训练信号,就算是小模型也不该只涨几个点。我看了训练集上的 loss,降到后来已经很低了,但验证集就是不动。说明模型学到的是训练数据的表面模式,不是真正的判定能力。小模型的容量瓶颈,在这个任务上体现得淋漓尽致。
3.5 补救尝试组:最后挣扎
- 实验 13:把 Qwen2.5-1.5B 当交叉编码器用,输入“类别定义 + 待判定文本”,让模型输出 0-100 的分数。F1 54.6%,是全场最高,但距离可用还差太远。而且推理速度太慢,单条 500ms,线上完全顶不住。
- 实验 14:用大模型的打分结果蒸馏 bge-reranker-base,标签改成软标签,损失函数用 KL 散度。F1 53.4%。蒸馏拿到了大模型的一部分能力,但有限。
- 实验 15:尝试在打分之后加一个规则后处理:如果文本包含某些强特征词,直接强制判定;如果不包含,再用模型分数。F1 55.8%。规则带来的提升是可解释的,但规则本身是人工设计的,覆盖不了长尾。
- 实验 16:对照实验,直接用 Qwen2.5-1.5B 做分类微调,CLS 分类头,交叉熵损失。F1 61.2%。比所有 rerank 方案都高,但也就比规则组合强了 5 个点。
16 组实验结果全部跑完,最优方案是实验 15 的规则加模型后处理,F1 55.8%。和我可接受的标准差了至少 15 个点。我认输了。
4. 复盘:rerank 做判定,死在了哪几个环节
4.1 排序分数和分类边界,根本不在一个维度上
这是我这次踩得最深的一个坑,也是理解整个问题最关键的一环。
rerank 模型做的是相关性排序。它告诉你的答案是:在给定的这批候选文档中,哪个和 query 最相关。它天然是一个相对概念。而分类任务要的是绝对判断:这个文本属于还是不属于,和别的候选关系不大。当你把分类转成排序,等于强行把一个绝对问题降维成相对问题,信息量必然丢失。
具体表现就是:分数分布极度畸形。我对实验 07 的差值分数做了统计,发现正例和反例的差值分布基本叠在一起,中心都在 0.2 附近。也就是说,模型对它认为“有点像”的那一类给分,但没有能力做出“这一类是而那一类不是”的抉择。排序模型学习到的表征空间里,相关性信号和类别信号纠缠在一起,没有一个清晰的分界面。
4.2 候选文档侧的信息瓶颈
另一个核心问题是候选文档侧的构造。我手动写的类别描述,本质上是一段几百字的抽象总结。但真实业务文本分布在语义空间里的位置极其分散,一个类别下的所有文本并不是围绕一个中心点聚集的,而是呈多个子簇。用一段抽象描述代表这个类别的全部语义,等于是用一个点去代表一片星云,误差可想而知。
更麻烦的是反例侧。你可以写“不属于该类别”的通用描述,但这个描述和任意真实文本的相似度都差不多低,模型根本没法从“低相似度”里区分出“这其实是另一个类别的高相似度”。我看过实验 08 的分数明细,相当一部分反例文本在 doc_negative 上的分数反而高于正例在 doc_positive 上的分数。排序任务彻底失效。
4.3 小模型的容量天花板
这个话题现在铺天盖地都是“开源小模型真香”,我自己也写过不少推荐文。但这次实验让我重新认识到一个冷酷的事实:小模型在通用任务上能力强悍,但在高度领域化的判定任务上,尤其是需要综合上下文、业务背景、隐性规则才能判断的任务上,容量就是硬约束。
评判一个文本是不是属于某个风险类型,往往靠的是几个隐性格线的综合判断。这需要模型在推理时把多个信息点组合起来,形成高阶特征。小模型的参数量不足以支撑这种多跳组合推理。它的参数空间里存得下词语搭配模式,存不下“某个词出现在这个语境里配合另一个背景信息就等于高风险”这种复杂逻辑。T-SNE 可视化里,正例和反例在最后的表征层上已经完全纠缠成一团。
5. 如果重来一次,我会怎么设计这个任务
5.1 替代方案深度对比
表格整理一下我尝试过的所有技术路线,按效果排序:
| 方案 | F1 | 推理速度(单条) | 可解释性 | 实施复杂度 |
|---|---|---|---|---|
| 规则 + Qwen2.5-1.5B 分类微调(实验 15+16 组合) | 61.2% | 约 150ms | 中 | 中 |
| Qwen2.5-1.5B 分类微调(实验 16) | 61.2% | 约 120ms | 低 | 低 |
| 大模型蒸馏到小模型(实验 14) | 53.4% | 约 30ms | 低 | 高 |
| 二选一对比排序 + 微调(实验 08) | 50.2% | 约 40ms | 低 | 中 |
| 零样本 rerank 基线(实验 01) | 38.7% | 约 30ms | 低 | 低 |
如果你和我一样资源有限,我的建议是:
第一个选择:直接放弃 rerank,用小模型做交叉熵分类微调。Qwen2.5-1.5B 虽然也不算大,但它的底座语言能力比 bge-reranker 那种纯粹的检索模型强太多。分类微调是把语言模型的语义理解能力和分类头结合起来,这是一个更直接、更匹配任务本质的做法。
第二个选择:如果精度还是不够,升级方案是先用更大模型(比如 Qwen2.5-7B)在少量标注数据上产出软标签,再蒸馏给小模型。这个方向我实验 14 已经跑通了,效果虽然没到理想线,但技术路线是可行的,主要瓶颈还是我的训练数据太少。数据量上来以后,蒸馏的收益会大幅增加。
第三个选择:如果必须用 rerank,那就不要用类别定义当候选。正确做法是:先从训练集里按类别抽取出若干个簇心样本作为锚点,每个类别放 5-10 条真正有代表性的原始文本,把待判定文本和这些锚点做 rerank。这相当于用同类文本做相似度匹配,匹配的是文本风格和语义,而不是抽象定义。这和实验 03 的思路接近,但我当时把真实样本混进类别描述里,反而污染了候选。
5.2 rerank 在这个任务里的正确位置
在这次任务里 rerank 确实有它的价值,但不是做主模型,而是做后置精排链路。我设想的最终架构应该是这样:
第一层用 Qwen2.5-1.5B 分类微调模型的软得分做粗筛,把所有候选样本分成“高置信正例”“高置信反例”“边界不确定”三档。第二层把边界不确定的样本拿出来,和正例锚点样本做 rerank,用相关性分数纠正分类头的模糊判断。第三层再叠加规则兜底,处理强特征命中的情况。
这个架构其实就是一个混合决策融合系统,各层用各自的擅长能力,比任何单一模型都稳。问题是我只有两天,从头跑这个方案显然来不及了。
5.3 关于小模型的几个真话
这轮实验下来,我对“小模型”的认知也刷新了一遍。它们确实能落地、能部署、能在本地跑,甚至在某些垂直场景下已经逼近大模型的水平。但那是针对“知识回忆型”任务——你要它总结、检索、抽取、改写,它表现得相当好。一旦进入“规则推断型”任务——需要依据业务潜规则做分类决策,小模型的劣势就暴露得很彻底。
原因也不复杂。小模型因为参数量有限,它的记忆主体是高频语言模式,而不是低频业务规则。业务规则往往在训练语料里出现频率极低,即使出现了,也难以被小模型有效编码。这不是调参能解决的,是模型容量和信息密度的物理限制。
我后来复盘了一下我实际拿到的标注数据,里面真正含金量高的判断点其实就那么十来条规则。如果能把这些规则显式抽取出来,做成打分卡或者决策树,再用小模型覆盖剩余长尾,效果大概率比在这次实验里做任何模型方案都好。这也是我接下来打算推进的方向:让规则系统做骨架,让模型做肌肉,各司其职。
6. 最后两点心得,给同样想抄近路的人
如果这篇复盘只留两句话,我会说这两句:
第一句,分类问题不要轻易转成排序问题。排序和分类的本质逻辑相差太远,强行转化需要额外设计候选文档侧,而候选文档侧的信息瓶颈很快会成为新的天花板。你不是在简化问题,你是在把问题搬进一个更难控制的容器里。
第二句,小模型不是万能药。它的优势在于高效、低成本、可部署,但在这之前,你得先确认你的任务复杂度在小模型的能力边界之内。验证方法很简单:先拿一个中尺寸模型(7B)跑一下,看能不能达到目标效果。如果中尺寸都做不到,小模型微调也救不了你,问题不在于模型规模,在于任务定义和数据质量。
这次两天 16 组实验虽然全部失败了,但值。它把我对 rerank、小模型、判定任务三者之间关系的理解掰得很清楚。以后再碰到类似需求,我不会再抱着“换个模型就能搞定”的侥幸心理,而是先老老实实把任务拆解清楚,该上规则上规则,该上蒸馏上蒸馏,该上大模型就上大模型。毕竟方案选型这东西,从来不是看哪个技术最新,而是看哪个技术最匹配你手里的资源和真实业务约束。