工作台课程

CS221 · 人工智能:原理与技术

Lecture 15 · 逻辑 I:命题逻辑

Lecture 15 · 逻辑 I:命题逻辑

CS221: Artificial Intelligence — Principles and Techniques · Stanford · Autumn 2025
📅 Nov 10 · 💻 propositional_logic.py


从概率推理到逻辑推理

上一单元(贝叶斯网络,Lecture 12 · 贝叶斯网络 I:建模与推断Lecture 14 · 贝叶斯网络 III:参数学习概率表示对世界的不确定信念:先用联合分布与条件独立建模,再做推断 $P(\text{query} \mid \text{evidence})$。概率的强项是处理不确定性——真实世界是嘈杂的、随机的,我们的观测也不完整。

本单元(逻辑推理) 换一种表示知识、进行推理的语言:逻辑(logic)。本讲先讲最简单的命题逻辑(propositional logic),下一讲 Lecture 16 · 逻辑 II:一阶逻辑 再升级到一阶逻辑。逻辑的强项不在处理随机性,而在表达力(expressivity)——用非常紧凑的公式表示复杂的知识,并支持精确的推理。

graph LR
    P["感知 Perceive"] --> R["推理 Reason"]
    R --> A["行动 Act"]
    A -.反馈.-> L["学习 Learn"]
    L -.改进.-> R

    style R fill:#ffe0b2,stroke:#e65100
    style L fill:#e1f5fe,stroke:#0277bd

一个引子:若 $A + B = 10$ 且 $A - B = 4$,问 $A$ 是多少?你当然不会穷举 $A, B$ 的所有取值去搜索,而是用代数:两式相加得 $2A = 14$,于是 $A = 7$。这就是符号推理 / 逻辑推理的原型——不逐个枚举可能的世界,而是操作符号,几步就把结论推出来。

提示

逻辑推理的威力在于:面对天文数字般的可能世界($n$ 个变量就有 $2^n$ 种组合),它不逐个检查,而是用少数几条符号变换规则直接把结论”算”出来。本讲的主线就是搞清楚:什么时候这种符号操作是合法的(可靠性),以及它能不能把所有真结论都推出来(完备性)。

历史小注:逻辑是 1990 年代之前 AI 的主导范式——专家系统、Prolog、定理证明都建立在它之上。它有两块短板:(1)它是确定性的,不处理不确定性——后来由概率图模型(本课的贝叶斯网络单元)来补;(2)它靠人手写规则,不利用数据——后来由机器学习(Lecture 1 · 张量、梯度与监督学习 开启的整条主线)来补。但它的长板至今无可替代:以极紧凑的方式提供强大的表达力精确推理,这也是 Z3 这类求解器今天仍在程序验证、芯片验证中大量使用的原因。

把逻辑当作一门语言,它有两个目标:表示(represent) 关于世界的知识,推理(reason) 推出新知识。


1. 逻辑系统的三要素

1.1 语法 / 语义 / 推理规则

任何一个逻辑系统都由三样东西定义——语法规定哪些符号串合法、语义赋予它们含义、推理规则在不看语义的前提下做纯符号操作:

要素 回答什么问题 命题逻辑中的具体内容
语法(syntax) 哪些表达式是合法的? 命题符号 + 联结词 $\neg\ \wedge\ \vee\ \to\ \leftrightarrow$ 组成的公式
语义(semantics) 这些表达式是什么意思 在每个模型(真值赋值)下公式为真/假
推理规则(inference rules) 如何推出新的合法公式? modus ponens、归结……(纯操作符号)

提示

为什么要把三层严格分开?打个编程的比方:语法像 parser(判断程序合不合法),语义像解释器(跑出程序的结果),推理规则像编译器的等价变换(不运行程序就改写它,且保证结果不变)。分开的收益是:语义计算(枚举所有模型)代价可能是指数级,而推理规则只做符号操作,可以快得多——前提是我们能证明这些符号操作忠于语义(第 8 节的可靠性与完备性)。

1.2 为什么不用自然语言?

因为自然语言太滑溜(slippery)了:同一个词在不同语境含义漂移,推理很容易翻车。

注意

  • 一分钱(penny)比什么都没有(nothing)好。
  • 什么都没有(nothing)比世界和平(world peace)好。
  • 所以……一分钱比世界和平好??

问题出在 “nothing” 在两句话里含义漂移:第一句是”空无一物”,第二句是”没有任何事物(比世界和平好)”。符号相同、语义不同,三段论就失效了。

例子

一角钱比五分钱好;五分钱比一分钱好;所以一角钱比一分钱好。这里 “better than” 全程含义一致,且具有传递性,推理才站得住。

形式语言(编程语言、逻辑语言)通过精确的语法和语义消除了这种歧义。命题逻辑就是最简单的一种形式逻辑语言。

1.3 语法 vs 语义:不要混淆

语法符号串本身,语义是它指向的含义,两者可以脱钩:

关系 例子
不同语法,相同语义 $2 + 3$ 和 $3 + 2$,语义都是 $5$
相同语法,不同语义 3 / 2 在 Python 2.7 里是 1,在 Python 3 里是 1.5

说明

语法管”怎么写”,语义管”什么意思”,推理规则管”怎么算”。学逻辑就是把这三层严格分开,再用解释函数(语法→语义)和可靠性/完备性(推理规则→语义)把它们缝起来。

源码骨架propositional_logic.py 用 Z3 的表达式类型作为底层):

