首页 / 第二章

02

知识表示

知识是智能的基础。要让机器拥有智能,首先要解决的问题是:如何把人类的知识"装进"计算机? 本章依次实现谓词逻辑、产生式规则、框架、语义网络、面向对象五种经典表示法, 并延伸到今天的知识图谱——它们是第三章推理与第八章专家系统的地基。

2.1 知识与知识表示的基本概念

◈ 定义

知识是人们在改造客观世界的实践中积累起来的认识和经验的总和, 它把有关信息中两个或多个信息联系在一起,形成新的信息(如"如果乌云密布且空气潮湿,那么很可能要下雨")。 知识具有相对正确性、不确定性、可表示性与可利用性。

2.1.1 知识的分类

分类标准类型说明与示例
按作用范围常识性知识人人皆知的普遍知识,如"鸟会飞""水在 100℃ 沸腾"。
领域性知识特定领域的专业知识,如医学诊断规则、法律条文。
按确定性确定性知识真值明确,可用逻辑表示,如"三角形内角和为 180°"。
不确定性知识含经验、模糊成分,如"发高烧多半是感染"(第 5 章处理)。
按内容层次事实知识"雪是白的""李白是诗人"——描述对象的属性、状态。
规则知识事实间的因果关系,形如"若 P 则 Q"。
控制知识关于如何运用知识的知识(策略),如搜索顺序。
元知识"知识的知识",如"这条规则比那条更可靠"。

2.1.2 什么是知识表示

知识表示就是将人类知识形式化或模型化,用一组约定的符号结构把知识编码成计算机可以接受的数据结构。 对同一知识,往往有多种可行的表示方法,选择时主要考虑四条标准:

◈ 一条主线

本章五种经典方法的演进,本质是表示粒度越来越大、结构化程度越来越高、越来越接近人类认知: 谓词(原子命题)→ 规则(条件—结论)→ 框架(对象全貌)→ 语义网络(关系网络)→ 知识图谱(大规模实体关系网络)。

2.2 一阶谓词逻辑表示法

2.2.1 命题逻辑

命题是具有真假意义的陈述句,如"雪是白的"(真)、"3 大于 5"(假)。 用大写字母 P, Q, R 表示命题,用联结词组合:

联结词含义读法
¬P否定非 P
P ∧ Q合取P 且 Q
P ∨ Q析取P 或 Q
P → Q蕴含若 P 则 Q
P ↔ Q等价P 当且仅当 Q

但命题逻辑无法刻画命题内部结构。"苏格拉底是哲人"和"柏拉图是哲人"在命题逻辑中只能记成两个无关的 P 和 Q, 无法表达"是哲人"这个共同成分。谓词逻辑把命题进一步分解为谓词 + 个体。

2.2.2 谓词、个体与量词

形如 Man(socrates) 的原子公式中,Man 是谓词(表示性质或关系,首字母大写), socrates 是个体(客体)。表示 n 个个体间关系的谓词称为 n 元谓词,如 Greater(3, 2)、Love(xiaoming, xiaohong)。

用谓词公式表示知识的步骤

  1. 定义用哪些谓词及其含义,确定个体与个体域;
  2. 把事实表示为谓词公式的合取(与);
  3. 把规则表示为蕴含式;
  4. 用 ∧、∨、量词等连接成谓词公式集(知识库)。

示例——"凡是人都会死;苏格拉底是人"可表示为: ∀x (Human(x) → Mortal(x)) 与 Human(socrates), 由第三章的归结推理即可推出 Mortal(socrates)。

2.2.3 Python 实现:一个微型谓词知识库

下面把谓词实现为 Python 对象,支持事实存储、带变量的规则、查询与反向链推理。 变量以 ? 开头(如 ?x),与 Prolog 的思想一致,是 2.3 节之前的一个小预告。

predicate_kb.py
"""微型一阶谓词知识库:事实 + 规则 + 反向链查询"""

