📑 查看全课大纲(第 12 / 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未收录的属性。

  • 方法:使用基于模板(正则表达式)的方法进行抽取。
  • 流程
    1. 观察文本,人工总结描述特定属性的语言模式(Pattern)。
    2. 编写正则表达式模板。
    3. 应用模板从文本中抽取出(属性,值)对。
    4. 转换为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”中关于同一部电影的信息,你会如何解决片名、评分、演员列表可能存在的冲突?

—— 小象教研组

配套学习资源与课件
  • 第3章课件:知识抽取与挖掘 I
    下载
  • 第3章练习(Assignment:Java + .ttl 数据)
    下载
  • 知识图谱课程思维导图(KG_Centralized.xmind 全课程结构图)
    下载
🎁 免费学习资源

领取《小象 11GB VIP 课件资料包与大厂真题手册》

包含全套实战 Jupyter 源码、清洗后数据集、大厂高频面试真题与专属学员答疑交流群。

  • 完整 Python / 数据分析 Jupyter 实战源码
  • 大厂真实业务数据集与练习题
  • 微信扫码添加顾问免费领取;想学什么,直接告诉顾问
微信二维码:扫码添加课程顾问微信扫码添加顾问