from z3 import Bool, BoolRef, ExprRef, Not, And, Or, Implies, \
    is_not, is_and, is_or, is_implies, is_eq, Solver, sat
import itertools

# 三个核心类型别名
PropositionalSymbol = BoolRef            # 命题符号,如 Bool("Rain")
Formula = ExprRef                        # 公式(符号 + 联结词组成的表达式树)
Model = dict[PropositionalSymbol, bool]  # 模型 = 对所有符号的真值赋值

2. 命题逻辑:语法

2.1 公式的归纳定义

语法回答:哪些是合法公式(formula)?定义是归纳的:

  • 基础情形:命题符号(原子公式 / atomic formula)本身是公式;
  • 归纳情形:设 $f, g$ 为任意公式,则 $\neg f$、$f \wedge g$、$f \vee g$、$f \to g$、$f \leftrightarrow g$ 也是公式;
  • 封闭条款:除此之外一律不是公式(nothing else is a formula)。
# 基础情形:命题符号(原子公式)
P = Bool("P"); Rain = Bool("Rain"); Wet = Bool("Wet")

# 归纳情形:设 f、g 是公式,则以下也是公式(五个联结词 ¬ ∧ ∨ → ↔)
f, g = Rain, Wet
h1 = Not(f)          # ¬f     非,negation
h2 = And(f, g)       # f ∧ g  合取,conjunction
h3 = Or(f, g)        # f ∨ g  析取,disjunction
h4 = Implies(f, g)   # f → g  蕴涵,implication
h5 = (f == g)        # f ↔ g  等价 / 双条件,biconditional

提示

归纳定义的妙处:任意复杂的公式都是一棵语法树——叶子是命题符号、内部节点是联结词。这棵树正是下一节解释函数递归求值的结构基础:语法怎么归纳定义,语义就怎么递归计算。

2.2 合法与非法公式

# ✅ 合法公式
f = Or(Not(P), Q)              # ¬P ∨ Q
f = Implies(P, Or(Q, Not(P)))  # P → (Q ∨ ¬P)

# ❌ 不是命题逻辑的公式
#   P ¬Q          两个符号并排,没有联结词
#   P + Q         "+" 不是逻辑联结词
#   P(A) ∨ Q(B)   带参数的谓词——那是一阶逻辑(下一讲)

我们现在知道了哪些是合法公式,但它们到底是什么意思? → 语义。


3. 命题逻辑:语义

公式本身只是符号(语法),还没有含义(语义)。语义部分要引入一连串概念,源码的组织顺序就是最好的学习路线:

flowchart LR
    M["模型 model<br/>世界的状态"] --> I["解释函数 ℐ(f,w)<br/>缝合语法与语义"]
    I --> MF["M(f)<br/>公式的模型集"]
    MF --> KB["知识库 KB<br/>公式的集合"]
    KB --> REL["蕴含 / 矛盾 / 或然"]
    REL --> AT["Ask / Tell"]
    AT --> SAT["可满足性 satisfiability"]

    style M fill:#c8e6c9,stroke:#2e7d32
    style I fill:#f3e5f5,stroke:#7b1fa2
    style MF fill:#e1f5fe,stroke:#0277bd
    style KB fill:#e1f5fe,stroke:#0277bd
    style REL fill:#ffe0b2,stroke:#e65100
    style SAT fill:#fce4ec,stroke:#c2185b

3.1 模型(model):世界的一种状态

定义:命题逻辑中的一个模型 $w$,就是对所有命题符号的一次真值赋值(assignment of truth values)

一个模型 = 一个”可能的世界”。$n$ 个命题符号就有 $2^n$ 个模型——每个符号独立地取真或假。

A = Bool("A"); B = Bool("B"); C = Bool("C")

# 2^3 = 8 个可能的模型(对 A, B, C 的全部真值组合)
models = [
    {A: False, B: False, C: False},   # (F, F, F)
    {A: False, B: False, C: True},    # (F, F, T)
    # ... 中间 4 个省略 ...
    {A: True,  B: True,  C: True},     # (T, T, T)
]

说明

“模型”这个词在机器学习里指训练出来的函数,在逻辑里却指”世界的一种可能状态”,两个含义完全不同。逻辑里的模型对应贝叶斯网络里的一次完整赋值(assignment)——第 5.2 节会把这组对应关系列全。