class Atom:
    """原子谓词,如 Human(?x)、Parent(bob, anna)。"""
    def __init__(self, pred, *terms):
        self.pred = pred
        self.terms = list(terms)

    @staticmethod
    def parse(expr):
        """把字符串 'Human(?x)' 解析成 Atom 对象。"""
        pred, rest = expr.split("(", 1)
        terms = rest.rstrip(")").split(",")
        return Atom(pred.strip(), *[t.strip() for t in terms])

    def is_var(self, term):
        return isinstance(term, str) and term.startswith("?")

    def match(self, fact, subst):
        """将本模式(可含变量)与事实(不含变量)在已有置换 subst 下匹配。"""
        if self.pred != fact.pred or len(self.terms) != len(fact.terms):
            return None
        for pat_t, fact_t in zip(self.terms, fact.terms):
            pat_t = subst.get(pat_t, pat_t) if self.is_var(pat_t) else pat_t
            if self.is_var(pat_t):
                subst = dict(subst)
                subst[pat_t] = fact_t          # 变量绑定
            elif pat_t != fact_t:
                return None
        return subst

    def substitute(self, subst):
        terms = [subst.get(t, t) if self.is_var(t) else t for t in self.terms]
        return Atom(self.pred, *terms)

    def __repr__(self):
        return f"{self.pred}({', '.join(self.terms)})"


class KnowledgeBase:
    def __init__(self):
        self.facts = []          # 事实:Atom 列表
        self.rules = []          # 规则:(前提原子列表, 结论原子)

    def add_fact(self, expr):
        self.facts.append(Atom.parse(expr))

    def add_rule(self, head, *body):
        """head 为结论,body 为若干前提,例如
        add_rule('Mortal(?x)', 'Human(?x)')  表示  Human(x) → Mortal(x)"""
        self.rules.append((Atom.parse(head), [Atom.parse(b) for b in body]))

    def query(self, goal_expr):
        """查询目标,返回所有使目标成立的变量绑定(反向链)。"""
        goal = Atom.parse(goal_expr)
        answers = []

        def prove(goals, subst):
            if not goals:                          # 所有子目标都已证明
                answers.append(subst)
                return
            first, rest = goals[0], goals[1:]
            first = first.substitute(subst)
            # 路径 A:与已知事实匹配
            for fact in self.facts:
                new_subst = first.match(fact, subst)
                if new_subst is not None:
                    prove(rest, new_subst)
            # 路径 B:使用规则,把目标替换成规则的前提
            for head, body in self.rules:
                new_subst = first.match(head, subst)
                if new_subst is not None:
                    new_body = [a.substitute(new_subst) for a in body]
                    prove(new_body + [a.substitute(new_subst) for a in rest], new_subst)

        prove([goal], {})
        return answers


if __name__ == "__main__":
    kb = KnowledgeBase()

    # ---- 事实:家庭关系 ----
    for f in [
        "Human(socrates)", "Human(bob)", "Human(anna)", "Human(tom)",
        "Parent(bob, anna)", "Parent(anna, tom)",
    ]:
        kb.add_fact(f)

    # ---- 规则 ----
    kb.add_rule("Mortal(?x)", "Human(?x)")          # 人都会死
    kb.add_rule("Ancestor(?x, ?y)", "Parent(?x, ?y)")
    kb.add_rule("Ancestor(?x, ?y)", "Parent(?x, ?z)", "Ancestor(?z, ?y)")

    # ---- 查询 ----
    print("会死者:", kb.query("Mortal(?who)"))
    print("Bob 的孩子:", kb.query("Parent(bob, ?c)"))
    print("Tom 的祖先:", kb.query("Ancestor(?a, tom)"))
运行输出
会死者: [{'?who': 'socrates'}, {'?who': 'bob'}, {'?who': 'anna'}, {'?who': 'tom'}]
Bob 的孩子: [{'?c': 'anna'}]
Tom 的祖先: [{'?a': 'anna'}, {'?a': 'bob'}]

