﻿---
title: "Tensor Logic：逻辑规则和 einsum 是同一种运算吗"
description: "Pedro Domingos 在 2025 年的论文里说，一条 Datalog 规则就是一次 einsum 加一个阶跃函数，AI 只需要“张量方程”这一种语句。我们从一条“姑姨”规则出发，用 numpy 把它跑一遍，再顺着论文往下追：整门语言只有一种语句会是什么样，稀疏关系写成稠密矩阵代价多大，把关系压进嵌入空间以后推理还对不对，温度调到 0 是否真能“不幻觉”，规则能不能学出来，最后看这些想法里哪些是新的。"
pubDate: 2026-10-24
tags: ["AI", "论文解读", "逻辑编程", "神经符号", "嵌入"]
clc: "TP181"
hero: "./hero.svg"
refs:
  - id: tl
    title: "Tensor Logic: The Language of AI"
    url: https://arxiv.org/abs/2510.12269
    site: arXiv
    author: Pedro Domingos
    date: 2025-10-14
    note: "文中引用的是 v3（2025-10-16），17 页，没有图，也没有实验。第 3 节定义语言，第 4 节实现各范式，第 5 节讲嵌入空间推理，第 6 节讲扩展。"
  - id: tl-site
    title: "tensor-logic.org"
    url: https://tensor-logic.org/
    site: tensor-logic.org
    note: "截至 2026-10-24，站上只有论文、ECML/PKDD 2025 开幕报告的幻灯片、一个访谈视频和邮件订阅，没有官方实现。"
  - id: impl
    title: "Implementing Tensor Logic: Unifying Datalog and Neural Reasoning via Tensor Contraction"
    url: https://arxiv.org/abs/2601.17188
    site: arXiv
    author: Swapn Shah, Wlodek Zadrozny
    date: 2026-01-23
    note: "第三方复现：圣经家谱 1972 人、1727 条父子关系，74 轮收敛出 33945 条祖先关系；FB15k-237 上链接预测 MRR 0.3068，去掉直连边的组合推理 MRR 0.3346。"
  - id: impl-code
    title: "sshah100-clt/tensor_logic_implementation"
    url: https://github.com/sshah100-clt/tensor_logic_implementation
    site: GitHub
    note: "上一篇复现论文的代码。"
  - id: einsum
    title: "Einsum is all you need: Einstein summation in deep learning"
    url: https://rockt.ai/2018/04/30/einsum
    author: Tim Rocktäschel
    date: 2018-04-30
    note: "论文引用它来说明“神经网络里几乎所有线性和多线性运算都能写成 einsum”。"
  - id: smolensky
    title: "Tensor product variable binding and the representation of symbolic structures in connectionist systems"
    url: "https://doi.org/10.1016/0004-3702(90)90007-M"
    site: Artificial Intelligence 46
    author: Paul Smolensky
    date: "1990"
    note: "张量积表示：用向量外积把“角色”和“填充物”绑在一起，再叠加起来。论文第 5 节的关系嵌入就是这种表示。"
  - id: bloom
    title: "Space/time trade-offs in hash coding with allowable errors"
    url: https://doi.org/10.1145/362686.362692
    site: Communications of the ACM 13(7)
    author: Burton H. Bloom
    date: "1970"
  - id: rescal
    title: "A Three-Way Model for Collective Learning on Multi-Relational Data"
    url: https://icml.cc/2011/papers/438_icmlpaper.pdf
    site: ICML 2011
    author: Maximilian Nickel, Volker Tresp, Hans-Peter Kriegel
    date: "2011"
    note: "RESCAL：每个关系一个矩阵 M_r，三元组打分 e_hᵀ M_r e_t。凭领域知识整理，发文前需要核对。"
  - id: grefenstette
    title: "Towards a Formal Distributional Semantics: Simulating Logical Calculi with Tensors"
    url: https://arxiv.org/abs/1304.5823
    site: arXiv
    author: Edward Grefenstette
    date: 2013-04-22
  - id: tensorlog
    title: "TensorLog: A Differentiable Deductive Database"
    url: https://arxiv.org/abs/1605.06523
    site: arXiv
    author: William W. Cohen
    date: 2016-05-20
    note: "把一类概率 Datalog 编译成可微的矩阵运算，交给 TensorFlow / Theano 训练。名字和 Tensor Logic 几乎一样，论文没有引用。"
  - id: sato
    title: "A Linear Algebraic Approach to Datalog Evaluation"
    url: https://arxiv.org/abs/1608.00139
    site: arXiv（后刊于 TPLP 2017）
    author: Taisuke Sato
    date: 2016-07-30
    note: "在向量空间里求值 Datalog：矩阵乘法加阈值算传递闭包。"
  - id: ntp
    title: "End-to-End Differentiable Proving"
    url: https://arxiv.org/abs/1705.11040
    site: arXiv
    author: Tim Rocktäschel, Sebastian Riedel
    date: 2017-05-31
    note: "神经定理证明器：用嵌入的相似度代替符号合一，相似的符号可以“软”匹配。"
  - id: kojima
    title: "A tensorized logic programming language for large-scale data"
    url: https://arxiv.org/abs/1901.08548
    site: arXiv
    author: Ryosuke Kojima, Taisuke Sato
    date: 2019-01-20
    further: true
  - id: notes
    title: "本文的实验代码与调研笔记"
    note: "`/mnt/project-files/tensor-logic-research/`：`experiment/exp1_datalog.py` 到 `exp4_learn_rule.py`，输出在 `logs/`。numpy 2.5.3，4 核 CPU。"
---

逻辑编程里有一条教科书式的规则：x 是 y 的姐妹，y 是 z 的父母，那么 x 是 z 的姑姑或姨妈。用 Datalog 写出来是这样：

```prolog
Aunt(x, z) :- Sister(x, y), Parent(y, z).
```

而用 numpy 写，是这样一行：

```python
Aunt = np.einsum("xy,yz->xz", Sister, Parent) > 0
```

华盛顿大学的 Pedro Domingos 在 2025 年 10 月的论文《Tensor Logic: The Language of AI》里说，这两行不只是“结果碰巧一样”，它们**本来就是同一种运算**；而且神经网络、逻辑推理、核方法、概率图模型，全都可以只用这一种语句写出来[^tl]。他据此提出一门新语言，名字就叫张量逻辑（tensor logic），并且说它能在嵌入空间里做“可靠、透明、不会幻觉”的推理。

这话听上去很大。逻辑规则和矩阵乘法真的是一回事吗？如果是，为什么符号 AI 和神经网络各走各路走了几十年？只剩一种语句的语言长什么样，能不能真把规则学出来、真不幻觉？我们就从上面这一行 einsum 开始，边跑代码边往下看。

## 一、规则与 einsum 的对应

### 先认识两边

**Datalog** 是最简单的逻辑编程语言，可以看成去掉函数符号的 Prolog。一个 Datalog 程序由两样东西组成：**事实**，比如 `Parent(舅舅, 表妹)`，说的是“舅舅是表妹的父母”；**规则**，比如上面那条 `Aunt`，箭头（`:-`）左边叫头，右边叫体，体里的逗号是“并且”。规则的意思是：只要能找到一组取值，让体里的每一项都是已知事实，头就成立。