3.2 解释函数(interpretation function)$\mathcal{I}$

解释函数语法(公式)和语义(模型)缝合起来:

定义:解释函数 $\mathcal{I}(f, w)$ 把公式 $f$ 和模型 $w$ 映射到一个真值——true 表示 $f$ 在 $w$ 下为真,false 表示为假。

它沿着公式的语法树递归求值:原子公式直接查 $w$ 的赋值表,复合公式先递归求子公式、再按联结词组合。其中蕴涵的定义值得单独写出来:

$$\mathcal{I}(g \to h,\ w) \;=\; \neg\,\mathcal{I}(g, w) \vee \mathcal{I}(h, w)$$

即 $g \to h$ 与 $\neg g \vee h$ 语义等价。源码里的 I 按参数个数分派联结词:

def I(f: Formula, w: Model) -> bool:
    """解释函数:给定公式 f 和模型 w,判断 w 是否满足 f。"""
    if f.num_args() == 0:                     # 原子公式:直接查表
        return w[f]
    if f.num_args() == 1 and is_not(f):       # 一元:¬g
        return not I(f.arg(0), w)
    if f.num_args() == 2:                      # 二元联结词
        g, h = f.arg(0), f.arg(1)
        if is_and(f):     return I(g, w) and I(h, w)       # g ∧ h
        if is_or(f):      return I(g, w) or  I(h, w)       # g ∨ h
        if is_implies(f): return (not I(g, w)) or I(h, w)  # g → h ≡ ¬g ∨ h
        if is_eq(f):      return I(g, w) == I(h, w)        # g ↔ h
    raise ValueError(f"Unsupported formula: {f}")

例子

公式 $f = (\neg A \wedge B) \leftrightarrow C$ 在模型 $w = \{A{:}\,\text{T},\ B{:}\,\text{T},\ C{:}\,\text{F}\}$ 下:$\neg A = \text{F}$ → $\neg A \wedge B = \text{F}$ → $\text{F} \leftrightarrow \text{F} = \text{T}$,所以 $\mathcal{I}(f, w) = \text{true}$。

注意

$f \to g$ 只在”$f$ 真而 $g$ 假”时为假,其余情况一律为真——包括前件 $f$ 为假的情况(所谓”空真”,vacuously true)。别用日常的”因果关系”直觉去套:逻辑蕴涵只是真值表定义,”如果 $1+1=3$ 那么月亮是奶酪做的”是一个命题。

五个联结词的真值表

$f$ $g$ $\neg f$ $f \wedge g$ $f \vee g$ $f \to g$ $f \leftrightarrow g$
F F T F F T T
F T T F T T F
T F F F T F F
T T F T T T T

3.3 公式的模型集合 $\mathcal{M}(f)$

定义:公式 $f$ 的模型集合是所有使它为真的模型:
$$\mathcal{M}(f) = \{\, w : \mathcal{I}(f, w) = \text{true} \,\}$$

计算上,枚举所有 $2^n$ 个赋值、逐个用 I 检查即可:

def get_models(f: Formula, symbols: list[PropositionalSymbol]) -> list[Model]:
    """返回所有满足公式 f 的模型。"""
    models = []
    for values in itertools.product([False, True], repeat=len(symbols)):
        w = dict(zip(symbols, values))   # 一个模型 = 对符号的一次真值赋值
        if I(f, w):                      # f 在 w 下为真吗?
            models.append(w)
    return models

Rain = Bool("Rain"); Wet = Bool("Wet")
get_models(Or(Rain, Wet),  [Rain, Wet])   # M(Rain ∨ Wet):3 个模型
get_models(And(Rain, Wet), [Rain, Wet])   # M(Rain ∧ Wet):1 个模型

提示

一个(小小的)公式紧凑地表示了一个(可能很大的)模型集合——这正是逻辑”表达力”的来源。Or(Rain, Wet) 只用三个符号就框定了 $\{FT, TF, TT\}$ 三种世界(只排除了 $FF$);若符号有 100 个,一条短公式就在 $2^{100}$ 个世界中划出了一半。用集合直接列举做不到这么省。

3.4 知识库(knowledge base, KB)

定义知识库 KB 是一个公式的集合;把它想成一堆你会随时间不断添加的事实。
语义:$\mathcal{M}(\text{KB})$ 是同时满足 KB 中每一条公式的模型——“给定我们的知识,我们可能身处的世界”。

关键性质:KB 的模型集等于各公式模型集的交集,也等价于把各公式用合取(conjunction)串起来:

$$\mathcal{M}(\text{KB}) \;=\; \bigcap_{f \in \text{KB}} \mathcal{M}(f) \;=\; \mathcal{M}\Big(\bigwedge_{f \in \text{KB}} f\Big)$$

def intersect(l1: list[Model], l2: list[Model]) -> list[Model]:
    return [m for m in l1 if m in l2]          # 两个模型列表的交集