系统不仅查到了直接事实,还通过规则推出了"Bob 是 Tom 的祖父,因此也是 Tom 的祖先", 这就是知识表示与推理结合的雏形。谓词逻辑表示法自然、严密、易于理解, 但表达能力有限(不便表达不确定知识与启发式知识),且当事实增多时推理效率会下降。

2.3 产生式表示法

1943 年美国数学家波斯特(Post)首先提出产生式(Production)。 它是目前专家系统中应用最多的一种知识表示方法,形如 P → Q 或 IF P THEN Q:P 是产生式的前提(条件/前件),Q 是结论或动作(后件)。

◈ 结构

一个产生式系统由三部分组成:
① 综合数据库(Context / 工作存储器):存放当前问题的初始事实、中间结果;
② 规则库(Rule Base):存放所有产生式规则;
③ 控制系统(推理机):决定当前哪些规则的前提被满足(匹配)、 当多条规则同时满足时选哪一条(冲突消解)、以及执行后件更新数据库(动作)。

产生式表示的知识示例

Python 实现:通用产生式推理引擎

production_system.py
"""产生式系统:综合数据库 + 规则库 + 正向推理机"""

class Rule:
    def __init__(self, name, conditions, conclusion, desc=""):
        self.name = name
        self.conditions = set(conditions)   # 前件:需要同时成立的事实集合
        self.conclusion = conclusion        # 后件:推出的新事实
        self.desc = desc

    def __repr__(self):
        return f"<{self.name}: IF {sorted(self.conditions)} THEN '{self.conclusion}'>"


class ProductionSystem:
    def __init__(self):
        self.facts = set()          # 综合数据库
        self.rules = []             # 规则库

    def add_fact(self, fact):
        self.facts.add(fact)

    def add_rule(self, rule):
        self.rules.append(rule)

    def forward_chaining(self, trace=True):
        """正向推理:从已知事实出发,反复触发规则,直到不再产生新事实。"""
        fired = set()               # 已触发过的规则,避免重复
        log = []
        changed = True

        while changed:
            changed = False
            conflict_set = []       # 冲突集:当前所有可触发的规则

            for rule in self.rules:
                if rule.name in fired:
                    continue
                if rule.conditions.issubset(self.facts):   # 前件全部满足
                    conflict_set.append(rule)

            if conflict_set:
                # 冲突消解策略:规则按名称排序(也可按优先级、专一性等)
                rule = sorted(conflict_set, key=lambda r: r.name)[0]
                self.facts.add(rule.conclusion)
                fired.add(rule.name)
                changed = True
                msg = f"触发 {rule.name}:{rule.desc} => 新事实「{rule.conclusion}」"
                log.append(msg)
                if trace:
                    print(msg)

        return log


if __name__ == "__main__":
    # 经典动物识别规则库(第八章还会扩展为完整专家系统)
    ps = ProductionSystem()
    for f in ["有毛发", "吃肉", "黄褐色", "有黑色条纹"]:
        ps.add_fact(f)

    ps.add_rule(Rule("R1", ["有毛发"], "是哺乳动物", "有毛发→哺乳动物"))
    ps.add_rule(Rule("R2", ["有奶"], "是哺乳动物", "有奶→哺乳动物"))
    ps.add_rule(Rule("R3", ["是哺乳动物", "吃肉"], "是食肉动物", "哺乳动物且吃肉→食肉动物"))
    ps.add_rule(Rule("R4", ["是哺乳动物", "有蹄"], "是有蹄类动物", "哺乳动物且有蹄→有蹄类"))
    ps.add_rule(Rule("R5", ["是食肉动物", "黄褐色", "有黑色条纹"], "是老虎",
                     "食肉+黄褐色+黑条纹→老虎"))
    ps.add_rule(Rule("R6", ["是食肉动物", "黄褐色", "有暗斑点"], "是金钱豹",
                     "食肉+黄褐色+暗斑点→金钱豹"))

    print("初始事实:", sorted(ps.facts))
    print("-" * 50)
    ps.forward_chaining()
    print("-" * 50)
    print("最终结论:", "老虎" if "是老虎" in ps.facts else "未能识别")