**einsum**（爱因斯坦求和）是一种写张量运算的记号。张量就是多维数组：向量有一个下标，矩阵有两个。`"xy,yz->xz"` 的意思是：第一个矩阵的下标叫 x、y，第二个叫 y、z，结果保留 x、z；**在箭头右边没出现的下标 y，要被求和掉**。所以它就是矩阵乘法 $C_{xz} = \sum_y A_{xy} B_{yz}$。

### 现象：两边算出同样的五条

我们拿一个七口之家试一下：外婆有三个孩子，妈妈、小姨和舅舅；妈妈有“我”，舅舅有表妹和表弟。把 `Sister` 和 `Parent` 各写成一个 7×7 的 0/1 矩阵，第 x 行第 y 列是 1 就表示这条事实成立，再跑上面那行 einsum：

```text
einsum[妈妈,表妹] = 1  经由 y = ['舅舅']  -> Aunt = 1
einsum[妈妈,表弟] = 1  经由 y = ['舅舅']  -> Aunt = 1
einsum[小姨,我]   = 1  经由 y = ['妈妈']  -> Aunt = 1
einsum[小姨,表妹] = 1  经由 y = ['舅舅']  -> Aunt = 1
einsum[小姨,表弟] = 1  经由 y = ['舅舅']  -> Aunt = 1
```

五条结果，和按 Datalog 规则一条条推出来的完全一样[^notes]。

### 原因：关系就是布尔张量

为什么会一样？把规则拆开看，每一部分都能在 einsum 里找到对应。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="tl-rule-title tl-rule-desc">
<title id="tl-rule-title">同一条姑姨规则的 Datalog 写法和 einsum 写法</title>
<desc id="tl-rule-desc">上半部分是 Datalog 规则和六条事实。下半部分是 Sister 矩阵乘 Parent 矩阵：两者共享的下标 y（妈妈、舅舅）被求和，得到每对 x、z 之间有几个 y 能连起来的计数矩阵，再经过阶跃函数 H 得到 Aunt 矩阵。小姨对我、表妹、表弟都是 1，妈妈对表妹、表弟是 1。</desc>
<defs>
<marker id="tl-rule-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="tl-rule-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="400" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="132" rx="8"/>
<rect class="dd-zone" x="16" y="164" width="768" height="220" rx="8"/>
<text class="t-zone" x="32" y="44">Datalog 写法</text>
<text class="t-mono" x="32" y="76">Aunt(x, z) :- Sister(x, <tspan class="t-acc">y</tspan>), Parent(<tspan class="t-acc">y</tspan>, z).</text>
<text class="t-val" x="32" y="104">Sister(小姨, 妈妈)   Sister(小姨, 舅舅)   Sister(妈妈, 舅舅)</text>
<text class="t-val" x="32" y="130">Parent(妈妈, 我)     Parent(舅舅, 表妹)   Parent(舅舅, 表弟)</text>
<text class="t-say" x="452" y="76">逗号是“并且”，y 只在体里出现，</text>
<text class="t-say" x="452" y="98">意思是“存在某个 y”</text>
<text class="t-zone" x="32" y="192">einsum 写法</text>
<text class="t-mono" x="140" y="192">Aunt[x,z] = H( Sister[x,<tspan class="t-acc">y</tspan>] Parent[<tspan class="t-acc">y</tspan>,z] )</text>
<line class="dd-edge-acc" x1="336" y1="268" x2="370" y2="268" marker-end="url(#tl-rule-arrow-acc)"/>
<line class="dd-edge" x1="540" y1="268" x2="574" y2="268" marker-end="url(#tl-rule-arrow)"/>
<text class="t-acc t-mono" x="354" y="258" text-anchor="middle">Σ y</text>
<text class="t-mono" x="558" y="258" text-anchor="middle">H</text>
<text class="t-name" x="162" y="274" text-anchor="middle">×</text>
<text class="t-note" x="94" y="230" text-anchor="middle"><tspan class="t-acc">妈妈</tspan></text>
<text class="t-note" x="130" y="230" text-anchor="middle"><tspan class="t-acc">舅舅</tspan></text>
<text class="t-note" x="36" y="259">小姨</text>
<text class="t-note" x="36" y="287">妈妈</text>
<rect class="dd-card" x="76" y="240" width="72" height="56" rx="4"/>
<line class="dd-hair" x1="76" y1="268" x2="148" y2="268"/>
<line class="dd-hair" x1="112" y1="240" x2="112" y2="296"/>
<text class="t-mono" x="94" y="259" text-anchor="middle">1</text>
<text class="t-mono" x="130" y="259" text-anchor="middle">1</text>
<text class="t-note" x="94" y="287" text-anchor="middle">0</text>
<text class="t-mono" x="130" y="287" text-anchor="middle">1</text>
<text class="t-note" x="234" y="230" text-anchor="middle">我</text>
<text class="t-note" x="270" y="230" text-anchor="middle">表妹</text>
<text class="t-note" x="306" y="230" text-anchor="middle">表弟</text>
<text class="t-acc t-note" x="180" y="259">妈妈</text>
<text class="t-acc t-note" x="180" y="287">舅舅</text>
<rect class="dd-card" x="216" y="240" width="108" height="56" rx="4"/>
<line class="dd-hair" x1="216" y1="268" x2="324" y2="268"/>
<line class="dd-hair" x1="252" y1="240" x2="252" y2="296"/>
<line class="dd-hair" x1="288" y1="240" x2="288" y2="296"/>
<text class="t-mono" x="234" y="259" text-anchor="middle">1</text>
<text class="t-note" x="270" y="259" text-anchor="middle">0</text>
<text class="t-note" x="306" y="259" text-anchor="middle">0</text>
<text class="t-note" x="234" y="287" text-anchor="middle">0</text>
<text class="t-mono" x="270" y="287" text-anchor="middle">1</text>
<text class="t-mono" x="306" y="287" text-anchor="middle">1</text>
<text class="t-note" x="438" y="230" text-anchor="middle">我</text>
<text class="t-note" x="474" y="230" text-anchor="middle">表妹</text>
<text class="t-note" x="510" y="230" text-anchor="middle">表弟</text>
<text class="t-note" x="384" y="259">小姨</text>
<text class="t-note" x="384" y="287">妈妈</text>
<rect class="dd-card" x="420" y="240" width="108" height="56" rx="4"/>
<line class="dd-hair" x1="420" y1="268" x2="528" y2="268"/>
<line class="dd-hair" x1="456" y1="240" x2="456" y2="296"/>
<line class="dd-hair" x1="492" y1="240" x2="492" y2="296"/>
<text class="t-mono" x="438" y="259" text-anchor="middle">1</text>
<text class="t-mono" x="474" y="259" text-anchor="middle">1</text>
<text class="t-mono" x="510" y="259" text-anchor="middle">1</text>
<text class="t-note" x="438" y="287" text-anchor="middle">0</text>
<text class="t-mono" x="474" y="287" text-anchor="middle">1</text>
<text class="t-mono" x="510" y="287" text-anchor="middle">1</text>
<text class="t-note" x="642" y="230" text-anchor="middle">我</text>
<text class="t-note" x="678" y="230" text-anchor="middle">表妹</text>
<text class="t-note" x="714" y="230" text-anchor="middle">表弟</text>
<text class="t-note" x="588" y="259">小姨</text>
<text class="t-note" x="588" y="287">妈妈</text>
<rect class="dd-card" x="624" y="240" width="108" height="56" rx="4"/>
<line class="dd-hair" x1="624" y1="268" x2="732" y2="268"/>
<line class="dd-hair" x1="660" y1="240" x2="660" y2="296"/>
<line class="dd-hair" x1="696" y1="240" x2="696" y2="296"/>
<text class="t-mono" x="642" y="259" text-anchor="middle">1</text>
<text class="t-mono" x="678" y="259" text-anchor="middle">1</text>
<text class="t-mono" x="714" y="259" text-anchor="middle">1</text>
<text class="t-note" x="642" y="287" text-anchor="middle">0</text>
<text class="t-mono" x="678" y="287" text-anchor="middle">1</text>
<text class="t-mono" x="714" y="287" text-anchor="middle">1</text>
<text class="t-name" x="112" y="320" text-anchor="middle">Sister[x,y]</text>
<text class="t-name" x="270" y="320" text-anchor="middle">Parent[y,z]</text>
<text class="t-name" x="474" y="320" text-anchor="middle">计数：有几个 y</text>
<text class="t-name" x="678" y="320" text-anchor="middle">Aunt[x,z]</text>
<text class="t-say" x="32" y="352">每一格数的是“有几个 y 能把 x 和 z 连起来”。这家人每格最多 1 个，计数和结果看不出差别；</text>
<text class="t-say" x="32" y="374">若再加一条 Parent(妈妈, 表妹)，小姨→表妹 这格会数出 2，H 把它压回 1。</text>
</svg>
</div>
<figcaption>图 1 · 关系是 0/1 矩阵，“并且”是相乘，只在体里出现的变量被求和，阶跃函数把路径计数压回真假</figcaption>
</figure>