def to_formula(kb: list[Formula]) -> Formula:
    f = kb[0]
    for g in kb[1:]:
        f = And(f, g)                          # 用 ∧ 把 KB 串成单个公式
    return f

kb = [Rain, Implies(Rain, Wet)]                # KB = {Rain, Rain → Wet}
models_kb = intersect(get_models(Rain, [Rain, Wet]),
                      get_models(Implies(Rain, Wet), [Rain, Wet]))   # = M(KB)
# 语义上 KB 等价于其合取:M(to_formula(kb)) 与 models_kb 相同

提示

KB 里每加一条事实,就是加一个”过滤器”:只有同时通过所有过滤器的世界才留在交集里。事实越多,剩下的可能世界越少——知识增长的过程,就是不确定性收缩的过程。这个”收缩”的三种程度正是下一节的主题。


4. 蕴含、矛盾与或然

4.1 添加知识 = 收缩模型集

核心洞见:把新公式 $f$ 加进 KB 后模型集只会收缩不会扩大

$$\text{KB} \subseteq \text{KB} \cup \{f\} \implies \mathcal{M}(\text{KB} \cup \{f\}) \subseteq \mathcal{M}(\text{KB})$$

原因很直接:新模型集是旧模型集与 $\mathcal{M}(f)$ 的交集,交集不可能比原集合大。

提示

随着知识增加,我们缩小了可能身处的世界范围。问题只剩一个——这次收缩了多少? 完全没缩、缩成空集、缩掉一部分,三种情况恰好对应三个核心概念。

graph TD
    ALL["M(KB)<br/>当前可能的世界"] --> E["蕴含 Entailment<br/>完全不缩:M(KB∪f) = M(KB)"]
    ALL --> CON["矛盾 Contradiction<br/>缩成空集:M(KB∪f) = ∅"]
    ALL --> CTG["或然 Contingency<br/>部分收缩:∅ ≠ M(KB∪f) ≠ M(KB)"]

    style ALL fill:#ffe0b2,stroke:#e65100
    style E fill:#c8e6c9,stroke:#2e7d32
    style CON fill:#ffcdd2,stroke:#c62828
    style CTG fill:#e1f5fe,stroke:#0277bd

4.2 三个定义

三个概念的实现都只是比较 $\mathcal{M}(\text{KB})$ 与 $\mathcal{M}(\text{KB} \cup \{f\})$:

  • ① 蕴含(entailment) $\text{KB} \models f$:$\mathcal{M}(\text{KB} \cup \{f\}) = \mathcal{M}(\text{KB})$。直觉:$f$ 没带来新信息——KB 的每个模型本来就满足 $f$。
  • ② 矛盾(contradiction):$\mathcal{M}(\text{KB} \cup \{f\}) = \varnothing$。直觉:$f$ 与 KB 不相容,没有任何世界能同时满足二者。
  • ③ 或然(contingency):$\varnothing \neq \mathcal{M}(\text{KB} \cup \{f\}) \neq \mathcal{M}(\text{KB})$。直觉:$f$ 带来了真正的新信息——排除了一部分世界,但没有全排除。
symbols = [Rain, Wet]
def M(kb): return get_models(to_formula(kb), symbols)   # 取 KB 的模型集

def entails(kb, f):     return M(kb + [f]) == M(kb)                        # ①
def contradicts(kb, f): return M(kb + [f]) == []                          # ②
def contingent(kb, f):  return M(kb + [f]) != [] and M(kb + [f]) != M(kb)  # ③

assert entails([Rain, Implies(Rain, Wet)], Rain)   # KB 显然蕴含 Rain
assert contradicts([Rain, Wet], Not(Wet))          # 已知 Wet 又说 ¬Wet,冲突
assert contingent([Rain], Wet)                     # 只知道 Rain,Wet 可真可假

蕴含与矛盾的关系(一个非常有用的等价,第 6 节归约到可满足性时全靠它):

$$\text{KB 与 } f \text{ 矛盾} \iff \text{KB} \models \neg f$$

说明

“KB 与 $f$ 矛盾”意思是没有模型同时满足 KB 和 $f$;换句话说,KB 的每一个模型都不满足 $f$,即都满足 $\neg f$;而”KB 的每个模型都满足 $\neg f$”正是 $\text{KB} \models \neg f$ 的定义。三步改写,两个概念就接通了。

4.3 重言、矛盾、或然(相对空 KB)

当 KB 为空(无任何背景知识、$\mathcal{M}(\text{KB})$ 为全体模型)时,上述三类关系退化为公式自身的分类:

术语 定义(相对空 KB) 例子
重言式(tautology) 所有模型下都为真,$\mathcal{M}(f)$ = 全体 $P \vee \neg P$
矛盾式(contradiction) 任何模型下都为假,$\mathcal{M}(f) = \varnothing$ $P \wedge \neg P$
或然式(contingency) 有时真有时假,$\varnothing \neq \mathcal{M}(f) \neq$ 全体 $P$、$P \to Q$

