简介:这份PDF文档面向自然语言处理、专利分析与知识图谱方向的研究者与工程技术人员,聚焦如何用改进BERT算法提升专利实体抽取的效率与准确度。内容以石墨烯制备技术为案例,提出两种改进方案:一是融合中文特征与句法语义特征的BERT-BiLSTM-CRF命名实体识别算法,二是结合注意力机制与句法语义特征的实体关系抽取算法,并通过数值实验验证其在专利内容分析与技术动态演化揭示上的效果。资源包内含1个PDF文件,大小约1.55MB,即论文全文,涵盖摘要、关键词、引言、文献综述与实验分析等完整章节,便于按需精读与引用。目前已有141人学习下载,适合希望了解实体关系抽取、知识图谱构建及专利文本自动获取技术的中高级读者参考,可据此掌握算法设计思路、实验验证方法与石墨烯技术演化分析框架。
1. 专利实体抽取为什么在石墨烯领域格外棘手
做专利文本挖掘的同行大多有过这种体验:拿一个在通用语料上跑分很漂亮的 BERT 模型,直接往石墨烯专利上套,F1 值能掉十几个点。原因不玄学——石墨烯专利里充斥着「单层」「少层」「氧化石墨烯」「还原氧化石墨烯」「CVD 法」「Hummers 法」这类高度领域化的表述,通用预训练权重里对这些词的上下文建模非常薄。而专利实体抽取要抽的恰恰是这些:材料名称、制备方法、工艺参数、性能指标、应用场景。
这个方向要解决的问题很具体:把非结构化的专利全文,自动拆成「谁用什么方法在什么条件下做出了什么性能的材料」。适合两类人——一类是做材料情报、专利分析的从业者,想批量从几千篇专利里提取技术路线;另一类是做 NLP 落地的人,想找一个领域迁移的真实场景练手。下面这套流程,是我在石墨烯专利语料上反复调过之后相对稳的做法,从数据标注一路讲到模型微调和踩坑。
2. 从专利全文到标注语料:实体体系怎么定、数据怎么切
2.1 先定实体类别,别一上来就标
很多人第一步就翻车:直接打开标注工具开始标,标到一半发现类别边界模糊,返工成本极高。石墨烯专利的实体体系,我一般收敛到五类,够用且互斥性好:
| 实体类别 | 标签 | 典型样例 | 边界说明 |
|---|---|---|---|
| 材料 | MAT | 氧化石墨烯、单层石墨烯 | 只标具体物质,不标泛指「碳材料」 |
| 方法 | MTH | Hummers 法、化学气相沉积 | 含工艺名称,不含设备 |
| 参数 | PAR | 800℃、2 小时、1:3 | 数值+单位整体标注 |
| 性能 | PER | 比表面积 2630 m²/g | 指标名+数值一起标 |
| 应用 | APP | 锂离子电池负极、传感器 | 只标具体应用场景 |
提示:参数和性能最容易混。判断标准是——这个数值是在描述「怎么做」还是「做出来多好」。前者是 PAR,后者是 PER。
2.2 用 BIO 标注把专利文本转成训练格式
确定类别后,把每篇专利的标题、摘要、权利要求书拼成一条样本,按字符级做 BIO 标注。下面是把标注结果转成模型输入的核心脚本:
import json def char_bio_to_sample(text, spans, label_map): """ text: 专利原文(标题+摘要+权利要求拼接) spans: [(start, end, label), ...] 字符级实体区间 label_map: {'MAT': 0, 'MTH': 1, ...} """ tags = ['O'] * len(text) for start, end, label in spans: # 首字符用 B-,后续字符用 I- tags[start] = f'B-{label}' for i in range(start + 1, end): tags[i] = f'I-{label}' # 转成 id,O 统一映射到 0 tag_ids = [label_map.get(t, 0) for t in tags] return {'text': text, 'tags': tag_ids} # 读取标注文件,逐条转换 with open('graphene_annotated.jsonl', 'r', encoding='utf-8') as f: samples = [char_bio_to_sample(**json.loads(line), label_map=LABEL_MAP) for line in f]逻辑说明:字符级标注比词级更适合中文专利,因为专利里大量出现「氧化石墨烯」这种没有明显分词边界的复合词,词级标注一旦分词错误,实体边界就跟着错。参数说明:label_map里 O 必须映射到 0,因为后续 CrossEntropyLoss 默认 ignore_index=-100,而 padding 位要单独设成 -100,别和 O 混。
2.3 数据切分的两个硬约束
专利数据切分不能随机打乱。同一专利的标题、摘要、权利要求高度重复,随机切会让训练集和验证集「串味」,验证分数虚高。我一般按专利号分组切,8:1:1,保证同一专利的所有段落只出现在一个集合里。另外石墨烯专利里「方法」类实体分布极不均,Hummers 法能占一半以上,切分时要对稀有方法做分层抽样,否则验证集里某些类别样本数为零,F1 直接算不出来。
3. 改进 BERT 的三个着力点:领域适配、实体边界、类别不平衡
3.1 为什么直接用 bert-base-chinese 不够
通用中文 BERT 的预训练语料以新闻、百科为主,对「石墨烯」「CVD」这类词的子词切分很碎。比如「氧化石墨烯」可能被切成「氧」「化」「石」「墨」「烯」五个 token,模型要在这五个 token 上维持一致的实体表示,难度陡增。改进的第一个着力点就是领域适配:用石墨烯专利语料做继续预训练(continue pretraining),让 tokenizer 和权重都往领域靠。
# 用领域语料继续预训练,MLM 任务,约 5 万篇专利摘要 python run_mlm.py \ --model_name_or_path bert-base-chinese \ --train_file graphene_corpus.txt \ --do_train \ --line_by_line \ --max_seq_length 128 \ --per_device_train_batch_size 32 \ --learning_rate 5e-5 \ --num_train_epochs 3 \ --output_dir ./bert-graphene参数说明:line_by_line表示每行一条样本,适合已经按段落切好的专利语料;学习率 5e-5 是继续预训练的常用值,比从头预训练低一个量级,避免把通用知识冲掉;epoch 别超过 3,专利语料重复度高,训多了会过拟合到模板句式。
3.2 在 BERT 后面接什么:BiLSTM+CRF 还是直接线性层
实体抽取的经典结构是 BERT + BiLSTM + CRF。BiLSTM 捕捉字符级上下文,CRF 保证标签序列合法(比如 I-MAT 不能出现在 B-MAT 之前)。但专利实体边界相对规整,我实测下来,如果领域预训练做得充分,BERT + 线性分类头 + CRF 就够了,加 BiLSTM 提升不到 0.5 个点,反而拖慢推理。下面是一个精简版模型定义:
import torch.nn as nn from transformers import BertModel class BertCrfForNER(nn.Module): def __init__(self, bert_path, num_labels, dropout=0.1): super().__init__() self.bert = BertModel.from_pretrained(bert_path) self.dropout = nn.Dropout(dropout) # 线性头把 768 维映射到标签数 self.classifier = nn.Linear(self.bert.config.hidden_size, num_labels) self.crf = CRF(num_labels, batch_first=True) def forward(self, input_ids, attention_mask, labels=None): outputs = self.bert(input_ids, attention_mask=attention_mask) sequence_output = self.dropout(outputs.last_hidden_state) emissions = self.classifier(sequence_output) if labels is not None: # 用 -100 屏蔽 padding 位,不参与 loss loss = -self.crf(emissions, labels, mask=attention_mask.bool()) return loss return self.crf.decode(emissions, mask=attention_mask.bool())逻辑说明:attention_mask同时用于 BERT 和 CRF 的 mask,保证 padding 位不参与 loss 和解码。参数说明:dropout 设 0.1,专利数据量通常几千到几万条,太大欠拟合,太小过拟合;CRF 的batch_first=True要和输入维度对齐,否则会报维度错。
3.3 类别不平衡:给损失函数加权还是重采样
石墨烯专利里 MAT 和 MTH 占实体总数七成以上,APP 和 PER 加起来不到一成。直接训练,模型会倾向预测高频类。两种处理方式:一是对损失函数按类别频率加权,二是对稀有类样本过采样。我一般先用加权,简单且不改变数据分布:
# 统计训练集各类实体 token 数,取倒数做权重 from collections import Counter counter = Counter(all_tags) total = sum(counter.values()) weights = torch.tensor([total / (len(counter) * counter[i]) for i in range(num_labels)]) criterion = nn.CrossEntropyLoss(weight=weights, ignore_index=-100)参数说明:ignore_index=-100对应 padding 位,必须和标签构造时一致;权重别设得太极端,否则高频类 F1 会明显掉,一般把最大权重限制在 10 以内。如果加权后稀有类召回还是上不去,再考虑对含稀有实体的句子做 2 倍过采样。
4. 训练、评估与推理:把 F1 从 0.72 拉到 0.86 的实操细节
4.1 训练超参怎么设
在石墨烯专利语料上,我最终稳定的一组超参如下:
| 参数 | 取值 | 说明 |
|---|---|---|
| max_seq_length | 256 | 专利段落平均 180 字,256 覆盖 95% |
| batch_size | 16 | 单卡 16G 显存的上限 |
| learning_rate | 3e-5 | 微调 BERT 的常用区间 |
| epochs | 8 | 配合早停,实际 5-6 轮收敛 |
| warmup_ratio | 0.1 | 防止初期梯度震荡 |
| weight_decay | 0.01 | 轻微正则 |
训练时盯两个信号:验证集 F1 连续 2 轮不涨就停;训练 loss 降到 0.1 以下但验证 F1 不涨,说明过拟合,回去加 dropout 或减 epoch。
4.2 评估别只看整体 F1
整体 F1 会被高频类拉高,掩盖稀有类的问题。我一般按类别分别算 P/R/F1,重点看 APP 和 PER。如果 APP 的召回低于 0.6,八成是标注里应用场景边界没统一——有的标了「锂离子电池」,有的标了「锂离子电池负极」,模型学混了。这时候要回去统一标注规范,而不是调模型。
from seqeval.metrics import classification_report # preds 和 labels 都是 BIO 标签序列的列表 print(classification_report(true_labels, pred_labels, digits=4))seqeval按实体级别算指标,比按 token 算更贴近真实需求。参数说明:传入的是标签字符串列表,不是 id,转换时注意把 -100 和 padding 位去掉。
4.3 推理阶段的长文本处理
专利权利要求书经常超过 512 token,直接截断会丢实体。常见做法是滑窗切分,每段 256 token,重叠 64 token,分别推理后合并。合并时重叠区的实体要去重,判断标准是实体区间有交集就保留置信度高的那个。这一步不做,长专利的实体召回会掉 10 个点以上。
5. 避坑与排查:石墨烯专利实体抽取的五个血泪教训
现象一:验证集 F1 0.9,上线后惨不忍睹。原因:随机切分导致同专利段落泄漏。解决:按专利号分组切分,切完检查训练集和验证集的专利号交集必须为空。
现象二:模型把「石墨烯」和「氧化石墨烯」标成同一实体。原因:领域预训练不充分,模型没学到「氧化」这个前缀的区分度。解决:加大领域语料继续预训练轮次,或在标注时明确「氧化石墨烯」是独立实体类别。
现象三:参数实体「800℃」被拆成「800」和「℃」两个实体。原因:tokenizer 把摄氏度和数字切开了,BIO 标注在字符级没问题,但模型解码时边界错位。解决:后处理阶段做规则合并,数字+单位正则匹配后强制合并为一个 PAR 实体。
现象四:训练 loss 正常下降,但 CRF 解码报非法序列。原因:CRF 的转移矩阵没约束,或者 mask 传错。解决:检查attention_mask是否转成 bool,CRF 的batch_first是否和输入一致。
现象五:稀有类 APP 的 F1 始终低于 0.5。原因:样本太少,加权也救不回来。解决:先确认标注一致性,再对含 APP 的句子过采样,或者用规则兜底——专利里应用场景常出现在「用于」「应用于」后面,可以用正则先抽一遍补充训练数据。
6. 一个提效技巧:用规则+模型做两阶段抽取
纯模型方案在石墨烯专利上能到 0.86 左右,但参数和性能这两类实体有很强的模式性——「数字+单位」「指标名+数字+单位」。我后来改成两阶段:模型先抽 MAT、MTH、APP 三类语义实体,PAR 和 PER 用正则规则抽,两者结果合并。规则部分大概长这样:
import re # 参数:数字+单位,覆盖温度、时间、比例 PAR_PATTERN = re.compile(r'\d+(\.\d+)?\s*(℃|°C|小时|h|分钟|min|:|\d+)') # 性能:指标名+数值+单位 PER_PATTERN = re.compile(r'(比表面积|电导率|迁移率|强度)[^\d]{0,6}\d+(\.\d+)?\s*[a-zA-Z²/]+') def rule_extract(text): spans = [] for m in PAR_PATTERN.finditer(text): spans.append((m.start(), m.end(), 'PAR')) for m in PER_PATTERN.finditer(text): spans.append((m.start(), m.end(), 'PER')) return spans逻辑说明:规则抽出的区间和模型抽出的区间做合并,重叠时以模型结果为准(模型对边界判断更准),不重叠的直接保留。参数说明:正则里的单位列表要按你的语料补充,石墨烯专利里「m²/g」「S/cm」这类复合单位很常见,别漏。这套组合方案把整体 F1 又提了 3 个点,而且推理速度比纯模型快,因为规则部分不占 GPU。
我自己的习惯是,每换一个材料领域的专利语料,先花半天把规则模板过一遍,再决定模型要重点补哪些类别。规则能覆盖的,别硬塞给模型;模型擅长的语义边界,别用规则去凑。这个分工想清楚了,整个抽取流水线才稳。希望帮到你。
本文还有配套的精品资源,点击获取