**关系就是布尔张量。** 一个二元关系 `Parent(y, z)` 等价于一个 0/1 矩阵，三元关系就是一个三维的 0/1 数组。论文把这句话反过来说：关系是稀疏布尔张量的一种**紧凑写法**，只列出值为 1 的那些格子。

**体里的逗号是乘法，共享的变量是共享的下标。** 两个 0/1 数相乘，只有都为 1 时才是 1，正好是“并且”。`Sister(x, y)` 和 `Parent(y, z)` 用的是同一个 y，对应到 einsum 里就是两边用同一个下标 y。数据库里把这叫**连接**（join）：把两张表里 y 相同的行拼起来。

**只在体里出现的变量被求和。** 头里只有 x 和 z，y 没出现，einsum 就把 y 求和掉。数据库里这叫**投影**（projection）：把不需要的列扔掉。逻辑上，它对应的是“存在某个 y”。

**最后套一个阶跃函数。** 求和得到的是“有几个 y 能连起来”，可能大于 1；比如表妹若同时是妈妈和舅舅的孩子，小姨就能经由两条路成为她的姨妈，这一格会数出 2[^notes]。逻辑只关心真假，所以论文在外面套了一个阶跃函数 $H(x)$：大于 0 就是 1，否则是 0。写成公式就是：

$$
\text{Aunt}_{xz} = H\Big(\sum_y \text{Sister}_{xy}\,\text{Parent}_{yz}\Big)
$$

这就是论文的核心观察：**一条 Datalog 规则，等于在布尔张量上做一次 einsum，再逐元素过一个阶跃函数**。体里有几项就是几个张量相乘，关系有几元就是几维；对应关系对任意多项、任意元数都成立。

### 递归规则：反复做到不动点

再看一条递归的规则，祖先：父母是祖先，祖先的孩子的祖先也是祖先。

```prolog
Ancestor(x, y) :- Parent(x, y).
Ancestor(x, z) :- Ancestor(x, y), Parent(y, z).
```

Datalog 的标准算法叫**前向链**：把所有规则拿到已知事实上反复用，推出新事实，加进去，再用，直到一轮下来什么新东西都推不出为止。这个“推不出新东西”的状态叫**不动点**。换成矩阵，就是反复算 $A \leftarrow H(A + A P)$，直到 $A$ 不再变化。在那个七口之家上，第一轮新增了外婆是“我”、表妹、表弟的祖先这三条，第二轮什么也没新增，停下[^notes]。

到这里，“规则就是 einsum”是站得住的。而 einsum 本来就是神经网络里最常用的运算：全连接层、卷积、注意力，几乎都能写成 einsum[^einsum]。那么，**能不能干脆只保留这一种语句，用它把两边都写出来？**

## 二、只有一种语句的语言

### 张量方程

论文的回答是能，并且把这当成整门语言的定义。张量逻辑程序就是一组**张量方程**，每条方程长这样：

```text
Aunt[x, z] = H(Sister[x, y] Parent[y, z])
```

左边是要算的张量，右边是几个张量的连接，加一个可选的逐元素非线性函数（阶跃、sigmoid、ReLU 都行）。规则只有这么几条：右边有、左边没有的下标被求和；没算出来的元素默认是 0；左边相同的几条方程结果相加（这正好对应 Datalog 里同名的几条规则取“或”）。用圆括号写的张量表示布尔张量，所以任何 Datalog 程序原样就是合法的张量逻辑程序[^tl]。

论文说这就是全部定义：**没有关键字，没有别的语句**。为了方便，可以加一些语法糖，比如下标里写 `t+1`、用 `max=` 代替默认的求和、`softmax`，但它们不增加表达能力。

### 现象：神经网络和图模型都写得进去

有了这一种语句，论文第 4 节把几类 AI 方法挨个写了一遍。挑几条放在一起看：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="tl-paradigm-title">几种 AI 方法写成张量方程（摘自论文第 3、4 节）</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="tl-paradigm-title">
<colgroup><col style="width:22%"><col><col style="width:28%"></colgroup>
<thead>
<tr><th scope="col">方法</th><th scope="col">张量方程</th><th scope="col">说明</th></tr>
</thead>
<tbody>
<tr><th scope="row">感知机</th><td class="mono">Y = step(W[i] X[i])</td><td>i 被求和，就是点积</td></tr>
<tr><th scope="row">多层感知机</th><td class="mono">X[i,j] = sig(W[i,j,k] X[i-1,k])</td><td>i 是层号，一条方程写完所有层</td></tr>
<tr><th scope="row">卷积层</th><td class="mono">F[x,y] = relu(K[dx,dy,ch] Img[x+dx,y+dy,ch])</td><td>下标里可以写加法</td></tr>
<tr><th scope="row">图神经网络聚合</th><td class="mono">Agg[n,l,d] = Neig(n,n') Z[n',l,d]</td><td>邻接关系是布尔张量，乘上就把非邻居清零</td></tr>
<tr><th scope="row">注意力</th><td class="mono">Comp[p,p'.] = softmax(Q[p,dk] K[p',dk] / sqrt(Dk))<br>Attn[p,dv] = Comp[p,p'] V[p',dv]</td><td>整个 Transformer 约十几条方程</td></tr>
<tr><th scope="row">核机器</th><td class="mono">Y[Q] = f(A[i] Y[i] K[Q,i] + B)</td><td>核函数另写一条方程</td></tr>
<tr><th scope="row">贝叶斯网络</th><td class="mono">P_X[x] = CPT[x,p1,p2] P_1[p1] P_2[p2]</td><td>边缘化是投影，因子相乘是连接</td></tr>
<tr><th scope="row" class="acc">Datalog 规则</th><td class="mono acc">Aunt(x,z) = Sister(x,y) Parent(y,z)</td><td>布尔张量，原样可用</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">论文还用带“虚拟下标”的 RNN 推出张量逻辑是图灵完备的。</p>
</div>
<figcaption>图 2 · 神经网络、核方法、概率图模型和逻辑规则，写出来都是同一种“连接 + 求和 + 非线性”</figcaption>
</figure>