5. Ask 与 Tell:操作知识库

有了 KB,我们能做两件事:Ask[f](向 KB 问是非问题)和 Tell[f](向 KB 添加新陈述)。两者的回答都取决于 KB 与 $f$ 的关系(蕴含 / 矛盾 / 或然)——所以恰好各有三种回答。

5.1 Ask 与 Tell 的实现

Ask 三种回答:Yes / No / I don’t knowTell 三种回答:I already knew that / I don’t buy that / I learned something new。二者共享同一套分派(old/new 分别是 $\mathcal{M}(\text{KB})$ 与 $\mathcal{M}(\text{KB} \cup \{f\})$):

def ask(kb, f) -> str:
    old, new = M(kb), M(kb + [f])
    if new == old: return "Yes"           # 蕴含
    if new == []:  return "No"            # 矛盾
    return "I don't know"                 # 或然

def tell(kb, f):
    old, new = M(kb), M(kb + [f])
    if new == old: return kb, "I already knew that"     # 蕴含
    if new == []:  return kb, "I don't buy that"        # 矛盾
    return kb + [f], "I learned something new"          # 或然:才真正更新 KB

ask([Rain, Wet],      Or(Rain, Wet))   # → Yes
ask([Wet, Not(Rain)], Rain)            # → No
ask([Rain],           Wet)             # → I don't know
tell([Rain, Implies(Rain, Wet)], Wet)  # → "I already knew that"
tell([Rain, Wet],     Not(Rain))       # → "I don't buy that"
tell([Rain],          Not(Wet))        # → "I learned something new"

提示

Ask/Tell 是逻辑智能体与世界打交道的接口:Tell 把观测灌进 KB,Ask 从 KB 里问出决策依据。注意只有”或然”时 Tell 才真正扩充 KB——蕴含的陈述是废话(早知道了),矛盾的陈述是谎话(不能收)。一个理性的 agent 只吸收带新信息且自洽的知识。

KB 与 $f$ 的关系 模型集 Ask[f] Tell[f]
蕴含 $= \mathcal{M}(\text{KB})$ Yes I already knew that
矛盾 $= \varnothing$ No I don’t buy that
或然 部分收缩 I don’t know I learned something new

5.2 与贝叶斯网络的联系(承接上一单元)

命题逻辑与 Lecture 12 · 贝叶斯网络 I:建模与推断 的概率推理在结构上高度对应:

贝叶斯网络 命题逻辑
随机变量(random variables) 命题符号(symbols)
赋值(assignments) 模型(models)
证据(evidence) 知识库 KB
查询(query) Ask[] 的对象

概率推断 $P(\text{query} \mid \text{evidence})$ 对应 Tell[evidence]; Ask[query],例如 $P(\text{Rain} \mid \text{Wet}=1)$ 对应 Tell[Wet]; Ask[Rain]

关键区别:贝叶斯网络里 query/evidence 只能是简单的变量赋值(如 $\text{Rain}=1, \text{Wet}=0$);命题逻辑里可以是任意公式(如 $(\text{Rain} \wedge \text{Wet}) \vee (\text{Rain} \wedge \neg\text{Snow})$)——这是逻辑表达力更强之处。反过来,逻辑的 Yes/No/不知道 可以被概率推广为 0 到 1 之间的数:给每个世界 $w$ 一个概率 $P(W = w)$,则

$$P(\text{KB}) = \sum_{w \in \mathcal{M}(\text{KB})} P(W = w), \qquad P(f \mid \text{KB}) = \frac{P(\text{KB} \cup \{f\})}{P(\text{KB})}$$

其中分子是同时满足 KB 与 $f$ 的世界的概率质量。蕴含对应 $P(f \mid \text{KB}) = 1$,矛盾对应 $P(f \mid \text{KB}) = 0$,或然对应中间值——逻辑是概率推理在 $\{0, 1\}$ 两端的特例。源码用 bayes.pyProbTable 演示 $P(\text{Rain}, \text{Wet})$ 的联合分布,把逻辑的”是非判断”嵌入概率框架。


6. 可满足性(satisfiability)

6.1 归约:三合一

到目前为止 Ask/Tell 都靠枚举 $\mathcal{M}(\text{KB})$——$O(2^n)$ 级别,符号一多就指数爆炸。为了更快,先把蕴含/矛盾/或然三个判断全部归约到一个操作

定义:KB 是可满足的(satisfiable)当且仅当 $\mathcal{M}(\text{KB}) \neq \varnothing$(至少存在一个满足它的世界)。

由于一次可满足性检查只给 1 比特信息,而我们要区分 3 种结果(至少需要 2 比特),所以需要两次调用

