📘 第12章 知识表示和数据结构
📄 一句话概括

语义网络把知识拆成「主谓宾」三条一组的断言,再用一张图把它们连起来。 语义网是同一套想法放到整个互联网上。 这一节从 17 条三元组出发,走图查询、跑规则推理、看属性继承, 最后落到两个最难的地方:实体对齐和开放世界假设。

📝 本节和前面几节的关系

上一节的检索还是「按字面找」,这一节要的是「按关系找」。 问「张三的祖父是谁」,字面检索帮不上忙,得沿着「父亲」这条边跳两跳。 它和 11.2 的共现语义是一对照:那边靠统计猜「谁跟谁像」,这边靠断言说「谁跟谁是什么关系」。 一个是软的、模糊的;一个是硬的、可推理的。

一、三元组:一句话就是一个事实

最朴素的存法:每句话拆成「主体、关系、客体」三条,一条一行。

主语 关系 宾语
张三 父亲 张一
张一 父亲 张老
宜昌 位于 湖北
湖北 位于 中国
十七条三元组,十六个节点。
按「主语 + 关系」归类,就得到一张邻接表——这就是图。
它和关系数据库的差别在哪?
数据库的列是固定的,加一种新关系就得改表结构;
三元组里加一种关系,只是多几行。
代价是没有约束,什么都能往里塞。

二、图查询:沿边跳,跳数必须限定

「张三的祖父是谁」拆成两跳:先找父亲,再找父亲的父亲。 一条 1 跳、一条 2 跳,拼起来就是答案:张老。 单跳还好,多跳就麻烦了。从张三出发、不限关系地走:

步数 这一步新摸到 累计
1 4 个 5
2 6 个 11
3 4 个 15
4 1 个 16
四步就把全图十六个节点摸遍了——从任意一个点出发,一个不漏。
而这个图只有 17 条边,真实知识图谱是十亿条量级。
所以图查询的第一条纪律是:写清「走哪种边、最多几步」。
不限类型地乱走,三步就铺满半个图,查询根本跑不完。

三、推理:没写下的事实能不能自己长出来

加一条规则:如果 x 位于 y、y 位于 z,那么 x 位于 z。 然后反复套,直到再也推不出新东西。

轮次 新增事实 推出来的是
1 2 条 宜昌 位于 中国、湖北 位于 亚洲
2 1 条 宜昌 位于 亚洲
3 0 条 不动点,停
17 条事实推出 3 条新的,最后 20 条。
「宜昌 位于 中国」这一条,原库里根本没有,是算出来的。
注意必须跑到不动点。
只跑一遍会漏:「宜昌 位于 亚洲」要等第二轮才推得出来,
因为它依赖第一轮刚生成的「湖北 位于 亚洲」。
这就是「推理会链式触发」的意思,也是它比查询贵得多的原因。
规则一多、图一大,一轮就能生成海量新事实,还可能是错的。

四、本体:让属性只写一份

把「类」也写成节点,就能表达层次:动物 → 人 → 学生 → 大学生。 每个类只声明自己新增的属性: 动物需要呼吸、人会说话、学生有学号、大学生一条都没声明。 实例属于某个类,就顺着层次往上把属性全继承下来: 张三(学生)拿到「有学号、会说话、会用工具、需要呼吸」四条。 李四(大学生)拿到的完全一样。 这就是本体的用处:知识只写一份,靠层次传下去。 跟数据库的继承是两回事——数据库的表结构没有「大学生是学生」这种语义。 代价也很实在:继承链一长,改一个上层属性会影响多少下层,很难一眼看清。 这也是大型本体难维护的原因。

五、两个绕不开的难点

第一个:实体对齐。「张三」「张 三」「张先生」「Zhang San」「张三(工程师)」—— 五个字符串指的是同一个人,字面比对只有一个能对上。 这是知识图谱里最贵的工序。 存、查、推理都不难,难的是「把说的都是谁对齐」。 第二个:没说的事算不算假?

假设 「张三会游泳」怎么判
封闭世界(数据库) 没写就是假
开放世界(语义网) 没写就是不知道,可能为真
数据库默认封闭世界,所以「查不到」等于「不存在」,这是 SQL 能高效执行的前提。
语义网默认开放世界,所以它永远不敢断言「没有」——这也是语义网喊了二十年却推不动的原因之一。
两种假设各有道理,混着用就会出荒谬结论。
今天的知识图谱工程,大多是「图的结构 + 数据库的封闭世界」这种折中。

六、动手 Demo:把知识拆成一句一句的主谓宾