这张表里，左边那一列来自完全不同的几个学派，右边的写法却是同一个模子。

### 推理和学习也只有一套

**推理**有两种方式，都是从 Datalog 搬过来的。前向链把方程当成一段顺序代码，挨个执行，能算的元素都算出来，重复到不动点或满足停止条件；后向链把每条方程当成函数，从要问的那个张量出发，递归地去调右边那些张量的方程，只算用得到的元素。

**学习**靠求导。因为只有一种语句，求导规则也只有一条：不看非线性，方程左边对右边某个张量的导数，就是右边**其余**张量的乘积。比如 $Y_{ij} = M_{ik} X_{kj}$，$\partial Y_{ij}/\partial M_{ik} = X_{kj}$。所以一个张量逻辑程序的梯度，本身也是一个张量逻辑程序。第六节学规则时，我们会用数值差分核对这一点。

### 第一次尝试的疑问：这不就是 einsum 吗

读到这里，很自然会有一个疑问：PyTorch 和 numpy 里早就有 `einsum`，上面那张表的每一行都能直接用它写出来，张量逻辑多了什么？

论文的回答是，Python 的张量库只管求导和 GPU，不管推理和知识；逻辑语言只管推理，不管学习和扩展。张量逻辑想做的是让同一个程序里，**布尔的事实和规则、实数的权重和嵌入可以混在一起写、一起推理、一起求导**。

可是反过来想，既然 einsum 在每个深度学习框架里都有，为什么没人拿它来跑 Datalog？**用矩阵跑规则，代价在哪里？**

## 三、稀疏关系的代价

### 现象：结果一样，速度差了上千倍

我们生成一个随机家谱：N 个人，每人平均有一个更早出生的父母，然后用两种办法算出全部祖先关系。一种是 Datalog 引擎常用的**半朴素求值**：存一个事实集合，每一轮只拿上一轮新推出的事实去连接；另一种是上面的稠密矩阵写法，每一轮做一次 N×N 的矩阵乘法。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="tl-dense-title">随机家谱上求全部祖先关系：集合求值 vs 稠密矩阵</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="tl-dense-title">
<colgroup><col><col><col><col><col class="hl"><col></colgroup>
<thead>
<tr><th scope="col">人数 N</th><th scope="col">父子事实</th><th scope="col">推出祖先</th><th scope="col">集合求值</th><th scope="col" class="acc">稠密矩阵</th><th scope="col">矩阵里非零的比例</th></tr>
</thead>
<tbody>
<tr><th scope="row" class="num">500</th><td class="num">509</td><td class="num">4,133</td><td class="num">6.4 ms</td><td class="num acc">189 ms</td><td class="num">1.65%</td></tr>
<tr><th scope="row" class="num">1,000</th><td class="num">935</td><td class="num">6,159</td><td class="num">3.6 ms</td><td class="num acc">407 ms</td><td class="num">0.62%</td></tr>
<tr><th scope="row" class="num">2,000</th><td class="num">1,980</td><td class="num">16,613</td><td class="num">11.8 ms</td><td class="num acc">2.7 s</td><td class="num">0.42%</td></tr>
<tr><th scope="row" class="num">4,000</th><td class="num">4,049</td><td class="num">45,555</td><td class="num">27.6 ms</td><td class="num acc">24.4 s</td><td class="num">0.28%</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">两种办法推出的祖先关系逐条比对完全一致。迭代轮数相同（4000 人时 40 轮）。numpy float32，4 核 CPU。</p>
</div>
<figcaption>图 3 · 结果完全一样，但矩阵里 99% 以上是 0，稠密写法把时间花在了乘 0 上</figcaption>
</figure>

### 原因：矩阵要为不存在的事实付钱

原因就在最后一列。4000 个人的祖先关系有 4.5 万条，可 4000×4000 的矩阵有 1600 万个格子，99.7% 是 0。集合求值只碰真实存在的事实，事实多一倍，时间大约多一倍；矩阵乘法每轮要做 $N^3$ 次乘加，人数翻倍，每轮就慢 8 倍，加上轮数也在增加，总时间涨了约 9 倍。

换到 GPU 上，常数会小很多，但阶数不变。更麻烦的是维数：一个三元关系是 $N^3$ 个格子，论文自己也说，用元组列表代替稠密张量，省下的空间随元数指数增长。所以用稠密 einsum 跑规则，在“几个人的家谱”上很优雅，在真实的知识库上走不通。

### 论文给的两条路

论文第 6 节承认了这一点，给了两条路[^tl]。

第一条是**分工**：稠密的部分交给 GPU，稀疏的部分当成关系交给数据库查询引擎，用上数据库几十年的查询优化。这条路靠得住，但它其实就是今天大家在做的事：向量算在框架里，关系存在数据库里，中间写胶水代码。张量逻辑在这里的作用，是把两边写成同一种记号。

第二条是论文更看好的：**把稀疏张量分解成稠密的小张量，全部放到 GPU 上算**。论文说这比直接操作稀疏张量“指数级更高效”，而且随机的分解就够用，代价是有一点出错的概率。这条路通向论文第 5 节，也是论文自己说最有意思的部分：在嵌入空间里推理。**把关系压进嵌入以后，推出来的东西还对吗？**

## 四、在嵌入空间里推理

### 先从一个集合开始

**嵌入**就是给每个对象分配一个 D 维向量。论文先考虑最简单的情况：每个对象一个随机的单位向量。D 足够大时，两个随机单位向量几乎正交，点积在 0 附近，标准差约 $1/\sqrt{D}$；一个向量和自己的点积是 1。

想存一个集合，比如“哪些人喜欢猫”，就把集合里每个人的向量加起来，得到一个向量 $S$。问某人 A 在不在集合里，算 $S \cdot e_A$：A 在里面，结果约等于 1；不在，约等于 0。噪声来自集合里另外 N 个向量和 $e_A$ 的点积，标准差约 $\sqrt{N/D}$。取 ½ 当阈值，就能判断在不在，出错的概率随 D 增大而减小。论文把它比作 **Bloom filter**：一种用固定大小的位数组判断“是否在集合里”的数据结构，可能误报，不会漏报[^bloom]。

