2.1 实体识别与关系抽取


2.1 实体识别与关系抽取 — GraphRAG 知识图谱增强

本节导读:本节将详细介绍GraphRAG系统的核心构建模块——实体识别与关系抽取技术,从基础理论到实践应用,帮助你掌握如何从非结构化文本中构建结构化知识图谱。我们将从 NER 的基本原理出发,逐步深入到基于规则和深度学习的关系抽取方案,最终实现一套完整的混合抽取流水线。

学习目标

  • 掌握命名实体识别(NER)的核心技术和常用工具
  • 理解关系抽取的算法原理和实现方法
  • 学会选择合适的预训练模型进行实体关系抽取
  • 实现完整的实体识别与关系抽取流程
  • 了解实体关系抽取的评估指标和优化策略

核心概念

实体识别与关系抽取是构建知识图谱的基础技术,负责从文本中自动识别出有意义的实体(如人名、地名、组织机构等)以及它们之间的关系,将非结构化文本转换为结构化的三元组(实体-关系-实体)。在 GraphRAG 的完整技术栈中,这一环节的质量直接决定了知识图谱的准确性和后续检索的效果——如果实体识别遗漏了关键实体或关系抽取产生了错误的连接,那么即使检索算法再精妙,系统也无法返回正确的答案。

实体识别(Named Entity Recognition, NER)

实体识别是自然语言处理的基础任务,旨在识别文本中具有特定意义的实体,并将其分类到预定义的类别中。在 GraphRAG 中,实体识别的质量直接影响知识图谱的准确性和完整性。

NER 问题在形式化上是一个序列标注问题:给定一段文本,为每个 Token 分配一个标签,标签采用 BIO(Begin-Inside-Outside)或 BIOES(Begin-Inside-Outside-End-Single)标注方案。例如,对于文本"张三在北京大学工作",BIO 标注结果可能是:张(B-PER) 三(I-PER) 在(O) 北(B-ORG) 京(I-ORG) 大(I-ORG) 学(I-ORG) 工(B-REL) 作(I-REL)。其中 PER 表示人物、ORG 表示组织、REL 表示职位关系触发词。

关系抽取(Relation Extraction, RE)

关系抽取是在实体识别的基础上,识别实体之间存在的语义关系,形成三元组(头实体-关系-尾实体)。关系抽取是构建知识图谱的关键步骤,为后续的语义检索和推理提供基础。

关系抽取的核心难点在于关系多样性上下文依赖性。同一对实体在不同的上下文中可能存在不同的关系。例如"苹果"和"库克"之间的关系,在商业语境下可能是"CEO-公司",在产品语境下可能是"消费者-产品"。模型需要能够根据上下文准确判断关系的语义类别。

环境准备 / 前置知识

基础环境配置

在开始实战之前,需要确保以下基础环境已就绪:

# 创建虚拟环境 python -m venv graphrag-ner source graphrag-ner/bin/activate # 安装核心依赖 pip install torch transformers spacy networkx pip install sklearn seqeval # 下载 SpaCy 中文模型 python -m spacy download zh_core_web_sm # 如果使用 BERT 模型,建议安装 sentencepiece pip install sentencepiece

预训练模型选择

中文NER模型

  • BERT-CRF模型:适合中文NER任务,准确率高,CRF 层能有效约束标签转移的合法性
  • SpaCy中文模型:快速部署,支持多语言,适合原型验证和快速迭代
  • HanLP模型:中文NLP工具包,内置NER功能,对中文分词和命名实体识别有良好的原生支持
  • LAC(Lexical Analysis of Chinese):百度开源的中文词法分析工具,NER 速度极快

英文NER模型

  • BERT-NER:基于BERT的序列标注模型,准确率在 CoNLL-2003 数据集上达到 F1=92.8
  • RoBERTa-NER:性能更优的变体,尤其在少量标注数据场景下表现突出
  • BioBERT:专业领域NER(如生物医学),在 PubMed 和 PMC 语料上预训练

分步实战

步骤 1:基于SpaCy的实体识别

SpaCy 是最快捷的 NER 入门方案,适合在原型阶段快速验证思路。以下是一个完整的中文实体识别示例:

import spacy # 加载中文模型 nlp = spacy.load("zh_core_web_sm") text = "华为技术有限公司总部位于深圳市南山区,由任正非于1987年创立。" doc = nlp(text) # 提取实体及其类型 entities = [] for ent in doc.ents: entities.append({ "text": ent.text, "label": ent.label_, "start": ent.start_char, "end": ent.end_char }) print(f"实体: {ent.text:20s} | 类型: {ent.label_:10s} | 位置: [{ent.start_char}, {ent.end_char})") # 输出示例: # 实体: 华为技术有限公司 | 类型: ORG | 位置: [0, 9) # 实体: 深圳市南山区 | 类型: GPE | 位置: [14, 20) # 实体: 任正非 | 类型: PERSON | 位置: [23, 26)

SpaCy 的优点是零配置即可使用,缺点是中文模型的准确率有限,尤其是对专业领域实体的识别。对于生产环境,建议基于 BERT 进行微调。

步骤 2:基于BERT的实体识别

BERT(Bidirectional Encoder Representations from Transformers)通过双向 Transformer 编码器学习上下文相关的词向量表示,是目前 NER 任务中最主流的骨干网络。在 BERT 之上添加一个线性分类层即可完成序列标注。

import torch from transformers import AutoTokenizer, AutoModelForTokenClassification # 加载预训练的 NER 模型(以 BERT-base-Chinese 为例) model_name = "bert-base-chinese" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForTokenClassification.from_pretrained(model_name, num_labels=9) # 定义标签映射(BIOES 方案) label_map = { 0: "O", 1: "B-PER", 2: "I-PER", 3: "E-PER", 4: "B-ORG", 5: "I-ORG", 6: "E-ORG", 7: "B-LOC", 8: "E-LOC" } def extract_entities(text, model, tokenizer, label_map): """使用 BERT 模型从文本中提取实体""" inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=512) with torch.no_grad(): outputs = model(**inputs) predictions = torch.argmax(outputs.logits, dim=-1)[0] tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0]) entities = [] current_entity = None for token, pred_id in zip(tokens, predictions.tolist()): label = label_map.get(pred_id, "O") if label.startswith("B-"): if current_entity: entities.append(current_entity) current_entity = {"type": label[2:], "tokens": [token]} elif label.startswith("I-") and current_entity: current_entity["tokens"].append(token) elif label.startswith("E-") and current_entity: current_entity["tokens"].append(token) current_entity["text"] = current_entity["type"].join(current_entity["tokens"]).replace("##", "") entities.append(current_entity) current_entity = None else: if current_entity: entities.append(current_entity) current_entity = None return entities # 测试 text = "阿里巴巴集团在杭州成立了达摩院" entities = extract_entities(text, model, tokenizer, label_map) for ent in entities: print(f"实体: {ent.get('text', ''.join(ent['tokens']))} | 类型: {ent['type']}")

步骤 3:关系抽取实现

关系抽取通常采用**流水线(Pipeline)**方式:先用 NER 模型识别实体,再用关系分类模型判断实体对之间的关系。这种方式实现简单,但在实体嵌套和长距离依赖上存在局限。

基于规则的关系抽取适合关系模式较为固定且数据量不大的场景:

import re from typing import List, Tuple class RuleBasedRelationExtractor: """基于规则模板的关系抽取器""" def __init__(self): # 定义关系模板:正则表达式 → 关系类型 self.patterns = [ (r"(\S+?)在(\S+?)工作", "work_at"), # X在Y工作 (r"(\S+?)担任(\S+?)的(\S+)", "position"), # X担任Y的Z (r"(\S+?)总部位于(\S+?)", "headquarters"), # X总部位于Y (r"(\S+?)创立了(\S+?)", "founder"), # X创立了Y (r"(\S+?)收购了(\S+?)", "acquired"), # X收购了Y ] def extract(self, text: str, entities: List[dict]) -> List[Tuple]: """从文本和实体列表中抽取关系三元组""" triples = [] for pattern, relation_type in self.patterns: for match in re.finditer(pattern, text): groups = match.groups() if len(groups) >= 2: triples.append((groups[0], relation_type, groups[1])) return triples # 使用示例 extractor = RuleBasedRelationExtractor() text = "华为技术有限公司总部位于深圳市南山区,由任正非于1987年创立。" triples = extractor.extract(text, []) for head, relation, tail in triples: print(f"三元组: ({head}, {relation}, {tail})") # 三元组: (华为技术有限公司, headquarters, 深圳市南山区) # 三元组: (任正非, founder, 华为技术有限公司)

基于深度学习的关系抽取使用 BERT 编码句子,通过分类器预测关系类型:

import torch import torch.nn as nn from transformers import BertModel, BertPreTrainedModel class BertForRelationExtraction(BertPreTrainedModel): """基于 BERT 的关系分类模型""" def __init__(self, config, num_relations=10): super().__init__(config) self.bert = BertModel(config) self.dropout = nn.Dropout(config.hidden_dropout_prob) self.classifier = nn.Linear(config.hidden_size * 3, num_relations) # hidden_size * 3:头实体向量 + 尾实体向量 + [CLS]向量 def forward(self, input_ids, attention_mask, head_positions, tail_positions): outputs = self.bert(input_ids, attention_mask=attention_mask) sequence_output = outputs.last_hidden_state # [batch, seq_len, hidden_size] cls_output = outputs.pooler_output # [batch, hidden_size] batch_size = input_ids.size(0) # 提取头实体和尾实体的向量表示 head_vectors = [] tail_vectors = [] for i in range(batch_size): head_vec = sequence_output[i, head_positions[i], :] tail_vec = sequence_output[i, tail_positions[i], :] head_vectors.append(head_vec) tail_vectors.append(tail_vec) head_vector = torch.stack(head_vectors) # [batch, hidden_size] tail_vector = torch.stack(tail_vectors) # [batch, hidden_size] # 拼接三个向量作为关系分类的输入 combined = torch.cat([head_vector, tail_vector, cls_output], dim=-1) combined = self.dropout(combined) logits = self.classifier(combined) # [batch, num_relations] return logits

步骤 4:实体关系抽取流程整合

将上述模块整合为一个完整的抽取流水线:

from typing import List, Dict, Tuple from dataclasses import dataclass @dataclass class Triple: """知识三元组""" head: str relation: str tail: str source: str # 来源文本 confidence: float # 置信度 class KnowledgeExtractionPipeline: """完整的知识抽取流水线""" def __init__(self, ner_model, re_model, rule_extractor): self.ner_model = ner_model self.re_model = re_model self.rule_extractor = rule_extractor def process(self, text: str) -> List[Triple]: """处理一段文本,返回提取的三元组列表""" triples = [] # 第一步:实体识别 entities = self.ner_model.extract(text) # 第二步:基于规则的关系抽取(高置信度,速度快) rule_triples = self.rule_extractor.extract(text, entities) for head, relation, tail in rule_triples: triples.append(Triple( head=head, relation=relation, tail=tail, source=text, confidence=0.95 )) # 第三步:基于深度学习的关系抽取(覆盖规则未捕获的模式) entity_pairs = self._generate_entity_pairs(entities) for head_ent, tail_ent in entity_pairs: relation, conf = self.re_model.predict(text, head_ent, tail_ent) if conf > 0.7: # 置信度阈值 triples.append(Triple( head=head_ent["text"], relation=relation, tail=tail_ent["text"], source=text, confidence=conf )) # 第四步:去重和冲突消解 triples = self._deduplicate(triples) return triples def _generate_entity_pairs(self, entities): """生成所有可能的实体对""" pairs = [] for i in range(len(entities)): for j in range(i + 1, len(entities)): pairs.append((entities[i], entities[j])) pairs.append((entities[j], entities[i])) return pairs def _deduplicate(self, triples): """三元组去重:保留置信度最高的""" seen = {} for t in triples: key = (t.head, t.relation, t.tail) if key not in seen or t.confidence > seen[key].confidence: seen[key] = t return list(seen.values())
graph LR A[原始文本] --> B[文档分块] B --> C[NER 实体识别] C --> D[实体对生成] D --> E1[规则抽取器] D --> E2[深度学习抽取器] E1 --> F[三元组合并] E2 --> F F --> G[去重与冲突消解] G --> H[知识三元组] H --> I[图谱写入]

图 2-1 实体关系抽取完整流水线

完整示例:企业知识图谱构建系统

下面是一个完整的企业知识图谱构建示例,展示如何将上述组件串联起来:

def build_enterprise_kg(documents: List[str]) -> List[Triple]: """从一组企业文档中构建知识图谱""" pipeline = KnowledgeExtractionPipeline( ner_model=BertNERModel("bert-base-chinese"), re_model=BertREModel("bert-base-chinese"), rule_extractor=RuleBasedRelationExtractor() ) all_triples = [] for doc in documents: triples = pipeline.process(doc) all_triples.extend(triples) print(f"文档处理完成,提取 {len(triples)} 个三元组") # 统计 relations = {} for t in all_triples: relations[t.relation] = relations.get(t.relation, 0) + 1 print(f"\n总计提取 {len(all_triples)} 个三元组") print(f"关系分布: {relations}") return all_triples

