知识图谱自动构建:NER、关系抽取、实体链接

知识图谱的构建从手工编辑走向自动化是降低成本、扩大规模的关键。本文系统介绍知识图谱自动构建的三大核心任务——命名实体识别(NER)、关系抽取(RE)、实体链接(EL)——的技术方法、LLM增强方案与工程实践。

一、知识图谱构建流水线

1.1 全流程概览

非结构化文本 → 知识图谱的自动构建流水线:

原始文本
    │
    ▼
┌──────────────┐
│ 1. 文本预处理  │  分句、分词、清洗
└──────────────┘
    │
    ▼
┌──────────────┐
│ 2. 命名实体识别│  识别人名、地名、机构名等
│    (NER)      │  输出: [(实体, 类型, 位置)]
└──────────────┘
    │
    ▼
┌──────────────┐
│ 3. 关系抽取   │  识别实体间的语义关系
│    (RE)       │  输出: [(实体1, 关系, 实体2)]
└──────────────┘
    │
    ▼
┌──────────────┐
│ 4. 实体链接   │  将提及映射到知识库实体
│    (EL)       │  输出: [提及 → KB实体ID]
└──────────────┘
    │
    ▼
┌──────────────┐
│ 5. 知识融合   │  去重、冲突消解、置信度
└──────────────┘
    │
    ▼
知识图谱 (Neo4j / RDF Store)

1.2 数据源类型

数据源 结构化程度 抽取难度 典型示例
结构化数据 数据库、CSV、表格
半结构化数据 JSON、XML、HTML
非结构化文本 新闻、报告、论文
多模态数据 极高 图片、视频、音频

二、命名实体识别(NER)

2.1 方法演进

NER技术发展:

规则方法 (1990s-2000s)
├── 正则表达式 + 字典匹配
├── 手工规则 + 上下文模板
└── 优势:可控性强;劣势:扩展性差

统计方法 (2000s-2010s)
├── HMM(隐马尔可夫模型)
├── CRF(条件随机场)
├── 最大熵模型
└── 优势:数据驱动;劣势:特征工程繁重

深度学习 (2015-2020)
├── BiLSTM-CRF
├── CNN-CRF
├── BERT + Token Classification
└── 优势:端到端学习;劣势:需要标注数据

LLM时代 (2023+)
├── In-context Learning (Few-shot NER)
├── 指令微调 NER
├── LLM + 传统NER混合
└── 优势:零/少样本;劣势:速度与成本

2.2 BERT-based NER实现

from transformers import AutoTokenizer, AutoModelForTokenClassification
from transformers import pipeline
import torch

# 加载预训练NER模型
model_name = "dslim/bert-base-NER"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForTokenClassification.from_pretrained(model_name)

# 使用Pipeline简化调用
ner_pipeline = pipeline(
    "ner",
    model=model,
    tokenizer=tokenizer,
    aggregation_strategy="simple"  # 合并子词token
)

# 执行NER
text = "Apple CEO Tim Cook announced the new iPhone at the Steve Jobs Theater in Cupertino."
results = ner_pipeline(text)

for entity in results:
    print(f"  {entity['word']:20s} | {entity['entity_group']:5s} | "
          f"score={entity['score']:.3f} | "
          f"pos=[{entity['start']}, {entity['end']}]")

# 输出:
#   Apple                | ORG   | score=0.998 | pos=[0, 5]
#   Tim Cook             | PER   | score=0.999 | pos=[10, 18]
#   iPhone               | MISC  | score=0.987 | pos=[37, 43]
#   Steve Jobs Theater   | LOC   | score=0.965 | pos=[51, 70]
#   Cupertino            | LOC   | score=0.998 | pos=[74, 83]

2.3 LLM-based NER

# 使用LLM进行零样本NER
def llm_ner(text, entity_types, llm_client):
    prompt = f"""从以下文本中提取命名实体。

实体类型: {', '.join(entity_types)}

文本: {text}

请以JSON格式输出,格式为:
[{{"entity": "实体文本", "type": "实体类型", "start": 起始位置, "end": 结束位置}}]

只输出JSON,不要其他内容。"""

    response = llm_client.generate(prompt)
    return json.loads(response)

# 调用
entities = llm_ner(
    text="华为创始人任正非在深圳总部发布了新款Mate系列手机",
    entity_types=["人物", "组织", "地点", "产品"],
    llm_client=client
)
# 预期输出:
# [
#   {"entity": "华为", "type": "组织", "start": 0, "end": 2},
#   {"entity": "任正非", "type": "人物", "start": 5, "end": 8},
#   {"entity": "深圳", "type": "地点", "start": 9, "end": 11},
#   {"entity": "Mate系列手机", "type": "产品", "start": 16, "end": 23}
# ]

2.4 NER评估指标

指标 计算方式 说明
Precision TP / (TP + FP) 识别结果中正确的比例
Recall TP / (TP + FN) 应识别实体中被正确找到的比例
F1 2 * P * R / (P + R) 精确率与召回率的调和平均
Exact Match 边界和类型都正确 严格评估
Partial Match 部分重叠即可 宽松评估

三、关系抽取(RE)

3.1 任务定义

输入: 文本 + 实体对
输出: 实体间的关系类型

示例:
  文本: "任正非创立了华为公司,总部位于深圳。"
  实体对: (任正非, 华为公司)
  输出: 创始人(founder)

  实体对: (华为公司, 深圳)
  输出: 总部所在地(headquartered_in)

3.2 方法分类

关系抽取方法:

1. 管道式(Pipeline)
   先NER → 再RE
   优势: 模块化,各环节可独立优化
   劣势: 错误传播

2. 联合抽取(Joint Extraction)
   同时识别实体和关系
   优势: 避免错误传播,共享信息
   劣势: 模型更复杂

3. 远程监督(Distant Supervision)
   利用已有知识库自动标注训练数据
   优势: 无需人工标注
   劣势: 噪声标签

4. LLM提示式
   直接用LLM抽取三元组
   优势: 零样本、灵活
   劣势: 速度慢、成本高

3.3 LLM驱动的关系抽取

def extract_relations(text, llm_client, relation_types=None):
    """使用LLM从文本中抽取关系三元组"""

    relation_hint = ""
    if relation_types:
        relation_hint = f"\n可能的关系类型: {', '.join(relation_types)}"

    prompt = f"""从以下文本中抽取所有实体关系三元组。
{relation_hint}

文本: {text}

输出格式(JSON数组):
[
  {{"subject": "主语实体", "relation": "关系", "object": "宾语实体", "confidence": 0.95}},
  ...
]

规则:
1. 只抽取文本中明确表达的关系
2. confidence表示你对这个三元组的确信程度(0-1)
3. 关系用简洁的中文动词短语表达"""

    response = llm_client.generate(prompt)
    triples = json.loads(response)
    return triples

# 示例
text = """
腾讯公司由马化腾于1998年在深圳创立。目前腾讯是中国市值最高的互联网公司之一,
旗下产品包括微信、QQ、腾讯云等。2024年,腾讯宣布与华为合作开发AI大模型。
"""

triples = extract_relations(text, client)
# 预期输出:
# [
#   {"subject": "马化腾", "relation": "创立", "object": "腾讯公司", "confidence": 0.98},
#   {"subject": "腾讯公司", "relation": "创立时间", "object": "1998年", "confidence": 0.97},
#   {"subject": "腾讯公司", "relation": "总部位于", "object": "深圳", "confidence": 0.96},
#   {"subject": "微信", "relation": "属于", "object": "腾讯公司", "confidence": 0.95},
#   {"subject": "QQ", "relation": "属于", "object": "腾讯公司", "confidence": 0.95},
#   {"subject": "腾讯云", "relation": "属于", "object": "腾讯公司", "confidence": 0.94},
#   {"subject": "腾讯", "relation": "合作", "object": "华为", "confidence": 0.93}
# ]

四、实体链接(EL)

4.1 任务定义

实体链接(Entity Linking):
将文本中的实体提及(mention)映射到知识库中的标准实体(entity)

示例:
  提及: "苹果"
  候选知识库实体:
    - Q312 (Apple Inc., 科技公司)
    - Q89 (苹果, 水果)
    - Q7860 (Apple Records, 唱片公司)

  上下文: "苹果公司CEO库克宣布了新产品"
  链接结果: Q312 (Apple Inc.)

核心挑战: 歧义消解(同一名称对应多个实体)

4.2 实体链接流水线

实体链接三步骤:

Step 1: 候选生成(Candidate Generation)
├── 方法:
│   ├── 精确匹配:名称/别名完全匹配
│   ├── 模糊匹配:编辑距离、拼音匹配
│   ├── 别名表:维护实体的各种称呼
│   └── 锚文本:Wikipedia超链接文本
├── 目标:高召回率(不遗漏正确候选)
└── 典型候选数:5-20个

Step 2: 候选排序(Candidate Ranking)
├── 特征:
│   ├── 上下文相似度(实体描述 vs 提及上下文)
│   ├── 实体先验概率(热门度)
│   ├── 类型一致性(期望类型 vs 候选类型)
│   └── 共现信息(同文档中其他实体的关联)
├── 方法:
│   ├── 传统:SVM/LR + 手工特征
│   ├── 深度学习:双塔模型 + Cross-encoder
│   └── LLM:上下文理解 + 知识推理
└── 目标:高精确率(排第一的是正确答案)

Step 3: NIL检测
├── 判断提及是否不在知识库中
├── NIL实体可触发知识库扩展
└── 避免错误链接(宁可不链,不可错链)

4.3 基于嵌入的实体链接

from sentence_transformers import SentenceTransformer
import numpy as np

class EmbeddingEntityLinker:
    def __init__(self, knowledge_base):
        self.model = SentenceTransformer('all-MiniLM-L6-v2')
        self.kb = knowledge_base
        self._build_index()

    def _build_index(self):
        """构建知识库实体的嵌入索引"""
        descriptions = [
            f"{entity['name']}: {entity['description']}"
            for entity in self.kb
        ]
        self.entity_embeddings = self.model.encode(descriptions)

    def link(self, mention, context, top_k=5):
        """将提及链接到知识库实体"""
        # 编码提及+上下文
        query = f"{mention}: {context}"
        query_embedding = self.model.encode([query])

        # 计算相似度
        similarities = np.dot(
            self.entity_embeddings, query_embedding.T
        ).flatten()

        # 排序取Top-K
        top_indices = np.argsort(similarities)[::-1][:top_k]

        results = []
        for idx in top_indices:
            results.append({
                "entity_id": self.kb[idx]["id"],
                "entity_name": self.kb[idx]["name"],
                "score": float(similarities[idx])
            })

        return results

五、端到端知识图谱构建

5.1 LLM驱动的一体化构建

def build_kg_from_text(text, llm_client, schema=None):
    """使用LLM从文本一步构建知识图谱"""

    schema_hint = ""
    if schema:
        schema_hint = f"""
图谱Schema:
- 节点类型: {', '.join(schema['node_types'])}
- 关系类型: {', '.join(schema['relation_types'])}
"""

    prompt = f"""从以下文本中构建知识图谱。
{schema_hint}
文本:
{text}

请输出JSON格式的图谱数据:
{{
  "nodes": [
    {{"id": "唯一标识", "label": "节点类型", "properties": {{"name": "...", ...}}}}
  ],
  "edges": [
    {{"source": "源节点id", "target": "目标节点id", "type": "关系类型", "properties": {{}}}}
  ]
}}

要求:
1. 实体去重(相同实体只出现一次)
2. 关系必须有方向性
3. 属性尽可能丰富"""

    response = llm_client.generate(prompt)
    graph_data = json.loads(response)
    return graph_data

def import_to_neo4j(graph_data, driver):
    """将图谱数据导入Neo4j"""
    with driver.session() as session:
        # 导入节点
        for node in graph_data["nodes"]:
            session.run(
                f"MERGE (n:{node['label']} {{id: $id}}) "
                f"SET n += $props",
                id=node["id"],
                props=node["properties"]
            )

        # 导入边
        for edge in graph_data["edges"]:
            session.run(
                f"MATCH (a {{id: $src}}), (b {{id: $tgt}}) "
                f"MERGE (a)-[r:{edge['type']}]->(b) "
                f"SET r += $props",
                src=edge["source"],
                tgt=edge["target"],
                props=edge.get("properties", {})
            )

5.2 质量保障

环节 质量指标 保障方法
NER F1 > 0.85 多模型投票 + 人工校验
RE F1 > 0.75 置信度过滤 + 规则校验
EL Accuracy > 0.90 Top-K候选 + 上下文消歧
融合 一致性 > 0.95 冲突检测 + 多源验证

六、工程实践建议

6.1 方法选择决策树

选择NER/RE/EL方法:

数据量 > 10K标注样本?
├── 是 → BERT/微调方案(性价比最高)
└── 否 → 标注样本 > 100?
    ├── 是 → Few-shot LLM + 主动学习
    └── 否 → Zero-shot LLM + 规则补充

实时性要求?
├── <100ms → BERT/小模型部署
├── <1s → 中等模型 + 缓存
└── 无要求 → LLM(最灵活)

领域特异性?
├── 高(医学/法律) → 领域微调 + 领域词典
├── 中 → 通用模型 + 少量领域样本
└── 低 → 通用预训练模型即可

知识图谱的自动构建正在从传统NLP流水线向LLM驱动的端到端方案演进。两者并非替代关系,而是互补:传统方法提供速度和可控性,LLM提供灵活性和零样本能力。在实际工程中,混合方案往往是最优选择。


Maurice | maurice_wen@proton.me