我们用 5000 个对象试了一下，集合外得分的标准差和 $\sqrt{N/D}$ 吻合得很好：D=1024、N=50 时实测 0.211，理论 0.221；这时错判率 0.92%。集合放到 200 个，错判率涨到 12%[^notes]。

### 再存一个关系

同样的办法可以存关系。一条事实 `Parent(Bob, Carl)` 用两个向量的外积 $e_{Bob} \otimes e_{Carl}$ 表示，得到一个 D×D 的矩阵；把所有事实的外积加起来，就是整个关系的嵌入：

$$
\text{EmbR}_{ij} = \sum_{(x,y) \in R} e_{x,i}\, e_{y,j}
$$

这是 Smolensky 在 1990 年提出的**张量积表示**[^smolensky]。查询 `Parent(A, B)` 时，用 $e_A$ 和 $e_B$ 从两边各点一次：$D_{AB} = e_A^\top\, \text{EmbR}\, e_B$。展开后，每条事实贡献 $(e_x \cdot e_A)(e_y \cdot e_B)$，只有 $x=A$、$y=B$ 那条贡献约 1，其余都是接近 0 的噪声。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="tl-emb-title tl-emb-desc">
<title id="tl-emb-title">把关系叠加进一个 D×D 矩阵，再用两次点积取回</title>
<desc id="tl-emb-desc">左边是存：1000 条 Parent 事实，每条是两个 256 维随机向量的外积，全部加成一个 256×256 的 EmbR。右边是取：对 2000 个对象的 400 万个候选对算 e_A·EmbR·e_B，不在关系里的对得分集中在 0 附近，标准差 0.139；真事实集中在 1 附近。阈值 0.5 处有一条虚线，越过它的假事实约 1122 条，比真事实还多。</desc>
<defs>
<marker id="tl-emb-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="tl-emb-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="360" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="376" height="328" rx="8"/>
<rect class="dd-zone" x="408" y="16" width="376" height="328" rx="8"/>
<text class="t-zone" x="32" y="44">存 · 每条事实一个外积，叠成一块</text>
<text class="t-zone" x="424" y="44">取 · 两次点积，得分 = 信号 + 噪声</text>
<rect class="dd-card" x="32" y="64" width="344" height="136" rx="6"/>
<path class="dd-card-head" d="M32 70 a6 6 0 0 1 6 -6 h332 a6 6 0 0 1 6 6 v26 h-344 z"/>
<text class="t-name" x="48" y="85">EmbR[i,j]</text>
<text class="t-mono" x="360" y="85" text-anchor="end">256 × 256</text>
<text class="t-mono" x="48" y="117">+ e_Bob ⊗ e_Carl</text>
<text class="t-note" x="360" y="117" text-anchor="end">Parent(Bob, Carl)</text>
<line class="dd-hair" x1="32" y1="126" x2="376" y2="126"/>
<text class="t-mono" x="48" y="143">+ e_Ann ⊗ e_Dan</text>
<text class="t-note" x="360" y="143" text-anchor="end">Parent(Ann, Dan)</text>
<line class="dd-hair" x1="32" y1="152" x2="376" y2="152"/>
<text class="t-mono" x="48" y="169">+ …</text>
<text class="t-note" x="360" y="169" text-anchor="end">共 1000 条事实</text>
<line class="dd-hair" x1="32" y1="178" x2="376" y2="178"/>
<text class="t-val" x="48" y="194">65,536 个实数</text>
<text class="t-say" x="32" y="232">对象一共 2000 个，向量是随机的单位向量，</text>
<text class="t-say" x="32" y="254">两两点积在 0 附近抖动，标准差约 1/√256。</text>
<text class="t-say" x="32" y="292">存同样 1000 条事实，元组列表只要 2000 个整数；</text>
<text class="t-say" x="32" y="314">稠密的 2000×2000 矩阵要 400 万格。</text>
<text class="t-mono" x="424" y="76">D[A,B] = e_A · EmbR · e_B</text>
<text class="t-note" x="424" y="98">400 万个候选对，每对一个得分</text>
<line class="dd-edge" x1="432" y1="280" x2="760" y2="280"/>
<path class="dd-edge" d="M432 280 L440 279 L448 278 L457 274 L465 266 L473 252 L481 230 L489 202 L498 172 L506 149 L514 140 L522 149 L530 172 L539 202 L547 230 L555 252 L563 266 L571 274 L580 278 L588 279 L596 280"/>
<path class="dd-edge" d="M604 280 L612 279 L621 277 L629 274 L637 268 L645 259 L653 246 L662 234 L670 224 L678 220 L686 224 L694 234 L703 246 L711 259 L719 268 L727 274 L735 277 L744 279 L752 280 L760 280"/>
<line class="dd-edge-acc dd-dash" x1="596" y1="120" x2="596" y2="288"/>
<text class="t-mono" x="514" y="302" text-anchor="middle">0</text>
<text class="t-acc t-mono" x="596" y="302" text-anchor="middle">0.5</text>
<text class="t-mono" x="678" y="302" text-anchor="middle">1</text>
<text class="t-note" x="514" y="130" text-anchor="middle">不在关系里：std 0.139</text>
<text class="t-note" x="690" y="210" text-anchor="middle">1000 条真事实</text>
<text class="t-acc t-say" x="606" y="134">越过 0.5 的假事实 ≈ 1122 条</text>
<text class="t-say" x="424" y="326">两条曲线各自缩放；左边那条的面积是右边的 4000 倍。</text>
</svg>
</div>
<figcaption>图 4 · 误判率只有 0.028%，可分母是 400 万个候选对，冒出来的假事实比真事实还多</figcaption>
</figure>

### 现象：误判率很低，假事实却很多

我们在 2000 个对象上扫了一遍维度 D 和事实数 |R|，阈值取 ½：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="tl-embr-title">关系叠加的错判（2000 个对象，400 万个候选对）</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="tl-embr-title">
<colgroup><col><col><col><col><col><col class="hl"></colgroup>
<thead>
<tr><th scope="col">维度 D</th><th scope="col">D²</th><th scope="col">事实数 |R|</th><th scope="col">漏判</th><th scope="col">误判率</th><th scope="col" class="acc">误判条数</th></tr>
</thead>
<tbody>
<tr><th scope="row" class="num">64</th><td class="num">4,096</td><td class="num">100</td><td class="num good">0%</td><td class="num">0.148%</td><td class="num acc">≈ 5,932</td></tr>
<tr><th scope="row" class="num">128</th><td class="num">16,384</td><td class="num">100</td><td class="num good">0%</td><td class="num good">0.000%</td><td class="num acc">≈ 5</td></tr>
<tr><th scope="row" class="num">128</th><td class="num">16,384</td><td class="num">1,000</td><td class="num warn">3.8%</td><td class="num">2.79%</td><td class="num acc">≈ 111,701</td></tr>
<tr><th scope="row" class="num">256</th><td class="num">65,536</td><td class="num">1,000</td><td class="num good">0.10%</td><td class="num">0.028%</td><td class="num acc">≈ 1,122</td></tr>
<tr><th scope="row" class="num">256</th><td class="num">65,536</td><td class="num">4,000</td><td class="num warn">3.5%</td><td class="num">3.57%</td><td class="num acc">≈ 142,438</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">误判条数 = 误判率 × 不在关系里的候选对数。</p>
</div>
<figcaption>图 5 · 要让假事实少于真事实，D² 得比事实数大两个数量级以上</figcaption>
</figure>

