本节导读:本节将详细介绍GraphRAG系统的核心构建模块——实体识别与关系抽取技术,从基础理论到实践应用,帮助你掌握如何从非结构化文本中构建结构化知识图谱。我们将从 NER 的基本原理出发,逐步深入到基于规则和深度学习的关系抽取方案,最终实现一套完整的混合抽取流水线。
实体识别与关系抽取是构建知识图谱的基础技术,负责从文本中自动识别出有意义的实体(如人名、地名、组织机构等)以及它们之间的关系,将非结构化文本转换为结构化的三元组(实体-关系-实体)。在 GraphRAG 的完整技术栈中,这一环节的质量直接决定了知识图谱的准确性和后续检索的效果——如果实体识别遗漏了关键实体或关系抽取产生了错误的连接,那么即使检索算法再精妙,系统也无法返回正确的答案。
实体识别是自然语言处理的基础任务,旨在识别文本中具有特定意义的实体,并将其分类到预定义的类别中。在 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 表示职位关系触发词。
关系抽取是在实体识别的基础上,识别实体之间存在的语义关系,形成三元组(头实体-关系-尾实体)。关系抽取是构建知识图谱的关键步骤,为后续的语义检索和推理提供基础。
关系抽取的核心难点在于关系多样性和上下文依赖性。同一对实体在不同的上下文中可能存在不同的关系。例如"苹果"和"库克"之间的关系,在商业语境下可能是"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
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 进行微调。
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']}")
关系抽取通常采用**流水线(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
将上述模块整合为一个完整的抽取流水线:
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())
图 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
A:专业领域 NER 是一个经典的领域适应问题。推荐的解决方案按优先级排列:
A:在实际应用中,大多数实体对之间并不存在有意义的关系。如果简单地将其全部标记为"无关系"(NA 类别),会导致严重的类别不平衡问题。推荐的策略包括:
A:嵌套实体(如"北京大学计算机系"中同时包含"北京大学"和"计算机系")是 NER 中的经典难题。传统的 BIO 序列标注无法处理嵌套,解决方案包括:
A:噪音控制是知识图谱质量保障的核心。建议采用多级过滤策略:
本节从 NER 的序列标注原理出发,讲解了基于 SpaCy 的快速原型方案和基于 BERT 的生产级方案,深入探讨了基于规则和深度学习两种关系抽取范式,最终整合为一个完整的混合抽取流水线。通过规则抽取提供高置信度的基线结果,深度学习模型负责捕获更复杂的语义模式,两者的协同使得 GraphRAG 的知识构建既高效又准确。
关键词:实体识别, NER, 关系抽取, BERT-CRF, 混合抽取, 知识三元组
难度:进阶
预计阅读:50分钟