运行输出
初始事实: ['吃肉', '有毛发', '有黑色条纹', '黄褐色']
--------------------------------------------------
触发 R1:有毛发→哺乳动物 => 新事实「是哺乳动物」
触发 R3:哺乳动物且吃肉→食肉动物 => 新事实「是食肉动物」
触发 R5:食肉+黄褐色+黑条纹→老虎 => 新事实「是老虎」
--------------------------------------------------
最终结论: 老虎

产生式表示法的优点是模块性强(规则之间通过数据库间接联系,可独立增删)、 自然性好(IF-THEN 接近人类经验表达)、推理路径可解释(能回答"为什么")。 缺点是规则间关系隐晦、大型规则库存在组合爆炸与冲突消解困难、效率不高。

2.4 框架表示法

1975 年明斯基(Minsky)在论文《A Framework for Representing Knowledge》中提出框架理论: 人在面对新情境时,会从记忆中选取一个称为"框架"的结构(类似填空表格),根据需要修改其中的细节。 框架表示法正是以这一理论为基础的结构化知识表示方法,擅长表示典型情境、对象的全貌。

◈ 框架的组成

一个框架由框架名和若干槽(slot)组成;每个槽描述对象的一个属性, 槽又可包含若干侧面(facet)和值,常见侧面有 value(值)、 default(默认值)、if-needed(需要时计算的过程)、 if-added(赋值时触发的过程)。槽值可以是具体值、另一框架名,从而构成框架网络, 子框架可通过 AKO / ISA 槽继承父框架的属性。

框架表示示例:"教师"框架

frame_net.py
"""框架表示法:槽/侧面、默认值、过程性附件、继承"""

class Frame:
    def __init__(self, name):
        self.name = name
        self.slots = {}      # 槽名 -> {侧面名: 值}

    def add_slot(self, slot, value=None, default=None,
                 if_needed=None, if_added=None):
        self.slots[slot] = {
            "value": value,
            "default": default,
            "if_needed": if_needed,    # 缺值时调用的函数(frame, slot)
            "if_added": if_added,      # 写入值时触发的函数(frame, slot, val)
        }

    def get(self, slot, seen=None):
        """取值顺序:本框架值 → 默认值 → if_needed 过程 → 沿 AKO 继承"""
        seen = seen or set()
        if self.name in seen:
            return None
        seen.add(self.name)

        s = self.slots.get(slot)
        if s is not None:
            if s["value"] is not None:
                return s["value"]
            if s["default"] is not None:
                return s["default"]
            if s["if_needed"] is not None:
                return s["if_needed"](self, slot)

        # 沿 ISA / AKO 到父框架中继承
        for parent_slot in ("AKO", "ISA"):
            if parent_slot in self.slots and self.slots[parent_slot]["value"]:
                parent = FrameSystem.get_frame(self.slots[parent_slot]["value"])
                val = parent.get(slot, seen)
                if val is not None:
                    return val
        return None

    def setv(self, slot, value):
        s = self.slots.setdefault(slot, {"value": None, "default": None,
                                         "if_needed": None, "if_added": None})
        s["value"] = value
        if s["if_added"]:
            s["if_added"](self, slot, value)

    def show(self):
        print(f"【框架:{self.name}】")
        for slot, facets in self.slots.items():
            if slot in ("AKO", "ISA"):
                continue
            print(f"   {slot: <6} = {self.get(slot)}")


class FrameSystem:
    _frames = {}

    @classmethod
    def get_frame(cls, name):
        return cls._frames.get(name)

    @classmethod
    def create(cls, name):
        f = Frame(name)
        cls._frames[name] = f
        return f