看百分比，D=256 存 1000 条事实只误判 0.028%，好像很准了。可换成条数就是另一回事：**多出来约 1122 条假事实，比 1000 条真事实还多**。逻辑推理里，一条假事实接着往下推，可能带出一串假结论。

问题出在分母上。这个误差和 Bloom filter 一样，单次查询出错的概率小，但“把整个关系取回来”要查 $N^2$ 次。实验里要让假事实降到个位数，D² 大约要比事实数大两个数量级（D=128 存 100 条，误判约 5 条）。而 D×D 的稠密矩阵本身并不比元组列表省地方：存 1000 条事实，列表是 2000 个整数，D=256 的 EmbR 是 6.5 万个实数。嵌入真正省下的是相对 $N^2$ 的稠密矩阵，以及“全部在 GPU 上做稠密运算”这件事。

### 规则在嵌入空间里连着用

规则也能搬进嵌入空间。`Ancestor(x, z) :- Ancestor(x, y), Parent(y, z)` 里，共享变量 y 在嵌入空间里变成共享的下标，两个关系嵌入直接做矩阵乘法：$\text{EmbAnc}' = \text{EmbAnc} \cdot \text{EmbP}$。展开后，中间多出来的是 $e_y \cdot e_{y'}$ 这一项，y 和 y′ 相同时约为 1，不同时约为 0，所以近似成立。

每多连一次，就多一层近似。我们用 1000 个对象、611 条父子事实（含一条 12 代的长链），D=512，算“恰好 k 代”的祖先关系，看错判怎么随跳数变化。论文还建议了一个降噪办法：每推一步就把结果取出来、按 ½ 阈值化、再重新嵌入。两种做法都试了：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="tl-hop-title">嵌入空间里连乘 k 次 Parent（D=512，100 万个候选对）</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="tl-hop-title">
<colgroup><col style="width:14%"><col style="width:16%"><col><col><col class="hl"><col class="hl"></colgroup>
<thead>
<tr><th scope="col" rowspan="2">跳数</th><th scope="col" rowspan="2">真事实</th><th scope="colgroup" colspan="2">直接连乘，最后读出</th><th scope="colgroup" colspan="2" class="acc">每步阈值化再重嵌</th></tr>
<tr><th scope="col">漏判</th><th scope="col">误判</th><th scope="col">漏判</th><th scope="col">误判</th></tr>
</thead>
<tbody>
<tr><th scope="row" class="num">1</th><td class="num">611</td><td class="num good">0%</td><td class="num good">0</td><td class="num good">0%</td><td class="num good">0</td></tr>
<tr><th scope="row" class="num">2</th><td class="num">377</td><td class="num">0.3%</td><td class="num">78</td><td class="num">0.3%</td><td class="num">78</td></tr>
<tr><th scope="row" class="num">3</th><td class="num">287</td><td class="num">0.3%</td><td class="num">1,351</td><td class="num">0.7%</td><td class="num good">340</td></tr>
<tr><th scope="row" class="num">4</th><td class="num">213</td><td class="num">4.2%</td><td class="num warn">7,376</td><td class="num good">0.5%</td><td class="num good">1,780</td></tr>
<tr><th scope="row" class="num">5</th><td class="num">162</td><td class="num warn">10.5%</td><td class="num bad">25,163</td><td class="num">4.9%</td><td class="num bad">26,592</td></tr>
<tr><th scope="row" class="num">6</th><td class="num">136</td><td class="num bad">22.1%</td><td class="num bad">61,932</td><td class="num bad">17.6%</td><td class="num bad">337,792</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">D=128 时，直接连乘第 2 跳就有约 16 万条误判。</p>
</div>
<figcaption>图 6 · 噪声随跳数累积；每步阈值化在前几跳有用，但混进去的假事实一旦被当成真的重新嵌入，就会扩散</figcaption>
</figure>

直接连乘时，第 1 跳完全正确，到第 6 跳漏掉了 22% 的真事实，还多出 6 万多条假的。每步阈值化在第 3、4 跳确实把误判压了下来，但到第 6 跳反而更糟：早期混进去的少量假事实被当成真事实重新嵌入，下一跳从它们出发又推出一批，越滚越多。

所以论文说的“出错概率随嵌入维度下降”是对的，但要读全：**维度要跟着事实数、对象数和推理深度一起涨**，降噪也只能延缓误差累积，挡不住它。随机嵌入下的“可靠”，是一个有代价的概率保证。

不过随机嵌入只是第一步，它和查表没有本质区别。论文真正想要的是**学出来的嵌入**：相似的对象向量相近，可以互相“借”结论。**这时候，论文说的“不会幻觉”还成立吗？**

## 五、温度与“不会幻觉”

### 论文的主张

嵌入是学出来的时候，$\text{Sim} = \text{Emb}\,\text{Emb}^\top$ 不再接近单位阵，而是一个衡量对象两两相似度的 **Gram 矩阵**。查询 `Parent(Rob, Carl)` 时，即使库里只有 `Parent(Bob, Carl)`，只要 Rob 和 Bob 的向量相近，也会得到一个不小的分数。论文把这叫**类比推理**：相似的对象按相似程度分享推论。

接着论文在每条方程外面套一个带**温度** T 的 sigmoid：$\sigma(x, T) = 1/(1+e^{-x/T})$。T 越大，曲线越平缓；T 趋于 0，曲线变成阶跃。论文的说法是：T 设为 0，等于把 Gram 矩阵变成单位阵，推理就成了纯演绎；“这和 LLM 不同，LLM 即使在 T=0 时也会幻觉”。T 调高，推理就越来越偏类比；每条规则还可以有自己的 T，数学定理用 0，积累弱证据的规则用高温[^tl]。

### 现象：T 趋于 0，相似的对象照样“借”到结论