常见问题 FAQ

Q1:NER 模型在专业领域的准确率不够怎么办?

A:专业领域 NER 是一个经典的领域适应问题。推荐的解决方案按优先级排列:

  • 领域数据微调:收集 500-1000 条领域标注数据,在预训练 BERT 上微调,通常可将 F1 提升 10-20 个百分点。这是最有效的方法。
  • 远程监督(Distant Supervision):利用已有知识库自动标注训练数据。例如,用维基百科的实体链接信息生成训练样本。
  • 领域词典增强:构建领域实体词典,在推理时通过词典匹配辅助 NER 模型。这种方法不需要训练,适合冷启动场景。
  • Few-shot Learning:在标注数据极度稀缺(少于 100 条)的情况下,使用 Pattern-Exploiting Training(PET)等少样本学习方法。

Q2:关系抽取中的"无关系"类别如何处理?

A:在实际应用中,大多数实体对之间并不存在有意义的关系。如果简单地将其全部标记为"无关系"(NA 类别),会导致严重的类别不平衡问题。推荐的策略包括:

  • 负采样:从所有可能的实体对中按比例采样负例,而非使用全部负例
  • 难度分层负例:将负例分为"容易负例"(完全不相关的实体对)和"困难负例"(可能存在关系但不在标签集中的实体对),分别赋予不同的权重
  • 阈值过滤:对于置信度低于阈值的预测结果,直接过滤而非输出"无关系"标签

Q3:如何处理嵌套实体和重叠实体?

A:嵌套实体(如"北京大学计算机系"中同时包含"北京大学"和"计算机系")是 NER 中的经典难题。传统的 BIO 序列标注无法处理嵌套,解决方案包括:

  • 边界分类(Boundary Classification):将问题转化为"实体开始-实体结束"的分类问题,支持嵌套标注
  • 超图方法(Hypergraph):将实体识别建模为超图中的团检测问题
  • 两阶段方法:先识别外层实体,再在内层实体上进行二次识别

Q4:抽取的三元组存在大量噪音如何处理?

A:噪音控制是知识图谱质量保障的核心。建议采用多级过滤策略:

  • 置信度阈值:设置最低置信度阈值(如 0.7),过滤低质量的三元组
  • 一致性校验:检查同一对实体是否被抽取为相互矛盾的关系
  • 外部验证:将抽取结果与已有知识库进行交叉验证
  • 人工审核:对高影响力实体(如核心人物、关键产品)的三元组进行人工抽样审核

最佳实践与避坑

  • 先规则后模型:在项目初期,优先使用基于规则的抽取方案快速建立基线。规则方案的可解释性强,便于快速发现问题。当规则方案的覆盖率成为瓶颈时,再引入深度学习模型进行补充。
  • 标注数据比模型架构更重要:与其追求最新的模型架构,不如投入更多精力在标注数据的质量和数量上。BERT-CRF 在高质量标注数据上的表现,通常优于最先进的模型在低质量标注数据上的表现。
  • 增量式构建图谱:不要试图一次性构建完美的知识图谱。从核心实体和核心关系开始,逐步扩展覆盖范围。定期评估图谱质量,有针对性地补充标注数据和优化模型。
  • 版本化管理抽取流程:知识抽取是一个持续迭代的过程,每次修改模型或规则都可能影响图谱质量。建议对每次抽取流程的版本进行记录,支持回滚和对比。

本节小结

本节从 NER 的序列标注原理出发,讲解了基于 SpaCy 的快速原型方案和基于 BERT 的生产级方案,深入探讨了基于规则和深度学习两种关系抽取范式,最终整合为一个完整的混合抽取流水线。通过规则抽取提供高置信度的基线结果,深度学习模型负责捕获更复杂的语义模式,两者的协同使得 GraphRAG 的知识构建既高效又准确。

延伸阅读

  • 官方文档:Transformers NER Pipeline 使用指南
  • 相关章节:本教程2.2节知识图谱构建与存储
  • 深入学习:BERT-CRF 在中文 NER 中的应用研究

关键词:实体识别, NER, 关系抽取, BERT-CRF, 混合抽取, 知识三元组
难度:进阶
预计阅读:50分钟


作者与出处
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: Star-10b78764的小龙虾 转发
评论区 (0)
U