五段:三元组与邻接表、沿边查询、规则推理到不动点、类层次与属性继承、 实体对齐和两种世界假设。 没有用图数据库,也没有用推理机。 十七个节点、三个 while 循环,够把每一步看清了。

TRI = [(u"张三", u"父亲", u"张一"), (u"张一", u"父亲", u"张老"),
       (u"宜昌", u"位于", u"湖北"), (u"湖北", u"位于", u"中国")]

EDGE = {}                         # 主语 + 关系 -> 宾语列表
for s, p, o in TRI:
    EDGE.setdefault((s, p), []).append(o)

# 反复套规则,直到不动点
while True:
    new = set()
    for s, p, o in KNOWN:
        if p != u"位于":
            continue
        for s2, p2, o2 in KNOWN:
            if p2 == u"位于" and s2 == o:
                new.add((s, u"位于", o2))
    new -= KNOWN
    if not new:
        break
    KNOWN |= new

实跑输出:

--------------------------------------------------------------------
把知识拆成一句一句的主谓宾
--------------------------------------------------------------------
==============================================================

[1] 三元组:一句话就是一个事实

  17 条三元组,每条都是「主体、关系、客体」:

    主语      关系      宾语
    张三      父亲      张一
    张一      父亲      张老
    张三      居住在    宜昌
    宜昌      位于      湖北
    湖北      位于      中国
    张三      是        学生
    ……(共 17 条)

  按「谁 + 什么关系」归一下,就是一张邻接表:

    节点      出边
    张三      父亲→张一、居住在→宜昌、是→学生、喜欢→编程
    张一      父亲→张老、是→工程师
    宜昌      位于→湖北、属于→城市
    学生      属于→人

  **同一批事实,换一种存法就换一种问法。**
  **属性表和三元组能互转,区别只在于「关系」能不能带层次。**

[2] 查「张三的祖父是谁」:沿边跳两跳

  单跳只要查表,两跳就要拼:先找父亲,再找父亲的父亲。
    张三 --父亲--> 张一
    张一 --父亲--> 张老
    所以张三的祖父是:张老

  跳数一多,能摸到的范围会迅速铺开。下面这张表是「从张三出发、不限关系、只走 k 步」:

    步数 k    这一步新摸到  累计节点数    新摸到的是
    1         4             5             学生、宜昌、张一、编程
    2         6             11            人、城市、工程师、张老、技能、湖北
    3         4             15            中国、动物、省、退休工人
    4         1             16            亚洲

  **四步就把全图 16 个节点摸遍了——从 1 个出发,一个不漏。**
  **而这个图只有 17 条边,真实图谱是十亿条量级。**
  **真实知识图谱里一个节点有几十种关系,不限定类型地乱走,三步就能铺满半个图。**
  **所以图查询必须写清「走哪种边、最多几步」。**

[3] 推理:没写下来的事实,能不能自己推出来

  加一条规则:**如果 x 位于 y,且 y 位于 z,那么 x 位于 z。**
  然后反复套这条规则,直到再也推不出新东西:

    轮次      新增事实    这次推出来的
    第 1 轮   2           宜昌 位于 中国、湖北 位于 亚洲
    第 2 轮   1           宜昌 位于 亚洲
    第 3 轮   0           (不动点,停)

  **17 条事实推出 3 条新事实,最后停在 20 条。**
  现在问「宜昌在哪个国家」,一条链两跳就答上了:宜昌 → 湖北 → 中国。
  这两条事实里,「宜昌 位于 中国」原来并不在库里,是推出来的。

  **注意推理是可以链式触发的:第一轮推出的「宜昌 位于 中国」,**
  **第二轮又能和「中国 位于 亚洲」接着拼。所以必须跑到不动点,不能只跑一遍。**

[4] 本体:把「类」也写成节点,属性就能继承

    类        父类      自己声明的属性
    动物      —         需要呼吸
    人        动物      会说话、会用工具
    学生      人        有学号
    大学生    学生      (一条都没声明)

  实例属于某个类,就能顺着类层次往上把属性全继承下来:

    张三(学生):有学号、会说话、会用工具、需要呼吸
    李四(大学生):有学号、会说话、会用工具、需要呼吸

  **「大学生」自己一条属性都没声明,可它继承了学生的学号、人的说话、动物的呼吸。**
  **这就是本体的用处:知识只写一份,靠层次传下去。**
  **代价是——继承链一长,改一个上层属性会影响多少下层,很难一眼看清。**