我们用一个最小的例子检查。D=256，库里只存一条事实 `Parent(Bob, Carl)`。另造三个对象：Rob 和 Bob 余弦相似度 0.8（像学出来的近义对象），Bobby 是 0.4，Alice 和 Zed 是无关的随机向量。原始得分分别是：Bob 1.0、Rob 0.8、Bobby 0.4，无关的在 ±0.02 附近。再把得分过 sigmoid，看 T 变小时会怎样：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="tl-temp-title">只存 Parent(Bob, Carl)，查询结果随温度 T 的变化</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="tl-temp-title">
<colgroup><col style="width:24%"><col><col class="hl"><col><col></colgroup>
<thead>
<tr><th scope="col">查询</th><th scope="col">Bob → Carl</th><th scope="col" class="acc">Rob → Carl</th><th scope="col">Bobby → Carl</th><th scope="col">Zed → Alice</th></tr>
</thead>
<tbody>
<tr><th scope="row">原始得分</th><td class="num">1.000</td><td class="num acc">0.800</td><td class="num">0.400</td><td class="num">0.005</td></tr>
<tr class="dt-group"><th colspan="5" scope="rowgroup">论文原式 σ(x/T)，阈值在 0</th></tr>
<tr><th scope="row">T = 0.3</th><td class="num">0.966</td><td class="num acc">0.935</td><td class="num">0.791</td><td class="num">0.504</td></tr>
<tr><th scope="row">T = 0.0001</th><td class="num">1</td><td class="num acc bad">1</td><td class="num bad">1</td><td class="num bad">1</td></tr>
<tr class="dt-group"><th colspan="5" scope="rowgroup">阈值挪到 ½：σ((x − ½)/T)</th></tr>
<tr><th scope="row">T = 0.3</th><td class="num">0.841</td><td class="num acc">0.731</td><td class="num">0.417</td><td class="num">0.161</td></tr>
<tr><th scope="row">T = 0.0001</th><td class="num">1</td><td class="num acc bad">1</td><td class="num good">0</td><td class="num good">0</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">按原式，2000 个无关对象在 T=0.0001 时有 50.5% 被判成 Carl 的父母；阈值挪到 ½ 后是 0%。</p>
</div>
<figcaption>图 7 · 温度只决定曲线有多陡，决定真假的是阈值和嵌入之间的相似度</figcaption>
</figure>

先看论文原式。阈值在 0，T 趋于 0 时得分只要大于 0 就判真，而无关对象的得分是围着 0 抖动的噪声，于是**一半无关对象都被判成了 Carl 的父母**。这显然不是论文本意，应该理解成方程里还有一个偏置，把阈值放在 ½，就像第四节的 Bloom filter 那样。

阈值挪到 ½ 以后，T 趋于 0 确实把弱相似（Bobby）和噪声都清掉了，但 **Rob 仍然是 1**。原因很简单：Rob 的原始得分是 0.8，已经越过了阈值，曲线再陡也只会把它推向 1，不会推向 0。

### 原因：演绎和类比的冲突在嵌入里，不在温度里

所以“T=0 让 Gram 矩阵变成单位阵”需要一个前提：**任意两个不同对象的相似度都低于阈值**。这在随机嵌入下成立（这就是第四节），而学出来的嵌入恰恰是要让相似的对象靠近，余弦 0.8 很平常。下面这段是我的推断，论文没有讨论：想要同一套嵌入既能类比又能在需要时严格演绎，可能得给演绎用的规则单独一套近乎正交的嵌入，或者在读出时把“是不是同一个对象”和“像不像”分开判断，光调 T 做不到。

再说和 LLM 的比较。张量逻辑的推理过程里，每一步推出的张量都能取出来看，这一点确实比 LLM 的推理过程透明。但“T=0 不会幻觉”成立的那个区间，正好是随机嵌入、不做类比的区间，而在那个区间里，它仍然有第四节看到的、随维度和推理深度变化的误判。把它说成“免疫幻觉”，是把一个概率保证说成了绝对保证。

前面讨论的都是事实和嵌入怎么存、怎么推。张量逻辑另一半的卖点是学习：**规则本身能不能也学出来？**

## 六、规则的学习

### 归纳逻辑编程与一条带权重的方程

从数据里学出逻辑规则，有一个专门的领域，叫**归纳逻辑编程**（ILP）。它难在搜索：可能的规则组合是天文数字。论文的思路是，把“候选规则”写成一条带权重的张量方程，让梯度下降来挑。论文原话是，一条方程可以表示**连接结构相同的所有规则**。

我们照这个思路，在家庭数据上学“姑姨”关系。候选方程是：

```text
Aunt[x, z] = sig(W[r, s] Rel[r, x, y] Rel[s, y, z] + b)
```

`Rel` 把四种基本关系（Parent、Sister、Brother、Spouse）叠成一个三维张量，r、s 取遍这四种。所以这一条方程同时代表了 16 条形如 `Aunt(x, z) :- r(x, y), s(y, z)` 的候选规则，`W[r, s]` 就是每条候选规则的权重。

### 现象：权重自己挑出了正确的规则

随机生成 40 个家庭（452 人，195 条真的姑姨关系）训练，20 个新家庭（232 人，84 条）测试。训练完的权重是这样的：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="tl-learn-title">学到的 W[r, s]（行是第一段关系 r，列是第二段关系 s，偏置 b = −10.1）</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="tl-learn-title">
<colgroup><col style="width:20%"><col class="hl"><col><col><col></colgroup>
<thead>
<tr><th scope="col">r ＼ s</th><th scope="col" class="acc">Parent</th><th scope="col">Sister</th><th scope="col">Brother</th><th scope="col">Spouse</th></tr>
</thead>
<tbody>
<tr><th scope="row">Parent</th><td class="num">−0.06</td><td class="num">−0.04</td><td class="num">−0.03</td><td class="num">−0.04</td></tr>
<tr><th scope="row" class="acc">Sister</th><td class="num acc hl">20.60</td><td class="num">−0.01</td><td class="num">−0.04</td><td class="num">−0.03</td></tr>
<tr><th scope="row">Brother</th><td class="num">−0.02</td><td class="num">−0.02</td><td class="num">−0.03</td><td class="num">−0.02</td></tr>
<tr><th scope="row">Spouse</th><td class="num">−0.08</td><td class="num">−0.01</td><td class="num">−0.01</td><td class="num">−0.03</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">阈值化（W + b &gt; 0）后只剩 Aunt(x,z) :- Sister(x,y), Parent(y,z)。新家庭上 84 条全中，误判 0。</p>
</div>
<figcaption>图 8 · 16 条候选规则同时训练，梯度把权重集中到了唯一正确的那一条上</figcaption>
</figure>

16 个权重里，只有 `W[Sister, Parent]` 长到了 20.6，其余都在 0 附近。阈值化以后，留下的正是 `Aunt(x, z) :- Sister(x, y), Parent(y, z)`，拿到没见过的新家庭上 84 条全中，没有误判。学出来的不是一个黑盒，而是一条能读、能拿去推理任何家庭的规则。

顺便核对第二节说的求导规则。损失对 `W[r, s]` 的梯度写出来也是一个 einsum：$\partial L/\partial W_{rs} = \sum G_{xz}\,\text{Rel}_{rxy}\,\text{Rel}_{syz}$，其中 $G$ 是损失对 sigmoid 输入的导数。和数值差分比，`W[Sister, Parent]` 这一格是 −2.742338e-05 对 −2.742324e-05，前五位一致[^notes]。

### 不足：权重能学，结构要先写对

真实的“姑姨”还包括姻亲：舅舅的妻子是舅妈，叔叔的妻子是婶婶。这类关系是三段连接，`Spouse(x, w), Brother(w, y), Parent(y, z)`。我们把舅妈也加进训练目标（训练集里 134 条），候选方程仍然只有两段，再训一次：模型还是只学出了 `Sister∘Parent`，新家庭上 80 条舅妈全部漏掉。