flowchart TD
    START["输入 KB 和公式 f"] --> C1{"KB ∪ {f}<br/>可满足?"}
    C1 -->|不可满足| CONTRA["矛盾 / No"]
    C1 -->|可满足| C2{"KB ∪ {¬f}<br/>可满足?"}
    C2 -->|不可满足| ENT["蕴含 / Yes<br/>(KB 与 ¬f 矛盾)"]
    C2 -->|可满足| CTG["或然 / I don't know"]

    style START fill:#e1f5fe,stroke:#0277bd
    style CONTRA fill:#ffcdd2,stroke:#c62828
    style ENT fill:#c8e6c9,stroke:#2e7d32
    style CTG fill:#ffe0b2,stroke:#e65100

第二步用到了 4.2 的等价:

$$\text{KB} \models f \iff \text{KB 与 } \neg f \text{ 矛盾} \iff \text{KB} \cup \{\neg f\} \text{ 不可满足}$$

6.2 模型检查(model checking)与 Z3

模型检查(model checking):判定一个 KB 是否可满足的任务——输入一个 KB,输出它是否可满足(可满足时还能给出一个满足它的模型作为”证据”)。

源码直接调用 Z3 求解器做模型检查:

Rain = Bool("Rain"); Wet = Bool("Wet")
kb = [Rain, Wet]

solver = Solver()
for f in kb:
    solver.add(f)              # 把每条公式加入求解器
result = solver.check()        # 执行模型检查
if result == sat:              # 若可满足
    w = solver.model()         # 返回 M(KB) 中的一个模型

说明

命题逻辑的可满足性就是著名的 SAT 问题——1971 年 Cook 证明它是第一个 NP-完全问题,理论上最坏情况仍是指数时间。但现代 SAT 求解器在实际问题上远比朴素枚举快:DPLL 本质上是带回溯的深度优先搜索(与 Lecture 5 · 搜索算法 I 的回溯搜索同源),加上单元传播(unit propagation)等剪枝;CDCL(conflict-driven clause learning,冲突驱动子句学习)进一步在每次搜索撞上矛盾时学出一条新子句加入 KB,避免将来再犯同样的错。工业级求解器能处理百万变量级的实例,这是”NP-完全 ≠ 实践中不可解”的经典案例。

6.3 合取范式(CNF)与归结(resolution)

SAT 求解器通常先把公式转成合取范式(Conjunctive Normal Form, CNF)——一堆子句(clause)的合取,每个子句是若干文字(literal,即命题符号或其否定)的析取,例如 $(P \vee \neg Q) \wedge (\neg P \vee R) \wedge (Q \vee R)$。任意命题公式都可以等价(或保持可满足性地)转成 CNF。在 CNF 上,一条核心推理规则是归结(resolution):两个子句若含互补文字,可消去该文字、合并剩余部分:

$$\frac{P \vee Q \qquad \neg P \vee R}{Q \vee R}$$

例子

  1. 把 KB 与结论的否定转成 CNF 子句:$\{\text{Rain}\}$、$\{\neg\text{Rain} \vee \text{Wet}\}$(即 $\text{Rain} \to \text{Wet}$)、$\{\neg\text{Wet}\}$(结论的否定)。
  2. 对 Wet 归结:$\neg\text{Rain} \vee \text{Wet}$ 与 $\neg\text{Wet}$ 得 $\neg\text{Rain}$。
  3. 对 Rain 归结:$\text{Rain}$ 与 $\neg\text{Rain}$ 得空子句 $\square$(矛盾)。
  4. $\text{KB} \cup \{\neg\text{Wet}\}$ 不可满足 $\implies \text{KB} \models \text{Wet}$。∎

注意

精确地说,归结是反驳完备(refutation-complete)的:若一个子句集不可满足,反复归结必定能推出空子句;但它并不能从 KB 直接”生成”所有被蕴含的公式(比如从 $\{P\}$ 归结不出 $P \vee Q$ 这个新子句)。好在这已经足够——判定任何蕴含 $\text{KB} \models f$,都可以转化为对 $\text{KB} \cup \{\neg f\}$ 做反驳。所以”归结 + 反证法”构成命题逻辑上可靠且完备的判定程序。

小结:蕴含/矛盾/或然都可归约到可满足性;用模型检查(Z3 / SAT 求解器,内部 DPLL/CDCL、工作在 CNF 上)来高效计算它。


7. 推理规则:从语义到语法

前面 Ask/Tell 走的是语义路线(枚举或检查模型)。但回到开头的引子——解 $A + B = 10,\ A - B = 4$ 时,你并没有枚举世界,而是操作符号。这就是推理规则要做的事:完全绕开模型,在语法层直接生成新公式。

7.1 Modus Ponens(假言推理)

一个具体推理:下雨(Rain);如果下雨则地湿($\text{Rain} \to \text{Wet}$);所以地湿(Wet)。写成一般的 modus ponens 规则(横线上是前提,横线下是结论):

$$\frac{p \qquad p \to q}{q}$$

一般的推理规则(inference rule)形如:前提是一组公式 $f_1, \dots, f_k$,结论是公式 $g$,记作 $f_1, \dots, f_k \vdash g$。