[5] 落地难在哪:同一件事有好几种写法

  五个字符串,指的是同一个人。字面比对的结果:

    写法              和「张三」完全一样  字数
    张三              是                  2
    张 三             否                  3
    张先生            否                  3
    Zhang San         否                  9
    张三(工程师)    否                  7

  **五个里只有一个能对上。**这就是实体消解问题——
  **图谱里最贵的工序不是存,是把「说的都是谁」对齐。**

  还有一条更根本的分歧:**没写下来的事实,算「不知道」还是算「假」?**
    假设                「张三会游泳」怎么判
    封闭世界(数据库)  没写就是假
    开放世界(语义网)  没写就是不知道,可能为真

  **数据库默认封闭世界,所以「查不到」等于「不存在」。**
  **语义网默认开放世界,所以它永远不敢说「没有」——这也是它推不动的原因之一。**
  两种假设各有各的道理,混着用就会出荒谬的结论。

  五段连起来:**三元组把知识拆成一句一句的主谓宾**;
  **图查询靠沿边走,跳数必须限定**;
  **规则能把没写下的事实推出来,但要跑到不动点**;
  **本体靠层次让属性只写一份**;
  **而真正贵的是「对齐」——同一个东西有几个名字,几套假设。**

第 1 段那张邻接表就是图。 「张三」有三条出边,「学生」只有一条——出边条数就是这个词在知识里的角色丰富程度。 第 2 段表格最右一列(累计)比中间那列更值得看: 第三、四步新摸到的越来越少,说明这个图很小、已经快摸完了。 真实图谱的半径是五六跳,而且每跳都成千上万。 第 3 段那个「第二轮才推出宜昌位于亚洲」是关键。 它说明推理顺序会影响结果——只跑一遍和跑到不动点,答案不一样。 第 4 段「李四」和「张三」的属性完全一样,不是巧合: 大学生自己没声明属性,它拿到的每一条都是继承来的。 第 5 段那张两行的表,是这一节真正的结论。 封闭世界和开放世界不是技术细节,是两种看世界的态度。

30 秒实验

  1. 往第 1 段加一条「张老 是 退休工人」,重跑第 2 段。 四步就摸不到全图了——多一个节点,半径就得再加一跳。
  2. 把第 3 段的规则从「位于」换成「属于」,重跑。 能推出「张三 属于 动物」这类新事实—— 同一个引擎,换个关系名就是另一套推理,这就是规则和数据结构分离的好处。
  3. 在第 5 段加一个「张三 会 游泳」的三元组,重跑。 封闭世界下它从「假」变成「真」—— 一条事实就能翻转结论,这是开放世界的麻烦,也是它的老实。

常见误区

  1. 以为「知识图谱就是一张大图」。图只是存法。 真正让它是「知识」的,是关系的语义和能推理的规则。
  2. 以为「三元组能表达一切」。「张三每个月工资从 8000 涨到 9000」这种带时间、带变化的事实, 朴素三元组表达不了,得加时间戳或者把事件本身变成节点。
  3. 以为「推理越多越好」。规则一多,推出来的废话和错话也成倍增长。 真实系统要限制推理深度,宁可少推,不可乱推。
  4. 以为「语义网失败是因为技术不行」。它的核心假设是「开放世界加全球统一本体」。
  5. 这个假设和工程现实冲突太大。今天的知识图谱走的是另一条路:小范围、封闭、够用就行。
自测三问(点开对答案) 1. 为什么「张三的祖父」要跳两跳? —— 因为**知识里只写了「谁的父亲是谁」,没写「谁的祖父是谁」**。 **祖父这个关系是两跳拼出来的:先找人,再找那个人的父亲。** **跳数越多,路径数量涨得越快,所以查询必须限定步数。** 2. 为什么推理要跑到不动点? —— 因为**新推出的事实可能又能和别的规则拼**。**Demo 第 3 段:「宜昌 位于 亚洲」要等第二轮,** **因为它要用第一轮刚生成的「湖北 位于 亚洲」。** **只跑一遍就会漏掉这类链式结论。** 3. 封闭世界和开放世界的差别在哪? —— **只在于「没写下来的事实」怎么处理。** **数据库说「没写就是假」(封闭),语义网说「没写就是不知道」(开放)。** **前者能高效查询,后者更老实但永远不敢下结论。**

第 12 章到这里结束。 下一章换到机器那一侧:这些计算到底跑在什么机器上,一台不够时怎么拼成一群。 分布式、并行、硬件配置、软件配置,四节把「算力」这件事讲清楚。

相关笔记