if __name__ == "__main__":
    # ---- 定义父框架:教职工 ----
    staff = FrameSystem.create("教职工")
    staff.add_slot("类别")
    staff.add_slot("工作单位", default="人工智能学院")
    staff.add_slot("健康状况", default="良好")

    # ---- 子框架:教师,AKO 继承自教职工 ----
    teacher = FrameSystem.create("教师")
    teacher.add_slot("AKO", value="教职工")
    teacher.add_slot("学历", default="博士研究生")
    teacher.add_slot("职称")
    # if_needed 过程性知识:没有填写职称时按年龄推断
    teacher.add_slot("退休年龄",
                     if_needed=lambda f, s: 65 if f.get("性别") == "男" else 60)

    # ---- 实例框架:具体某位教师 ----
    wang = FrameSystem.create("王老师")
    wang.add_slot("ISA", value="教师")
    wang.setv("姓名", "王知行")
    wang.setv("性别", "男")
    wang.setv("职称", "教授")
    wang.setv("所授课程", "人工智能导论")

    wang.show()
    print("\n—— 继承与推理效果 ——")
    print("工作单位(继承自教职工):", wang.get("工作单位"))
    print("学历(继承自教师默认值):", wang.get("学历"))
    print("退休年龄(if_needed 过程推算):", wang.get("退休年龄"))
运行输出
【框架:王老师】
   姓名   = 王知行
   性别   = 男
   职称   = 教授
   所授课程 = 人工智能导论
   退休年龄 = 65

—— 继承与推理效果 ——
工作单位(继承自教职工): 人工智能学院
学历(继承自教师默认值): 博士研究生
退休年龄(if_needed 过程推算): 65

框架法把陈述性知识(属性值)与过程性知识(if_needed)组织在一起, 层次清晰、继承性强,特别适合表达有固定结构的典型对象与场景(如病历、简历、事故情境)。 缺点是不善于表达过程性的流程知识,且多重继承时可能产生属性冲突。

2.5 语义网络表示法

1968 年奎廉(Quillian)在研究人类联想记忆时提出语义网络。 它通过节点和带标注的弧描述事物间的语义关系:节点表示事物、概念、属性值; 弧表示节点间的关系。一个最简单的语义单元是三元组 (节点 A,弧,节点 B)。

常用的基本语义关系

关系含义示例
ISA / instance-of具体与抽象(实例关系)小燕子 —ISA→ 鸟
AKO / subclass-of子类关系(可继承属性)鸟 —AKO→ 动物
part-of部分与整体(不继承属性)轮子 —part-of→ 汽车
have / can属性、能力关系鸟 —have→ 翅膀
located-at位置关系书 —located-at→ 图书馆
caused-by因果关系下雨 —导致→ 地湿

Python 实现:语义网络与继承推理

用语义网络表示知识后,推理的主要形式是匹配与继承: 查询"小燕子有什么器官"时,沿 ISA → AKO 链向上,继承"鸟有翅膀""动物有心脏"等属性。

semantic_network.py
"""语义网络:有向图(三元组)+ 沿继承关系的属性推理"""

class SemanticNetwork:
    def __init__(self):
        self.triples = []          # [(主体, 关系, 客体)]
        # 可继承、指向"父概念"的关系
        self.isa_rels = {"ISA", "AKO", "subclass-of", "instance-of"}

    def add(self, subj, rel, obj):
        self.triples.append((subj, rel, obj))

    def direct(self, subj, rel):
        """直接关系查询。"""
        return [o for s, r, o in self.triples if s == subj and r == rel]

    def parents(self, node):
        """沿 ISA/AKO 一步可达的父概念。"""
        return [o for s, r, o in self.triples
                if s == node and r in self.isa_rels]

    def inheritable_props(self, node):
        """继承推理:收集节点自身及所有祖先概念的属性(have/can/属性...)。"""
        result = {}
        visited = set()
        stack = [node]
        while stack:
            cur = stack.pop()
            if cur in visited:
                continue
            visited.add(cur)
            for s, r, o in self.triples:
                if s == cur and r not in self.isa_rels and r != "part-of":
                    result.setdefault(r, []).append((o, cur))
            stack.extend(self.parents(cur))
        return result

    def all_nodes(self):
        return sorted({s for s, _, _ in self.triples} | {o for _, _, o in self.triples})

    def describe(self, node):
        print(f"「{node}」可知的属性(含继承):")
        for rel, vals in self.inheritable_props(node).items():
            for val, source in vals:
                tag = "自身" if source == node else f"继承自「{source}」"
                print(f"   --{rel}--> {val}   ({tag})")