注意

推理规则只操作语法(符号模式匹配),不碰语义(不看任何模型)。它是纯粹的符号变换——这正是它比枚举 $2^n$ 个模型高效的原因,但也意味着必须另行证明它忠于语义(第 8 节)。

graph LR
    P["p :Rain"] --> MP["modus ponens"]
    IMP["p → q :Rain → Wet"] --> MP
    MP --> Q["⊢ q :Wet"]

    style P fill:#e1f5fe,stroke:#0277bd
    style IMP fill:#e1f5fe,stroke:#0277bd
    style MP fill:#ffe0b2,stroke:#e65100
    style Q fill:#c8e6c9,stroke:#2e7d32

7.2 前向推理(forward inference)

反复套用规则,把能推出的公式全部加进 KB:

  1. 输入:一组推理规则 Rules,初始知识库 KB;
  2. 重复直到 KB 不再变化:从 KB 中选一组公式 $f_1, \dots, f_k$,若存在匹配的规则 $f_1, \dots, f_k \vdash g$,则把 $g$ 加入 KB。

若 $f$ 最终被加入 KB,就说 KB 推导出(derives)/ 证明了(proves) $f$,记作 $\text{KB} \vdash f$。

例子

从 $\{\text{Rain},\ \text{Rain} \to \text{Wet}\}$ 出发,用 modus ponens 可推出 Wet;但推不出 $\neg\text{Wet}$、$\text{Rain} \to \text{Slippery}$——KB 里没有能匹配上的前提与规则。这两条本来也不被 KB 蕴含,推不出是好事(可靠性的体现)。真正暴露 modus ponens 局限的是:$\{\text{Rain}\} \models \text{Rain} \vee \text{Wet}$,但 modus ponens 无法推出它——这是不完备(见第 8 节)。

graph LR
    R["Rain"] --> W["Wet ✓"]
    RW["Rain → Wet"] --> W
    W -.推不出.-> NW["¬Wet ✗"]
    W -.推不出.-> RS["Rain → Slippery ✗"]

    style R fill:#c8e6c9,stroke:#2e7d32
    style RW fill:#c8e6c9,stroke:#2e7d32
    style W fill:#c8e6c9,stroke:#2e7d32
    style NW fill:#ffcdd2,stroke:#c62828
    style RS fill:#ffcdd2,stroke:#c62828

8. 可靠性与完备性:缝合语法与语义

现在有了两条平行的路线,必须问:它们一致吗

  • 语法:$\text{KB} \vdash f$(能从 KB 推导 / 证明出的公式)
  • 语义:$\text{KB} \models f$(被 KB 蕴含、即在 KB 的所有模型中为真的公式)

课程用一个绝妙的比喻(法庭誓词)说明理想目标是 “the truth, the whole truth, and nothing but the truth“(真相、全部真相、且只有真相):

性质 定义 直觉 一句话
可靠性(soundness) $\{f : \text{KB} \vdash f\} \subseteq \{f : \text{KB} \models f\}$ 推出来的都是对的 nothing but the truth(只有真相)
完备性(completeness) $\{f : \text{KB} \models f\} \subseteq \{f : \text{KB} \vdash f\}$ 所有对的都能推出来 the whole truth(全部真相)

提示

可靠 = 不说假话(推出的绝不出错,但可能推不全);完备 = 不漏真话(真的都能推到,但若规则太猛可能连假的也一起推出)。二者兼备时 $\vdash$ 与 $\models$ 完全重合——纯符号操作精确复刻了语义真理,这是逻辑系统的圣杯。对 AI 而言这意味着:agent 可以只做快速的符号计算,却得到与”枚举所有可能世界”完全相同的结论。

规则集的强弱决定落在哪一侧

  • 规则太弱(如只有 modus ponens):可靠但不完备——$\{\text{Rain}\} \models \text{Rain} \vee \text{Wet}$ 却推不出。不过有一个重要特例:当 KB 全部由 Horn 子句(至多一个正文字的子句,即”事实”与”$a_1 \wedge \cdots \wedge a_k \to b$”型规则)组成、且查询是原子公式时,modus ponens 就是完备的——这正是下一讲确定子句推理的理论基础。
  • 规则太猛(乱加规则):完备但不可靠——什么都能推出来,包括假的。
  • 恰到好处归结(resolution)配合反证法,对整个命题逻辑既可靠又(反驳)完备——这正是 SAT 求解如此重要的原因。
graph LR
    E["⊢ ⊂ ⊧<br/>只可靠:推不全"] --> BAL["⊢ = ⊧<br/>可靠 且 完备<br/>(归结)"]
    O["⊧ ⊂ ⊢<br/>只完备:会推错"] --> BAL

    style E fill:#e1f5fe,stroke:#0277bd
    style O fill:#ffcdd2,stroke:#c62828
    style BAL fill:#c8e6c9,stroke:#2e7d32

