简介:这份文档资料面向自然语言处理、文本分类及专利情报分析领域的研究者与开发者,完整呈现了基于预训练模型的多标签专利分类研究方案。内容围绕IPC国际专利分类标准,针对传统人工分类效率低、细粒度分类困难等痛点,构建可扩展的大规模专利数据集,并将分类粒度精确到“小类”级别。文档详细给出了BERT、RoBERTa与RBT3三种预训练模型的微调流程,采用Sigmoid激活函数和BCEWithLogitsLoss损失函数搭建多标签分类模型,同时通过高频标签筛选提升效果,最终在大型数据集上取得91.2%的准确率和71.7%的Micro-F1值。全文以单个docx文档提供,大小约413KB,结构完整,适合作为相关课题设计、毕业论文写作或专利自动分类实验的参考资料。目前已获得279人学习使用,对想要复现实验结果或改进分类方法的学习者具有直接参考价值。
1. 从一篇专利文本到多个分类号:预训练模型怎么改写出专利分类的答案
你以为专利分类是简单的文本打标签,打开一个专利详情页才发现事情没这么简单。一件发明专利的权利要求书动辄几千字,摘要里提到的技术特征往往横跨多个领域,审查员给出的分类号也不是一个,而是 IPC 主分类号加副分类号,甚至 CPC 分类号一标就是五六个。把“一种基于磁共振成像的肿瘤辅助诊断方法”分给 G01R 和 A61B 两个小类,这种多标签输出才是常态。传统 TF-IDF 加逻辑回归的做法,在长文本、强术语、标签高共现的专利场景里早就顶不住了。预训练模型恰好把语义表示这个问题前置解决,我们只需要在最后一层接一个多标签分类头,把任务建模成多个二分类任务。这篇文章会从数据构造、模型选型、训练参数到阈值调优,把一套能落地的方案完整讲清楚,适合正在做专利检索系统、企业知识产权管理平台或技术情报分析的工程师直接参考。
2. 专利分类为什么是多标签问题:标签体系、样本构造与评估指标的底层逻辑
2.1 IPC/CPC 分类号的层级结构与多标签语义
专利分类号不是扁平的一串字符串,它自带层级。IPC 分类号从部、大类、小类到大组、小组,A61B 6/00 这种写法,前面的 A61B 是“医学诊断”,后面的 6/00 才是具体的磁共振成像。实际项目里,我们很少直接预测到小组,因为小组太多、样本太少,多标签分类器的输出维度会爆炸。常见的做法是预测到小类级别,也就是 IPC 的前四位,比如 G01R、A61B、H04L 这一层。一个专利在 IPC 里有主分类号、副分类号,来到 CPC 里又会有更细的补充标签,把多个层级拼起来,一个专利的标签数经常超过五个。多标签分类要做的事情,不是从 N 个类别里挑一个,而是对 N 个类别分别做二分类判断,每个类别的答案只有“属于”或“不属于”。
2.2 多标签评估指标:为什么不能用准确率一锤定音
如果你拿普通准确率去评估多标签分类模型,会发现自己被判了死刑。一条样本预测了 5 个标签,哪怕猜对 4 个,只要漏掉一个,准确率就是 0,这在专利检索场景里毫无意义。业界更常用 Micro-F1 和 Macro-F1 作为主指标。Micro-F1 把所有样本的所有标签看成一个大池子,统计全局的精确率和召回率,它会被高频标签主导,适合专利审查中大类别的检索需求。Macro-F1 先算每个标签自己的 F1,再取平均,低频小类也能获得同等权重。如果你的专利分类模型服务于企业专利导航,小类常常比大类更有情报价值,那 Macor-F1 才是你该盯的指标。此外还要关注样本级标签完全正确的比例,也就是 exact-match accuracy,这个指标通常低得让你怀疑模型,但它直接对应“自动标引结果能否直接入库”的底线。
2.3 数据准备:从专利 XML 到训练样本的规范化流程
训练数据从哪里来?国内可以用专利公开文本的 XML 格式,国外可以用 USPTO 的 bulk data。每个专利的标题、摘要、权利要求、说明书、分类号都在里面。我一般只抽出三个字段:标题、摘要、权利要求书第一项,把它们用分隔符拼成一个长文本。分类号取 IPC 小类前四位,去掉空格,去重,然后排序,保证标签顺序稳定。训练样本的标签用 multi-hot 向量表示,每个类别对应一维,属于就置 1,否则置 0。这一步最需要注意的是分类号的标准化,同样是 IPC G01R 33/48,不同源数据的写法可能是“G01R 33/48”也可能缺空格,处理不好会让同一个标签被拆成两条,直接污染评估指标。清洗完之后,按 8:1:1 切分训练、验证、测试集,注意同一个专利的同族文本不能既进训练集又进测试集。
提示:专利同族衍生文本之间的相似度特别高,如果不去重,验证集和测试集的分数会虚高,上线后被真实数据教育。
3. 选型对比:RoBERTa中文预训练模型比 ResNet 预训练模型更适合文本分类的四个理由
3.1 图像预训练模型与文本预训练模型的分工边界
看到预训练模型,很多人第一反应会想到 ResNet 预训练模型,但 ResNet 是图像领域的特征提取器。专利分类如果纯粹处理文字,用图像模型等于南辕北辙。当然,专利里如果有附图,你可以用 ResNet 预训练模型做附图分类,再把图像特征和文本特征拼接起来,这种多模态方案适合外观设计专利分类,而发明专利文本分类的主战场仍然是文本预训练模型。文本这边,BERT、RoBERTa、ELECTRA 都属于预训练模型,其中 RoBERTa 中文预训练模型在专利语料上的整体表现通常优于原始中文 BERT,原因是它用了更大的语料和更长的训练步数,动态掩码机制对专利里大量的专业词汇更加友好。
3.2 领域预训练模型与通用中文模型的实际差距
专利文本的特点是术语密集、句子结构复杂、存在大量公式和编号。通用 RoBERTa 中文预训练模型在常识类任务上很强,但一些专利特有的写法,比如“其特征在于”“所述”“优选地”,它未必理解得很充分。常见的做法是先拿 RoBERTa 中文预训练模型跑一版基线,再去 Hugging Face 上找专门在专利或法律语料上继续预训练的模型。如果你有足够多的未标注专利文本,也可以自己在通用模型基础上做领域增量预训练,用 Masked Language Modeling 损失继续训练两三个 epoch。这一步能显著降低专利术语被切成无意义 token 的概率,代价是训练时间和 GPU 显存会拉高,需要自己权衡。大多数中小团队我建议直接用 RoBERTa-wwm-ext,它在中文下游任务上性价比最高。
3.3 输出层改造:从序列模型到多标签分类头的标准接法
预训练模型本身输出的是每个 token 的向量,或者序列的 [CLS] 向量。多标签专利分类的标准做法是取 [CLS] 向量,接一个线性层,输出维度等于标签数量,最后用 sigmoid 激活,对每个维度独立判断。Hugging Face 的 AutoModelForSequenceClassification 支持直接设置 problem_type="multi_label_classification",它会自动帮你在输出层用 sigmoid。这个方案不需要手动改模型结构,省去很多调试工作。需要留意的是分类头的初始化,线性层最好用预训练模型输出维度和标签数自动初始化,不要手动随机一个大矩阵,否则训练初期梯度会非常不稳定。标签数量如果超过几千个,分类头本身就是显存瓶颈,这种场景下一开始就不该用全连接头,换成标签文本匹配或检索式分类更合适。
3.4 训练参数设置:学习率、batch size 与权重衰减的经验区间
多标签专利分类的微调参数和单标签分类没有本质区别,但有几个值必须重新调。学习率建议从 2e-5 起步,预训练模型参数用较小的学习率,分类头可以用稍大的学习率,常见做法是把分类头学习率设为 1e-4,让新参数学得快一点。batch size 受限于文本长度,专利长文本截断到 512 之后,单卡 16 的 batch 已经要吃 16G 显存,梯度累积可以帮你解决小显存跑大批量的需求。权重衰减设 0.01,warmup ratio 设 0.1,训练轮数不要贪多,3 到 5 个 epoch 足够。如果你发现训练集损失持续下降但验证集 Micro-F1 不再上升,说明已经进入过拟合区间,这时候保存最佳模型要比多跑 epoch 更有用。
| 参数 | 建议值 | 调整方向 |
|---|---|---|
| learning rate | 2e-5(主干)/ 1e-4(分类头) | 乱震荡就减半 |
| batch size | 16 + 梯度累积 2 | 显存不够就累积 |
| max length | 512 | 看长文本信息分布 |
| warmup ratio | 0.1 | 数据集大可以降低 |
| epochs | 3 到 5 | 验证集 F1 不再涨就停 |
4. 用 RoBERTa 中文预训练模型跑通多标签专利分类:数据转换、模型加载与训练脚本
4.1 数据预处理:把专利 XML 转成模型能读的样本
先写一个从 JSON 格式专利数据生成训练样本的脚本。假设每条专利记录的 text 已经拼接好,labels 是去重后的 IPC 四位列表。我们需要先把所有出现过的标签收集起来,建立标签到 ID 的映射,这个映射在训练、验证、测试三部分必须一致,否则标签编号错位会带来灾难。下面这段代码完成标签映射和样本转换。
import json from sklearn.preprocessing import MultiLabelBinarizer with open('patents.json', 'r', encoding='utf-8') as f: data = json.load(f) mlb = MultiLabelBinarizer() y = mlb.fit_transform([item['ipc_list'] for item in data]) with open('label_map.json', 'w', encoding='utf-8') as f: json.dump(mlb.classes_.tolist(), f, ensure_ascii=False) for idx, item in enumerate(data): item['label_vector'] = y[idx].tolist() with open('patents_ready.json', 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False)MultiLabelBinarizer 会自动把每个 IPC 标签转换成一个二进制向量,所有标签的列表顺序由全部训练数据决定。这一步最常见的坑是测试集或未来新出现的标签没有进入映射,会导致模型输出维度对不上。所以我通常先合并全部数据的标签集合再 fit,并且在推理阶段对没见过的标签直接忽略,而不是报错退出。标签映射保存成文件,后面推理还要继续用,json 格式足够稳定。
4.2 加载 RoBERTa 中文预训练模型并配置多标签分类头
接下来用 transformers 库加载模型。这里我指定 checkpoint 为 hfl/chinese-roberta-wwm-ext,如果你有领域预训练模型,直接把名字换成你本地路径就行。AutoModelForSequenceClassification 会加载 RoBERTa 主干,上面的线性分类头输出维度等于标签数量,problem_type 指定为 multi_label_classification,让模型内部计算损失时用 BCEWithLogitsLoss,而不是交叉熵损失。这个配置是跑通整个流程的关键。
import torch from transformers import AutoTokenizer, AutoModelForSequenceClassification checkpoint = "hfl/chinese-roberta-wwm-ext" num_labels = 317 # 换成你自己标签映射的长度 tokenizer = AutoTokenizer.from_pretrained(checkpoint) model = AutoModelForSequenceClassification.from_pretrained( checkpoint, num_labels=num_labels, problem_type="multi_label_classification" ).cuda()模型的输出是 logits,形状是 (batch_size, num_labels),训练时可以直接和 target 算 BCE loss。这里有一个容易踩的细节:AutoModelForSequenceClassification 的 num_labels 如果你不传,默认是 2,二分类多标签场景下这个默认值会直接让输出维度出错。另外,如果标签数量有几千个,这个全连接分类头参数量很大,占显存也很大,你需要考虑模型结构改成先降维再接分类头,但那是进阶玩法,第一版跑通不建议引入。
4.3 自定义训练循环:处理长文本截断与梯度累积
transformers 的 Trainer 可以省很多事,但如果你希望灵活控制多标签阈值、样本权重,我建议手写一个训练循环。下面这段代码实现了基本的训练步进、梯度累积和模型保存逻辑,注释里标注了每个参数怎么调。
from torch.utils.data import DataLoader, Dataset from torch.optim import AdamW from transformers import get_linear_schedule_with_warmup class PatentDataset(Dataset): def __init__(self, data, tokenizer, max_len=512): self.texts = [item['text'] for item in data] self.labels = [item['label_vector'] for item in data] self.tokenizer = tokenizer self.max_len = max_len def __getitem__(self, idx): enc = self.tokenizer( self.texts[idx], truncation=True, max_length=self.max_len, padding='max_length', return_tensors='pt' ) return { 'input_ids': enc['input_ids'].squeeze(0), 'attention_mask': enc['attention_mask'].squeeze(0), 'labels': torch.tensor(self.labels[idx], dtype=torch.float) } def __len__(self): return len(self.texts) train_loader = DataLoader(PatentDataset(train_data, tokenizer), batch_size=8, shuffle=True) optimizer = AdamW(model.parameters(), lr=2e-5, weight_decay=0.01) total_steps = len(train_loader) * 4 scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=total_steps // 10, num_training_steps=total_steps) for epoch in range(4): model.train() for step, batch in enumerate(train_loader): batch = {k: v.cuda() for k, v in batch.items()} output = model(**batch) loss = output.loss # 梯度累积:每 2 个 step 更新一次参数 loss /= 2 loss.backward() if (step + 1) % 2 == 0: torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() scheduler.step() optimizer.zero_grad() torch.save(model.state_dict(), f'model_epoch_{epoch}.bin')batch size 只有 8,配合梯度累积 2,等效 batch size 是 16。这样可以让你在 16G 显存卡上跑长文本,同时不牺牲收敛稳定性。截断策略上,truncation 默认截断尾部,但专利的关键信息往往在权利要求书的中后部,直接截断尾部可能丢掉主权独权。我一般把文本倒序之后再截断,或者把摘要放在最前面保证前 512 个 token 尽量包含标题、摘要和权项开头。这个处理在代码里没有体现,但你做数据预处理时需要留意。损失除以 2 是因为梯度累积,如果忘记除,Loss 会比标准场景偏大,学习率需要相应调小。
4.4 推理与阈值调整:从 sigmoid 到预测标签的完整链路
模型训练好之后,推理阶段还需要做一步关键处理:确定预测阈值。多标签分类的默认阈值是 0.5,但专利分类的标签概率分布往往不是均匀的,高频标签更容易超过 0.5,低频标签可能永远到不了 0.5。常见的做法是在验证集上搜索全局最优阈值,让验证集的 Micro-F1 最大。这个搜索可以很简单,遍历 0.2 到 0.6 之间的 0.05 步长,对每个阈值把所有验证集预测概率二值化,计算 F1。搜索完成后再在测试集上做最终评估,这样才能避免阈值在验证集上过拟合。推理脚本中对每条样本取模型输出的概率,大于阈值就输出对应标签,标签 ID 通过之前保存的 classes_ 映射还原成 IPC 字符串。
5. 多标签专利分类的常见坑与排查:长文本截断、类别不均衡与阈值的玄学
5.1 长文本被截断,关键权利要求特征丢失
现象:训练损失下降得很好,验证集 Micro-F1 也不差,但抽几条长专利出来看,预测标签总缺一个重要的小类。排查发现输入序列被截断到 512,摘要和权利要求第 1 项太长,真正定义新颖性的特征已经被切掉了。专利文本的独权往往写在前 1000 字以内,但掺杂了大量背景技术和设备参数,直接截断会留下跟发明点无关的部分。解决方法是重新设计组装逻辑:把摘要放开头,然后接权利要求第 1 项,最后放技术效果;如果仍然超过 512,就采用滑动窗口切分长文本,分别过模型,再把标签概率做 max-pooling,这样可以保留长文本中后部的信息。代价是推理时间翻倍,但专利分类场景对实时性要求没有那么高,值得做。
5.2 标签类别不均衡,大分类吞掉小分类
现象:模型把所有样本都预测成 H04L 和 G06F,大类别的 Micro-F1 虚高,但一些小类如 B62D、C08F 的召回率接近 0。原因很明确,专利分类号的长尾分布比普通文本分类更严重,头部几十个类别占据了大多数样本,尾部小类每个可能只有几百条训练数据。解决方法有几个层次:第一,对小类样本做过采样,让每个类别至少出现 200 个样本;第二,在 loss 里给不同标签加权重,使用 sklearn 的 class_weight 或者手动乘上 pos_weight,让少数类梯度信号更强;第三,训练完成后单独在小类上做阈值调优,不要用全局阈值一刀切。我的经验是先过采样加 pos_weight,收敛速度更稳,全局阈值调优放到最后一个阶段。
5.3 阈值默认 0.5,预测标签数量永远不对
现象:模型输出的平均标签数是 2.8,但测试集标注的平均标签数是 4.2,明显偏低。原因是验证集上最优阈值不是 0.5,而是 0.38 左右,每个维度用 0.5 判断会漏掉大量软概率标签。多标签分类的阈值完全可以通过验证集搜索得到,而且这个搜索方法极其朴素:设定一组候选阈值,对每个阈值把预测概率矩阵二值化,计算 Micro-F1。你也可以用 per-label 的独立阈值,但对 300 多个标签调出各自最优阈值需要较多数据,容易过拟合。我一般在标签数不超过 100 时使用 per-label threshold,超过 100 就退回到全局阈值加类别分组阈值。这个环节是提升线上效果的性价比之王。
5.4 预训练模型对专利术语分词不友好
现象:专利文本里的“所述”“其特征在于”“多晶硅薄膜晶体管”经常被 tokenizer 切成很碎的碎片,模型很难学到完整语义。RoBERTa 中文预训练模型用的是字级分词,本身不存在词语切分问题,但一些化学式、型号如“OLED-OLED”“IGZO”会被拆成奇怪的字母组合。解决方法是检查 tokenizer 的词表,把这些专有词汇用 additional_special_tokens 加到词表末尾,并在自己的数据上重新训练 embedding 层。还有一个简单动作:把专利文本中的数字和英文型号做统一归一化,比如“55nm”统一成“ nm”,“IGZO”统一成“igzo”,能减少模型对表面字符的依赖。这些细节处理对专利分类这类强术语任务影响非常大。
5.5 同族专利数据泄漏,验证指标虚高
现象:验证集 F1 很高,但上线到新公开专利上效果明显下滑。排查发现,同一专利族的同族文本高度相似,一个发明在美国专利、欧洲专利、中国专利里的摘要几乎一样,如果你随机切分数据,这些文本会同时出现在训练集和验证集,模型其实在背答案。解决方法是按专利族 ID 而不是按单个专利 ID 切分训练集和验证集,先把相同族的所有专利放进同一个集合,再做随机切分。如果你拿不到专利族信息,至少要按公开号的前四位去重。这个坑不踩掉,你后续所有实验结论都不可信。
6. 提升模型上限的最后一招:阈值搜索、标签数量约束与推理加速
多标签专利分类的最后一公里不在模型结构,而在预测后处理。阈值搜索之后,如果预测标签仍然过多或过少,可以加一个硬约束:统计训练集标签数量的分布,把预测概率排序,取概率大于阈值的标签,同时强制最多输出 N 个、最少输出 M 个。这个约束在专利检索场景下很实用,审查员希望系统先给 3 到 5 个候选分类号,而不是给出十几个弱标签。实现时用简单的排序截断即可,不需要改动模型输出。另外,推理速度如果是瓶颈,可以把 RoBERTa 进行量化,用 ONNX Runtime 替换 PyTorch 推理,在 CPU 上也能获得 2 到 3 倍加速。但量化对长文本有精度损失,建议先量化,然后在验证集上重新做一次阈值搜索,因为概率分布会被压缩。
加了这个约束后,模型的输出变得更符合人类标引习惯。我有一次在项目中只靠阈值搜索和最大标签数约束,就把测试集 Micro-F1 从 0.71 提到了 0.76,没有动任何模型结构。这个收益看起来不惊艳,但它在专利分类这种多标签任务里非常稳定,因为模型输出的概率本身就是近似分数。你还可以尝试用 RoBERTa 做特征抽取,训练一个多标签的 LightGBM,把树的非线性能力和预训练模型的语义能力结合,这也是一个低成本提效方向。但不要指望它能打赢端到端微调,尤其在训练数据充足的情况下。
我自己的习惯是每一版模型训练完,先保存预测概率,再单独跑一个脚本调阈值和标签数量,把所有后处理参数记录下来,不调进模型训练代码里。这样下次换模型、换数据,后处理参数可以快速迁移。回看这几个项目的经验,最值得反复调试的永远是数据清洗和阈值搜索,预训练模型选型反而没有想象中影响大。希望这篇笔记让你的多标签专利分类少走点弯路,也帮你把那些容易被忽略的细节一次排干净。
本文还有配套的精品资源,点击获取