AI Pulse

两个287M模型替代昂贵LLM跑500万份判决,F1差3个点

我把一个LLM蒸馏成两个287M编码器(GLiNER + 多选题)用于文档抽取,始终追不上教师模型。我是不是哪里做错了?

不久前我在这里问过:如何在不给每篇文书都跑一遍昂贵LLM的情况下,把约500万份法院判决转成结构化图?感谢大家当时的建议。

我采用了“小型抽取器 + 分类器”的思路,基本能跑通,但效果一直比LLM差一点。而且就像上次说的:我可不想跑完500万份文档之后才发现某个环节是错的。下面是我具体做的每一步,请帮我看看方案是否合理,或者哪里犯了错。另外,因为数据量实在太大,部分表格用AI整理了,见谅。

每份判决输出什么

(目前只做节点,关系下一步再做。)三个列表:

- 实体(entities):每个自然人、组织、法律、文书或事物。每个实体有全文档唯一的id、一个类型(共9种)、一个子类(共724种加“other”),以及全部出现位置。
- 动作(actions):做了什么事、请求了什么事或决定了什么事。每个动作有一个归一化动词、一个“法院是否决定此项”标志,以及它的出现位置。
- 值(values):金额、日期、时长,以归一化形式存储。

举个简单例子,对句子 "The court dismisses the creditor's proposal to enforce 341.08 EUR against the debtor":

- 实体 “the court”:组织,子类:court。与文书头部出现的法院全名是同一实体
- 实体 “the creditor”:组织,子类:creditor。与前面提到的城市是同一实体
- 实体 “the debtor”:自然人,子类:debtor
- 动作 “dismisses”:动词 = dismiss,法院决定 = 是
- 值 “341.08 EUR”:金额

第1步:用强LLM标注约700份判决

- 把每份判决切成4句话一个的窗口
- 每个窗口调用4次Claude Sonnet,使用严格JSON schema:实体、动作、针对动作的第二次“漏了什么”检查、值
- 窗口以编号词的形式输入(如 12:court),模型用词范围 [first, last, "text"] 作答,代码随后逐一核对每个范围与原文是否吻合
- 每次调用都会带上之前窗口已找到的实体和动作列表,这样id在整篇文档中保持一致
- 约25条代码规则负责清理标记短语的起止位置、法律引用和数字格式
- 此阶段实体“子类”是自由文本,产生了2,373个不同字符串(和我第一个帖子里的情况一样乱)。我做归一化、手工合并同义词,保留出现3次及以上的:得到724个子类加“other”

这一步是整个数据飞轮的起点:小模型能学到什么完全取决于这里的标注质量,教师信号的噪声会一路传导到下游。

第2步:训练一个标记文本的模型

- 它标出每个提及:指代实体、动作或值的确切文本片段(一个“span”,从起始字符到结束字符),并打上17种标签之一(9种实体类型、1种动作、7种值类型)
- 模型:fastino/gliner2.5-multi-v1(287M)
- 每个窗口一行训练数据:文本加上每个标记短语的精确起止位置。共9,699个窗口、207k个标记短语
- 我给trainer打了补丁,让只有被标注的那次出现算正样本(默认会把同一字符串的所有出现都标为正),并且每行都包含全部17种标签
- 全参数微调,fp32精度(bf16会出现NaN),14个epoch,每步16行,encoder学习率3e-5,head学习率5e-4,线性调度,10% warmup
- 最终模型 = epoch 9到14的权重平均,阈值0.5

标记模型的F1是整个pipeline的瓶颈:span的起止是否精确,直接影响后续所有判断。

第3步:用第二个小模型回答多选题

- 模型:fastino/GLiNER2.5-multi-Decide(287M)。代码把LLM的标注转成247k个问题:
- “这个提及是之前某个实体,还是新的?”提及用「」标出,前后各给±300字符的上下文。选项:同一文档中最多16个之前的实体(用它们的提及文本表示)加 new
- “属于哪个子类?”选项:724个子类的候选列表加 other
- 对动作:同一动作还是新动作、动词是哪个(64个候选加 other)、法院是否决定(是/否)
- 训练时选项来自LLM的分组结果;推理时选项来自模型自己之前的回答
- 全参数微调,fp32,2个epoch,每步16个问题,encoder学习率2e-5,head学习率3e-4,线性调度,6% warmup,选项打乱,最多丢弃30%的错误选项

这步的实质是把“合并同一实体、判断子类、规范化动词”这类需要全局信息的决策,压缩成一个个局部多选题,让287M的小模型也能学会。

推理流程:先跑标记模型,再跑同样的代码规则,然后第二个模型按阅读顺序逐条处理提及。单张RTX 5090上大约每秒处理2.3份判决。

当前结果

选了30份从未参与训练的判决,由LLM标注两遍。第二列是LLM第二次运行对照第一次的得分,我把它视为天花板。一个提及只有在起点和终点都与LLM标注完全一致时才算找到。

指标我的模型LLM对比自身
实体提及找到(F1)0.9010.935
“同一实体还是新实体”判断正确0.9590.984
实体分组完全一致0.8470.934
实体子类0.9210.948
动作提及找到(F1)0.8570.919
动作动词0.9200.938

需要帮助的地方

1. 提及查找卡在0.90 F1。多加200份标注文档没有任何改善。用XLM-R large做标记器(560M)得到相同的分数:找到的提及更多,但起止点出错也更多。把窗口之前的文本一并输入也没有效果。你们会怎么尝试?
2. LLM对自己标注的内容一致率也只有93.5%,而我训练用的是单次运行的结果。把所有样本标注3次再投票?还是说这个天花板本来就是这样?
3. “从16个之前实体中选一个”这种方案做长文档共指消解,合理吗?用LLM的选项训练、再跑自己的选项,会不会反而害了自己?
4. 整个配方里有没有什么看起来就是错的?学习率、2个epoch、权重平均、每次运行只用一个随机种子。

感谢阅读。

AI TL;DR:我把LLM对法院判决的抽取能力蒸馏成一个负责标记提及的GLiNER模型加一个小型多选题模型。单张GPU上约每秒处理2.3份文档,效果比LLM低几个点(提及查找F1 0.90 vs 0.935,精确分组0.85 vs 0.93)。完整配方、学习率和训练数据构造方式见上文。在跑500万份文档之前,想找出错误和改进思路。

阅读原文
📚 相关主题 模型蒸馏信息提取

订阅 AI Pulse

每天 08:00 · 12:30 · 18:30 · 23:50 更新