知识图谱构建实战:实体识别与关系抽取
“知识图谱的价值取决于知识的质量——而知识的质量取决于抽取的精度。”
一、知识构建的整体流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 原始数据(文本/表格/数据库) ↓ ┌───────────────────────────────────────┐ │ Step 1: 知识抽取 │ │ - 命名实体识别(NER):提取实体 │ │ - 关系抽取(RE):提取关系 │ │ - 事件抽取:提取事件及参与者 │ ├───────────────────────────────────────┤ │ Step 2: 实体链接 │ │ - 实体消歧:解决"苹果"是多义词 │ │ - 实体统一:将不同写法映射到同一实体 │ ├───────────────────────────────────────┤ │ Step 3: 知识融合 │ │ - 实体对齐:合并来自不同源的重复实体 │ │ - 冲突检测:检测矛盾的知识 │ ├───────────────────────────────────────┤ │ Step 4: 知识补全 │ │ - 知识推理:发现缺失的知识 │ │ - 知识验证:检测错误的知识 │ └───────────────────────────────────────┘ ↓ 知识图谱(三元组存储)
二、命名实体识别(NER) 2.1 什么是 NER? NER(Named Entity Recognition)是从文本中识别出有特定意义的实体 ,并将其分类到预定义类别。
1 2 3 4 5 6 7 8 文本: "马化腾创办了腾讯公司,总部位于深圳。" NER 结果: 实体 类别 位置 马化腾 → 人物 [0 , 3 ] 腾讯公司 → 组织 [6 , 10 ] 深圳 → 地点 [14 , 16 ]
常见实体类型:
类别
示例
人物(PER)
马化腾、习近平
组织(ORG)
腾讯、Google
地点(LOC)
北京、深圳
时间(TIME)
1998年、昨天
货币(MON)
100万美元
百分比(PERCENT)
增长 30%
2.2 基于规则的方法 早期的 NER 主要依赖规则,但适用性有限。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import redef rule_based_ner (text ): """基于规则的 NER(简单示例)""" entities = [] company_pattern = re.compile (r'[\u4e00-\u9fa5]{2,10}(公司|集团|企业|医院|学校)' ) for match in company_pattern.finditer(text): entities.append({ "text" : match .group(), "type" : "ORG" , "start" : match .start(), "end" : match .end() }) return entities
2.3 基于深度学习的 NER 现代 NER 主要使用 BERT + CRF / BERT + Span 方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 from transformers import AutoTokenizer, AutoModelForTokenClassificationimport torchclass NERModel : def __init__ (self, model_name="dslim/bert-base-NER" ): self .tokenizer = AutoTokenizer.from_pretrained(model_name) self .model = AutoModelForTokenClassification.from_pretrained(model_name) self .labels = ['O' , 'B-MISC' , 'I-MISC' , 'B-PER' , 'I-PER' , 'B-ORG' , 'I-ORG' , 'B-LOC' , 'I-LOC' ] def extract_entities (self, text ): inputs = self .tokenizer(text, return_tensors="pt" , truncation=True , max_length=512 ) with torch.no_grad(): outputs = self .model(**inputs) predictions = torch.argmax(outputs.logits, dim=2 )[0 ].tolist() tokens = self .tokenizer.convert_ids_to_tokens(inputs["input_ids" ][0 ]) entities = [] current_entity = None for token, pred in zip (tokens, predictions): label = self .labels[pred] if label.startswith('B-' ): if current_entity: entities.append(current_entity) current_entity = {"text" : token, "type" : label[2 :], "start" : None } elif label.startswith('I-' ) and current_entity: current_entity["text" ] += token.replace("##" , "" ) else : if current_entity: entities.append(current_entity) current_entity = None if current_entity: entities.append(current_entity) return entities ner = NERModel() entities = ner.extract_entities("Bill Gates founded Microsoft in New Mexico" )
2.4 中文 NER 的挑战 中文 NER 比英文更难,因为:
1 2 3 4 5 6 7 8 英文: "Elon Musk founded SpaceX in California" 分词: [Elon Musk] [founded] [SpaceX] [in] [California] 识别: B-PER I-PER O B-ORG O B-LOC I-LOC 中文: "埃隆·马斯克创办了SpaceX" 分词: 埃隆 / 马斯克 / 创办 / 了 / SpaceX NER: ↓_________↓ O O O ↓______↓ 人物实体 B-PER I-PER 组织 B-ORG I-ORG
挑战 :
没有天然分词边界 :英文 tokenize 后每个词有空格分隔,中文需要额外分词
嵌套实体 :”清华大学”是组织,”清华”是大学的一部分,中文里嵌套很常见
简称与全称 :”腾讯”和”深圳市腾讯计算机系统有限公司”是同一个实体
解决方案 :使用 BERT-Biaffine-NER 或 GlobalPointer 等嵌套NER模型。
1 2 3 4 5 6 7 8 from transformers import BertTokenizerFastfrom modelscope.outputs import OutputKeysfrom modelscope.pipelines import pipelinep = pipeline("token-classification" , model="damo/nlp_raner_named-entity-recognition_chinese-base-company" ) result = p("腾讯是一家中国互联网巨头,总部位于深圳" )
3.1 什么是关系抽取? 关系抽取是从文本中识别出实体之间的语义关系。
1 2 3 4 5 文本: "马化腾创办了腾讯公司" 三元组: ("马化腾" , "创办" , "腾讯公司" ) 主语 谓语 宾语
3.2 关系类型
关系类型
示例
创始人关系
马化腾 — 创办 — 腾讯
任职关系
张小龙 — 任职于 — 腾讯
地理位置关系
腾讯 — 总部位于 — 深圳
产品关系
腾讯 — 产品 — 微信
股权关系
马化腾 — 持股 — 腾讯
竞争关系
腾讯 — 竞争 — 阿里巴巴
合作关系
腾讯 — 合作 — 京东
3.3 关系抽取的方法 方法1:监督学习(Supervised RE) 1 2 3 4 5 6 7 8 9 10 11 entities = ner.extract_entities("马化腾创办了腾讯" ) for entity1 in entities: for entity2 in entities: if entity1 != entity2: relation = relation_classifier(entity1, entity2, text) if relation != "无关系" : triplets.append((entity1, relation, entity2))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from transformers import BertModel, BertPreTrainedModelclass RelationClassifier (BertPreTrainedModel ): def __init__ (self, config, num_labels ): super ().__init__(config) self .bert = BertModel(config) self .dropout = nn.Dropout(config.hidden_size) self .classifier = nn.Linear(config.hidden_size * 3 , num_labels) def forward (self, input_ids, attention_mask, entity_positions ): outputs = self .bert(input_ids=input_ids, attention_mask=attention_mask) sequence_output = outputs.last_hidden_state pooled = torch.cat([ sequence_output[:, 0 , :], head_entity_vec, tail_entity_vec ], dim=-1 ) logits = self .classifier(self .dropout(pooled)) return logits
方法2:远程监督(Distant Supervision) 核心思想 :利用已有知识图谱自动生成训练数据,减少人工标注。
1 2 3 4 5 6 7 8 9 10 句子1 : "马化腾是腾讯公司的创始人" 句子2 : "1998年,马化腾与张志东等人共同创办了腾讯"
问题 :远程监督会引入噪声标签 ——实体对同时出现在某关系中,不代表包含该关系的句子就表达了这种关系。
缓解方法 :多示例学习(Multi-Instance Learning),将句子打包成袋(bag),只对袋子做标签。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class PCNN (nn.Module): def __init__ (self, embedding_dim, hidden_dim, num_relations ): super ().__init__() self .embedding = nn.Embedding(vocab_size, embedding_dim) self .position_embedding = nn.Embedding(max_seq_len * 2 , embedding_dim) self .conv = nn.Conv2d(1 , 100 , (3 , embedding_dim * 3 )) self .maxpool = nn.AdaptiveMaxPool1d(3 ) self .classifier = nn.Linear(300 , num_relations) def forward (self, x ): x = x.unsqueeze(1 ) x = torch.relu(self .conv(x)) x = x.squeeze(-1 ) x = self .maxpool(x).view(x.size(0 ), -1 ) return self .classifier(x)
方法3:Prompt-based RE(大模型时代) 用大模型做关系抽取,无需训练:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from openai import OpenAIdef llm_relation_extraction (text: str , entities: list ) -> list : """用 GPT-4 做零样本关系抽取""" entity_str = ", " .join([f"{e['text' ]} ({e['type' ]} )" for e in entities]) prompt = f""" 从以下文本中抽取实体之间的关系,以三元组格式输出。 文本:{text} 已识别的实体:{entity_str} 请输出所有实体对之间的关系,格式: (主体, 关系, 客体) 如果没有特殊关系,输出"无特殊关系"。 """ response = client.chat.completions.create( model="gpt-4" , messages=[{"role" : "user" , "content" : prompt}] ) return parse_triplets(response.choices[0 ].message.content)
传统 Pipeline 方法(先NER后RE)会积累错误。联合抽取 模型同时做 NER 和 RE。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class CasRel (nn.Module): """ 假设给定句子和所有主语,识别所有(主语, 关系, 宾语)三元组 """ def __init__ (self, bert_model, num_relations ): super ().__init__() self .bert = bert_model self .num_relations = num_relations self .head_select = nn.Linear(768 , 2 ) self .tail_select = nn.ModuleList([ nn.Linear(768 , 2 ) for _ in range (num_relations) ]) def forward (self, input_ids, attention_mask ): outputs = self .bert(input_ids, attention_mask) sequence_output = outputs.last_hidden_state head_logits = self .head_select(sequence_output) for r_idx in range (self .num_relations): tail_logits = self .tail_select[r_idx](sequence_output) return triplets
四、实体链接(Entity Linking) 4.1 什么是实体链接? 将文本中识别的实体,链接到知识图谱中唯一的实体 ID 。
1 2 3 4 5 6 文本中的实体: "苹果" 知识图谱中的候选实体: - 苹果公司 (Apple Inc.) - 苹果 (水果) 实体链接:确定这里的"苹果"指的是哪个实体
4.2 实体消歧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def entity_disambiguation (entity_mention: str , context: str , candidate_entities: list ) -> str : """上下文消歧""" context_embedding = embed(context) scores = [] for entity in candidate_entities: entity_desc = f"{entity.name} : {entity.description} " entity_embedding = embed(entity_desc) similarity = cosine_similarity(context_embedding, entity_embedding) scores.append((entity.id , similarity)) best_entity = max (scores, key=lambda x: x[1 ]) return best_entity[0 ] entity_disambiguation( entity_mention="苹果" , context="乔布斯创立了苹果公司,开发了iPhone" , candidate_entities=[ {"id" : "Apple_Inc." , "name" : "苹果公司" , "description" : "美国科技公司" }, {"id" : "Apple_fruit" , "name" : "苹果" , "description" : "一种水果" } ] )
4.3 共指消解(Coreference Resolution) 处理同一个实体在不同表达中的指代问题。
1 2 3 4 5 6 7 8 9 10 11 12 文本: "马化腾创办了腾讯。他后来成为全球最富有的人之一。" ↑ 指代"马化腾" 共指消解结果: "他" → 马化腾 from span_transformers import SpanResolverresolver = SpanResolver.from_pretrained("michiyasunaga/spanscope-espire-base" ) clusters = resolver.resolve(document)
五、知识融合(Knowledge Fusion) 5.1 实体对齐(Entity Alignment) 将来自不同数据源的同一实体合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 def entity_alignment (kg1: Graph, kg2: Graph ) -> list : """发现两个知识图谱中的等价实体对""" aligned_pairs = [] for entity1 in kg1.entities: for entity2 in kg2.entities: similarity = calculate_similarity(entity1, entity2) if similarity > threshold: candidate_pairs.append((entity1, entity2, similarity)) aligned_pairs = gnn_entity_matching(candidate_pairs) return aligned_pairs def calculate_similarity (e1: Entity, e2: Entity ) -> float : """计算实体对的相似度""" score = 0 for attr in shared_attributes(e1, e2): score += string_similarity(e1[attr], e2[attr]) neighbors_e1 = get_neighbors(e1) neighbors_e2 = get_neighbors(e2) score += len (neighbors_e1 & neighbors_e2) * 0.3 return score / len (shared_attributes)
5.2 冲突检测与解决 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def conflict_detection_and_resolution (triplets1, triplets2 ) -> Graph: """检测并解决冲突知识""" kg = merge(triplets1, triplets2) conflicts = [] for entity_pair in find_entity_pairs_with_conflicts(kg): relations = get_relations_between(entity_pair) if has_conflict(relations): conflicts.append({ "entity" : entity_pair, "conflicting_relations" : relations }) for conflict in conflicts: resolved_value = resolve_conflict( conflict["conflicting_relations" ], strategy="confidence" ) kg.update(entity_pair, resolved_value) return kg
六、知识补全(Knowledge Completion) 6.1 知识图谱嵌入(KGE) 用嵌入向量表示知识图谱中的实体和关系,支持链接预测 (预测缺失的关系)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class TransE (nn.Module): def __init__ (self, num_entities, num_relations, embedding_dim ): super ().__init__() self .entity_embedding = nn.Embedding(num_entities, embedding_dim) self .relation_embedding = nn.Embedding(num_relations, embedding_dim) def forward (self, head, relation, tail ): """TransE 核心思想:h + r ≈ t""" h = self .entity_embedding(head) r = self .relation_embedding(relation) t = self .entity_embedding(tail) score = torch.norm(h + r - t, p=2 , dim=-1 ) return score model = TransE(num_entities=10000 , num_relations=100 , embedding_dim=256 ) optimizer = torch.optim.Adam(model.parameters(), lr=0.001 ) for epoch in range (100 ): for batch in dataloader: h, r, t = batch pos_score = model(h, r, t) neg_h = torch.randint_like(h, num_entities) neg_t = torch.randint_like(t, num_entities) neg_score = model(neg_h, r, t) loss = torch.relu(pos_score + margin - neg_score).mean() optimizer.zero_grad() loss.backward() optimizer.step() def predict_tail (head, relation, top_k=10 ): h_emb = model.entity_embedding(head) r_emb = model.relation_embedding(relation) target = h_emb + r_emb all_t_emb = model.entity_embedding.weight distances = torch.norm(all_t_emb - target, p=2 , dim=-1 ) return torch.argsort(distances)[:top_k]
6.2 其他嵌入模型
模型
核心思想
特点
TransE
h + r ≈ t
简单高效,适合一对一关系
TransH
h + r ≈ t(t在关系超平面上)
解决多关系建模
TransR
M_r × h ≈ M_r × t
实体和关系在不同空间
RotatE
h × r ≈ t(旋转)
建模对称/反对称/合成关系
ConvE
2D卷积 + 全连接
高表达能力
CompGCN
GNN + 组合操作
利用图结构信息
七、开源工具推荐
工具
类型
特点
LSP4KG
完整pipeline
Stanford开源,支持NER+RE
DeepKE
中文NER/RE/RE
清华开源,专为中文设计
SPADE
事件抽取
基于LSTM的事件流水线
OpenNRE
关系抽取
清华开源,多种RE模型
REL
实体链接
综合实体链接框架
Siegrist
实体对齐
GNN-based对齐模型
总结
知识构建 :知识抽取 → 实体链接 → 知识融合 → 知识补全
NER :从文本识别实体,BERT+CRF 是主流方案,中文需注意嵌套实体
关系抽取 :Pipeline(先NER后RE)或联合抽取,大模型可以做零样本 RE
实体链接 :上下文消歧 + 向量相似度匹配
知识补全 :TransE 等嵌入模型支持链接预测,发现缺失知识
相关文章: