阶段5
|
NLP传统 · 第28课/共30课
📖 第28课:知识图谱构建
🎯 课程目标
知识图谱以结构化方式存储实体和关系,是智能问答、推荐系统的核心基础设施。本课学习从零构建知识图谱并实现基于KG的问答。
- 实现知识图谱的存储与查询
- 掌握多跳推理查询
- 实现基于模板的KG问答
📐 核心原理
知识图谱的数据模型
知识图谱的核心是三元组:(Subject, Relation, Object)
例如:(马云, 创建, 阿里巴巴)、(阿里巴巴, 总部位于, 杭州)
三元组可以自然地表示为有向图:实体是节点,关系是边。
💡 多跳推理:单跳查询找直接关系,多跳查询可以回答"马云创建的公司在哪里"这类复合问题。这是KG区别于简单数据库的核心能力。
🔬 知识图谱构建与查询
KG存储与多跳查询
class KnowledgeGraph:
def __init__(self):
self.entities = set()
self.relations = []
self.entity_attrs = {}
def add_triple(self, subject, relation, obj):
self.entities.add(subject)
self.entities.add(obj)
self.relations.append((subject, relation, obj))
def add_entity_attr(self, entity, attr, value):
if entity not in self.entity_attrs:
self.entity_attrs[entity] = {}
self.entity_attrs[entity][attr] = value
def query_by_entity(self, entity):
results = []
for s, r, o in self.relations:
if s == entity:
results.append((r, o, 'outgoing'))
if o == entity:
results.append((r, s, 'incoming'))
return results
def query_by_relation(self, relation):
return [(s, o) for s, r, o in self.relations if r == relation]
def multi_hop_query(self, start_entity, hops=2):
paths = [[start_entity]]
for _ in range(hops):
new_paths = []
for path in paths:
last = path[-1]
for s, r, o in self.relations:
if s == last and o not in path:
new_paths.append(path + [r, o])
paths = new_paths
return paths
kg = KnowledgeGraph()
# 构建知识图谱
triples = [
("马云", "创建", "阿里巴巴"),
("阿里巴巴", "总部位于", "杭州"),
("阿里巴巴", "行业", "电子商务"),
("张勇", "担任", "CEO"),
("张勇", "就职于", "阿里巴巴"),
("腾讯", "总部位于", "深圳"),
("马化腾", "创建", "腾讯"),
("腾讯", "行业", "社交"),
("腾讯", "投资", "美团"),
("美团", "总部位于", "北京"),
("美团", "行业", "本地生活"),
]
for s, r, o in triples:
kg.add_triple(s, r, o)
print("=== 知识图谱查询 ===")
print(f"实体数: {len(kg.entities)}, 关系数: {len(kg.relations)}")
print("\n阿里巴巴的关联:")
for rel, entity, direction in kg.query_by_entity("阿里巴巴"):
arrow = "→" if direction == "outgoing" else "←"
print(f" {entity} {arrow} [{rel}]")
print("\n总部位于关系:")
for s, o in kg.query_by_relation("总部位于"):
print(f" {s} → {o}")
print("\n马云的2跳查询:")
paths = kg.multi_hop_query("马云", hops=2)
for path in paths:
print(f" {' → '.join(path)}")
=== 知识图谱查询 ===
实体数: 13, 关系数: 11
阿里巴巴的关联:
马云 ← [创建]
杭州 → [总部位于]
电子商务 → [行业]
张勇 ← [就职于]
总部位于关系:
阿里巴巴 → 杭州
腾讯 → 深圳
美团 → 北京
马云的2跳查询:
马云 → 创建 → 阿里巴巴 → 总部位于 → 杭州
马云 → 创建 → 阿里巴巴 → 行业 → 电子商务
✅ 验证通过:代码实机运行结果如上
🔧 基于KG的问答
KGQA知识图谱问答
class KGQA:
def __init__(self, kg):
self.kg = kg
def answer(self, question):
# 简单的基于模板的KG问答
import re
patterns = [
(r'(.+?)的(.+?)是什么', self._query_attr),
(r'(.+?)的(.+)', self._query_relation),
(r'谁(.+?)了(.+)', self._query_reverse),
]
for pattern, handler in patterns:
match = re.match(pattern, question)
if match:
return handler(match)
return "无法理解该问题"
def _query_attr(self, match):
entity = match.group(1)
attr = match.group(2)
if entity in self.kg.entity_attrs and attr in self.kg.entity_attrs[entity]:
return self.kg.entity_attrs[entity][attr]
# 尝试关系查询
for rel, obj, direction in self.kg.query_by_entity(entity):
if attr in rel:
return obj if direction == 'outgoing' else f"{obj} (反向)"
return f"未知: {entity}的{attr}"
def _query_relation(self, match):
entity = match.group(1)
relation = match.group(2)
for rel, obj, direction in self.kg.query_by_entity(entity):
if relation in rel or rel in relation:
return obj if direction == 'outgoing' else f"{obj} (反向)"
return f"未知: {entity}的{relation}"
def _query_reverse(self, match):
relation = match.group(1)
obj = match.group(2)
for s, r, o in self.kg.relations:
if o == obj and relation in r:
return s
return f"未知: 谁{relation}了{obj}"
qa = KGQA(kg)
questions = [
"阿里巴巴的行业是什么",
"阿里巴巴的总部位于",
"谁创建了腾讯",
"马云的2跳查询",
]
print("=== 知识图谱问答 ===")
for q in questions:
if '2跳' in q:
paths = kg.multi_hop_query("马云", hops=2)
answer = ' | '.join(['→'.join(p) for p in paths[:3]])
else:
answer = qa.answer(q)
print(f" 问: {q}")
print(f" 答: {answer}")
[STDERR] Traceback (most recent call last):
File "<string>", line 48, in <module>
NameError: name 'kg' is not defined
⚠️ 部分验证:代码实机运行结果如上
📝 练习
动手练习
- 从维基百科文本自动抽取三元组,构建大规模KG
- 实现图数据库存储(Neo4j),替代内存存储
- 实现基于图嵌入(KGE)的链接预测
🧠
🏆 成就解锁:图谱建造师
掌握了知识图谱的构建与查询,能将非结构化文本转化为结构化知识。
🧪 扩展:知识图谱的存储方案
图数据库 vs 关系数据库
| 方面 | Neo4j(图) | MySQL(关系) |
| 多跳查询 | 高效 | 需要多次JOIN |
| 模式灵活 | 无模式 | 固定模式 |
| 可视化 | 内置 | 需第三方工具 |
| 事务支持 | ACID | ACID |
知识图谱嵌入(KGE)
将实体和关系映射到向量空间,用于链接预测:
- TransE:h + r ≈ t,简单但无法处理1-to-N关系
- TransH:投影到关系超平面,解决1-to-N
- DistMult:双线性模型,效果更好
- ComplEx:复数空间,处理非对称关系
📚 参考文献
推荐阅读
- Jurafsky & Martin. Speech and Language Processing (3rd ed). 第28课相关章节
- Manning, Raghavan & Schutze. Introduction to Information Retrieval
- Goodfellow, Bengio & Courville. Deep Learning. 自然语言处理章节
在线资源
- NLTK文档: https://www.nltk.org/
- spaCy文档: https://spacy.io/
- gensim文档: https://radimrehurek.com/gensim/
- sklearn文本处理: https://scikit-learn.org/stable/modules/feature_extraction.html
知识图谱 - 关键概念速查
| 概念 | 定义 | 应用 |
| Pipeline | 处理流水线,各步骤串行执行 | 文本处理系统 |
| Tokenization | 将文本切分为词/子词单元 | 分词、子词分割 |
| Embedding | 将离散符号映射到连续向量空间 | 词向量、句向量 |
| Feature Engineering | 从原始数据提取有效数值特征 | 传统ML模型输入 |
| Fine-tuning | 在预训练模型基础上微调 | BERT等模型适配 |
本课要点回顾
- 知识图谱是NLP技术栈的重要组件
- 理解原理比调用API更重要——知道为什么比知道怎么做更有价值
- 代码验证是学习的好方法——动手跑一遍胜过读十遍
- 从简单方法开始,逐步过渡到复杂方法——简单方法的基线作用不可替代
- 实践出真知——用自己的数据跑一遍,发现课本上没写的问题
❓ 常见问题FAQ
- Q: 这个方法的数据需求量是多少?
- A: 传统方法通常需要几百到几千条标注数据。深度学习方法通常需要万级以上。数据量少时优先用传统方法。
- Q: 如何处理中文的特殊情况?
- A: 中文没有天然词边界,分词质量直接影响下游效果。建议:1)使用领域词典优化分词 2)比较不同分词工具的效果 3)对关键任务做分词错误分析。
- Q: 传统方法和深度学习如何选择?
- A: 考虑三个因素:1)数据量——少数据用传统方法 2)可解释性——需要解释选传统方法 3)计算资源——资源受限选传统方法。深度学习在大数据+强算力场景下优势明显。
- Q: 如何评估和调优?
- A: 1)建立基线:用最简单方法确定性能下限 2)错误分析:找出模型最常犯的错误类型 3)针对性改进:根据错误模式选择改进策略 4)避免过拟合:在独立测试集上验证。
- Q: 生产环境部署要注意什么?
- A: 1)延迟要求:简单模型推理更快 2)吞吐量:批处理提升吞吐 3)模型版本:做好版本管理和回滚 4)监控:持续跟踪线上模型性能 5)A/B测试:新模型上线前小流量验证。
🛠️ 实用工具箱
| 工具 | 用途 | 安装 |
| jieba | 中文分词 | pip install jieba |
| gensim | 词向量训练 | pip install gensim |
| scikit-learn | 传统ML | pip install scikit-learn |
| nltk | 英文NLP工具 | pip install nltk |
| spaCy | 工业NLP管线 | pip install spacy |
| hanlp | 中文NLP工具包 | pip install hanlp |
| fasttext | Facebook文本分类 | pip install fasttext |
| textblob | 简单NLP接口 | pip install textblob |