📑 查看全课大纲(第 12 / 26 节)
- 1.知识图谱和语音技术概述
- 2.典型知识库项目简介
- 3.知识图谱技术概览
- 4.典型应用案例
- 5.早期知识表示简介
- 6.基于语义网的知识表示框架
- 7.典型知识库项目的知识表示
- 8.基于本体工具的知识建模实践
- 9.面向非结构化数据的知识抽取
- 10.面向结构化数据的知识抽取
- 11.面向半结构化数据的知识抽取
- 12.实践:基于百科数据的知识抽取
- 13.面向文本的知识抽取
- 14.知识挖掘
- 15.从一个例子开始
- 16.图数据库介绍
- 17.什么是知识融合
- 18.知识融合的基本技术流程
- 19.典型知识融合工具简介
- 20.典型案例简介
- 21.LIMES实战演练
- 22.知识推理
- 23.语义搜索
- 24.知识问答
- 25.IBM watson Lite
- 26.行业知识图谱应用
实践:基于百科数据的知识抽取
约 7 分钟
基于百科数据的知识图谱构建实践
小象实战讲义 · 知识图谱
在掌握了从结构化、半结构化和非结构化数据中抽取知识的基本方法后,我们面临一个现实问题:如何将这些方法整合,从头构建一个特定领域的知识图谱?本节将以“佛学知识图谱”为例,展示一个完整的实践流程。我们将学习如何从一个开放的百科知识库(如DBpedia)出发,通过知识收集、融合、补全与清理,构建一个聚焦于特定领域的、高质量的知识图谱,并了解其背后的技术思路与挑战。
💡 核心导读
- 从通用到垂直:学习如何从一个庞大的通用知识库(如DBpedia)中,通过分类、命名规则等方法,精准地筛选和抽取特定领域(如佛学)的实体与知识。
- 知识融合是关键:理解在整合多源数据(如百度百科、互动百科、维基百科)时,如何进行主语(实体)融合与宾语(属性值)融合,解决“同名不同义”和“同义不同名”等核心问题。
- 实践驱动理解:通过两个具体的实践任务——基于正则模板的属性值抽取和基于多特征融合的实体清理,亲手体验知识抽取与知识融合中最基础但至关重要的“苦活累活”。
实践蓝图:从百科到领域知识图谱
构建一个领域知识图谱,并非总是从零开始。一个高效的策略是:以现有的、大规模、高质量的开放知识库(如DBpedia、CN-DBpedia)为起点,进行裁剪、扩充和精化。这好比在一片富饶的森林(通用知识库)中,规划并培育一片我们自己的果园(领域知识图谱)。
下图展示了这一实践的核心流程:
+---------------------+
| 开放百科知识库 |
| (DBpedia, CN-DBpedia)|
+----------+----------+
| 知识收集与裁剪
v
+----------------+ +----------------+ +----------------+
| 基于分类收集 | | 基于命名规则 | | 基于垂直网站 |
| (Category) | | (启发式规则) | | (门户网站) |
+----------------+ +----------------+ +----------------+
| 多源数据汇总
v
+---------------------+
| 原始领域知识集合 |
| (多源、异构、含噪声)|
+----------+----------+
| 知识融合与精化
+--------------+--------------+
| 主语融合 | 宾语融合 |
| (实体对齐) | (属性值冲突消解)|
+--------------+--------------+
| 知识补全与清理
v
+---------------------+
| 高质量领域知识图谱 |
| (佛学人物、寺庙等) |
+---------------------+我们的目标“佛学知识图谱”将包含佛学人物、寺庙、宗派等核心概念。接下来,我们将一步步拆解这个流程。
知识收集:从百科中定位佛学领域实体
面对海量的百科数据,第一步是识别出哪些内容与“佛学”相关。这里介绍两种核心方法:
1. 基于分类(Category)的收集
百科条目通常被组织在一个分类树中。我们可以通过人工观察,找到与目标领域直接相关的分类。
- 操作:在百科中定位如“佛教人物”、“中国佛教寺庙”、“佛教宗派”等分类节点。
- 抽取:将这些分类下的所有文章对应的实体(即词条)抽取出来,作为初始的领域实体集合。
- 优点:准确性相对较高,能直接获得符合领域范畴的实体。
2. 基于命名规则的启发式收集
许多领域实体在命名上具有明显的模式(Pattern)。
- 操作:总结佛学人物、寺庙的命名规律。
- 人物:
“.+菩萨”(如“观世音菩萨”)、“.+禅师”(如“虚云禅师”)、“.+活佛”等。 - 寺庙:
“.+寺”、“.+庙”、“.+庵”等。
- 人物:
- 抽取:使用正则表达式匹配整个知识库中符合这些模式的实体名。
- 优点:可以召回那些未被正确分类,但名称上符合领域特征的实体,提高覆盖率。
- 注意:这种方法可能引入噪声(如地名“石家庄”也以“庄”结尾),需要后续清理。
现代Python等价思路:使用 rdflib 查询百科的RDF数据,结合SPARQL的 REGEX 函数或本地Python的 re 模块进行匹配筛选。
import re
# 模拟从某数据源读取的实体列表(实际中可能来自SPARQL查询结果)
all_entities = ["玄奘", "观世音菩萨", "龙泉寺", "苏轼", "虚云禅师", "北京", "弘一法师", "白马寺"]
# 定义佛学相关的命名规则(正则表达式)
buddhist_patterns = [
r'.+菩萨$', # 以“菩萨”结尾
r'.+禅师$', # 以“禅师”结尾
r'.+法师$', # 以“法师”结尾
r'.+寺$', # 以“寺”结尾
r'.+庵$', # 以“庵”结尾
]
# 使用正则匹配进行筛选
buddhist_candidates = []
for entity in all_entities:
for pattern in buddhist_patterns:
if re.match(pattern, entity):
buddhist_candidates.append(entity)
break # 匹配一个规则即可
print("通过命名规则筛选出的佛学相关实体候选:")
print(buddhist_candidates)
# 输出: ['观世音菩萨', '龙泉寺', '虚云禅师', '弘一法师', '白马寺']
# 注意:'苏轼'、'北京'被正确过滤。但此方法仍需后续更精细的清理。知识融合:解决多源数据的冲突与对齐
从百度百科、互动百科、维基百科等多个来源收集到的数据,必须进行融合,才能形成一个统一、一致的知识图谱。融合主要在两个层面进行:主语融合(实体对齐) 和 宾语融合(属性值冲突消解)。
主语融合:实体对齐与URI标准化
核心问题是:不同来源的条目可能指向现实世界中的同一个实体(同义异名),也可能同名但指向不同实体(同名异义)。
1. 同义异名(同实体不同名)
- 问题:同一实体在不同百科中可能使用不同的条目名称。
- 例一(玄奘):
- 百度百科:
玄奘(别名:唐三藏,唐僧,玄奘法师) - 互动百科:
唐三藏(别名:玄奘,三藏法师) - 维基百科:
玄奘(别名:玄奘法师,三藏法师)
- 百度百科:
- 例二(龙泉禅寺,课件 P155):以下三条指向现实世界中的同一座寺庙(北京凤凰岭),只是名称不同,应融合为同一实体。
- 百度百科:
龙泉寺(别名:龙泉寺,北京龙泉寺) - 互动百科:
龙泉禅寺(别名:龙泉禅寺,北京凤凰岭龙泉寺) - 维基百科:
龙泉寺_(海淀区)(别名:龙泉寺_(海淀区),海淀区龙泉寺)
- 百度百科:
- 例一(玄奘):
- 解决方案:
- 将每个实体的正式名称和所有别名(包括重定向链接)收集起来,形成一个“别名集合”。
- 如果两个实体来自不同来源,且它们的别名集合存在交集(即至少有一个完全匹配的别名),则认为它们指向同一实体。
- 为这个统一实体分配一个标准的URI,例如:
<http://www.kg-buddhism.com/entity/玄奘>。 - 将所有来源中关于该实体的三元组,其主语都替换为这个标准URI,并将所有别名集合合并。
2. 同名异义(同名不同实体)
- 问题:例如,“龙泉寺”可能指北京凤凰岭的龙泉寺,也可能指南京的龙泉寺,二者是不同的寺庙。
- 百度百科
龙泉寺:别名{龙泉寺, 北京龙泉寺},地址在北京。 - 维基百科
龙泉寺:别名{龙泉寺, 南京龙泉寺},地址在南京。
- 百度百科
- 解决方案:仅凭名称和别名无法区分(两条的别名集合都含
龙泉寺,按别名匹配会被归为同组,必须再靠属性判定)。需要引入属性值冲突检测。- 检查疑似相同实体的关键属性(如“地址”、“建造时间”)是否一致。
- 如果属性值冲突(如地址完全不同),则判定为不同实体,应保留为两个独立的URI(可通过添加后缀区分,如
龙泉寺_(北京)和龙泉寺_(南京))。 - 如果属性值一致或兼容,则判定为同一实体,进行融合。
现代Python等价思路:使用集合运算进行别名匹配,并结合关键属性字典进行冲突判断。
# 模拟两个来源的实体信息(同名异义:北京龙泉寺 vs 南京龙泉寺)
source_data = {
"baidu": {
"name": "龙泉寺",
"aliases": {"龙泉寺", "北京龙泉寺"},
"address": "北京市海淀区凤凰岭"
},
"zhwiki": {
"name": "龙泉寺",
"aliases": {"龙泉寺", "南京龙泉寺"},
"address": "江苏省南京市"
}
}
# 1. 基于别名集合进行初步分组
entity_groups = []
processed_aliases = set()
for src, info in source_data.items():
current_aliases = info['aliases']
# 检查是否与已有组有别名交集
found_group = None
for group in entity_groups:
if current_aliases & group['all_aliases']: # 集合求交集
found_group = group
break
if found_group:
# 合并到现有组
found_group['sources'].append(src)
found_group['all_aliases'].update(current_aliases)
found_group['attributes'][src] = info['address']
else:
# 创建新组
entity_groups.append({
'sources': [src],
'all_aliases': current_aliases.copy(),
'attributes': {src: info['address']} # 记录各来源属性
})
print("初步基于别名分组:")
for i, group in enumerate(entity_groups):
print(f" 组{i}: 来源 {group['sources']}, 别名集 {group['all_aliases']}")
# 2. 检查组内属性冲突,决定是否进一步拆分
final_entities = []
for group in entity_groups:
if len(group['sources']) > 1:
# 检查地址属性是否冲突
addresses = list(group['attributes'].values())
# 简单判断:如果所有地址都包含“北京”,则认为一致
if all('北京' in addr for addr in addresses):
print(f" -> 组内地址一致,融合为同一实体。")
# 生成标准URI (这里用核心别名之一)
core_name = next(iter(group['all_aliases']))
final_entities.append({'uri': f'<http://kg.example/entity/{core_name}_(北京)>', 'sources': group['sources']})
else:
print(f" -> 组内地址冲突,拆分为不同实体。")
# 按来源拆分为不同实体
for src in group['sources']:
core_name = source_data[src]['name']
addr = source_data[src]['address']
# 用地址中的城市/地区作区分后缀(如 北京/南京),确保同名实体 URI 真正不同、不冲突
region = '北京' if '北京' in addr else ('南京' if '南京' in addr else src)
final_entities.append({'uri': f'<http://kg.example/entity/{core_name}_({region})>', 'sources': [src]})
else:
# 单独来源,直接作为一个实体
src = group['sources'][0]
core_name = source_data[src]['name']
final_entities.append({'uri': f'<http://kg.example/entity/{core_name}>', 'sources': group['sources']})
print("\n最终确定的实体及URI:")
for ent in final_entities:
print(f" URI: {ent['uri']}, 来自: {ent['sources']}")宾语融合:属性值冲突消解与补全
当确定实体唯一后,需要合并来自不同来源的属性值。
1. 单值属性冲突消解 对于一个实体,某些属性理论上只应有一个值,如出生日期、建造年代、具体地址。如果多源数据提供不同值,则产生冲突。
- 精确性原则:对于日期、地点等,选择最精确的值。例如,“1990年” vs “1990年5月1日”,选择后者。
- 投票/大多数原则:选择出现次数最多的值。例如,两个来源说生于“1880年”,一个来源说生于“1881年”,选择“1880年”。
- 外部验证:利用搜索引擎、权威数据库或地图服务进行校验。例如,对寺庙地址,使用地图API验证哪个坐标更准确。
2. 多值属性合并 对于像“别名”、“作品”这类天然可拥有多个值的属性,直接合并所有来源的值并去重即可。
3. 谓语(属性名)融合 不同百科对同一属性可能使用不同的谓词(属性名)。
- Infobox属性映射:例如,出生日期可能被标记为
birthDate、出生日期、生于。需要人工或基于映射表,将这些不同的谓词统一到本体中定义的标准属性上(如dbo:birthDate)。 - 直接替换命名空间:对于已明确来自某百科的属性(如
dbpedia:abstract),在融合到自有图谱时,通常会将其命名空间替换为自己定义的URI。
知识补全与清理
基于非结构化文本的属性补全
百科条目的摘要(Abstract)或正文包含丰富的非结构化文本,其中可能包含InfoBox未收录的属性。
- 方法:使用基于模板(正则表达式)的方法进行抽取。
- 流程:
- 观察文本,人工总结描述特定属性的语言模式(Pattern)。
- 编写正则表达式模板。
- 应用模板从文本中抽取出
(属性,值)对。 - 转换为RDF三元组,并入知识图谱。
- 特点:准确率高(Precision-oriented),但覆盖率取决于模板的完备性。是实践中快速启动、获取高质量数据的有效手段。
示例:从李叔同的摘要中抽取“法名”。
- 文本:
“李叔同,...出家后法名演音,号弘一,晚号晚晴老人。” - 模板:
r".*(?:法名|法号)(?:为|曰|称|叫|即)?([\S]+?)(?=字|祖籍|,|。).*" - 抽取结果:
(法名, 演音) - 生成三元组:
<http://www.kg-buddhism.com/entity/李叔同> <http://www.kg-buddhism.com/property/法名> "演音"@zh .
说明:终止符必须用前瞻
(?=...)而非可选分组(...){0,1}——后者在惰性量词下会立即交出单字符,只能捕获到“演”而非“演音”(课件 P161 的原正则即有此缺陷)。
以下用 rdflib 演示如何将正则抽取得到的属性-值对,正式构建为 RDF 三元组并验证:
from rdflib import Graph, Namespace, Literal
# 定义领域命名空间
KG = Namespace("http://www.kg-buddhism.com/entity/")
KP = Namespace("http://www.kg-buddhism.com/property/")
# 模拟从摘要中正则抽取出的属性-值对(上例的输出)
extracted_pairs = [
("李叔同", "法名", "演音"),
("李叔同", "号", "弘一"),
]
g = Graph()
for entity, prop, value in extracted_pairs:
subj = KG[entity]
pred = KP[prop]
obj = Literal(value, lang="zh")
g.add((subj, pred, obj))
# 验证:遍历图中三元组
for s, p, o in g:
print(f"({s}) -> ({p}) -> ({o})")
print(f"共 {len(g)} 条三元组")实体清理:过滤非领域实体
通过分类或简单规则收集的初始实体集合必然包含噪声(非佛学相关实体)。清理是构建高质量领域图谱的必要步骤。
- 清理依据:综合利用实体的多种特征进行过滤。
- 实体名特征词:包含“清真寺”、“女寺”、“题…寺”(可能是诗名)的,很可能不是佛教寺庙。
- 摘要文本特征词:摘要中出现“官署”、“阿訇”、“伊斯兰教”、“《实体名》出版”等,表明实体不属于佛学领域。
- 分类(Category)特征:实体分类中包含“伊斯兰教”、“天主教”等非佛教类目。
- 宗派(Section)属性:宗派属性值为“木教派”、“虎非耶教派”等非佛教宗派。
- 方法:制定一个包含上述多种规则的过滤器,对实体列表进行逐一筛查和过滤。
实践任务解析
基于上述流程,我们可以设计具体的实践任务来巩固理解。
📝 动手练一练
任务一:基于正则模板的属性值抽取 假设你有一个RDF文件 abstracts.ttl,其中包含了许多寺庙实体的摘要(dbo:abstract)。请编写Python程序,使用正则表达式从摘要中抽取出以下属性值:别名、始建时间、宗派、建筑风格。
- 提示:你需要先观察摘要文本的句式,为每个属性设计1-2个正则表达式模板。
👉 点击查看参考答案思路
import re
# 模拟一条摘要数据 (实际应从Turtle文件解析)
abstract_text = '大昭寺,又名“祖拉康”、“觉康”,位于拉萨老城区中心,是一座藏传佛教寺院,始建于公元647年,是藏王松赞干布为纪念尺尊公主入藏而建。大昭寺的建筑风格是藏式风格,融合了唐、尼泊尔、印度的风格。'
# 定义属性与对应的正则模板(需根据更多样本优化)
patterns = {
'别名': r'(?:又名|亦称|俗称)[::]?(.+?)(?=[,。]|$)',
'始建时间': r'始建于(公元)?(\d+年)',
'宗派': r'是(一?[座所])?([^,。]+?(?:教|宗))寺院',
'建筑风格': r'建筑风格是([^,。]+?风格)'
}
results = {}
for prop, pattern in patterns.items():
match = re.search(pattern, abstract_text)
if match:
# 通常取最后一个捕获组作为值,具体取决于模板设计
val = match.groups()[-1]
# 去除别名中的中英文引号
val = re.sub(r'[\u201c\u201d\u0022\u300c\u300d]', '', val).strip()
results[prop] = val
else:
results[prop] = None
print("抽取结果:")
for k, v in results.items():
print(f" {k}: {v}")
# 输出示例:
# 别名: 祖拉康、觉康
# 始建时间: 647年
# 宗派: 藏传佛教
# 建筑风格: 藏式风格注意:这是一个简化示例。实际应用中,需要针对大量文本优化正则表达式,并处理多个匹配结果。
任务二:基于多特征融合的实体清理 给定一个从DBpedia分类中初步抽取的“佛教寺庙”实体列表文件 entities.txt,以及这些实体对应的别名、摘要、分类、宗派等属性文件。请设计一个清理规则,过滤掉其中明显不属于佛教寺庙的实体。
- 提示:结合实体名、摘要中的关键词、所属分类等多维度信息制定过滤规则。
👉 点击查看参考答案思路
import re
# 模拟实体及其属性数据
entities = [
{'name': '大昭寺', 'abstract': '藏传佛教寺院...', 'category': ['佛教寺庙', '全国重点文物保护单位']},
{'name': '化觉巷清真寺', 'abstract': '西安化觉巷清真寺是伊斯兰教寺院...', 'category': ['清真寺', '全国重点文物保护单位']},
{'name': '题破山寺后禅院', 'abstract': '《题破山寺后禅院》是唐代诗人常建的诗...', 'category': ['唐诗']},
{'name': '雍和宫', 'abstract': '北京藏传佛教寺院...', 'category': ['藏传佛教寺庙']},
]
# 定义过滤规则(特征词列表)
filter_keywords = {
'name': ['.*清真寺$', '^题.*', '^赠.*', '^记.*'], # 名称匹配
'abstract': ['清真', '阿訇', '穆斯林', '伊斯兰教', '《.*?》'], # 摘要正则(纯关键词也可直接作正则)
'category': ['伊斯兰教', '天主教', '基督教', '唐诗', '宋词'] # 分类黑名单
}
def should_filter(entity, rules):
"""判断实体是否应被过滤"""
# 规则1: 检查实体名
for pattern in rules['name']:
if re.match(pattern, entity['name']):
return True, f"名称匹配规则: {pattern}"
# 规则2: 检查摘要(按正则匹配,'《.*》' 才能命中书名号)
abstract = entity.get('abstract', '')
for pattern in rules['abstract']:
if re.search(pattern, abstract):
return True, f"摘要匹配规则: {pattern}"
# 规则3: 检查分类
for category in entity.get('category', []):
if category in rules['category']:
return True, f"分类在黑名单中: {category}"
return False, "保留"
print("实体清理结果:")
for ent in entities:
filter_flag, reason = should_filter(ent, filter_keywords)
status = "【过滤】" if filter_flag else "【保留】"
print(f"{status} {ent['name']} - 原因: {reason}")本章小结
本节通过“佛学知识图谱”构建这一完整案例,串联了从多源百科数据中抽取、融合、补全知识的核心流程。我们看到了如何将通用的知识库裁剪为领域专用的图谱,并深刻体会到知识融合是保证图谱质量的关键,而基于规则的方法(无论是抽取还是清理)在特定领域、冷启动阶段具有不可替代的价值。
📋 行动清单
- 尝试运行示例代码:修改并运行本节中的Python示例,理解基于规则的知识抽取与实体清理的基本逻辑。
- 设计你自己的领域规则:设想一个你感兴趣的领域(如“武侠人物”、“动漫角色”),尝试为其设计3-5条用于识别领域实体的命名规则或摘要关键词。
- 思考融合策略:如果让你融合“豆瓣电影”和“IMDb”中关于同一部电影的信息,你会如何解决片名、评分、演员列表可能存在的冲突?
—— 小象教研组
领取《小象 11GB VIP 课件资料包与大厂真题手册》
包含全套实战 Jupyter 源码、清洗后数据集、大厂高频面试真题与专属学员答疑交流群。
- ✔完整 Python / 数据分析 Jupyter 实战源码
- ✔大厂真实业务数据集与练习题
- ✔微信扫码添加顾问免费领取;想学什么,直接告诉顾问
微信扫码添加顾问