if __name__ == "__main__":
    net = SemanticNetwork()
    # 概念层次
    net.add("动物", "AKO", "生物")
    net.add("鸟", "AKO", "动物")
    net.add("鱼", "AKO", "动物")
    net.add("燕子", "ISA", "鸟")
    net.add("麻雀", "ISA", "鸟")
    # 属性
    net.add("生物", "能", "新陈代谢")
    net.add("动物", "能", "运动")
    net.add("动物", "have", "心脏")
    net.add("鸟", "have", "翅膀")
    net.add("鸟", "能", "飞行")
    net.add("鱼", "have", "鳃")
    net.add("鱼", "能", "游泳")
    net.add("燕子", "颜色", "黑色")
    net.add("燕子", "build", "泥巢")

    net.describe("燕子")
    print()
    print("直接会飞的:", net.direct("鸟", "能"))
    print("燕子的父概念链:", net.parents("燕子"))
运行输出
「燕子」可知的属性(含继承):
   --颜色--> 黑色   (自身)
   --build--> 泥巢   (自身)
   --have--> 翅膀   (继承自「鸟」)
   --能--> 飞行   (继承自「鸟」)
   --have--> 心脏   (继承自「动物」)
   --能--> 运动   (继承自「动物」)
   --能--> 新陈代谢   (继承自「生物」)

直接会飞的: ['飞行']
燕子的父概念链: ['鸟']

语义网络的优点是直观、联想方式自然、属性共享(继承)减少冗余; 缺点是推理结论依赖网络结构,没有统一的形式化语义,规模一大就难以管理和保证一致性。 ——这正是现代知识图谱用本体(Ontology)和 RDF/OWL 标准加以规范化的原因。

2.6 面向对象表示法

面向对象(Object-Oriented)表示法与框架法一脉相承,但以更规范的类(class)、对象(object)、 封装、继承、多态机制组织知识:类描述一类事物的共同属性(字段)与行为(方法),对象是类的实例; 子类继承父类并可改写方法。这种表示法天然能被 Java、C++、Python 等 OO 语言直接支持。

封装

把数据(属性)与处理数据的方法捆绑在对象内部,对外隐藏实现细节,仅暴露接口。

继承

子类自动获得父类的属性与方法,实现知识复用与分类层次。

多态

同名方法在不同子类中有不同实现,如 bird.move() 是飞、fish.move() 是游。

oo_knowledge.py
"""用面向对象方式表示'动物分类学'知识"""

class Animal:
    """动物基类:所有动物共享的知识。"""
    kingdom = "动物界"

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def move(self):
        raise NotImplementedError      # 由具体子类实现(多态)

    def info(self):
        return f"{self.name},{self.age} 岁,属于{self.kingdom},移动方式:{self.move()}"


class Bird(Animal):
    """鸟类继承动物类,补充鸟的共同知识。"""
    def __init__(self, name, age, can_fly=True):
        super().__init__(name, age)
        self.can_fly = can_fly
        self.body_cover = "羽毛"

    def move(self):
        return "飞行" if self.can_fly else "行走"

    def lay_egg(self):
        return f"{self.name} 产了一枚卵"


class Fish(Animal):
    def move(self):
        return "游泳"

    def breathe(self):
        return f"{self.name} 用鳃呼吸"