这不是训练不够，而是**两段连接的方程里根本没有三段的规则**，权重怎么调都补不上。论文那句“一条方程表示连接结构相同的所有规则”，反过来读就是：连接结构得由人先写好。ILP 最难的那部分，正是在不同的结构之间搜索。论文提到可以用张量分解来“发明”新的中间谓词（比如自动发现“兄弟姐妹”这个概念），这是一个有意思的方向，但论文没有给出实验，我们也没有验证。

到这里，论文的几个主张都摸了一遍。最后还剩一个问题：**这些想法里，哪些是新的？**

## 七、这篇论文的位置

### 这个观察并不新

“规则就是矩阵乘法加阈值”这个观察，在 2013 到 2017 年之间就有好几个人写过，论文几乎都没有引用：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="tl-prior-title">和张量逻辑相关的前人工作</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="tl-prior-title">
<colgroup><col style="width:12%"><col style="width:30%"><col></colgroup>
<thead>
<tr><th scope="col">年份</th><th scope="col">工作</th><th scope="col">和本文的关系</th></tr>
</thead>
<tbody>
<tr><th scope="row" class="num">1990</th><td>Smolensky，张量积表示<sup>引</sup></td><td>第四节关系嵌入的来源，论文引用了</td></tr>
<tr><th scope="row" class="num">2011</th><td>Nickel 等，RESCAL</td><td>每个关系一个矩阵，三元组打分 e_hᵀ M_r e_t，就是嵌入空间里的关系</td></tr>
<tr><th scope="row" class="num">2013</th><td>Grefenstette，用张量模拟逻辑演算</td><td>把谓词逻辑的真值计算写成张量运算</td></tr>
<tr><th scope="row" class="num acc">2016</th><td class="acc">Cohen，TensorLog</td><td>把一类概率 Datalog 编译成可微的矩阵运算，交给 TensorFlow 训练；名字几乎一样</td></tr>
<tr><th scope="row" class="num acc">2016</th><td class="acc">Sato，线性代数方法求值 Datalog</td><td>矩阵乘法加阈值算传递闭包，就是第一节的 Ancestor</td></tr>
<tr><th scope="row" class="num">2017</th><td>Rocktäschel 与 Riedel，神经定理证明器</td><td>用嵌入相似度代替符号匹配，第五节的“类比推理”</td></tr>
<tr><th scope="row" class="num">2026</th><td>Shah 与 Zadrozny，第三方复现</td><td>按论文实现并在家谱和 FB15k-237 上做了实验</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">“引”表示 Domingos 的论文引用了这项工作。</p>
</div>
<figcaption>图 9 · 规则与张量运算的对应、关系嵌入、软匹配，都早有人做过</figcaption>
</figure>

具体来说，Smolensky 1990 年就用外积叠加表示符号结构[^smolensky]；RESCAL 用每个关系一个矩阵打分[^rescal]；Grefenstette 2013 年把逻辑演算写成张量运算[^grefenstette]；Cohen 2016 年的 TensorLog 把 Datalog 编译成可微的矩阵运算[^tensorlog]；Sato 同年用矩阵乘法加阈值求值 Datalog[^sato]；神经定理证明器用嵌入相似度做软匹配[^ntp]。

### 新的是什么，缺的是什么

论文新的地方，是把这个对应关系推到了极致：**张量方程是整门语言唯一的语句**，并且把神经网络、核方法、概率图模型、逻辑程序都写成这一种形式；再加上第五节“学出来的嵌入 + 温度”这个统一演绎与类比的设想。作为一种看问题的视角，这很有启发：GNN 的聚合就是“邻居关系”和特征的连接，贝叶斯网络的边缘化就是投影，读完再看这些模型，确实会觉得它们是一家人。

缺的东西也很明显。论文 17 页，没有图，**没有一个实验**；项目网站上只有论文、幻灯片和访谈，截至本文写作时没有官方实现[^tl-site]。语言层面也有不少空白，比如 Datalog 里很重要的否定（“x 没有孩子”）怎么写、递归和否定混在一起时怎么分层，论文都没讲。

目前能看到的实验来自第三方。2026 年 1 月 Shah 和 Zadrozny 按论文实现了一版：在 1972 人的圣经家谱上，用矩阵乘法加阶跃 74 轮算出 33945 条祖先关系；在知识图谱数据集 FB15k-237 上，按论文的关系矩阵写法做链接预测，MRR 0.3068，去掉直连边后的组合推理 MRR 0.3346[^impl][^impl-code]。前者验证的正是第一节的对应关系，后者是一个合格但不算突出的知识图谱嵌入结果。“嵌入空间里可靠推理”“治好 LLM 的幻觉”这些更大的主张，还没有实验支撑。

## 小结

1. **逻辑规则和 einsum 是同一种运算吗？** 在布尔张量上是。关系是 0/1 张量，体里的“并且”是相乘，共享变量是共享下标，只在体里出现的变量被求和，最后用阶跃函数把路径计数压回真假。递归规则就是反复做到不动点。
2. **只有一种语句的语言长什么样？** 一组张量方程。感知机、卷积、注意力、核机器、贝叶斯网络和 Datalog 都能写进去，推理是前向链和后向链，梯度也是张量方程。
3. **直接用稠密矩阵跑规则行不行？** 结果对，但稀疏关系里 99% 以上是 0。4000 人的家谱，集合求值 28 ms，稠密矩阵 24 s。论文的出路是交给数据库，或者压进嵌入。
4. **压进嵌入以后推理还对吗？** 近似对。误差随维度下降，但要和事实数、对象数、推理深度一起看：D=256 存 1000 条事实，多出约 1122 条假事实；连乘 6 跳，漏判 22%。每步阈值化只能延缓累积。
5. **温度调到 0 就不会幻觉吗？** 不能这么说。温度只管曲线陡不陡；相似度 0.8 的对象在任何温度下都会“借”到结论。纯演绎要求不同对象的相似度都低于阈值，这和学出来的嵌入的目的相冲突。
6. **规则能学出来吗？** 在给定的连接结构里能：16 条候选规则同时训练，权重集中到了正确的 `Sister∘Parent`，新家庭全中。结构之外的规则（三段的舅妈）学不出来。
7. **哪些是新的？** 对应关系本身 2016 年前后就有人写过；新的是把它当作唯一的语言构造、统一几大范式，以及嵌入空间推理的设想。论文没有实验和实现，更大的主张还有待验证。

本文所有实验都是几十行 numpy，可以在调研笔记目录里找到[^notes]。想自己体会一下，最短的版本就是这几行：

```python
import numpy as np
rng = np.random.default_rng(0)
N, D = 2000, 256
E = rng.standard_normal((N, D)); E /= np.linalg.norm(E, axis=1, keepdims=True)

R = np.zeros((N, N)); R[rng.integers(0, N, 1000), rng.integers(0, N, 1000)] = 1
EmbR = E.T @ R @ E            # EmbR[i,j] = R(x,y) Emb[x,i] Emb[y,j]
Score = E @ EmbR @ E.T        # D[x,y]   = EmbR[i,j] Emb[x,i] Emb[y,j]

print("真事实", int(R.sum()), "判真", int((Score > 0.5).sum()),
      "其中假的", int(((Score > 0.5) & (R == 0)).sum()))
# 真事实 1000 判真 2171 其中假的 1171
```

## 参考资料