9. 总结

mindmap
  root((命题逻辑))
    三要素
      语法
      语义
      推理规则
    语法
      命题符号
      五个联结词
      公式的归纳定义
    语义
      模型即真值赋值
      解释函数
      公式的模型集
      知识库即公式集合
    三种关系
      蕴含
      矛盾
      或然
      Ask 与 Tell
    推理
      可满足性
      模型检查与 SAT
      DPLL 与 CDCL
      归结与 CNF
    元性质
      可靠性
      完备性

关键要点

  • 一门逻辑语法 / 语义 / 推理规则三要素定义;把逻辑当作一门语言——用于表示知识与推理出新知识。
  • 命题逻辑:命题符号 + 联结词 $\neg\ \wedge\ \vee\ \to\ \leftrightarrow$ 归纳出公式;解释函数 $\mathcal{I}(f, w)$ 沿语法树递归求值,把语法接到语义。
  • 模型是对所有符号的真值赋值($n$ 个符号 $2^n$ 个模型);$\mathcal{M}(f)$ 是使 $f$ 为真的模型集;一个小公式紧凑表示一大片世界——这是逻辑表达力的来源。
  • KB 加公式会收缩模型集,程度分三类:蕴含(不变)/ 矛盾(空集)/ 或然(部分收缩);对应 Ask 的 Yes/No/不知道 与 Tell 的三种回答。
  • 三类关系都可归约到可满足性(两次 SAT 调用),用模型检查(Z3,内部是 DPLL/CDCL)高效计算;归结在 CNF 上配合反证法可靠且反驳完备。
  • 推理规则(modus ponens、归结)只操作语法可靠性(推出的都对)与完备性(对的都能推出)度量 $\vdash$ 与 $\models$ 的重合程度。

下一讲预告:命题逻辑的表达力仍然有限——想说”每个学生都有一个 TA”,就得给每个学生各写一条公式;想谈论无穷多个对象(如”所有偶数”)则彻底无能为力。一阶逻辑(first-order logic)引入量词 $\forall\ \exists$、谓词、函数与对象,用一句话表达无穷多命题。→ Lecture 16 · 逻辑 II:一阶逻辑


复习自测

题目

例:3 / 2 在 Python 2 与 Python 3 中分别是 11.5(同语法不同语义);$2+3$ 与 $3+2$ 都指 $5$(不同语法同语义)。推理规则可以只做符号操作是因为我们另行证明了可靠性:规则的每次应用都保持”前提的模型集 ⊆ 结论的模型集”,所以符号层的推导链条在语义层步步为真。没有可靠性保证的符号操作只是乱涂乱画。

题目

(⟹)矛盾即 $\mathcal{M}(\text{KB} \cup \{f\}) = \varnothing$:没有模型同时满足 KB 与 $f$,故 KB 的每个模型都不满足 $f$,即都满足 $\neg f$,即 $\mathcal{M}(\text{KB} \cup \{\neg f\}) = \mathcal{M}(\text{KB})$,这就是 $\text{KB} \models \neg f$。(⟸)反向逐步倒回去即可。这个等价是”蕴含归约到可满足性”的桥梁:$\text{KB} \models f \iff \text{KB} \cup \{\neg f\}$ 不可满足。

题目

枚举 4 个模型:$\text{Rain} \vee \text{Wet}$ 排除 $(F,F)$,$\neg\text{Rain}$ 排除 $(T,\cdot)$,故 $\mathcal{M}(\text{KB}) = \{(\text{Rain}=F, \text{Wet}=T)\}$,单点集。加入 Wet 后模型集不变 ⟹ 蕴含 ⟹ 回答 Yes。(这其实是一次隐式的归结:$\text{Rain} \vee \text{Wet}$ 与 $\neg\text{Rain}$ 归结出 $\text{Wet}$。)

题目

一次可满足性检查只输出 1 比特(可满足/不可满足),而 Ask 有三种结果(Yes/No/不知道),信息论上至少要 2 比特。流程:先查 $\text{KB} \cup \{f\}$——不可满足则矛盾(No);再查 $\text{KB} \cup \{\neg f\}$——不可满足则蕴含(Yes);两者都可满足则或然(不知道)。两次调用的顺序可以交换,结论不变。

题目

例:KB $= \{\text{Rain}\}$,$f = \text{Rain} \vee \text{Wet}$。语义上 KB 的唯一约束是 Rain 为真,此时 $\text{Rain} \vee \text{Wet}$ 必真,故 $\text{KB} \models f$;但 modus ponens 需要”$p$ 与 $p \to q$”的模式,KB 里没有任何蕴涵式可用,推不出这个析取。这说明 modus ponens 可靠但不完备。补救:换用归结 + 反证法(对 $\text{KB} \cup \{\neg f\}$ 反驳),即可覆盖全部命题逻辑蕴含。


参考资料