class Penguin(Bird):
    """企鹅是鸟但不会飞——继承体系下的特化。"""
    def __init__(self, name, age):
        super().__init__(name, age, can_fly=False)


zoo = [Bird("白鹭", 3), Fish("鲤鱼", 1), Penguin("QQ", 2)]
for a in zoo:
    print(a.info())                 # 同一个 info(),行为随对象类型不同(多态)

print(zoo[0].lay_egg())
print(zoo[1].breathe())
运行输出
白鹭,3 岁,属于动物界,移动方式:飞行
鲤鱼,1 岁,属于动物界,移动方式:游泳
QQ,2 岁,属于动物界,移动方式:行走
白鹭 产了一枚卵
鲤鱼 用鳃呼吸

2.7 知识图谱入门(现代延伸)

知识图谱(Knowledge Graph)是语义网络思想在大数据时代的复兴与工程化, Google 于 2012 年正式提出。它以 RDF 三元组(头实体, 关系, 尾实体) 为基本数据模型描述世界中的实体及其关系,典型项目有 Wikidata、DBpedia、CN-DBpedia 等, 广泛用于搜索问答、推荐、风控、医疗等领域。

知识图谱 KG = (E, R, T),其中 E 为实体集合,R 为关系集合,T ⊆ E × R × E 为三元组集合

2.7.1 构建一个可推理的小型知识图谱

下面实现一个内存知识图谱,支持:三元组增删、多跳关系查询、逆关系、 基于规则的关系推理(如"祖父 = 父亲的父亲""首都的国家即位于"),并演示一个简单问答接口。

knowledge_graph.py
"""迷你知识图谱:三元组存储 + 多跳查询 + 规则推理 + 简单问答"""
from collections import defaultdict

class KnowledgeGraph:
    def __init__(self):
        self.triples = set()
        self.spo = defaultdict(list)     # (主体, 关系) -> [客体]
        self.ops = defaultdict(list)     # (客体, 关系) -> [主体]

    def add(self, s, p, o):
        if (s, p, o) not in self.triples:
            self.triples.add((s, p, o))
            self.spo[(s, p)].append(o)
            self.ops[(o, p)].append(s)

    def query(self, s, p, o):
        """三元组中任意一项为 None 表示查询变量,返回所有匹配。"""
        ans = []
        for (a, b, c) in self.triples:
            if (s is None or s == a) and (p is None or p == b) and (o is None or o == c):
                ans.append((a, b, c))
        return ans

    def hop(self, entity, *relations):
        """沿关系链做多跳查询,如 hop('红楼梦', '作者', '国籍')。"""
        current = [entity]
        for r in relations:
            nxt = []
            for e in current:
                nxt.extend(self.spo.get((e, r), []))
            current = nxt
        return current

    def add_rule(self, new_rel, rel_chain):
        """关系推理规则:若存在 rel_chain 关系链,则蕴含 new_rel 关系。
        例:new_rel='祖父', chain=('父亲','父亲')"""
        added = []
        for s in self.query(None, rel_chain[0], None):
            tails = self.hop(s[0], *rel_chain)
            for t in tails:
                before = len(self.triples)
                self.add(s[0], new_rel, t)
                if len(self.triples) > before:
                    added.append((s[0], new_rel, t))
        return added


