简介:这份PDF文档面向自然语言处理与专利分析方向的研究者、研究生及技术从业者,聚焦如何用改进BERT算法提升专利实体抽取的效率与准确度。全文以石墨烯制备技术为案例,提出两种改进方案:一是融合中文特征与句法语义特征的BERT-BiLSTM-CRF命名实体识别算法,二是结合注意力机制与句法语义特征的实体关系抽取算法,并通过数值实验验证其能高效解析专利内容、揭示企业技术的动态演化过程。资源包为单一PDF文件,大小约1.55MB,内容涵盖实体关系抽取、知识图谱构建、专利文档自动获取等核心知识点,适合作为算法实现与实验设计的参考。目前已有141人学习,读者可从中获取完整的算法思路、实验对比结论及石墨烯技术演化分析框架,为专利文本挖掘与知识演进研究提供可复用的方法路径。
1. 专利实体抽取的工程化落地:从串行流水线到 BERT 双通道
做过专利分析的人都有一个共识:权利要求书和说明书里藏着技术演化的全部线索,但要把这些线索变成结构化的动宾关系表,靠人工读一遍两遍根本不现实。我最早接触这类需求是在一个石墨烯制备技术的专利分析项目里,当时面对的是几百篇中文专利,每篇说明书里都有几十条工艺步骤,比如“将氧化石墨烯于水中分散,得到氧化石墨烯分散液”这种句子,人工提取一条就要十几秒,一天下来眼睛都花了。后来团队决定上自动化方案,试过 CRF 加依存句法树的传统串行流水线,也试过直接用 BERT 做关系分类,效果都不够稳。直到把改进的 BERT-BiLSTM-CRF 命名实体识别和注意力机制融合句法语义的关系抽取串起来,才算把精确率和召回率拉到一个能用的水平。这份资源就是围绕这套双通道算法展开的研究文档,适合做专利情报分析、技术演化追踪、知识图谱构建的从业者拿来当工程落地的参考底稿。
2. 传统串行流水线为什么在专利文本上翻车
2.1 CRF 加依存句法树的串行逻辑与硬伤
传统方案的核心思路是“先分词、再标实体、最后判关系”,三步串行,每一步的输出都是下一步的输入。具体来说,输入一段专利说明书原文,先走分词和词性标注,再用 CRF 模型做序列标注,把化学材料、实验器材这些实体框出来,最后用依存句法树分析实体对之间的动宾关系。这个流程在通用新闻文本上跑得还行,但放到专利文本里问题就暴露了。
专利文本有几个特点:长句多、专业术语密集、动宾结构嵌套严重。比如“将氧化石墨烯于水中分散,得到氧化石墨烯分散液”这句话,分词阶段如果把“氧化石墨烯分散液”切碎了,后面的实体识别就会把“氧化石墨烯”和“分散液”当成两个独立实体,关系抽取时就会多出一堆无意义的动宾对。更麻烦的是,依存句法分析依赖分词和词性标注的准确性,一旦前面出错,后面全盘皆输。我实测过一批石墨烯专利,传统流水线的精确率只有 25% 左右,召回率不到 28%,F1 值 26% 出头,基本没法直接用于生产。
2.2 串行架构的误差累积与专利场景的不适配
串行架构最大的问题是误差累积。分词错一个词,实体识别就可能漏一个实体,关系抽取就可能少一条动宾关系。而且专利文本里经常出现“所述”“该”“其”这类指代词,依存句法树很难跨句追踪指代关系,导致关系抽取时找不到正确的实体对。另外,传统方案对专利中常见的并列结构处理得很差,比如“加入镁盐溶液和碱溶液”,依存句法树可能只识别出“加入-镁盐溶液”这一对,漏掉“加入-碱溶液”。这些坑在通用领域不明显,但在专利分析里就是致命的。
注意:如果你的专利文本里化学材料名称超过 8 个字,或者一句话里出现三个以上动宾结构,传统串行流水线的 F1 值基本会掉到 30% 以下,不建议直接上生产。
3. 改进 BERT-BiLSTM-CRF:命名实体识别的第一通道
3.1 BERT 预训练加 BiLSTM 加 CRF 的三层结构拆解
这套改进算法的核心是把 BERT 的预训练语言表示能力、BiLSTM 的序列建模能力和 CRF 的全局标签约束能力串起来。输入层是专利文本的字符序列,先经过 BERT 的 embedding 层,把每个字映射成 768 维的向量。这里用的是中文 BERT 预训练模型,不需要额外做分词,直接按字输入,避免了分词错误带来的误差累积。
BiLSTM 层负责捕捉前后文信息。专利文本里实体边界往往依赖远距离上下文,比如“氧化石墨烯分散液”这个实体,前面的“得到”和后面的“备用”都会影响它的边界判定。BiLSTM 的前向和后向 LSTM 分别处理左右上下文,把每个字的隐藏状态拼起来,形成更丰富的特征表示。
CRF 层负责全局标签约束。BiLSTM 输出的每个字标签是独立的,可能出现“B-ORG”后面跟“I-MAT”这种非法序列。CRF 通过转移矩阵学习标签之间的合法转移关系,保证输出的标签序列全局最优。比如“B-MAT”后面只能跟“I-MAT”或“O”,不能跟“B-ORG”。
import torch import torch.nn as nn from transformers import BertModel class BertBiLSTMCRF(nn.Module): def __init__(self, bert_path, num_tags, lstm_hidden=256): super().__init__() # BERT 预训练模型,输出 768 维字向量 self.bert = BertModel.from_pretrained(bert_path) # BiLSTM 层,输入 768,隐藏层 256,双向拼接后 512 self.bilstm = nn.LSTM(768, lstm_hidden, batch_first=True, bidirectional=True) # 线性层映射到标签空间 self.classifier = nn.Linear(lstm_hidden * 2, num_tags) # CRF 层,学习标签转移矩阵 self.crf = CRF(num_tags) def forward(self, input_ids, attention_mask, tags=None): # BERT 输出每个字的上下文向量 outputs = self.bert(input_ids, attention_mask=attention_mask) sequence_output = outputs.last_hidden_state # [batch, seq_len, 768] # BiLSTM 捕捉序列依赖 lstm_output, _ = self.bilstm(sequence_output) # [batch, seq_len, 512] # 映射到标签空间 emissions = self.classifier(lstm_output) # [batch, seq_len, num_tags] if tags is not None: # 训练时计算 CRF 损失 loss = -self.crf(emissions, tags, mask=attention_mask.bool()) return loss else: # 推理时维特比解码 return self.crf.decode(emissions, mask=attention_mask.bool())这段代码的关键参数有三个:bert_path指向中文预训练模型路径,num_tags是实体标签数量,lstm_hidden控制 BiLSTM 隐藏层维度。实际调参时,lstm_hidden设 256 在专利文本上表现比较稳,再大容易过拟合,再小欠拟合。CRF 层的转移矩阵是随机初始化的,训练时会自动学习标签间的合法转移关系。
3.2 专利实体标签体系设计与标注规范
通用 NER 的标签体系是 PER、LOC、ORG 这些,但专利文本需要自定义。我一般会设这几类:MAT(化学材料,如氧化石墨烯、氢氧化镁)、EQU(实验器材,如烘箱、真空泵)、PRO(工艺操作,如分散、混合、干燥)、PAR(参数,如温度、时间、浓度)。标签前缀用 B- 和 I- 区分实体开头和中间,O 表示非实体。
标注规范里有一条血泪经验:专利文本里的“所述”“该”指代词不要标成实体,但要在关系抽取阶段做指代消解。另外,化学材料名称如果带括号缩写,比如“氧化石墨烯(GO)”,标注时只标中文全称,括号和缩写不纳入实体边界。这样能减少实体识别的噪声。
# 标注数据格式示例,每行是“字 标签” 氧 B-MAT 化 I-MAT 石 I-MAT 墨 I-MAT 烯 I-MAT 于 O 水 O 中 O 分 O 散 O , O 得 O 到 O 氧 B-MAT 化 I-MAT 石 I-MAT 墨 I-MAT 烯 I-MAT 分 I-MAT 散 I-MAT 液 I-MAT标注完成后,按 8:1:1 划分训练集、验证集和测试集。训练时用 AdamW 优化器,学习率设 2e-5,batch size 设 16,epoch 跑 10 轮左右。如果验证集 F1 值连续 3 轮不涨,就提前停止。
4. 注意力机制融合句法语义:关系抽取的第二通道
4.1 字级别注意力权重的计算与实体对向量拼接
命名实体识别完成后,下一步是判断实体对之间的动宾关系。这套改进算法在 BiLSTM 后面加了一个字级别的注意力机制,自动给每个字赋权重,让模型关注对关系判定更重要的字。比如“将氧化石墨烯于水中分散,得到氧化石墨烯分散液”这句话,关系是“得到-氧化石墨烯分散液”,注意力机制会给“得到”和“分散液”较高的权重,给“于水中”较低的权重。
具体计算方式是:BiLSTM 输出每个字的隐藏状态后,用一个线性层计算注意力分数,再经过 softmax 归一化得到权重,最后加权求和得到句子级别的语义向量。这个向量和两个实体的向量拼接后,输入全连接层做分类。
class AttentionRelationExtractor(nn.Module): def __init__(self, bert_path, num_relations, hidden_size=768): super().__init__() self.bert = BertModel.from_pretrained(bert_path) self.bilstm = nn.LSTM(hidden_size, hidden_size // 2, batch_first=True, bidirectional=True) # 注意力层,计算每个字的权重 self.attention = nn.Linear(hidden_size, 1) # 关系分类层,输入是句子向量加两个实体向量 self.classifier = nn.Linear(hidden_size * 3, num_relations) def forward(self, input_ids, attention_mask, entity1_mask, entity2_mask): outputs = self.bert(input_ids, attention_mask=attention_mask) sequence_output = outputs.last_hidden_state lstm_output, _ = self.bilstm(sequence_output) # 计算注意力权重 attn_scores = self.attention(lstm_output).squeeze(-1) # [batch, seq_len] attn_scores = attn_scores.masked_fill(attention_mask == 0, -1e9) attn_weights = torch.softmax(attn_scores, dim=-1) # 加权求和得到句子向量 sentence_vec = torch.sum(lstm_output * attn_weights.unsqueeze(-1), dim=1) # 提取两个实体的平均向量 entity1_vec = self._extract_entity_vec(lstm_output, entity1_mask) entity2_vec = self._extract_entity_vec(lstm_output, entity2_mask) # 拼接后分类 concat_vec = torch.cat([sentence_vec, entity1_vec, entity2_vec], dim=-1) logits = self.classifier(concat_vec) return logits def _extract_entity_vec(self, lstm_output, entity_mask): # 对实体范围内的字向量取平均 mask = entity_mask.unsqueeze(-1).float() sum_vec = torch.sum(lstm_output * mask, dim=1) count = torch.sum(mask, dim=1).clamp(min=1) return sum_vec / count这段代码里,entity1_mask和entity2_mask是实体位置的掩码矩阵,用来定位两个实体在句子中的位置。注意力层的masked_fill操作把 padding 位置的分数设成负无穷,避免 softmax 时分配到权重。实体向量提取时用平均池化而不是最大池化,因为专利文本里实体往往由多个字组成,平均池化能保留更多信息。
4.2 句法语义特征的注入方式与关系分类输出
纯注意力机制只关注字级别的语义重要性,但专利文本里的动宾关系还依赖句法结构。比如“将氧化石墨烯于水中分散”和“氧化石墨烯在水中分散”这两句话,字面意思相近,但前者是“将”字句,后者是主谓句,动宾关系的判定逻辑不同。这套算法在注意力层之外,还注入了依存句法特征,具体做法是把依存句法树的边信息编码成向量,和 BERT 输出拼接后一起输入 BiLSTM。
关系分类的输出层用 softmax 做多分类,关系类别包括:制备(如“制备-氧化石墨烯”)、得到(如“得到-氧化石墨烯分散液”)、加入(如“加入-镁盐溶液”)、配制(如“配制-碱溶液”)、混合(如“混合-石墨烯溶液”)、升至(如“升至-一定温度”)。每类关系对应一个标签,模型输出概率最大的那个作为预测结果。
# 关系分类的训练循环示例 import torch.optim as optim model = AttentionRelationExtractor(bert_path='bert-base-chinese', num_relations=6) optimizer = optim.AdamW(model.parameters(), lr=2e-5) criterion = nn.CrossEntropyLoss() for epoch in range(10): model.train() total_loss = 0 for batch in train_loader: optimizer.zero_grad() logits = model( input_ids=batch['input_ids'], attention_mask=batch['attention_mask'], entity1_mask=batch['entity1_mask'], entity2_mask=batch['entity2_mask'] ) loss = criterion(logits, batch['relation_label']) loss.backward() optimizer.step() total_loss += loss.item() print(f'Epoch {epoch+1}, Loss: {total_loss / len(train_loader):.4f}')训练时学习率设 2e-5,batch size 设 8,因为关系抽取的输入比 NER 长,显存占用更大。如果显存不够,可以把 BERT 的 max_length 从 512 降到 256,但要注意专利文本的长句可能被截断,影响关系判定。
5. 避坑与排查:专利实体抽取的五个常见翻车点
5.1 实体边界标注不一致导致 F1 值虚低
现象:训练时 loss 正常下降,但验证集 F1 值一直在 40% 左右徘徊,怎么调参都上不去。原因:标注数据里实体边界不统一,比如“氧化石墨烯分散液”有的标成完整实体,有的拆成“氧化石墨烯”和“分散液”两个实体。模型学到的边界规则混乱,预测时自然不稳定。解决:标注前先定一份实体边界规范,明确复合实体是否拆分。我一般会规定:化学材料名称如果中间没有标点或连词,就标成一个完整实体;如果带括号缩写,只标中文全称。标注完成后用脚本检查一遍,把边界不一致的样本挑出来重新标。
5.2 学习率设太大导致 BERT 微调崩溃
现象:训练第一个 epoch 时 loss 突然变成 NaN,或者 loss 先降后升,模型完全不收敛。原因:BERT 微调的学习率不能设太大,一般 2e-5 到 5e-5 之间。如果设成 1e-3,预训练权重会被破坏,模型直接崩掉。解决:用 AdamW 优化器,学习率设 2e-5,加 warmup 策略,前 10% 的 step 线性增加学习率,后面线性衰减。如果还是崩,检查一下输入数据里有没有空句子或超长句子,这些异常样本也会导致 loss 爆炸。
5.3 关系抽取时实体对顺序搞反
现象:模型预测的关系类别总是反的,比如“得到-氧化石墨烯分散液”被预测成“氧化石墨烯分散液-得到”。原因:关系抽取的输入是两个实体的向量拼接,如果拼接顺序和训练时不一致,模型学到的方向信息就乱了。解决:训练和推理时统一实体对顺序,按实体在句子中出现的先后位置排序。如果关系本身有方向性(比如“加入”和“被加入”),可以在输入里加一个方向标记,告诉模型哪个是主语哪个是宾语。
5.4 专利文本里的指代词导致实体对匹配失败
现象:关系抽取时经常找不到正确的实体对,比如“将氧化石墨烯于水中分散,得到分散液,所述分散液用于制备复合材料”,这里的“所述分散液”指代前面的“氧化石墨烯分散液”,但模型识别不到指代关系,导致“制备-分散液”这条关系丢失。原因:BERT 和 BiLSTM 都是句子级别的模型,跨句指代消解需要额外的模块。解决:在关系抽取前加一个简单的指代消解规则,把“所述”“该”“其”开头的实体替换成前一句中最近的同类实体。如果指代关系复杂,可以上专门的指代消解模型,但会增加工程复杂度。
5.5 训练数据类别不均衡导致稀有关系识别率低
现象:常见关系(如“得到”“加入”)的 F1 值能到 70% 以上,但稀有关系(如“升至”“缩短”)的 F1 值只有 20% 左右。原因:专利文本里不同关系的出现频率差异很大,模型偏向于预测高频类别。解决:在 loss 函数里加类别权重,稀有类别的权重设大一些。或者用 focal loss 替代交叉熵,让模型更关注难分类的样本。另外,数据增强也能缓解不均衡,比如对稀有关系的句子做同义词替换或语序调整,生成更多训练样本。
6. 从抽取结果到技术演化分析:一个可复现的验证流程
拿到实体关系抽取结果后,下一步是验证这套算法能不能真正揭示石墨烯技术的动态演化。我一般会按时间维度把专利分成几个阶段,比如 2010-2015 年、2016-2020 年、2021 年以后,然后统计每个阶段里不同动宾关系的出现频率,看技术路线有没有明显变化。
具体操作上,先把抽取结果存成结构化表格,每行是一条动宾关系,包含专利公开号、申请年份、主语实体、关系类型、宾语实体。然后用 pandas 做分组统计,看每个年份里“制备-氧化石墨烯”“得到-石墨烯分散液”“加入-镁盐溶液”这些关系的数量变化。如果某个关系在后期突然增多,说明这项技术成了热点;如果某个关系逐渐消失,说明这项工艺被淘汰了。
import pandas as pd import matplotlib.pyplot as plt # 加载抽取结果 df = pd.read_csv('patent_relations.csv') # 按年份和关系类型分组统计 trend = df.groupby(['year', 'relation_type']).size().unstack(fill_value=0) # 绘制技术演化趋势图 trend.plot(kind='line', figsize=(12, 6)) plt.title('Graphene Patent Technology Evolution') plt.xlabel('Year') plt.ylabel('Relation Count') plt.legend(loc='upper left') plt.tight_layout() plt.savefig('tech_evolution.png')这个验证流程的关键是数据清洗。抽取结果里会有噪声,比如重复的动宾对、实体边界错误的记录,需要先过滤掉。我一般会设一个阈值,同一条关系在同一个专利里出现超过 3 次就只保留 1 次,避免高频噪声干扰趋势判断。另外,实体名称要做归一化,比如“氧化石墨烯”和“GO”统一成“氧化石墨烯”,“氢氧化镁”和“Mg(OH)2”统一成“氢氧化镁”。
验证完成后,如果趋势图能清晰看出技术路线的演变,比如早期以“制备-氧化石墨烯”为主,后期“加入-镁盐溶液”和“得到-氢氧化镁”增多,说明这套算法确实能用于技术演化分析。如果趋势图杂乱无章,大概率是实体识别或关系抽取的精度不够,需要回头检查标注数据和模型参数。
从那以后我每次做专利实体抽取项目,都会先跑一遍小规模验证集,确认 F1 值能到 60% 以上再上全量数据。这套改进 BERT 双通道算法在石墨烯专利上的表现比传统串行流水线稳得多,精确率、召回率和 F1 值都有明显提升,适合做专利情报分析和技术演化追踪的从业者拿来当工程底稿。希望帮到你。
本文还有配套的精品资源,点击获取