if __name__ == "__main__":
    kg = KnowledgeGraph()
    # ---- 文学领域知识 ----
    data = [
        ("红楼梦", "作者", "曹雪芹"), ("曹雪芹", "朝代", "清朝"),
        ("红楼梦", "体裁", "古典小说"), ("西游记", "作者", "吴承恩"),
        ("吴承恩", "朝代", "明朝"), ("三国演义", "作者", "罗贯中"),
        ("罗贯中", "朝代", "元末明初"), ("水浒传", "作者", "施耐庵"),
        ("施耐庵", "朝代", "元末明初"),
        ("贾宝玉", "登场于", "红楼梦"), ("孙悟空", "登场于", "西游记"),
        # ---- 地理知识 ----
        ("中国", "首都", "北京"), ("北京", "位于", "中国"),
        ("法国", "首都", "巴黎"), ("巴黎", "位于", "法国"),
        ("巴黎", "地标", "埃菲尔铁塔"),
    ]
    for t in data:
        kg.add(*t)

    # 1) 单条件查询:四大名著的作者
    print("《红楼梦》的作者:", kg.hop("红楼梦", "作者"))

    # 2) 多跳查询:《红楼梦》作者的朝代
    print("《红楼梦》作者所处朝代:", kg.hop("红楼梦", "作者", "朝代"))

    # 3) 反向查询:谁登场于《西游记》
    who = [s for s, p, o in kg.query(None, "登场于", "西游记")]
    print("《西游记》中的角色:", who)

    # 4) 两跳到达:从《红楼梦》出发,经'作者'能到谁,再经'朝代'
    print("全部三元组数量:", len(kg.triples))

    # 5) 规则推理:定义'名著作者时代' = 作者→朝代 的压缩关系
    for s, _, book in kg.query(None, "登场于", None):
        pass
    new_facts = kg.add_rule("作者朝代", ("作者", "朝代"))
    print("规则推理新增三元组:")
    for f in sorted(new_facts):
        print("  ", f)

    # 6) 埃菲尔铁塔在哪个国家(两跳:地标→位于)
    print("埃菲尔铁塔所在国家:", kg.hop("埃菲尔铁塔", "位于"))

    # 7) 简单模板问答
    def qa(question):
        q = question.replace("?", "").replace("?", "")
        if "的作者是谁" in q:
            book = q.split("的作者是谁")[0].strip("《》")
            return "、".join(kg.hop(book, "作者"))
        if "在哪个国家" in q:
            place = q.split("在哪个国家")[0]
            return "、".join(kg.hop(place, "位于"))
        return "抱歉,这个问题超出了我的知识范围。"

    print(qa("《红楼梦》的作者是谁?"))
    print(qa("埃菲尔铁塔在哪个国家?"))
运行输出
《红楼梦》的作者: ['曹雪芹']
《红楼梦》作者所处朝代: ['清朝']
《西游记》中的角色: ['孙悟空']
全部三元组数量: 15
规则推理新增三元组:
   ('三国演义', '作者朝代', '元末明初')
   ('水浒传', '作者朝代', '元末明初')
   ('西游记', '作者朝代', '明朝')
   ('红楼梦', '作者朝代', '清朝')
埃菲尔铁塔所在国家: ['法国']
《红楼梦》的作者是谁?
曹雪芹
埃菲尔铁塔在哪个国家?
法国

✦ 工程延伸

真实知识图谱的实体规模可达百亿三元组,工程上常用 Neo4j、JanusGraph 等图数据库存储, 用 SPARQL / Cypher 查询语言,用 OWL 本体 + 描述逻辑推理机 (如 Pellet、HermiT)做一致性检查与推理;实体与关系的自动获取则依赖第 9 章将介绍的 NLP 技术(命名实体识别、关系抽取)。

本章小结

  • 知识是智能的基础,按层次可分为事实、规则、控制知识和元知识;知识表示追求表示能力、可利用性与可维护性。
  • 一阶谓词逻辑:谓词+个体+量词,严密自然,可用反向链进行逻辑推理。
  • 产生式:IF-THEN 规则,由综合数据库、规则库、控制系统构成,是专家系统的主流表示法。
  • 框架:槽/侧面结构描述典型对象,支持默认值、过程附件与继承。
  • 语义网络:节点—弧—节点的图结构,靠匹配和继承推理,直观自然。
  • 面向对象:类与对象、封装、继承、多态,可被现代编程语言直接支持。
  • 知识图谱:三元组模型的大规模工程化,支持多跳查询与规则推理,是现代 AI 的重要基础设施。