特伦多人工智能基础笔记-全-

特伦多人工智能基础笔记(全)

001:课程介绍与人工智能概述

在本节课中,我们将学习本课程的基本信息和人工智能的宏观概述。课程将涵盖时间安排、考核方式、学习资源,并对人工智能的定义、历史视角和当前研究趋势进行初步探讨。

课程基本信息

我是Roberto Sebastiani,我将担任本学期的《人工智能基础》课程教师。课程将在第一学期进行。

课程时间与地点

  • 理论课:每周一 9:30 - 11:30,在本教室进行。
  • 实验课:每周一 11:30 - 13:30,在A104教室进行。请注意,“实验课”指的是习题练习课,而非上机操作。
  • 附加课:每周五 9:30 - 12:30(或可能延长至13:30),具体时长将根据教学进度灵活调整。

课堂与考勤
所有课程均为线下授课,但会被录音。录音内容包含教师的音频、幻灯片以及学生的提问。请注意,录音仅作为课后复习的辅助工具,强烈建议亲自到场听课。课堂互动和与同学的交流对学习至关重要。

办公时间
办公时间不固定,需要通过预约安排。通常可以在课间或课后直接提问,如需更深入的讨论,请提前与我或助教预约。办公时间仅在本学期授课期间有效。

沟通平台
所有学生必须在课程Moodle平台上注册。这将是我们发布通知、课程资料和进行论坛讨论的主要渠道。

邮件沟通规范
为避免邮件被忽略,请遵守以下规范:

  • 发送邮件时,请同时抄送给我和助教。
  • 邮件主题请注明 [Fundamentals AI] 及相关具体事项。
  • 请使用学校的官方邮箱(name.surname@unitn.it)发送邮件。
  • 请保持礼貌与尊重。

课程概述与目标

本课程旨在为人工智能领域的学习打下广泛的基础。人工智能是一个涵盖众多子领域的“伞式术语”,包括问题求解、知识表示、推理、规划、学习、模式识别等。

本课程的特点在于其广度而非深度。我们将快速浏览人工智能的多个核心主题,为你后续的专业课程构建知识框架。你可以将其视为建造摩天大楼前打下的地基。

课程涵盖主题
我们将学习以下主题:

  • 智能体与理性智能体
  • 搜索与问题求解
  • 约束满足问题
  • 逻辑(命题逻辑、一阶逻辑)及其在推理中的应用
  • 自动推理与规划
  • 不确定知识下的表示与推理

课程不涵盖主题
以下主题将在其他专门课程中学习,因此本课程不予涉及:

  • 机器学习
  • 自然语言处理
  • 计算机视觉与机器人学
  • 人工智能伦理
  • 人工智能哲学与心理学

学习资料与背景要求

主要学习资料

  • 课堂笔记:课堂上讲授的所有内容都可能成为考试范围,因此认真记笔记至关重要。
  • 课程幻灯片:可以从课程网站下载。请注意,幻灯片可能会更新,请确保下载最新版本。
  • 教科书:强烈推荐 Stuart Russell 和 Peter Norvig 所著的《人工智能:现代方法》(Artificial Intelligence: A Modern Approach,简称AIMA)。这本书是人工智能领域的经典教材,其网站也提供了丰富的补充材料。

所需背景知识
学习本课程,你需要具备以下基础:

  • 数学基础:包括指数、对数等基本概念。课程后半部分需要一些概率论知识,教科书附录提供了相关复习材料。
  • 算法与数据结构:需要理解栈、队列等基本数据结构,并能阅读伪代码。
  • 编程基础:虽然不要求编写代码,但需要能理解算法伪代码。
  • 布尔逻辑:课程会包含相关复习内容。

考核方式

考核方式为闭卷笔试,时长为3小时。试卷可能包含判断题、理论问答题和小型计算题。

考试每年有五次机会:一月、二月、六月、七月、九月。如果对成绩不满意,可以拒绝接受并参加下一次考试,但请注意,一旦拒绝,该次成绩将永久作废。

考试仅限线下进行,无远程考试选项。严禁作弊,一旦发现将取消考试资格。

什么是人工智能?

上一节我们介绍了课程的具体安排,本节中我们来看看人工智能这个宏观领域。对人工智能的定义充满挑战,因为这涉及到对“智能”本身的理解。智能通常涉及感知、理解、预测和操纵环境的能力。

历史上,人们从两个维度来思考人工智能:

1. 思维与行动

  • 思维:关注智能系统如何像人一样“思考”。
  • 行动:关注智能系统如何像人一样“行动”或做出理性行为。

2. 人类标准与理性标准

  • 人类中心:以人类的表现作为智能的衡量基准(如通过图灵测试)。
  • 理性中心:追求一种理想的、形式化的理性标准,而不局限于模仿人类。

这两个维度组合产生了四种研究途径:

四种研究途径
以下是四种主要的研究视角:

  • 像人一样思考:通过认知科学等方法研究人类思维过程,并尝试在计算机上模拟。
  • 理性地思考:利用逻辑等工具,实现可证明正确的形式化推理。
  • 像人一样行动:目标是让计算机的行为无法与人类区分,如图灵测试所设想的。
  • 理性地行动:这是当前的主流范式。它关注于构建理性智能体,即在给定感知、目标和环境背景下,能够选择期望收益最大化的行动的实体。

理性智能体方法更具一般性,它将“思考”作为“行动”的一个子部分。同时,它更符合科学方法,因为智能体的表现可以被客观衡量和评估。当然,完全的“完美理性”难以实现,因为智能体总是面临计算资源、信息不完备等限制。

人工智能的分类与现状

除了研究途径,人工智能还可以从其他角度分类。

强弱人工智能

  • 弱人工智能:指专门用于处理特定任务的智能系统,它们可能表现得非常智能,但并非真正拥有意识或理解力。例如,下棋程序、聊天机器人。
  • 强人工智能:指具有自我意识、真正理解能力、能在各方面媲美甚至超越人类智能的系统。这仍是远未实现的科学愿景。

通用与专用人工智能

  • 通用人工智能:指能够执行任何智力任务的系统,类似于人类的全能智能。
  • 专用人工智能:指在特定领域表现卓越的系统,如人脸识别、机器翻译。目前所有实用AI系统都属于此类。

当前研究趋势:符号主义与连接主义的融合
人工智能研究历史上主要有两种范式:

  • 符号主义:基于逻辑和符号表示,进行显式的知识推理。优点是可解释、严谨;缺点是难以处理不确定性和大规模问题,且计算复杂度高。
  • 连接主义:基于神经网络和机器学习,从数据中学习模式。优点是处理高维数据(如图像、语音)能力强、效率高;缺点是模型像“黑箱”,缺乏可解释性,且依赖大量数据。

当前的研究前沿是尝试将两者的优势结合,发展神经符号人工智能,旨在获得连接主义的高效性和符号主义的可解释性与推理能力。

人工智能是一门高度交叉的学科,融合了哲学、数学、经济学、神经科学、心理学、计算机科学、工程学、控制论和语言学等多个领域的知识。如今,AI技术已广泛应用于邮件过滤、股票预测、自动驾驶、游戏、内容生成等众多领域。未来的一个重要方向是将AI技术与生物、医药、物理、化学等具体学科深度结合。

总结

本节课我们一起学习了《人工智能基础》课程的基本规则、学习目标和考核方式。我们对人工智能进行了高层次的概述,探讨了其定义、不同的研究视角(思维vs行动,人类vs理性)以及当前主流的理性智能体范式。我们还简要了解了AI的分类(强弱AI,通用专用AI)和符号主义与连接主义两大技术流派的融合趋势。下节课,我们将开始深入探讨具体的概念和内容。

002:概率推理入门 🎲

在本节课中,我们将要学习概率推理的基本概念。概率论是人工智能中处理不确定性的核心数学工具。当智能体(无论是人工的还是自然的)面临信息不完整的情况时,它无法做出100%确定的决策,此时就需要借助概率来量化我们对不同结果的“信念程度”。

为什么需要概率?🤔

上一节我们介绍了课程概述,本节中我们来看看为什么在人工智能中需要概率推理。

智能体在决策时,常常无法获得完整信息。因此,它必须在不确定性下进行推理。你无法保证某个行动一定会成功,但可以给出该行动成功可能性的“信念度”。数学上,我们使用概率论来形式化这种“信念度”。

一个理性的决策,既取决于我们想要实现的目标的相对重要性(收益或成本规避),也取决于我们实现该目标的可能性。这本质上是成本与收益之间的权衡。科学为我们提供的、用来表示这种“信念度”或“可能性”概念的明显工具就是概率论。

一个经典例子:赶飞机 ✈️

以下是关于如何应用概率进行决策的一个具体例子。

假设你需要从旧金山开车去机场。这里存在许多不确定性来源:交通状况、其他司机的行为、车辆可能出问题等。虽然你可以查看地图应用,但这并非100%可靠。预测交通非常复杂,很难用纯粹的确定性(逻辑)方法处理,通常需要基于经验的启发式方法,并以概率来思考。

  • 行动A25:提前25分钟出发。这相当冒险,即使你认为25分钟通常足够到达机场。你可能遇到事故、堵车、找不到停车位等问题。
  • 行动A90:提前90分钟出发。这看起来更合理。
  • 行动A120:提前2小时出发。你有很高的概率(例如95%)达成目标。
  • 行动A1000:提前1000分钟(超过16小时)出发。这近乎是确定的,但完全不切实际(例如需要在机场过夜)。

那么,应该选择哪个行动?这通常取决于成本与收益的权衡。

另一个例子:诊断问题 🩺

诊断问题是概率推理的另一个典型应用场景。

诊断问题的关键在于,给定一系列症状,试图找出最可能的原因。尝试用纯逻辑(例如布尔逻辑)来表示这类问题非常困难且存在缺陷。

  • 逻辑表示的局限性:例如,“牙痛意味着有蛀牙”这个逻辑蕴含(Toothache ⇒ Cavity)并不总是成立,因为牙痛可能由许多其他原因引起(牙龈问题、脓肿等)。反之,“蛀牙意味着牙痛”(Cavity ⇒ Toothache)也不一定成立,可能蛀牙很大了才会痛。
  • 缺点:试图用确定性逻辑规则来表示存在复杂性高、可能原因太多、缺乏对领域的完整理论、对特定患者信息了解不全等问题。

因此,使用概率来处理不确定性是一个很好的理由。我们试图找出对某个陈述(如一个可能的诊断)的置信度或可能性。

概率的来源与条件概率 📊

概率论本身是一个数学理论。我们通常从统计数据和领域经验中获取概率信息。

  • 统计与经验:例如,医生从大量病例统计中知道,80%的牙痛病例是由蛀牙引起的。我们可以将此视为概率P(蛀牙 | 牙痛) = 0.8。统计数据越多,对真实概率的近似就越可靠。医生的经验也常常以启发式的方式量化这种可能性。
  • 条件概率:概率通常不是绝对的,而是基于某些已知信息(证据)。我们用 P(A | B) 表示在已知B发生的情况下,A发生的概率。例如,P(蛀牙 | 牙痛) = 0.8。
  • 证据的重要性:概率会随着新证据的出现而改变。例如,P(蛀牙 | 牙痛) 可能较高,但 P(蛀牙 | 牙痛 且 有长期牙龈病史) 可能就低得多,因为牙痛更可能是牙龈问题引起的。这就是为什么向医生提供完整信息至关重要。

基于概率的决策与效用函数 ⚖️

那么,如何基于概率做出决策呢?这通常是一个妥协和权衡的过程。

决策取决于不同结果的可能性,以及每个结果带来的成本或收益。我们使用效用函数来量化收益或成本。一个理性的智能体会选择能带来最大期望效用的行动,即对所有可能结果的效用按其概率进行加权平均。

但需要注意的是,效用函数并非总是线性的。例如,金钱的效用通常不是线性的:对大多数人来说,从0美元到100万美元的效用提升,远大于从100万美元到200万美元的效用提升。因此,在计算期望收益时需要谨慎。

概率论基础回顾 📚

由于概率推理建立在概率论基础上,我们接下来回顾一些核心概念。如果你已经熟悉,可以快速浏览。

  • 样本空间与事件:样本空间 Ω 是所有可能世界状态(与我们问题相关的部分)的集合。一个样本点 ω 是一个可能的世界(原子事件)。事件是样本空间的任意子集,表示我们感兴趣的某些特征的组合。
  • 概率模型:概率模型为样本空间中的每个可能结果分配一个概率值。概率是介于0和1之间的数,且所有可能结果的概率之和为1。例如,一个均匀的骰子,每个面朝上的概率是 1/6。
  • 随机变量:随机变量为可能世界(或事件)的某个特征提供抽象描述。随机变量有一个定义域(可能取值的集合),并关联一个概率分布,为其每个可能取值指定概率。例如,Weather 变量可能取值 {sunny, rain, cloudy},其分布 P(Weather) = <0.6, 0.1, 0.3>。
  • 联合概率分布:多个随机变量的联合概率分布为每个可能的取值组合指定一个概率。例如,P(Weather, Cavity) 是一个表格。联合分布的规模会随着变量数量指数级增长,这是概率推理中的一个主要挑战。

条件概率与链式法则 🔗

条件概率是概率推理中极其重要的概念。

  • 定义:在事件B发生的前提下,事件A发生的条件概率定义为:P(A | B) = P(A ∧ B) / P(B),要求 P(B) > 0。这可以理解为将样本空间缩小到B发生的情形,然后看其中A也发生的比例。
  • 乘积形式:上述定义可改写为 P(A ∧ B) = P(A | B) P(B)。这对联合分布也成立:P(X, Y) = P(X | Y) P(Y),注意这是元素级的乘积,而非矩阵乘法。
  • 链式法则:链式法则是乘积规则的推广。对于任意一组随机变量 X1, X2, ..., Xn,其联合分布可以分解为一系列条件概率的乘积:
    P(X1, X2, ..., Xn) = P(X1) * P(X2 | X1) * P(X3 | X1, X2) * ... * P(Xn | X1, ..., Xn-1)
    变量顺序可以任意选择。链式法则本身没有降低复杂度,但如果某些变量在给定其他变量时条件独立,就能大幅简化计算。这是贝叶斯网络的基础。

概率推理:枚举与边缘化 🧮

给定概率信息后,我们如何进行推理和计算呢?我们从最基本的方法开始。

  • 枚举法:最直接的方法是枚举。对于一个命题,计算其概率的方法是:对所有使该命题为真的原子事件(样本点)的概率求和。例如,从联合分布表 P(Toothache, Catch, Cavity) 中计算 P(Toothache ∨ Cavity),就是将表中所有 Toothache=true 或 Cavity=true 对应的概率值相加。
  • 边缘化:边缘化(或求和消元)是另一个基本操作。当我们想从联合分布中得到部分变量的概率分布时,需要对其他变量求和。例如,从 P(Toothache, Catch, Cavity) 得到 P(Toothache),需要对 Catch 和 Cavity 的所有可能取值组合求和:P(Toothache) = Σ_{catch} Σ_{cavity} P(Toothache, catch, cavity)
  • 条件概率计算:利用边缘化,我们可以计算条件概率。例如,计算 P(Cavity | Toothache):
    1. 用边缘化计算分子:P(Cavity ∧ Toothache) = Σ_{catch} P(Cavity, Toothache, catch)
    2. 用边缘化计算分母:P(Toothache) = Σ_{catch} Σ_{cavity} P(Toothache, catch, cavity)
    3. 代入公式:P(Cavity | Toothache) = P(Cavity ∧ Toothache) / P(Toothache)

总结 📝

本节课中我们一起学习了概率推理的基本动机和核心概念。我们了解到,智能体在信息不完全时必须处理不确定性,而概率论是量化这种不确定性的数学工具。我们通过赶飞机和医疗诊断的例子看到了概率的应用场景。我们回顾了概率论的基础,包括样本空间、随机变量、概率分布,并重点学习了条件概率的定义、乘积形式和链式法则。最后,我们介绍了两种基本的概率推理方法:枚举法和边缘化。这些概念是后续学习更高效的概率推理模型(如贝叶斯网络)的基石。

003:概率推理 🧮

在本节课中,我们将要学习概率推理中的核心概念,包括边缘化、归一化、独立性、条件独立性以及贝叶斯规则。这些工具对于在不确定性下进行推理至关重要,并能帮助我们简化复杂的计算。


边缘化与归一化 📊

上一节我们介绍了概率分布的基本概念,本节中我们来看看如何通过边缘化和归一化来操作这些分布。

边缘化

边缘化是指当我们想计算一个变量子集的分布时,需要对所有剩余变量(也称为隐藏变量)的可能取值进行求和。

公式
如果有一个关于变量 YZ 的联合分布 ( P(\textbf{Y}, \textbf{Z}) ),而我们想得到 Y 的边缘分布,公式如下:
[
P(\textbf{Y}) = \sum_{\textbf{z}} P(\textbf{Y}, \textbf{Z} = \textbf{z})
]
这意味着,对于 Y 的每一个可能取值,我们都要对 Z 的所有可能组合进行求和。

例如,在牙痛、蛀牙和探针卡住的例子中,要计算牙痛的分布 ( P(Toothache) ),我们需要对蛀牙和探针卡住的所有可能组合进行求和。

归一化

归一化是一种非常有用的技巧,它允许我们在计算条件概率时,避免直接计算分母(即证据的概率)。

核心思想
当我们计算条件分布 ( P(\textbf{Y} | \textbf{E} = \textbf{e}) ) 时,根据定义:
[
P(\textbf{Y} | \textbf{E} = \textbf{e}) = \frac{1}{P(\textbf{E} = \textbf{e})} P(\textbf{Y}, \textbf{E} = \textbf{e})
]
我们通常将 ( \frac{1}{P(\textbf{E} = \textbf{e})} ) 记作一个归一化常数 ( \alpha )。关键在于,我们不需要显式计算 ( \alpha ) 的值。

步骤

  1. 计算非归一化的联合概率 ( P(\textbf{Y}, \textbf{E} = \textbf{e}) )。
  2. 对这个非归一化分布进行求和,得到总和 ( S )。
  3. 将非归一化分布中的每个值除以 ( S ),即可得到归一化的条件概率分布。

优势
计算 ( P(\textbf{E} = \textbf{e}) ) 本身可能涉及复杂的求和(边缘化)。通过归一化技巧,我们推迟了这部分计算,直到最后一步,而最后一步的除法非常简单。这在处理复杂问题时可以节省大量计算步骤。

示例
计算 ( P(Cavity | Toothache) )。

  1. 计算非归一化的 ( P(Cavity, Toothache) )(通过对探针卡住变量求和得到)。
  2. 得到非归一化向量:<0.12, 0.08>(分别对应 Cavity=true 和 Cavity=false)。
  3. 总和为 0.2。归一化后得到:<0.6, 0.4>
    这比先计算 ( P(Toothache) = 0.2 ),再相除得到的结果相同,但思路更清晰。

独立性与条件独立性 🔗

理解了如何操作分布后,我们来看看变量间的关系如何帮助我们简化问题。

独立性

如果两个变量 XY 相互独立,那么知道其中一个变量的值不会影响另一个变量的概率分布。

公式
[
P(\textbf{X}, \textbf{Y}) = P(\textbf{X}) P(\textbf{Y})
]
等价于:
[
P(\textbf{X} | \textbf{Y}) = P(\textbf{X}) \quad \text{且} \quad P(\textbf{Y} | \textbf{X}) = P(\textbf{Y})
]

意义
独立性允许我们将联合分布分解为更小的因子乘积,从而指数级地减少存储和计算量。例如,如果32个变量完全独立,我们只需要存储32个单变量分布,而不是2^32个组合的联合分布。

条件独立性

条件独立性更为常见和有用。它指的是,在给定第三个变量 Z 的条件下,两个变量 XY 相互独立。

公式
[
P(\textbf{X}, \textbf{Y} | \textbf{Z}) = P(\textbf{X} | \textbf{Z}) P(\textbf{Y} | \textbf{Z})
]
等价于:
[
P(\textbf{X} | \textbf{Y}, \textbf{Z}) = P(\textbf{X} | \textbf{Z})
]

意义与示例
考虑牙痛(Toothache)、蛀牙(Cavity)和探针卡住(Catch)的例子。牙痛和探针卡住都依赖于是否蛀牙,但在给定是否蛀牙的条件下,牙痛和探针卡住是独立的。知道一个人牙痛,并不会改变在已知蛀牙情况下探针卡住的概率。
这反映了一个典型的“因果模型”:一个原因(蛀牙)可以导致多个独立的效应(牙痛、探针卡住)。

重要性
利用条件独立性可以极大地简化联合分布的表示和计算。对于1个原因和n个独立效应的情况,联合分布可以从需要约 (2^n) 个参数,简化为只需要 (2n+1) 个参数(每个效应给定原因的条件分布有2个参数,加上原因的先验分布1个参数)。


贝叶斯规则及其应用 🔄

最后,我们学习一个强大的规则,它允许我们将因果推理转换为诊断推理。

贝叶斯规则

贝叶斯规则是条件概率定义的一个简单代数变形。

公式
[
P(\textbf{Y} | \textbf{X}) = \frac{P(\textbf{X} | \textbf{Y}) P(\textbf{Y})}{P(\textbf{X})}
]
在应用中,我们常使用归一化形式:
[
P(\textbf{Y} | \textbf{X}) = \alpha P(\textbf{X} | \textbf{Y}) P(\textbf{Y})
]
其中 ( \alpha ) 是归一化常数。

应用:从因果推理到诊断推理

这是贝叶斯规则最重要的应用之一。通常,我们容易获得“因果”知识,即给定原因时出现某种证据的概率 ( P(Effect | Cause) )。但我们需要的是“诊断”知识,即观察到证据时推断原因的概率 ( P(Cause | Effect) )。

示例
假设:

  • 脑膜炎(M)的先验概率 ( P(M) = 1/50,000 )。
  • 脖子僵硬(S)的先验概率 ( P(S) = 1/1000 )。
  • 已知患脑膜炎时脖子僵硬的概率 ( P(S | M) = 0.7 )。
    那么,当观察到脖子僵硬时,患脑膜炎的概率为:
    [
    P(M | S) = \alpha P(S | M) P(M) = \alpha \times 0.7 \times (1/50,000)
    ]
    归一化后(与 ( P(\neg M | S) ) 比较),这个概率仍然非常低(约0.0014)。这说明了因果概率和诊断概率可能有巨大差异,原因在于先验概率的不同。

朴素贝叶斯模型

这是一个基于条件独立性假设的实用模型。它假设所有观测证据(效应)在给定原因的条件下都是相互独立的。

公式
[
P(Cause | Effect_1, ..., Effect_n) = \alpha P(Cause) \prod_{i=1}^{n} P(Effect_i | Cause)
]

优势
该模型将复杂的联合概率计算简化为多个简单因子的乘积,使计算从指数级复杂度降至线性复杂度。尽管独立性假设在现实中可能不完全成立,但它常常是一个有效的近似,广泛应用于分类、诊断等任务。


综合示例:Wumpus世界中的概率推理 🎯

为了展示上述所有技巧如何协同工作来解决复杂问题,我们分析一个来自教材的“Wumpus世界”变体示例。

问题设定

  • 在一个4x4网格中,智能体探索环境。
  • 每个格子可能有一个陷阱(Pit),概率为0.2,且格子间独立。
  • 智能体进入一个格子后,能感知到相邻格子(东西南北)是否有微风(Breeze),有陷阱的格子必然在其所有相邻格子产生微风。
  • 已知:智能体从(1,1)出发,已访问(1,1), (1,2), (2,1),这些格子安全(无陷阱),且(1,2)和(2,1)有微风。
  • 查询:智能体下一步应该去(1,3)还是(2,2)?需要计算 ( P(P_{1,3} | 已知证据) ) 和 ( P(P_{2,2} | 已知证据) )。

解决思路(以计算 ( P(P_{1,3} | evidence) ) 为例)

  1. 定义变量:共有16个Pit变量和13个Breeze变量,但只有与当前证据和查询相关的变量是重要的。
  2. 应用贝叶斯规则与归一化
    [
    P(P_{1,3} | evidence) = \alpha P(P_{1,3}, evidence)
    ]
  3. 边缘化隐藏变量:需要对其他所有未知的Pit变量求和,这最初看起来需要求和2^12 ≈ 4000项。
  4. 利用条件独立性简化:关键洞察是,在给定“边界”变量(即与已知有微风的格子相邻的未知格子,本例中是(1,3)和(2,2))的条件下,已知的微风证据与地图上其他遥远格子的陷阱状态独立。
  5. 重新表述问题:通过代数变换,将求和项分解,使得对“其他”遥远格子的庞大求和变成了一个概率和为1的项,从而被消去。
    [
    P(P_{1,3}, evidence) = \alpha' P(P_{1,3}) \sum_{fringe} P(evidence | P_{1,3}, fringe) P(fringe)
    ]
  6. 计算简化后的问题:现在求和只针对“边界”变量((1,3)和(2,2)),只有2^2=4种组合。对于每种组合:
    • ( P(fringe) ) 很容易计算(独立概率的乘积)。
    • ( P(evidence | P_{1,3}, fringe) ) 是确定性的(0或1),取决于该组合是否能产生观察到的微风模式。
  7. 得到结果:计算这4项的和,再进行归一化,最终得到 ( P(P_{1,3} = true | evidence) \approx 0.31 )。类似地,可以计算出 ( P(P_{2,2} = true | evidence) ) 更高,因此选择去(1,3)更安全。

总结技巧
这个例子综合运用了:

  • 归一化:避免直接计算证据概率。
  • 贝叶斯规则:将问题转化为联合概率的计算。
  • 条件独立性:识别出给定边界变量后,证据与远方变量的独立性,从而实现了从求和4000项到仅求和4项的戏剧性简化。

总结 📝

本节课中我们一起学习了概率推理的核心工具:

  1. 边缘化:通过对隐藏变量求和来获取变量子集的分布。
  2. 归一化:一种通过最后一步缩放来计算条件分布的高效技巧,避免了复杂的分母计算。
  3. (条件)独立性:识别变量间的独立关系,可以指数级地简化概率模型的表示和计算。条件独立性在因果模型中尤为常见。
  4. 贝叶斯规则:一个简单的公式,却是连接因果知识与诊断知识、实现反向推理的桥梁。
  5. 综合应用:通过Wumpus世界的复杂例子,我们看到了如何将这些技巧结合起来,通过智能地利用条件独立性和代数变换,将原本难以处理的计算问题变得可解。

掌握这些概念是理解后续更高级主题(如贝叶斯网络)的基础,它们构成了在不确定性环境下进行理性推理的数学核心。

004:贝叶斯网络

在本节课中,我们将要学习概率推理的核心工具——贝叶斯网络。我们将探讨其基本定义、全局与局部语义、关键性质,并了解如何构建贝叶斯网络模型。

概述

贝叶斯网络是一种用于表示随机变量间依赖关系的图模型。它通过有向无环图(DAG)来编码变量间的条件独立性,从而能够紧凑地表示复杂的联合概率分布。本节课我们将学习其基本概念、语义解释以及建模方法。

贝叶斯网络的定义

贝叶斯网络本质上是一个有向无环图。图中每个节点代表一个随机变量(可以是离散的或连续的,本章主要讨论离散变量,尤其是二元变量,这主要是为了方便举例)。节点之间的有向边连接成对的变量。

按照惯例,我们采用自上而下的绘图方式,即父节点位于子节点之上。如果存在一条从节点 X 指向节点 Y 的有向边,则称 X 是 Y 的父节点。

除了图结构,每个节点 X 还关联着一个条件概率分布:P(X | Parents(X))。这个分布量化了在给定其父节点取值的情况下,该节点取各个值的概率。如果一个节点没有父节点,则其关联的是一个先验概率分布 P(X)

条件概率分布通常以条件概率表(CPT)的形式给出。对于二元变量,CPT列出了在父节点取值的每一种可能组合下,该节点为“真”的概率。为“假”的概率可以通过互补(1 - P(True))得到。

贝叶斯网络的关键在于,它显式地表示了变量间的条件依赖关系。通常,一条从 X 指向 Y 的边意味着 X 对 Y 有直接的因果影响。

一个运行示例

我们将使用一个经典的“盗窃-地震-警报”例子作为贯穿本章的运行示例。该场景包含五个二元变量:

  • Burglary (B): 入室盗窃发生。
  • Earthquake (E): 发生地震。
  • Alarm (A): 警报响起。
  • JohnCalls (J): John打电话报警。
  • MaryCalls (M): Mary打电话报警。

其网络结构如下图所示,反映了因果知识:盗窃和地震可能直接导致警报响起,而警报响起可能导致John和Mary打电话。

每个节点的CPT提供了具体的概率数值。例如:

  • P(B = true) = 0.001, P(E = true) = 0.002
  • P(A = true | B=true, E=true) = 0.95
  • P(J = true | A=true) = 0.90, P(J = true | A=false) = 0.05
  • P(M = true | A=true) = 0.70, P(M = true | A=false) = 0.01

全局语义

上一节我们介绍了贝叶斯网络的结构化表示,本节中我们来看看如何从整体上理解其含义。全局语义揭示了贝叶斯网络如何完整地定义整个系统的联合概率分布。

贝叶斯网络提供了一个非常紧凑的联合概率分布表示方法。其核心优势在于,它利用了“每个变量只直接依赖于有限数量的其他变量”这一观察结果,从而避免了显式列出所有变量组合的指数级开销。

设网络中有 n 个变量,每个变量最多有 k 个父节点。完整网络所需指定的参数数量上界为 n * 2^k。相比之下,显式表示整个联合分布需要 2^n 个参数。当 n 很大而 k 较小时(这是常见情况),这种从指数级到近似线性的压缩是巨大的。

全局语义指出,贝叶斯网络所定义的完整联合概率分布,等于所有节点条件概率分布的乘积:

P(X1, X2, ..., Xn) = Π_i P(Xi | Parents(Xi))

这个等式的成立基于链式法则以及对变量进行一种特殊的排序:任何满足“父节点出现在子节点之前”的全序排列。在这种排序下应用链式法则,并利用贝叶斯网络所蕴含的条件独立性(即每个变量在给定其父节点时,条件独立于其所有非后代节点),就可以推导出上述乘积形式。

让我们通过一个计算示例来理解。假设我们想计算概率 P(J=true, M=true, A=true, B=false, E=false)

根据全局语义的乘积公式,我们有:
P(j, m, a, ¬b, ¬e) = P(j|a) * P(m|a) * P(a|¬b,¬e) * P(¬b) * P(¬e)

代入示例中的CPT数值:
= 0.90 * 0.70 * 0.001 * 0.999 * 0.998
≈ 一个非常小的数值

这个计算表明,我们可以利用网络结构高效地计算联合分布中的任一项。虽然我们通常不会显式计算整个联合分布表,但这个性质保证了贝叶斯网络是完整概率模型的有效表示。

局部语义与条件独立性

理解了全局视角后,我们来看看贝叶斯网络局部性质。局部语义揭示了网络中单个变量与其邻居之间的条件独立关系,这对于高效推理至关重要。

局部语义指出,在贝叶斯网络中,给定其父节点,任意节点 X 条件独立于其所有非后代节点。非后代节点包括除了其子孙(孩子、孩子的孩子等)之外的所有其他节点。

这意味着,在评估一个节点的概率时,如果不知道其子孙的信息,那么只需要关注其父节点的状态即可。子孙的信息可以通过贝叶斯规则反向影响该节点,但那是另一种情况。

另一个重要的局部性质是马尔可夫毯。一个节点 X 的马尔可夫毯包括:其父节点、其子节点、其子节点的其他父节点。性质指出:给定其马尔可夫毯,节点 X 条件独立于网络中所有其他节点

马尔可夫毯构成了节点 X 的一个“隔离层”,一旦知道了毯内所有变量的值,X 的概率就与毯外变量无关。这个性质在近似推理算法中非常有用。

例如,在运行示例中,根据局部语义,P(J | A, B, E, M) = P(J | A),因为给定父节点 A 后,J 独立于非后代节点 B, E, M。根据马尔可夫毯性质,对于节点 Burglary,其马尔可夫毯是 {Alarm, Earthquake}(其子节点Alarm的其他父节点是Earthquake)。因此,P(B | J, M, A, E) = P(B | A, E)

这些条件独立性是贝叶斯网络能够简化推理计算的数学基础。它们允许我们在计算求和时忽略许多变量,将复杂的全局计算分解为更易处理的局部计算。

如何构建贝叶斯网络

掌握了贝叶斯网络的语义和性质后,本节我们来看看如何从实际问题出发构建一个贝叶斯网络。构建一个好的网络是有效进行概率推理的前提。

构建贝叶斯网络涉及两个核心部分:图拓扑结构(变量和边)和条件概率表(CPT)。首要步骤是选择变量的排序。这里有一条黄金法则:遵循因果顺序。即,原因变量应该排在结果变量之前。

构建过程可以概括为以下步骤:

  1. 从第一个变量 X1 开始,它没有父节点。
  2. 对于后续的每个变量 Xi,从所有已添加的变量 {X1, ..., Xi-1} 中,选择一个子集作为其父节点。
  3. 选择父节点的依据是:哪些变量对 Xi 有直接影响(直接原因)。这需要利用领域知识来区分直接原因和间接原因。
  4. 为每个节点 Xi 指定条件概率分布 P(Xi | Parents(Xi))

如果按照因果顺序构建,并且正确识别了直接依赖关系,那么得到的网络通常会比较稀疏(边较少),从而更紧凑,后续推理也更高效。这是因为因果顺序天然地契合了条件独立性的假设。

反之,如果选择一个糟糕的排序(例如,将结果排在原因之前),为了正确表示所有依赖关系,你可能需要添加许多额外的边。例如,在运行示例中,如果按 MaryCalls, JohnCalls, Alarm, Burglary, Earthquake 的顺序构建,你会发现 JohnCalls 需要依赖于 MaryCalls,Alarm 需要同时依赖于 JohnCalls 和 MaryCalls,等等。最终的网络会更复杂,CPT 表更大,失去了紧凑性的优势。

因此,构建贝叶斯网络不仅是一个技术过程,更是一个建模艺术,深刻理解问题领域的因果关系是关键。

总结

本节课中,我们一起学习了贝叶斯网络的基础知识。我们首先定义了贝叶斯网络是一种表示变量间条件依赖关系的有向无环图。接着,我们探讨了其全局语义,即网络通过局部条件概率的乘积定义了完整的联合概率分布,并展示了其如何实现概率模型的紧凑表示。然后,我们学习了局部语义和马尔可夫毯等条件独立性性质,这些是进行高效概率推理的理论基础。最后,我们介绍了构建贝叶斯网络的方法,强调了遵循因果顺序对于获得简洁有效模型的重要性。贝叶斯网络将图论的直观性与概率论的严谨性相结合,为处理大规模不确定性问题提供了强大的框架。

005:贝叶斯推断

在本节课中,我们将学习如何构建贝叶斯网络,并利用它进行概率推断。我们将从回顾网络构建的关键原则开始,然后深入探讨一种实用的参数估计方法——噪声或分布。最后,我们将学习如何执行基本的枚举推断,并了解其计算挑战和优化方法。

回顾:贝叶斯网络的构建原则

上一节我们讨论了如何构建贝叶斯网络。其核心要点是,构建网络时,应基于领域知识,并遵循一个智能的顺序

贝叶斯网络本质上反映了因果关系的流向。它利用由因果关系诱导出的偏序关系,其中原因总是出现在其直接结果之前。这是你必须利用的基本原则。

因此,当你想用贝叶斯网络表示一个系统时,从图形角度看,你必须选择一个顺序,并且必须遵循因果关系。这意味着,原因必须出现在其直接结果之前。这是一条需要牢记的基本经验法则。

一旦确定了顺序,你逐步添加每个新节点,并选择其父节点——即该节点的直接原因。如果你能确保 P(Xi | 所有前序节点) = P(Xi | 父节点),那么你就保证了全局语义的一致性。

贝叶斯网络类似于软件工程中的UML图,它是一种形式化、描述和建模领域知识的方法。如果你拥有领域知识,你应该了解变量间的依赖关系,并据此绘制图表。如果你没有领域知识,有时可以通过机器学习自动完成,即利用大量数据样本来学习依赖关系和概率。

关键在于选择正确的顺序。如果顺序选择不当,最终得到的网络会非常糟糕。经验法则是:始终将原因放在结果之前。注意,通常因果顺序是偏序,可能两个元素间没有因果依赖。如果是这样,你可以选择任意一个扩展了该偏序的全序。

参数估计:噪声或分布

接下来我们讨论概率参数。这些概率通常基于你的知识进行估计。如今,你可以通过机器学习,利用大量统计数据来推断概率。即使在机器学习普及之前,许多领域(如医学诊断)也积累了描述事件间相互关系的大量统计数据,并据此估计条件概率表。

有一种非常简单的方法来构建条件概率表,称为噪声或分布。它并不总是精确的,但在许多情况下是一个合理的近似,并且是进行初步尝试的好方法。

其核心思想是什么?对于每一个父子关系,你需要找出相关的概率。如果你有海量数据,那很好。但如果你没有这些工具,只想用较少的信息进行实际估算,一个很好的起点或常用技术就是噪声或分布。

你假设原因在导致结果时彼此不交互。你已经绘制好了图表,现在试图找出某个节点的条件概率表。你做出的一个常见(有时是近似)假设是:原因彼此独立地导致结果。

首先,你假设所有父节点是且仅是该节点的全部原因。这就带来了第一个问题:有时你并不知道导致某个结果的所有可能原因。例如,导致高烧的原因,你可能只知道一个常见原因列表,但并非全部。

解决这个问题的标准技巧是添加一个所谓的泄露节点,一个代表“其他所有原因”的父节点。这样,你就包含了该结果的所有可能原因。这意味着,当所有已知原因都为假时,结果发生的概率为0(即,不发生结果的概率为1)。换句话说,这些就是全部且仅有的可能原因。

然后,你假设独立失效概率。这意味着,在给定某些原因为真、其他为假的情况下,结果不发生的概率,是各个原因单独为真时结果不发生概率的乘积。

我们用 qi 表示第 i 个原因单独为真时结果不发生的概率。在噪声或分布中,我们倾向于将结果不发生的概率计算为独立的。因此,结果不发生的概率,在给定所有原因子集的情况下,等于各个相关原因对应的 qi 的乘积。

这是一个在许多场景下都相当可靠且广泛使用的模型。在这种假设下,你所需的数据量大大减少。对于一个有 k 个父节点(原因)的节点,你只需要 kqi 值,而不是完整的 2^k - 1 个条件概率值。所有其他组合的概率都可以通过乘积和补运算自动计算出来。

贝叶斯推断:枚举法

现在,假设我们已经有了一个贝叶斯网络。网络是表示多变量联合分布的一种非常紧凑的方式,它利用了变量间的条件独立性。

那么,我们如何利用这一点进行推断?典型的查询形式是 P(X | E = e)。其中,X 是查询变量(可以是一个或多个),E 是证据变量(其取值 e 已知),Y 是其他未知变量(也称为隐藏变量)。

例如,我想计算盗贼入室 B 的概率,给定证据:约翰打电话 J 和玛丽打电话 M。我不知道是否发生了地震 E,也不知道警报是否响过 A。查询变量是 Burglary,证据变量是 JohnCallsMaryCalls,隐藏变量是 EarthquakeAlarm

典型的做法是使用枚举法进行推断。我们应用上一章学到的知识到贝叶斯网络定义的分布上。

关键点是,当我们有 P(X | E) 时,我们计算一个非归一化的分布 P(X, E),然后通过归一化常数 α 进行归一化。αP(E),在所有 X 的取值上对 P(X, E) 求和即可得到。

为了计算 P(X, E),我们需要对隐藏变量 Y 求和:P(X, E) = Σ_y P(X, E, Y)。这时,使用贝叶斯网络的优势就体现出来了。因为 P(X, E, Y) 可以利用全局语义,写成先验概率和条件概率的乘积形式,这大大减少了计算量。

以盗贼网络为例,我们想计算 P(B | j, m)。我们需要对隐藏变量 EA 求和:P(B, j, m) = Σ_e Σ_a P(B, e, a, j, m)。应用全局语义,将其分解为 P(B) * P(e) * P(a | B, e) * P(j | a) * P(m | a) 的乘积。

然后,我们可以进行代数简化。例如,P(B) 不依赖于 ea,可以提到求和符号外面。P(e) 不依赖于 a,可以提到对 a 的求和外面。这样,计算就变成了 P(B) * Σ_e P(e) * Σ_a P(a | B, e) * P(j | a) * P(m | a)

我们需要为 B 的每个取值(真/假)分别计算这个表达式。计算过程涉及嵌套求和,每次遇到对变量求和时,我们都需要分支计算其每个取值。这本质上是一种递归搜索。

这种计算的一个后果是,其复杂度会随着隐藏变量数量呈指数级增长。因为对 Y 求和意味着对 Y 中所有变量取值的所有组合进行求和。如果 Y 包含 k 个布尔变量,就有 2^k 个求和项。我们的目标是尽可能地进行因子分解,以避免这种完全的枚举。

优化推断:避免重复计算

然而,不出所料,最坏情况下的计算步骤仍然是指数级的。这是因为贝叶斯网络中的概率推断是 NP 难问题。我们需要使用许多技巧来优化。

这种计算效率低下的一个来源是,有时我们会重复相同的计算多次。为什么会这样?这是利用条件独立性所带来的一个副作用。

在我们之前的计算示例中,我们注意到乘积项 P(j | a) * P(m | a) 被计算了两次:一次在 E = true 的分支,一次在 E = false 的分支。同样,P(j | ¬a) * P(m | ¬a) 也被计算了两次。这是因为 JohnCallsMaryCalls 的概率只依赖于 Alarm,而不依赖于 Earthquake。这是贝叶斯网络条件独立性的好处,但在朴素枚举中导致了重复计算。

如何解决这个问题?本质上,我们需要将计算从树形结构转换为有向无环图结构,以便共享子计算。这类似于在搜索中,用图搜索(带缓存)代替树搜索

具体方法是:在计算子表达式时,不仅计算结果,还在返回结果前将其缓存到一个哈希表中。之后,每当需要计算某个表达式时,先检查哈希表中是否已有缓存结果。如果有,直接使用缓存值,避免重复计算。这就是所谓的记忆化

你的教材用另一种方式解释了这一点,称为变量消元法。其思想是,通过改变求和顺序和利用因子分解,尽可能地将求和操作向内推,并合并公共因子。

另一种优化是消除不相关变量。有些变量可能与特定查询完全无关,可以提前从求和式中移除。例如,如果某个概率项对某个变量所有取值的求和等于1,那么这项就可以被消去。

总结

本节课中,我们一起学习了贝叶斯推断的核心内容。

我们首先回顾了构建贝叶斯网络的基本原则,即遵循因果顺序。接着,我们学习了一种实用的参数估计方法——噪声或分布,它通过假设原因独立作用来简化条件概率表的构建。

然后,我们深入探讨了如何使用枚举法在贝叶斯网络中进行概率推断,计算诸如 P(查询变量 | 证据) 这样的条件概率。我们看到了计算过程如何利用网络的因子化表示进行简化,但也认识到最坏情况下的指数复杂度挑战。

最后,我们探讨了优化推断的方法,特别是通过缓存中间结果(记忆化/图搜索)来避免重复计算,以及通过识别和消除不相关变量来简化计算过程。这些技术对于在实际中处理复杂的贝叶斯网络至关重要。

006:理性智能体

在本节课中,我们将学习人工智能的核心概念之一:理性智能体。我们将探讨智能体的定义、它与环境的交互方式,以及如何评估其行为的理性。

概述

智能体可以被视为一个通过传感器感知环境,并通过执行器对环境施加影响的系统。其核心目标是基于感知序列,选择能够最大化其性能度量的行动。理解智能体的结构和工作原理是设计有效人工智能系统的第一步。

智能体与环境的交互

上一节我们介绍了人工智能的宏观视角,本节中我们来看看智能体的具体模型。

一个智能体可以被形式化地定义为一个函数,它将感知序列映射到行动。

公式行动 = 智能体函数(感知序列)

在实践中,这个函数通常由一个程序或算法来实现。智能体通过传感器接收环境信息(感知),经过内部处理(可能是基于程序或算法),然后决定并执行一个行动,旨在达成某个目标。

理性智能体的定义

理解了智能体的基本交互模型后,我们需要明确什么是“理性”的智能体。

理性智能体是指对于每一个可能的感知序列,它都会选择那个能使其性能度量最大化的行动。这里的性能度量是由设计者预先定义的,用于衡量智能体在环境中成功程度的客观标准。

理性取决于四件事:

  1. 性能度量:定义成功的标准。
  2. 智能体对环境的先验知识。
  3. 智能体可以执行的动作。
  4. 智能体到当前为止的完整感知序列。

理性智能体并非全知全能,它只能根据已获得的感知信息来行动。同时,理性行为也不一定是完美或最优的,因为它可能受到信息不完整或计算资源有限的限制。

任务环境规范(PEAS)

为了设计一个理性智能体,我们必须首先明确其任务环境。这可以通过PEAS描述来系统化地定义。

PEAS代表:

  • Performance(性能度量)
  • Environment(环境)
  • Actuators(执行器)
  • Sensors(传感器)

以下是几个任务环境的PEAS描述示例:

吸尘器世界

  • 性能度量:每单位时间清洁的方格数。
  • 环境:由方格A和B组成的房间。
  • 执行器:向左移动、向右移动、吸尘、关闭电源。
  • 传感器:位置传感器、污垢传感器。

自动驾驶汽车

  • 性能度量:安全性、行程时间、舒适度、交通规则遵守度、燃油效率。
  • 环境:城市道路、高速公路、包含其他车辆、行人、交通灯、天气变化的复杂场景。
  • 执行器:方向盘、油门、刹车、信号灯、喇叭。
  • 传感器:摄像头、激光雷达、雷达、GPS、惯性测量单元。

环境性质分类

不同的任务环境具有不同的性质,这些性质直接影响智能体的设计复杂度。我们可以从以下几个维度对环境进行分类:

以下是主要的环境分类维度:

  • 完全可观察 vs. 部分可观察:智能体的传感器是否能获取环境状态的全部信息。
  • 单智能体 vs. 多智能体:环境中是否存在其他也作为决策者的智能体(可能是协作的或竞争的)。
  • 确定性 vs. 随机性:下一个环境状态是否完全由当前状态和智能体的行动决定。
  • 片段式 vs. 延续式:智能体的行动是否被划分为独立的“片段”,当前行动不影响未来片段。
  • 静态 vs. 动态:环境在智能体思考时是否会自行改变。
  • 离散 vs. 连续:环境的状态、时间和感知值是否是离散且有限的。

最简单的环境是完全可观察、确定性的、片段式的、静态的、离散的单智能体环境。现实世界中的环境(如自动驾驶)则通常是部分可观察、随机的、延续式的、动态的、连续的多智能体环境,这给智能体设计带来了巨大挑战。

总结

本节课中我们一起学习了理性智能体的核心概念。我们首先将智能体定义为一个将感知映射到行动的函数。然后,我们探讨了理性智能体的含义,即它旨在基于给定的感知序列最大化其性能度量。为了系统化地设计智能体,我们引入了PEAS框架来描述任务环境。最后,我们学习了从多个维度(如可观察性、确定性、动态性等)对环境进行分类,这些性质决定了智能体设计的复杂度和适用算法。理解这些基础概念是构建更高级人工智能系统的基石。

007:理性智能体

概述

在本节课中,我们将学习理性智能体的核心概念、不同类型及其架构。我们将探讨智能体如何通过感知、内部状态和行动来与环境互动,并理解从简单的反射型智能体到更复杂的基于目标或效用的智能体的演进过程。


任务环境与知识状态

上一节我们介绍了不同类型的任务环境。本节中,我们来看看另一个关键特征:智能体对环境的知识状态

知识状态指的是智能体对环境规则的了解程度,这与环境的可观察性是不同的概念。

  • 已知环境:智能体完全了解环境的所有规则和行动结果。例如,在棋盘游戏中,规则是明确的。
  • 未知环境:智能体不了解环境规则,需要通过探索和学习来获取知识。例如,一个初次进入房间的扫地机器人。

需要注意的是,环境“已知”与“完全可观察”是正交的概念。一个已知环境可能并非完全可观察(例如,你知道扑克牌规则,但不知道对手的手牌);同样,一个未知环境也可能是完全可观察的(例如,你第一次看到一个游戏的所有组件,但还不知道规则)。


智能体的构成

理解了环境后,我们来看看智能体本身是如何构成的。一个智能体通常由架构智能体程序组成。

  • 架构:指智能体运行的物理或软件平台(例如,机器人硬件或聊天软件的运行环境)。
  • 智能体程序:这是人工智能设计的核心,它根据感知历史来决定执行什么行动。

这里需要区分两个概念:

  • 智能体函数:一个抽象的数学概念,将完整的感知历史映射到行动。
  • 智能体程序:一个具体的实现,它在每一步通常只处理当前感知。为了考虑历史信息,程序需要维护一个内部状态(即记忆)。

一个最基础的例子是基于表格的智能体,它直接将每一个可能的感知序列通过查表对应到一个行动。这只在理论上有意义。


智能体类型(全):简单反射型智能体

现在,我们来具体看看几种主要的智能体程序类型。首先是最简单的:简单反射型智能体

这类智能体没有记忆,其行动仅基于最新的感知。它的工作原理是“感知-反应”。

架构公式

行动 = 条件-行动规则集合(当前感知到的世界状态)

以下是其工作流程:

  1. 传感器从环境获取感知信息。
  2. 感知信息被转化为对当前世界状态的描述(这要求环境是完全可观察的,或者至少智能体认为感知包含了决策所需的全部信息)。
  3. 智能体根据一组预设的“条件-行动”规则(例如 if-then 语句),直接选择与当前状态匹配的行动。

优点:实现简单,计算高效。
缺点:功能有限,严重依赖于“完全可观察”这一强假设,无法处理部分可观察的环境。


智能体类型(二):基于模型的反射型智能体

当环境是部分可观察时,简单反射型智能体就失效了。这时我们需要更强大的智能体:基于模型的反射型智能体

这类智能体的核心是拥有一个内部状态,用于维护一个世界模型,以追踪那些无法直接感知的信息。

架构公式

内部状态 = 更新函数(上一内部状态, 当前感知, 上一行动)
行动 = 条件-行动规则集合(内部状态)

以下是其工作流程:

  1. 传感器获取部分感知信息。
  2. 智能体结合当前的内部状态最新的感知以及它自己上次执行的动作,利用内部模型来预测世界如何演化,从而更新其内部状态。这个内部状态代表了它对完整世界状况的最佳估计。
  3. 与简单反射型智能体类似,它根据更新后的内部状态,通过规则匹配来决定行动。

关键点:智能体通过模型和记忆来弥补部分可观察性带来的信息缺失。例如,司机转头看后视镜时,大脑会基于记忆和模型假设正前方的路况短时间内不会突变。


智能体类型(三):基于目标的智能体

前面两种智能体主要关注“生存”或对环境的即时反应。而基于目标的智能体则拥有明确的目标,其行动旨在实现这些目标。

这类智能体在基于模型的基础上,增加了目标信息。决策不再仅仅是“对当前状态做出反应”,而是需要寻找能达成目标的行动序列。

核心机制:这涉及到规划搜索。智能体需要:

  1. 考虑未来的状态,而不仅仅是下一步。
  2. 生成一个能达到目标的行动序列(即规划)
  3. 执行时,按计划采取行动,并根据实际感知到的状态与预期是否一致,来决定是继续执行计划还是重新规划。

优点:更加通用和灵活。不需要为所有可能情况预先编写规则,而是能针对不同目标自动生成计划。
缺点:规划过程通常计算量非常大(在计算复杂性上属于 PSPACE 完全问题,甚至比 NP 难问题更复杂)。


智能体类型(四):基于效用的智能体

基于目标的智能体有时会遇到问题:目标可能相互冲突,或者目标本身不是简单的“是/否”达成,而是有完成度的区别。这时,基于效用的智能体提供了更优的解决方案。

这类智能体使用一个效用函数来衡量状态或状态序列的“好坏”,该函数将状态映射到一个实数值。

决策原则:选择能最大化期望效用的行动。效用函数本质上是智能体内部实现的性能度量

与基于目标智能体的关系:两者并非严格割裂。许多规划系统本身就包含了效用优化。基于效用的方法能更好地处理目标冲突和折衷(例如帕累托最优),但实现起来也更复杂。


所有智能体的增强:学习型智能体

学习能力与上述所有智能体类型都是正交的,可以叠加在任何一种架构之上,形成学习型智能体

其核心思想是:智能体的知识(如规则、模型参数)并非完全由程序员预先设定,而是可以通过与环境的互动来自我改进。

学习型智能体架构包含四个主要部分

  1. 性能元件:即之前提到的任一种智能体(反射型、基于模型、基于目标等),它负责选择行动。
  2. 评价元件:将环境反馈(实际结果与预期的对比)转化为对性能元件表现的评价(好或坏)。
  3. 学习元件:根据评价元件的反馈,修改性能元件(例如,调整规则、改变参数)。
  4. 问题生成器:负责提出新的、有探索性的行动建议,以获取新信息,从而促进学习。它解决了“负面反馈只告诉你什么不好,但没告诉你怎么做才好”的问题。

这种通过试错和反馈进行学习的方式,使得智能体更加鲁棒,能更好地适应真实环境。现代人工智能,尤其是机器学习,很大程度上就建立在这种学习范式之上。


状态表示方法

在讨论智能体时,“状态”是一个核心概念。状态有三种主要的表示方法,其表达能力和计算复杂度依次增加:

  1. 原子表示:每个状态都是一个不可分割的整体,用一个标签(如“状态A”、“状态B”)表示。只能区分状态是否相同,无法表达相似性。表达能力最弱,但有时用于抽象分析。

    • 示例:用“在特伦托”、“在维罗纳”表示位置状态。
  2. 要素表示:状态由一组状态变量(属性)的值构成,就像一个记录或结构体。

    • 公式状态 = {变量1: 值1, 变量2: 值2, ... , 变量N: 值N}
    • 示例{位置: 特伦托, 速度: 50km/h, 天气: 晴}
    • 特点:可以表示部分信息(某些变量未知),可以计算状态间的相似度(如汉明距离)。这是最常用、最直观的表示方法,命题逻辑、贝叶斯网络、大多数机器学习都基于此。
  3. 结构化表示:状态中包含对象以及对象之间的关系,通常使用一阶逻辑等知识表示语言。

    • 示例:“所有凡人都会死”可以表示为:∀x (人(x) → 会死(x))
    • 特点:表达能力最强,可以非常紧凑地表示复杂关系和抽象概念。常用于知识库、自然语言理解等领域。推理复杂度也最高。

一般规律:表示语言的表达能力越强,在其上的推理计算通常就越复杂。


总结

本节课中,我们一起学习了理性智能体的核心框架。我们从对任务环境和知识状态的分类开始,逐步深入探讨了四种主要的智能体类型:从仅对环境做出即时反应的简单反射型智能体,到通过内部模型处理部分可观察性的基于模型的反射型智能体,再到拥有明确目的并能进行规划的基于目标的智能体,以及能通过效用函数处理复杂偏好和权衡的基于效用的智能体。我们还了解到,学习能力可以增强任何类型的智能体,使其通过经验自我改进。最后,我们介绍了原子要素结构化这三种状态表示方法,理解了它们在表达能力和计算成本上的权衡。这些概念为我们后续深入学习搜索、规划等具体人工智能技术奠定了坚实的基础。

008:搜索智能体

概述

在本节课中,我们将要学习人工智能中的一个核心组成部分:基于搜索的推理。我们将探讨如何将一个问题或任务形式化为在状态空间中的搜索,并介绍搜索智能体的基本框架和关键概念。

问题求解即搜索

上一节我们介绍了人工智能的基本范式,本节中我们来看看一个具体且核心的推理方法:搜索。基于搜索的推理是人工智能的基础部分。这并非对算法理论中图搜索的简单重复,而是从一个不同的视角看待问题。我们并非在一个现成的图中搜索,而是处理一个具有任意表示形式的问题。搜索空间是系统或环境可能状态的集合,图只是一种有时为了方便说明而使用的表示形式。

搜索智能体的基本框架

以下是构建一个搜索问题所需的核心要素:

  • 目标:首先需要明确目标。目标通常可以表示为一组明确的状态,或者是一个表达式,其可能的解释就是我们所期望的状态集合。
  • 问题形式化:我们需要定义搜索空间的表示形式,以及合法的行动(或称为转移函数)。这定义了如何从一个状态转移到另一个状态。
  • 搜索过程:一旦问题被形式化,我们就通过某种搜索算法来寻找解决方案。结果通常是一个行动序列或状态转移序列。
  • 计划执行:在头脑(或智能体的CPU)中制定好计划后,再将行动序列交给执行器去执行。搜索是在执行前离线完成的思维过程,这与通过试错在现实世界中探索有本质区别。

一个简化示例:罗马尼亚旅行问题

为了理解这些概念,我们来看一个经典的简化例子。假设你正在罗马尼亚度假,当前在阿拉德,而你的航班明天将从布加勒斯特起飞。你的目标是明天到达布加勒斯特。

在这个例子中,我们做了以下简化假设:

  • 原子状态表示:状态就是城市的名称(例如“我在阿拉德”)。
  • 确定性环境:你总是知道自己在哪里。
  • 离散环境:只考虑到达城市时的状态,不考虑途中。
  • 有限行动:从每个城市,你只能开车前往与之直接相连的邻近城市。
  • 已知环境:智能体通过某种方式(例如一张地图或一个查询表)知道从每个状态可以到达哪些后继状态。

关键点:这个例子用图来表示是为了教学方便。在真实的人工智能问题中,状态空间可能极其庞大(例如有 2^100 个状态),无法显式地构建出整个图。我们通常只有一种方法,给定一个状态,能计算出它的可能后继状态。这是AI中的搜索与经典图算法搜索的根本区别之一。

问题形式化的要素与术语

现在,我们正式定义问题形式化的组成部分,并引入标准术语和符号。

  • 初始状态:搜索开始的状态。在罗马尼亚例子中,就是 In(Arad)
  • 行动函数ACTIONS(s) 返回在状态 s 下可以应用的行动列表。例如,ACTIONS(In(Arad)) 可能返回 {GoTo(Sibiu), GoTo(Timisoara), GoTo(Zerind)}
  • 转移模型RESULT(s, a) 函数,给定状态 s 和行动 a,返回执行行动后到达的新状态。这假设行动是确定性的。例如,RESULT(In(Arad), GoTo(Sibiu)) = In(Sibiu)
  • 目标测试:一个函数或条件,用于检查给定的状态是否是目标状态。目标可以是一个单一状态(如 In(Bucharest)),也可以是一个描述一组状态的复杂条件(如国际象棋中的“将死”状态)。
  • 路径耗散:为每条路径分配一个数值成本。通常,每个行动有一个成本,路径的总成本是路径上各行动成本之和。最优解就是具有最小路径成本的解。

这些要素共同隐式地定义了问题的状态空间(可以想象成一个图)。再次强调,这个图通常太大而无法显式构建,它只是我们思维中的一个抽象。

抽象的重要性

每当我们将一个问题形式化为搜索问题时,我们总是在进行抽象。现实世界包含太多信息,我们需要忽略掉与当前目标无关的细节,只保留解决所必需的信息。

例如,如果目标只是统计房间人数,那么抽象掉每个人的性别、衣着等细节是有效的。但如果目标是分析性别比例,那么抽象掉性别信息就是无效的。一个好的抽象应该在简化问题的同时,保留解决它所需的关键信息。

搜索过程详解

搜索是在智能体内部(大脑或CPU)模拟执行的过程。其核心操作是扩展一个节点:给定一个状态,找出所有可应用的行动,并计算出所有可能的后继状态。

以下是搜索过程涉及的概念:

  • 边界:当前已生成但尚未扩展的节点集合。
  • 探索集:已经扩展过的节点集合(在图搜索中使用)。
  • 搜索策略:决定从边界中选择哪个节点进行扩展的规则。不同的策略(如深度优先、广度优先、代价优先)构成了不同的搜索算法。

搜索算法的一般结构(树搜索)可以用以下伪代码描述:

function TREE-SEARCH(problem) returns solution or failure
    frontier ← {Node(problem.INITIAL)}
    loop
        if frontier is empty then return failure
        node ← POP(frontier) // 根据策略选择节点
        if problem.GOAL-TEST(node.STATE) then return node
        frontier ← frontier ∪ EXPAND(node, problem)

其中 EXPAND 函数生成当前节点的所有后继节点。

树搜索 vs 图搜索

一个重要的区分是树搜索图搜索

  • 树搜索:不记录已访问的状态,可能会多次探索同一状态,导致冗余甚至无限循环。优点是内存占用小。
  • 图搜索:维护一个探索集(通常称为 closed set),避免重新扩展已经遇到过的状态。这避免了循环和冗余,但需要额外的内存来存储探索集。

图搜索的修改体现在:在扩展节点并将后继节点加入边界前,检查该后继节点对应的状态是否已经在探索集中。如果是,则忽略它。

实现注意事项

在实现时,需要区分:

  • 状态:世界配置的表示。
  • 节点:搜索树中的一个数据结构,用于记录状态、父节点、行动、路径成本等信息,是搜索算法的内部构造。

边界通常用特定的数据结构实现,不同的数据结构对应不同的搜索策略:

  • → 深度优先搜索
  • 队列 → 广度优先搜索
  • 优先队列 → 代价优先搜索等

总结

本节课中我们一起学习了人工智能中基于搜索的问题求解方法。我们了解到,搜索是将一个目标形式化为在状态空间中的寻找路径的过程。我们介绍了问题形式化的关键要素:初始状态、行动、转移模型、目标测试和路径耗散。我们通过罗马尼亚旅行问题这个例子说明了这些概念,并强调了抽象在问题形式化中的核心作用。最后,我们探讨了搜索的基本过程,区分了树搜索和图搜索,并简要提及了实现上的考虑。搜索是智能体在行动前进行离线规划的核心能力,是后续学习更复杂推理方法的基础。

009:盲目搜索 🧭

在本节课中,我们将要学习人工智能中的搜索问题,特别是盲目搜索策略。我们将介绍几种基本的搜索算法,分析它们的性能,并理解它们在不同场景下的应用。


概述

搜索是人工智能中的核心问题之一,用于在状态空间中寻找从初始状态到目标状态的路径。本节课我们将重点讨论盲目搜索(也称为无信息搜索),这类算法在搜索过程中不利用任何关于目标位置的额外知识。我们将学习广度优先搜索、深度优先搜索、一致代价搜索等经典算法,并通过关键参数(如完备性、最优性、时间和空间复杂度)来评估它们。


搜索策略的评估参数

在深入具体算法之前,我们首先需要了解用于评估不同搜索策略的几个关键参数。

  • 完备性:如果问题存在解,该策略是否能保证找到解?
  • 时间复杂性:找到解需要多少步(或扩展多少个节点)?
  • 空间复杂性:算法运行需要多少内存来存储状态信息?
  • 最优性:该策略是否能保证找到代价最小的解?(通常我们默认处理最小化问题)

在分析复杂度时,我们通常关注最坏情况,并使用以下符号:

  • b:搜索树的最大分支因子(即一个状态最多能产生的后继状态数)。
  • d:最浅解所在的深度(即到达目标所需的最少步数)。
  • m:状态空间的最大深度(可能为无穷大)。

一个深度为 m 的树,其节点总数上界为 O(b^m)


广度优先搜索 (BFS) 🌊

上一节我们介绍了评估搜索算法的通用参数,本节中我们来看看第一种具体策略——广度优先搜索。

广度优先搜索的核心思想是:总是扩展最浅的未扩展节点。这通过将边界(存储待扩展节点的集合)实现为一个先进先出队列来完成。

算法流程(图搜索版本)

以下是广度优先搜索图搜索版本的核心伪代码逻辑:

function BFS(problem):
    node = Node(problem.INITIAL_STATE) # 用初始状态创建节点
    if problem.GOAL_TEST(node.state):
        return SOLUTION(node)
    frontier = Queue() # 边界初始化为队列
    frontier.push(node)
    explored = Set() # 已探索集合初始化为空

    while not frontier.isEmpty():
        node = frontier.pop() # 取出队列中最老的节点
        explored.add(node.state) # 标记该状态已探索
        for action in problem.ACTIONS(node.state):
            child = CHILD_NODE(problem, node, action)
            if child.state not in explored and child not in frontier:
                if problem.GOAL_TEST(child.state): # 生成时即检查目标
                    return SOLUTION(child)
                frontier.push(child)
    return FAILURE

关键点:目标测试在生成子节点后立即进行,而不是在从边界取出时进行,这可以节省一整层的搜索时间。

BFS的性质分析

以下是广度优先搜索的主要性质:

  • 时间复杂性O(b^d)。在最坏情况下,需要探索到深度 d 的所有节点。
  • 空间复杂性O(b^d)。边界需要存储一整层的节点,内存消耗巨大。
  • 完备性:如果分支因子 b 有限,则完备。
  • 最优性:当所有单步代价相同时(即寻找最少步数解),BFS 是最优的。

BFS 的主要缺点是内存需求过高,在处理大规模问题时可能不切实际。


一致代价搜索 (UCS) ⚖️

上一节我们看到了按“层”搜索的 BFS,本节中我们来看看一种考虑路径代价的搜索策略——一致代价搜索。

一致代价搜索的核心思想是:总是扩展路径代价最小的节点。这通过将边界实现为一个按路径代价 g(n) 排序的优先队列(如最小堆)来完成。它是 BFS 的推广(当所有单步代价相等时,UCS 退化为 BFS)。

算法流程与关键区别

UCS 的算法框架与 BFS 类似,但有几个关键区别:

  1. 边界管理:使用优先队列,按 g(n)(从初始节点到节点 n 的实际路径代价)排序。
  2. 目标测试时机:目标测试在节点从边界中取出准备扩展时进行,而不是在生成时进行。这是为了确保找到的是最优解。
  3. 代价更新:如果新发现的到达某个状态的路径代价更低,则需要更新边界中该节点对应的路径代价。
function UCS(problem):
    node = Node(problem.INITIAL_STATE, path_cost=0)
    frontier = PriorityQueue(order_by=path_cost)
    frontier.push(node)
    explored = Set()

    while not frontier.isEmpty():
        node = frontier.pop() # 取出当前路径代价最小的节点
        if problem.GOAL_TEST(node.state): # 取出时进行目标测试
            return SOLUTION(node)
        explored.add(node.state)
        for action in problem.ACTIONS(node.state):
            child = CHILD_NODE(problem, node, action)
            if child.state not in explored:
                if child not in frontier:
                    frontier.push(child)
                else if child in frontier with higher path_cost:
                    replace the frontier node with child # 更新为代价更低的路径
    return FAILURE

UCS的性质分析

以下是一致代价搜索的主要性质:

  • 时间与空间复杂性:最坏情况下的复杂度可以表示为 O(b^(1 + ⌊C*/ε⌋)),其中 C* 是最优解的代价,ε 是最小的单步代价。这通常比 BFS 的 O(b^d) 更差,但 UCS 处理的是代价不均等的问题。
  • 完备性:如果单步代价均大于某个正数 ε,则完备。
  • 最优性:是。UCS 总能找到代价最小的解。

UCS 是许多更高级搜索算法(如 A* 搜索)的基础框架。


深度优先搜索 (DFS) 🌲

前面我们讨论了以广度或代价为导向的搜索,本节中我们来看看另一种截然不同的策略——深度优先搜索。

深度优先搜索的核心思想是:总是扩展最深的未扩展节点。这通过将边界实现为一个后进先出栈来完成。

算法特点

DFS 的算法结构与 BFS 几乎相同,只需将队列替换为栈。其探索顺序是沿着一条路径深入到底,直到无法继续,然后回溯。

DFS的性质分析

以下是深度优先搜索的主要性质:

  • 时间复杂性:最坏情况下为 O(b^m)。如果 m 远大于 d,且搜索树非常深,这可能比 BFS 差很多。
  • 空间复杂性O(bm)。只需要存储当前分支上的节点,内存消耗远小于 BFS 和 UCS。这是 DFS 的最大优势。
  • 完备性
    • 图搜索版本在有限状态空间中是完备的。
    • 树搜索版本在不含无限深路径或循环的有限状态空间中也是完备的(可通过检查当前路径是否出现重复状态来避免循环)。
  • 最优性:否。DFS 不保证找到最短路径或最小代价解。

在实际应用中,DFS 常以回溯搜索的形式实现,它只生成一个后继节点,并记录状态以在回溯时生成下一个后继,从而进一步节省空间。


迭代加深搜索 (IDS) 🔄

我们已经看到了 BFS(最优但耗内存)和 DFS(省内存但不完备/非最优)的优缺点。本节中我们来看看如何结合两者优势的一种策略——迭代加深搜索。

迭代加深搜索的核心思想是:重复运行深度受限搜索,并逐渐增加深度限制 l。深度受限搜索是 DFS,但在达到预设深度 l 时会停止并回溯。

算法流程

function ITERATIVE_DEEPENING_SEARCH(problem):
    for depth = 0 to INFINITY:
        result = DEPTH_LIMITED_SEARCH(problem, depth)
        if result != CUTOFF: # CUTOFF 表示因深度限制未找到解
            return result

function DEPTH_LIMITED_SEARCH(problem, limit):
    return RECURSIVE_DLS(Node(problem.INITIAL_STATE), problem, limit)

function RECURSIVE_DLS(node, problem, limit):
    if problem.GOAL_TEST(node.state):
        return SOLUTION(node)
    elif limit == 0:
        return CUTOFF
    else:
        cutoff_occurred = False
        for action in problem.ACTIONS(node.state):
            child = CHILD_NODE(problem, node, action)
            result = RECURSIVE_DLS(child, problem, limit - 1)
            if result == CUTOFF:
                cutoff_occurred = True
            elif result != FAILURE:
                return result
        if cutoff_occurred:
            return CUTOFF
        else:
            return FAILURE

IDS的性质分析

以下是迭代加深搜索的主要性质:

  • 时间复杂性O(b^d)。虽然浅层节点会被重复扩展,但大部分工作集中在最深一层,总时间复杂度和 BFS 同阶。
  • 空间复杂性O(bd)。与 DFS 相同,只需要存储当前路径上的节点。
  • 完备性:是。
  • 最优性:当单步代价相同时,是最优的(能找到最浅解)。

IDS 结合了 BFS 的最优性、完备性和 DFS 的空间效率,是处理状态空间大、解深度未知问题时的实用选择。


搜索策略对比与补充

本节课我们一起学习了多种盲目搜索策略,以下是它们的总结对比:

策略 边界数据结构 时间复杂性 空间复杂性 完备性? 最优性? 备注
广度优先搜索 队列 O(b^d) O(b^d) 是(步数) 内存消耗大
一致代价搜索 优先队列 (g(n)) O(b^(1+⌊C*/ε⌋)) O(b^(1+⌊C*/ε⌋)) 是(代价) 代价不均等问题的基础算法
深度优先搜索 O(b^m) O(bm) 有限状态空间是 内存效率高,常以回溯形式实现
深度受限搜索 O(b^l) O(bl) 否(若 d > l IDS 的组件
迭代加深搜索 栈(多次调用) O(b^d) O(bd) 是(步数) 结合 BFS 和 DFS 的优点

其他搜索变体

  • 双向搜索:同时从初始状态和目标状态开始搜索,直到两个搜索 frontier 相遇。理想情况下,时间复杂性可从 O(b^d) 降为 O(b^(d/2))
  • 搜索方向:除了前向搜索,也可以进行后向搜索(从目标状态反向推理),适用于某些特定问题。

在实际应用中,BFS 和 UCS 通常使用图搜索版本以避免重复探索,而 DFS 和 IDS 通常使用树搜索版本以保持其空间效率优势。现代实现也常使用缓存技术来平衡时间和空间开销。


总结

本节课中我们一起学习了人工智能中的盲目搜索策略。我们从评估搜索算法的通用参数开始,依次深入探讨了广度优先搜索、一致代价搜索、深度优先搜索以及结合两者优点的迭代加深搜索。我们分析了每种算法的时间、空间复杂度、完备性和最优性,并理解了它们各自的适用场景。盲目搜索是更高级的启发式搜索的基础,在下一节课中,我们将学习如何利用问题领域的知识来引导搜索,从而更高效地解决问题。

010:A*搜索与启发式函数 🧭

在本节课中,我们将学习如何利用领域知识或启发式信息来引导搜索过程。我们将重点介绍A*搜索算法,这是一种结合了代价和启发式信息的强大且流行的算法,广泛应用于规划等领域。

概述

上一节我们介绍了最佳优先搜索和一致代价搜索。本节中,我们将看看如何将这两种策略的优点结合起来,形成A*搜索算法。其核心思想是:在选择下一个要扩展的节点时,不仅要考虑从起点到达该节点的实际代价,还要考虑从该节点到目标的估计代价

A*搜索算法

A*搜索算法的核心是定义一个评估函数 f(n),用于为每个节点 n 打分。该函数是以下两项之和:

  • g(n):从起始节点到节点 n实际路径代价
  • h(n):从节点 ** n** 到目标节点的估计代价(即启发式函数)。

因此,评估函数定义为:
f(n) = g(n) + h(n)

f(n) 代表了通过节点 n 到达目标的估计总路径代价。A*算法总是优先扩展 f(n) 值最小的节点。

算法实现

A算法的实现与一致代价搜索(UCS)几乎完全相同,唯一的区别在于计算节点优先级时使用的值。在UCS中,我们使用 g(n);在A中,我们使用 f(n) = g(n) + h(n)

以下是算法的简要描述:

  1. 将起始节点放入优先队列(前沿)。
  2. 当队列不为空时:
    • 弹出 f(n) 值最小的节点 n
    • 如果 n 是目标节点,则成功返回路径。
    • 否则,扩展节点 n,生成其后继节点。
    • 对于每个后继节点 n'
      • 计算 g(n') = g(n) + cost(n, n')
      • 计算 f(n') = g(n') + h(n')
      • 如果节点 n' 的状态是首次遇到,或找到了到达该状态的更低 g(n') 值,则将其(或更新后的节点)加入优先队列。

算法示例与比较

为了理解A*的优势,我们将其与一致代价搜索(UCS)和贪婪最佳优先搜索进行比较。

考虑一个简单的图搜索问题。图中标有行动代价,不同算法会做出不同选择。

  • 一致代价搜索(UCS):只考虑到达节点的实际代价 g(n)。它可能会探索一些虽然到达代价低,但离目标很远的路径,导致搜索不够高效。
  • 贪婪最佳优先搜索:只考虑启发式估计 h(n),即节点到目标的“接近程度”。它可能会选择那些看起来离目标很近,但到达代价极高的节点,从而找到次优甚至代价很高的解。
  • A*搜索:结合 g(n)h(n)。它能够避免上述两种极端。例如,一个节点可能 h(n) 很小(离目标近),但 g(n) 很大(到达它很费劲),其 f(n) 值可能反而比另一个 h(n) 稍大但 g(n) 小很多的节点更大。A*会选择后者,实现更好的折中。

通过罗马尼亚地图问题的具体演算可以看到,A*在每一步都选择 f(n) 最小的节点,最终高效地找到了最优路径。

启发式函数的质量

并非所有启发式函数都是好的。为了保证A*算法能找到最优解,并对搜索进行有效剪枝,启发式函数需要满足一些性质。

可采纳性(乐观性)

一个启发式函数 h(n)可采纳的(或称乐观的),如果它永远不会高估从节点 n 到达目标的实际最小代价 h*(n)。即:
h(n) ≤ h*(n) 对所有节点 n 成立。

例如,两点间的直线距离就是一个可采纳的启发式,因为直线是最短路径。

一致性(单调性)

一个启发式函数 h(n)一致的(或称单调的),如果对于每个节点 n 及其任意后继节点 n'(通过行动 a 产生,代价为 c(n, a, n')),满足以下三角不等式:
h(n) ≤ c(n, a, n') + h(n')

这意味着从节点 n 到目标的估计代价,不会大于走一步到 n' 的代价加上从 n' 到目标的估计代价。一致性是可采纳性的一个更强条件。如果 h(n) 是一致的,那么它必然是可采纳的。

一致性带来一个关键性质:沿着任何路径,f(n) 的值是非递减的。随着搜索向目标推进,我们获得的信息越多,f(n) 的估计就越来越接近(但不超过)真实代价。

A*算法的性质

基于启发式函数的可采纳性和一致性,A*算法拥有以下重要性质:

  • 最优性:如果启发式函数 h(n) 是可采纳的,那么树搜索版本的A算法是最优的(能找到代价最小的解)。如果 h(n) 是一致的,那么图搜索版本的A算法也是最优的。
  • 完备性:如果所有行动代价都为正,且启发式函数满足一定条件,A*算法是完备的(只要解存在,就一定能找到)。
  • 效率:A算法的效率高度依赖于启发式函数的质量。启发式函数越好(越接近真实代价 **h(n)**),算法需要扩展的节点就越少。

有效分支因子

我们使用有效分支因子来衡量启发式函数的质量。它表示为了达到相同的搜索效果,一个均匀搜索树所需要的平均分支数。完美启发式(h(n) = h*(n))的有效分支因子为1,意味着搜索沿一条直线进行。启发式越差,有效分支因子越接近问题的真实分支因子,搜索复杂度也越高。

如何设计启发式函数

设计一个好的启发式函数是应用A*算法的关键。以下是几种常见方法:

基于问题知识

利用对特定问题的深入理解来设计。例如,在国际象棋中,可以根据棋子类型和位置赋予不同价值来评估棋局。

松弛问题法

这是一个系统化生成可采纳启发式的通用方法。其核心思想是:通过放松原问题的某些约束,得到一个更容易求解的“松弛问题”。松弛问题的最优解代价,可以作为原问题的一个可采纳启发式。

例如,在八数码问题中:

  • 松弛1:允许将任何棋子移动到任何相邻位置(忽略空白格条件)。这对应着曼哈顿距离启发式。
  • 松弛2:允许将任何棋子直接移动到其目标位置。这对应着错位棋子数启发式。

由于松弛问题有更少的限制,其最优解代价一定不大于原问题的最优解代价,因此由此得到的启发式函数是可采纳的。通常,松弛程度越低,得到的启发式越准确,但计算可能也越复杂。

机器学习方法

现代人工智能中,常使用机器学习(特别是深度学习)来从数据中学习启发式函数。例如,AlphaGo等程序就结合了搜索和神经网络学到的局面评估函数。

总结

本节课中我们一起学习了A*搜索算法及其核心——启发式函数。

  • A*算法通过结合路径实际代价 g(n) 和到目标的估计代价 h(n)(即 f(n)=g(n)+h(n)),有效地引导搜索方向。
  • 为了保证找到最优解,启发式函数 h(n) 需要满足可采纳性(不超估)和更强的一致性(单调性)。
  • A*算法的最优性、完备性和效率都依赖于启发式函数的质量。
  • 设计启发式函数的有效方法包括利用领域知识、构造松弛问题,以及使用机器学习

A*算法因其简洁性和强大性能,至今仍是解决许多组合搜索问题的基石算法。理解其原理和启发式函数的设计,是掌握人工智能搜索技术的关键一步。

011:局部搜索

在本节课中,我们将要学习一种新的搜索范式——局部搜索。我们将看到,当放弃对“解决方案必须是一个动作序列”这一假设时,搜索问题会如何演变,并探索其在解决优化和决策问题上的强大能力。

从经典搜索到局部搜索

上一节我们介绍了经典搜索,它基于几个关键假设:环境完全可观测、动作是确定性的、环境规则完全已知,并且解决方案是一个逐步构建的动作序列。

本节中,我们来看看当逐步放宽这些假设时会发生什么。首先,我们将放宽第四个假设,即不再要求解决方案必须是一个动作序列。这引导我们进入一种完全不同的搜索形式——局部搜索

局部搜索的核心思想

在局部搜索中,我们只关心最终达到的目标状态,而不关心到达目标的具体路径。解决方案就是目标状态本身。

这种方法适用于许多问题,例如N皇后问题。重要的不是放置皇后的顺序,而是最终找到一个皇后互不攻击的配置。其他应用包括约束满足问题、可满足性问题、调度、自动编程和电信网络优化等。

在这些问题中,我们通常面临两类子问题:

  • 决策问题:找到一个满足特定约束条件(即目标特征)的状态。
  • 优化问题:找到一个根据某种度量标准(如成本或收益)最优的配置。

两者关系密切。任何优化问题都可以转化为决策问题(例如,“是否存在成本不高于X的解决方案?”)。同样,决策问题也可以看作一个优化问题,其中我们试图最小化当前状态与目标状态之间的“距离”。

局部搜索的工作原理

局部搜索从一个完整的(但可能不理想的)状态开始,然后通过迭代地改进它来逼近目标。

其工作流程如下:

  1. 从某个初始状态(通常是随机生成的)开始。
  2. 评估当前状态。
  3. 考虑当前状态的“邻居”状态(例如,通过改变一个变量的值)。
  4. 根据某种准则(如选择使评估结果改进最大的邻居)移动到下一个状态。
  5. 重复步骤2-4,直到找到满意解或满足停止条件。

与系统性地探索搜索树的经典搜索不同,局部搜索通常只跟踪当前状态,不记录到达此状态的路径。这带来了巨大的内存优势(通常只需常量内存),并且对于许多经典搜索难以处理的大规模问题非常有效。

问题空间与局部最优

我们可以将目标函数(如成本或收益)想象成一个地形景观。在最大化问题中,我们试图登上最高的山峰;在最小化问题中,我们试图找到最深的谷底。

然而,这个地形中可能存在:

  • 全局最优:整个地形中的最高点或最低点。
  • 局部最优:某个局部区域的最高点或最低点,但从那里无法通过单步改进到达全局最优。
  • 平原地带:函数值没有变化的区域。

局部最优是局部搜索的主要挑战,因为搜索过程很容易陷入其中而无法逃脱。

爬山算法:基础的局部搜索

接下来,我们看看最直观的局部搜索技术——爬山算法(也称为贪婪局部搜索)。

其核心思想是:只有当移动能产生比当前状态更好的解时,才执行该移动。在最简单的版本(最陡上升爬山法)中,我们总是选择能带来最大改进的邻居状态。

以下是算法的伪代码表示:

function HILL-CLIMBING(problem) returns a state
    current ← problem.INITIAL
    loop do
        neighbor ← a highest-valued successor of current
        if neighbor.VALUE ≤ current.VALUE then
            return current
        current ← neighbor

然而,爬山算法是“短视”的,很容易陷入局部最优。例如,在解决八皇后问题时,它可能快速收敛到一个只有一个冲突的配置,但无法通过单步改进消除这最后一个冲突。

爬山算法的变体

由于基础爬山算法的局限性,发展出了多种变体以提高其性能:

  • 随机重启爬山法:多次运行爬山算法,每次从不同的随机初始状态开始。这是最常用且有效的变体之一。
  • 首选爬山法:随机生成后继节点,直到找到一个优于当前状态的后继。这在评估所有后继成本很高时很有效。
  • 随机爬山法:按一定概率随机选择后继,改进越大的后继被选中的概率越高,而非总是选择最好的。
  • 允许侧向移动的爬山法:允许移动到与当前状态评估值相等的邻居,以穿越平原地带。但需注意防止无限循环。

模拟退火算法

模拟退火是一种受冶金学启发的、更高级的局部搜索算法。其核心思想是:在搜索初期,以较高的概率接受“坏”的移动(即使评估值变差),以便跳出局部最优;随着时间推移,逐渐降低接受坏移动的概率。

这类似于将系统“加热”然后缓慢“冷却”(退火)的过程。算法使用一个“温度”参数 T 来控制接受坏移动的概率。温度随时间根据一个“退火计划表”逐渐降低。

关键步骤在于选择移动:

  1. 随机选择一个邻居。
  2. 计算评估值的变化 ΔE = neighbor.value - current.value
  3. 如果 ΔE > 0(改进),则接受该移动。
  4. 如果 ΔE ≤ 0(变差或不变),则以概率 p = e^(ΔE / T) 接受该移动。

在高温(T 大)时,即使是很差的移动也有相对较高的概率被接受,便于广泛探索。在低温(T 小)时,算法几乎只接受改进的移动,专注于局部开发。理论上,如果冷却足够慢,模拟退火能以概率1找到全局最优解。

局部束搜索与遗传算法

局部束搜索同时跟踪 k 个状态而不仅仅是一个。在每一步,它生成所有 k 个状态的后继,然后从所有后继中选择 k 个最好的。但这种方法可能导致缺乏多样性,所有状态很快聚集到同一个局部最优。

遗传算法是受生物进化启发的局部搜索算法。它维护一个状态(称为“个体”)的“种群”,并通过模拟自然选择(选择)、交叉(重组)和变异来进化出更好的解。

以下是遗传算法的工作流程:

  1. 初始化:随机生成一个包含 N 个个体的种群。
  2. 评估:计算每个个体的适应度(对应目标函数值)。
  3. 循环直到满足终止条件:
    a. 选择:根据适应度(适应度越高,被选中的概率越大)从种群中选择一对“父代”个体。
    b. 交叉:以一定概率对选中的父代进行交叉操作,产生一个或两个“子代”个体。这通常通过在表示状态的字符串中随机选择一个点,并交换两侧部分来完成。
    c. 变异:以很小的概率随机改变子代中某个位置的值。
    d. 将新的子代加入新种群。
  4. 替换:用新种群替代旧种群。
  5. 返回进化过程中找到的最好个体。

选择操作类似于爬山,推动种群向更好的方向进化。交叉操作结合了两个父代的特征,可能产生兼具双方优点的子代。变异操作引入了随机性,有助于保持种群多样性和探索新区域。

在实现时需要注意编码方式。对于数值优化,使用格雷码而非标准二进制码通常更好,因为格雷码相邻数值的汉明距离为1,能更好地反映数值的邻近关系。

总结

本节课中我们一起学习了局部搜索这一强大的问题求解范式。我们了解到,当只关心目标状态而非路径时,可以采用从完整解开始并迭代改进的策略。我们探讨了基础的爬山算法及其容易陷入局部最优的缺点,并介绍了随机重启、首选选择等改进变体。接着,我们学习了模拟退火算法,它通过以概率接受坏移动来逃离局部最优。最后,我们了解了受生物进化启发的遗传算法,它通过维护一个种群,并利用选择、交叉和变异操作来并行地搜索解空间。

这些局部搜索技术虽然通常不能保证找到最优解(除了模拟退火在理想条件下),但在实践中对于许多大规模、复杂的优化和决策问题非常有效,是人工智能工具箱中的重要工具。

012:非确定性搜索 🧭

在本节课中,我们将学习如何处理非确定性环境下的搜索问题。我们将探讨当智能体的行动结果不确定时,如何制定条件性计划,并介绍用于解决此类问题的“与或树”搜索方法。


概述

在经典的搜索问题中,我们假设环境是完全可观察的、确定性的,并且智能体完全了解每个行动的效果。这意味着,给定一个状态和执行一个行动,我们可以精确地知道下一个状态是什么。然而,现实世界往往并非如此确定。行动的结果可能无法完全预测,导致出现多种可能的结果状态。本节课,我们将放宽“行动是确定性的”这一假设,学习如何在非确定性环境中进行规划。


从确定性到非确定性

上一节我们介绍了局部搜索,现在我们继续探讨超越经典搜索的推理方法。我们想要放宽的第二个条件是行动的确定性。

回顾经典的确定性搜索场景:我们有一个环境,它是确定性的、完全可观察的,并且智能体知道每个行动的确切效果。这意味着,在一个状态下执行一个行动,我们无需感知就能完美地知道下一个状态。

然而,如果上述任何一个限制条件不成立,我们就需要感知。在非确定性环境中,未来的状态无法提前完全确定,未来的行动将取决于未来的感知结果。因此,我们需要一种能够根据感知做决策的计划,即条件性计划

条件性计划类似于我们日常所说的“如果……那么……”语句。它不仅仅是“做A,然后做B,然后做C”的线性序列,而是“做A,然后做B;如果发生X,那么做C,否则做D”。这种计划中可能包含条件分支,甚至可能通过“转到”先前状态而形成循环。本质上,你是在合成一个策略或一段程序代码。

条件性规划领域与程序合成、形式化方法等计算机科学领域有很强的交叉。


一个非确定性示例:故障吸尘器 🧹

首先,我们考虑一个非确定性的例子,它是吸尘器世界的一个变体。

回忆一下经典的吸尘器问题:有两个房间A和B,吸尘器可以执行左移右移吸尘三个动作,目标是让两个房间都变干净。在确定性版本中,只有8种可能的状态,我们可以用简单的搜索算法解决。

现在,假设吸尘器出现故障,行为变得不确定:

  1. 过度吸尘:当在一个脏房间吸尘时,它可能不仅清洁当前房间,还可能清洁相邻房间。
  2. 散布灰尘:当在一个干净房间吸尘时,机器可能故障并弄脏该房间。

由于这种非确定性的行为,我们需要制定一个应急计划,以确保即使发生意外情况,也能最终达成目标。这意味着我们需要一个更通用的转移模型。

在非确定性模型中,给定一个状态和一个行动,结果函数不再返回单一状态,而是返回一个可能状态的集合。在实践中,这通常表示为一个部分状态,即某些状态变量的值未指定,代表了多个候选状态。

例如,在初始状态(两个房间都脏)执行吸尘动作,结果可能进入状态5(仅A干净),也可能直接进入状态7(两个房间都干净)。


条件性计划与与或树

条件性计划,也称为策略,可以包含基于未来感知结果的条件嵌套。你需要感知状态的一些特征,然后根据感知结果决定做什么。这就引入了if-then-else结构。当某个结果迫使你回到路径上的先前状态时,就会形成循环。

这导致了一个有趣的场景:搜索不再是在所谓的或树中进行,而是在与或树中进行。

  • 确定性环境(或树):在每一步,你从多个可能的行动(或分支)中选择一个。你探索的搜索树是一个“或”树,每个节点代表一个选择点。
  • 非确定性环境(与或树):情况更复杂。你仍然有“或”节点来选择行动。但是,当你选择一个非确定性行动时,你必须处理该行动所有可能的结果。这意味着你有一个“与”节点,必须应对所有可能的结果状态。

因此,与或树交替出现“或”节点(选择行动)和“与”节点(处理行动的所有结果)。

与或树中的节点

  • 或节点:对应状态,你需要从多个可选行动中选择一个。
  • 与节点:对应行动,你需要处理该行动所有可能的结果状态。
  • 叶节点:可以是目标状态、失败状态或循环状态。

与或树的一个解是一个子树,它满足:

  1. 每个叶节点都是目标状态。
  2. 对于每个或节点,解中只指定一个选择的行动。
  3. 对于每个与节点,解中包含该行动所有结果分支的后续解。

这意味着,无论非确定性如何展开,该计划都能保证最终达到目标。


与或树搜索算法 🔍

我们可以用递归的深度优先方式来表示与或树搜索算法。

以下是算法的核心思路:

或搜索(处理状态/或节点)

  1. 如果当前状态满足目标,则返回一个空计划(成功)。
  2. 如果当前状态已在当前路径上(出现循环),则返回失败(稍后讨论循环处理)。
  3. 对于当前状态下每个可用的行动a
    • 计算应用行动a到当前状态所产生的所有可能结果状态的集合results)。
    • 调用与搜索来处理这个结果状态集合。
    • 如果与搜索返回的不是失败,则返回一个计划,形式为“执行行动a,然后执行与搜索返回的子计划”。
  4. 如果所有行动都导致失败,则返回失败

与搜索(处理状态集合/与节点)

  1. 对于结果状态集合中的每一个状态s
    • 调用或搜索来处理状态s
    • 如果对任何状态s的或搜索返回失败,则整个与搜索返回失败
  2. 如果对所有状态s的或搜索都成功,则返回一个条件性计划。这个计划通常是一个case语句:如果处于状态1,则执行计划1;如果处于状态2,则执行计划2;以此类推。

关于循环的处理
在或搜索中检测到循环时返回失败,并不意味着问题无解。它意味着从当前路径的早期状态开始,没有找到不包含循环的解。算法会回溯并尝试其他分支。如果状态空间有限,算法是完备的。


循环与公平性假设 🔄

在非确定性规划中,循环可能是有意义的解决方案的一部分。例如,一个计划可能是“重复执行行动A,直到条件X成立”。这形成了一个循环。

我们可以将循环状态视为有效的叶节点,前提是:从计划中的每一点出发,最终都能到达一个目标状态。这依赖于一个关键的隐含假设——公平性假设

公平性假设:在真正的非确定性环境中,我们假设每个行动每一个可能的结果都有非零的概率发生。因此,无论我们陷入某个循环多少次,只要存在一条能退出循环并到达目标的路径,那么“迟早”我们都会遇到那个结果,从而退出循环。

例如,考虑一个简单的协议:你排队等待使用公共厕所。这个协议“公平”吗?即,你是否保证最终能使用厕所?这依赖于一个隐含的假设:排在你前面的人最终都会离开厕所。如果没有这个假设(比如某人永远不出来),协议就会失败。这个“最终都会离开”就是公平性条件。

在吸尘器例子中,如果右移动作可能失败(有时会滑倒留在原地),一个可行的计划可能是:“当在房间A时,重复执行右移然后吸尘”。在公平性假设下,我们相信经过有限次尝试后,吸尘器最终会成功移动到房间B。

因此,在非确定性规划中,像 while state == dirty do suck 这样的循环结构是有效的解决方案,因为它们依赖于“迟早会成功”的公平性假设。


总结

本节课我们一起学习了非确定性环境下的搜索与规划:

  1. 我们首先将问题从确定性环境扩展到非确定性环境,其中行动可能导致多个结果。
  2. 我们引入了条件性计划的概念,它使用if-then-else和循环结构来应对不同的未来状态。
  3. 为了解决非确定性搜索问题,我们介绍了与或树的概念,它交替包含选择行动的“或”节点和处理所有结果的“与”节点。
  4. 我们探讨了用于求解与或树的递归搜索算法框架,包括或搜索与搜索函数。
  5. 最后,我们讨论了循环在解中的角色,以及使循环解有效的关键——公平性假设,即每个可能的结果最终都会发生。

理解非确定性搜索是处理现实世界不确定性问题的重要基础,它连接了人工智能规划、形式化验证和程序合成等多个领域。

013:部分可观测与不可观测搜索

在本节课中,我们将继续探讨广义搜索的变体,处理在部分可观测完全不可观测环境下的搜索问题,包括确定性和非确定性情况。

概述

上一节我们讨论了非确定性动作,其解决方案是条件规划。本节中,我们来看看当放宽一个非常重要的假设——可观测性——时会发生什么。当智能体没有传感器,或只能获取环境的部分信息时,搜索问题将变得更加复杂。

部分可观测性

回忆一下,在部分可观测环境中,感知器无法感知世界的完整状态。实际上,我们永远无法感知完整状态,但通常我们需要感知足够的信息来指导行动。假设我们只能获取状态的部分信息,这本质上意味着我们可能处于许多可能的状态之中,从而引入了不确定性。

例如,如果你无法感知另一个房间是干净还是脏的,那么你就无法确切知道自己处于哪个具体状态。你可能是处于“另一个房间干净”的状态,也可能是处于“另一个房间脏”的状态。因此,你处于这两个可能状态中的某一个,但不知道具体是哪一个。

这引出了一个重要的概念:信念状态

信念状态

信念状态代表了智能体基于先验知识、先前状态和动作序列等,对当前世界可能物理状态的信念。从数学上讲,一个信念状态对应一个物理状态的集合

在实践中,我们通常不将信念状态表示为状态列表,而是表示为部分状态,即关于状态变量的部分信息集合。这意味着我们知道某些变量的值,而对其他变量存在不确定性,这对应了许多可能的候选状态。

  • 核心概念:信念状态 B 是物理状态的一个集合 {s1, s2, ..., sn},通常由部分已知的变量值所定义。

不可观测搜索(无感知搜索)

首先,我们区分两种情况:完全无信息的搜索(也称为确证搜索无感知搜索),以及有部分观测的搜索。

确证搜索

假设智能体没有传感器。那么,我们将在信念状态空间中进行搜索。一旦我们使用信念状态(状态的集合)这个抽象概念,那么这个抽象的搜索空间对我们来说就是完全可观测确定性的。我们知道自己在哪个信念状态中,并且知道执行一个动作后,会确定性地到达另一个信念状态。

当然,这种方法的代价是状态空间可能指数级增长。如果物理状态有 N 个,那么可能的信念状态(即所有状态子集)最多有 2^N 个。这类似于将非确定性有限自动机转换为确定性有限自动机时所付出的指数代价。

确证规划示例

考虑一个无感知的吸尘器,它不知道自己的位置,也不知道任何房间是否干净。初始信念状态包含所有 8 种可能的物理状态。

智能体可以执行一个动作序列(例如,右移 -> 吸尘 -> 左移 -> 吸尘)。即使开始时对状态一无所知,这个序列也能保证最终到达目标状态(例如,位置 A,且所有房间干净)。这样的规划称为确证规划——一个无论初始状态如何,都能保证达到目标的通用规划

在确定性动作环境下,确证规划通常包含一个“启动”动作序列,其目的是将智能体带入一个完全已知的状态,之后便可以像在标准确定性环境中一样行动。

信念状态的形式化

我们使用下标 P 表示物理系统(如动作 P,结果 P,代价 P)。在信念状态系统中推理时,我们处理的是包含部分信息的状态空间。

  • 初始信念状态:在确证规划中,通常是“无信息状态”,即包含所有可能物理状态的集合。
  • 可用动作:在信念状态 B 中可用的动作,是 B 中每个物理状态可用动作的并集(假设对非法动作应用无效果)。
  • 转移模型:将动作 A 应用于信念状态 B 的结果。
    • 确定性物理动作:结果信念状态是 { RESULT_P(s, A) 对于所有 s ∈ B }
    • 非确定性物理动作:结果信念状态是 ∪_{s ∈ B} RESULT_P(s, A),即所有可能结果状态的并集。
  • 目标测试:对于信念状态 B,仅当 B 中的每一个物理状态都是目标状态时,B 才被视为目标状态。这是一个悲观但确保可靠性的定义。
  • 路径代价:通常假设动作代价在不同状态下相同,因此信念状态搜索中的代价可以简化。

确证搜索本质上是悲观的,因为它要求规划必须适用于所有可能的初始情况,这可能导致执行许多不必要的动作以确保万无一失。

利用部分可观测性

现在,我们来看看有部分感知的情况。智能体的传感器可以获取部分信息,例如某些变量的值,但不是全部。

部分感知的一个副作用是,许多不同的物理状态可能产生相同的感知信息。感知函数 Percept(s) 返回的是一组可能的感知结果(在确定性感知下是一个特定感知)。

预测-观测-更新循环

在部分可观测环境下执行动作可以分解为三个步骤:

  1. 预测:与无感知情况相同。计算执行动作 A 后“盲目”的信念状态 b+ = PREDICT(b, A)。这包含了所有可能的结果状态,信息量可能很大。
  2. 观测预测:计算在预测的信念状态 b+ 中,所有可能出现的感知的集合。即 { Percept(s) 对于所有 s ∈ b+ }。由于许多状态可能共享相同的感知,这个集合通常比 b+ 小。
  3. 更新:对于每一个可能的感知 o,通过将 b+ 过滤为与感知 o 兼容的状态,来计算更新后的信念状态 b_o。即 b_o = { s ∈ b+ | Percept(s) = o }

最终,执行动作 A 的结果不是一个单一的信念状态,而是一组可能的信念状态 { b_o },每个对应一种可能的后续感知。在实际执行时,智能体将观察到实际的感知 o,并据此确定自己处于哪个更精确的信念状态 b_o 中。

  • 核心概念b_o = UPDATE(PREDICT(b, A), o)。这通常通过逻辑合取操作来实现(例如,布尔公式的合取)。

示例与条件规划

通过利用部分观测,我们可以得到比单纯确证搜索更精确、更高效的信念状态。这使得解决方案表现为条件规划:即包含“如果感知到 X,则执行动作序列 Y;否则执行 Z”这样的分支结构。

每个分支下的子规划都是基于一个更小、信息更丰富的信念状态定制的,因此通常比通用的确证规划更优。

智能体执行与信念维护

在实际中,智能体首先通过信念状态搜索算法(通常使用布尔公式等逻辑形式表示信念状态)来构建一个条件规划

执行时,智能体需要维护其当前的信念状态 b。每执行一个动作 A 并接收到实际感知 o 后,智能体就通过 b = UPDATE(PREDICT(b, A), o) 来更新其信念状态。这个过程也称为状态估计滤波

由于信念状态的逻辑推理可能计算量很大,在实际的实时系统中,有时会使用近似的、计算更高效的信念状态表示和更新方法。

总结

本节课中,我们一起学习了在部分可观测不可观测环境下的搜索问题。

  • 我们引入了信念状态的概念,将其定义为智能体认为可能处于的物理状态的集合。
  • 无感知(确证)搜索中,我们在信念状态空间中进行确定性搜索,但可能面临状态空间指数级膨胀的问题,其解决方案是适用于所有可能情况的通用规划。
  • 部分可观测搜索中,我们可以利用感知信息来细化信念状态。通过预测-观测-更新循环,智能体能获得更精确的信念状态,从而制定出更高效的条件规划
  • 最终,智能体通过在线维护和更新信念状态,并执行预先计算好的条件规划,来在不确定的环境中实现目标。

理解如何表示和更新信念状态,是构建能在复杂、不确定现实世界中运作的智能系统的关键一步。

014:对抗搜索

在本节课中,我们将要学习一个超越传统搜索的新主题:对抗搜索。当环境中存在目标与我们相冲突的其他智能体(即对手)时,我们就进入了对抗搜索的领域。这不仅是游戏(如国际象棋、围棋)的核心,也是经济学、军事策略、体育竞技等众多领域的模型基础。

上一节我们介绍了单智能体环境下的搜索问题,本节中我们来看看当存在对手时,情况会发生怎样的变化。

对抗搜索概述

对抗搜索处理的是多智能体环境,尤其关注其中存在对手的情况。对手是指其目标在某种程度上与我们自身目标相冲突的其他智能体。这与随机环境不同,随机环境的行为并非针对我们,而对手则会主动采取行动来阻碍我们达成目标。

为什么对抗搜索如此重要?首先,许多现实场景(如市场竞争、策略博弈)都涉及对手。其次,即便是规则简单的游戏(如井字棋),其搜索空间也可能变得极其庞大和计算困难。例如,国际象棋的平均分支因子约为35,可能的棋局数量是一个天文数字。

游戏的形式化定义

一个游戏可以形式化定义为以下组成部分:

  • 初始状态S0,描述游戏开始时的设置。
  • 玩家函数Player(s),定义在状态s下该哪位玩家行动。
  • 行动集合Actions(s),给出在状态s下所有合法的行动。
  • 转移模型Result(s, a),描述在状态s采取行动a后到达的新状态。
  • 终止测试Terminal-Test(s),判断状态s是否为游戏结束状态。
  • 效用函数Utility(s, p),对玩家p而言,在终止状态s的得分(或收益)。

初始状态、行动集合和转移模型递归地定义了整个游戏树,其中节点是状态,边是行动。

游戏类型与假设

游戏可以根据多个维度进行分类:

  • 确定性 vs 随机性:确定性游戏(如国际象棋)的行动结果确定;随机性游戏(如大富翁)包含掷骰子等随机成分。
  • 完全信息 vs 不完全信息:完全信息游戏(如围棋)所有玩家都知道完整游戏状态;不完全信息游戏(如扑克)存在隐藏信息(如对手的手牌)。
  • 零和 vs 非零和:在零和游戏中,所有玩家的收益之和为常数(通常为0),一方的收益即另一方的损失。非零和游戏则不然(如足球联赛的积分制)。

在本课程中,我们首先关注确定性、完全信息、零和、双人、回合制的游戏。为了简化,我们将两位玩家分别称为 MAX(先手,试图最大化自身效用)和 MIN(后手,试图最小化MAX的效用)。

这里有一个关键假设:双方玩家都是理性且最优的。即,MAX总是选择能使自己最终效用最大的行动,而MIN总是选择能使MAX最终效用最小的行动。这意味着我们不能寄希望于对手犯错。

极小化极大算法

在对抗搜索中,最优策略被称为极小化极大策略。其核心思想是:MAX在选择行动时,必须考虑MIN会如何最优地回应,并在这个前提下选择对自己最有利的行动。

以下是该策略的递归定义:

  • 如果状态s是终止状态,返回其效用值 Utility(s)
  • 如果当前玩家是 MAX,则返回所有可能行动结果中的最大值
    value = max( Min-Value(Result(s, a)) ),对所有 a 属于 Actions(s)
  • 如果当前玩家是 MIN,则返回所有可能行动结果中的最小值
    value = min( Max-Value(Result(s, a)) ),对所有 a 属于 Actions(s)

算法伪代码

以下是该算法的伪代码实现:

function MinMax-Decision(state):
    # 选择能带来最大最小值的行动
    return argmax( Min-Value(Result(state, a)) ) for a in Actions(state)

function Max-Value(state):
    if Terminal-Test(state):
        return Utility(state)
    v = -∞
    for each a in Actions(state):
        v = max(v, Min-Value(Result(state, a)))
    return v

function Min-Value(state):
    if Terminal-Test(state):
        return Utility(state)
    v = +∞
    for each a in Actions(state):
        v = min(v, Max-Value(Result(state, a)))
    return v

图解示例

考虑一个简单的游戏树片段。矩形节点代表MAX的回合,倒三角节点代表MIN的回合,叶子节点数字代表对MAX的效用值。

  1. 在叶子节点层,效用值已知(例如3, 12, 8...)。
  2. 在MIN节点(倒三角),MIN会选择子节点中最小值作为该节点的值(例如,在3,12,8中选择3)。
  3. 在MAX节点(矩形),MAX会选择子节点中最大值作为该节点的值。
  4. 从根节点(MAX)开始,递归地执行此过程。最终,MAX会选择能导致最大“最小保证效用”的行动路径。

这个算法本质上是自底向上的:计算从叶子节点的效用值开始,逐层向上传播,直到根节点。

总结

本节课中我们一起学习了对抗搜索的基本概念。我们首先了解了对抗搜索与单智能体搜索的区别,以及它在模拟竞争环境中的重要性。接着,我们学习了如何形式化定义一个游戏,并介绍了不同的游戏分类。最后,我们深入探讨了解决确定性零和双人游戏的核心算法——极小化极大算法。该算法基于双方玩家都绝对理性的假设,通过递归计算,为MAX玩家找到一个即使在MIN玩家最优应对下也能最大化自身收益的策略。在接下来的课程中,我们将看到如何优化这个基础算法以提高搜索效率。

015:在线搜索与探索 🧭

在本节课中,我们将要学习在线搜索(Online Search)的概念。在线搜索与之前学习的离线搜索(Offline Search)不同,它发生在智能体对环境的动态或结果未知的情况下。智能体必须通过实际执行动作来探索环境,并在此过程中逐步构建对环境的认知。

离线搜索与在线搜索的区别

上一节我们介绍了经典搜索的假设,本节中我们来看看当其中一个关键假设不成立时会发生什么。在经典搜索中,我们假设智能体完全了解环境,包括动作的结果。然而,在线搜索处理的是智能体不知道动作结果的情况。

  • 离线搜索:智能体首先在“脑海”或CPU中完成完整的搜索计算,得到一个行动计划,然后才去执行这个计划。搜索和执行是分离的。
  • 在线搜索:智能体将计算和行动交织在一起。它执行一个动作,观察环境的结果,然后基于这个新的观察结果计算下一个动作,如此反复。

在线搜索也被称为探索。一个典型的例子是:一个机器人被放置在一个未知的建筑物中,它必须通过移动来探索并构建内部地图。另一个例子是婴儿学习自己动作的结果。

在线搜索的基本设定与挑战

在线搜索虽然环境未知,但我们仍保留一些基本的工作假设:

  1. 环境是确定性的完全可观察的。这意味着智能体在任何一个状态,都能完全感知到该状态的所有相关信息。
  2. 智能体知道在任何一个状态S下可以执行哪些动作,即拥有ACTIONS(S)函数。
  3. 智能体知道每个动作的代价c(S, A)
  4. 智能体拥有一个目标测试函数GOAL-TEST(S),可以判断是否到达目标。
  5. 智能体拥有一个可采纳的启发式函数h(S),用于估计从当前状态到目标的距离。例如,在一个迷宫中,你可能有一个GPS告诉你到目标的直线距离,但你不知道具体的路径。

关键的不同点在于:智能体没有结果函数RESULT(S, A)。获取此信息的唯一方法是实际执行动作A并观察结果。

在线搜索的目标通常是找到一条总代价最小的路径到达目标。衡量在线搜索算法性能的一个指标是竞争比,即在线搜索的路径代价与已知环境信息下离线搜索的最优路径代价之比。

一个核心挑战:死胡同与安全可探索性

在线搜索面临一个内在问题:如果环境中存在不可逆的动作(例如单行道),智能体可能会陷入死胡同而无法返回。

一个重要的结论是:没有任何算法能先验地避免所有可能的死胡同。因此,我们需要一个额外的假设:状态空间是安全可探索的。这意味着从任何可达状态,目标状态也是可达的,并且智能体不会被困住。

在线深度优先搜索算法 🔍

由于在线搜索中,智能体只能从当前物理所在的状态扩展节点(不能像离线搜索那样“跳转”到任意节点),因此深度优先搜索(DFS)或其变体成为自然的选择。

在线DFS的核心思想是:智能体像探索者一样行动,通过执行动作来探索,并记忆已经访问过的状态和尝试过的动作,逐步构建一个环境“地图”(即结果表result)。

与离线搜索的关键区别在于:

  • 回溯是物理性的:回溯不仅仅是从数据结构中弹出元素,而是需要智能体实际执行一系列动作返回到之前的状态。
  • 需要额外的数据结构:为了支持物理回溯,算法需要维护一个unbacktracked表,记录每个状态可以从哪些状态回溯回来。

以下是算法的核心数据结构与逻辑概述:

  • result: 一个表,存储已知的(S, A) -> S‘映射。
  • unbacktracked: 一个表,为每个状态S‘存储一个列表,记录那些已访问过S‘但尚未从S‘回溯回去的状态S
  • untried: 一个表,为每个状态S存储一个尚未尝试过的动作列表。

算法从初始状态开始循环,每次迭代接收新的感知状态S‘(即执行上一个动作后的结果)。主要步骤包括:

  1. 目标检测:如果S‘是目标,则成功终止。
  2. 状态记录:如果S‘是新状态,则初始化其untried列表(包含所有可能动作)。
  3. 更新结果与回溯信息:如果是从前驱状态S通过动作A到达S‘,则更新result[S, A] = S‘,并将S加入到unbacktracked[S‘]列表的前端(采用LIFO栈,实现深度优先)。
  4. 选择下一步
    • 如果当前状态S‘untried列表非空,则从中取出第一个动作作为下一步。
    • 如果untried列表为空,则需要回溯:
      • 如果unbacktracked[S‘]也为空,说明搜索失败。
      • 否则,从unbacktracked[S‘]中弹出(LIFO)一个状态S_back,并找到一个动作B使得result[S‘, B] = S_back(根据安全可探索假设,这样的动作存在),然后执行动作B进行物理回溯。
  5. 执行与循环:执行选定的动作,感知新状态,开始下一轮循环。

算法复杂度:在最坏情况下,每条边最多被遍历两次:一次作为探索性动作,一次作为回溯动作。

在线局部搜索与LRTA*算法 🧗

在线DFS是一种系统性的搜索。另一种自然的在线搜索范式是在线局部搜索,它不系统性地探索所有状态,而是基于当前局部信息做出“看似最好”的移动,类似于爬山法。

单纯的局部搜索(如随机游走)在线环境中可能效率极低,容易在局部来回震荡。为了改进,我们可以引入学习机制,这就是学习实时A*算法的核心思想。

LRTA*算法结合了启发式搜索和在线学习:

  1. 维护代价估计:算法为每个访问过的状态S维护一个估计代价H(S),表示从S到目标的最小代价估计。初始时,H(S)被设置为一个非常乐观的启发值h(S)(例如直线距离)。
  2. 基于估计行动:在状态S,智能体选择能最小化c(S, A) + H(RESULT(S, A))的动作A执行。如果RESULT(S, A)未知,则用h(RESULT(S, A))代替H
  3. 学习与更新:当智能体从S执行动作A到达S‘后,它会用获得的新信息更新前驱状态S的估计值:
    H(S) <- min_{A in ACTIONS(S)} [ c(S, A) + H(RESULT(S, A)) ]
    这个更新遵循三角不等式,总是使H(S)增加或保持不变,从而使其从乐观估计变得更接近真实代价。
  4. 逐步优化:通过不断执行动作和更新H值,智能体对环境的代价估计越来越准确,从而能够做出更好的决策,引导自己更高效地走向目标。

LRTA*保证了在有限、安全可探索的环境中能找到目标,并且通常比盲目的随机游走高效得多。

总结

本节课中我们一起学习了在线搜索的核心概念。我们首先明确了在线搜索与离线搜索的根本区别:在线搜索需要在未知环境中通过实际执行动作来探索和学习。我们探讨了在线搜索的基本设定和“安全可探索性”这一关键假设。

接着,我们详细分析了在线深度优先搜索算法,理解了它如何通过维护resultuntriedunbacktracked等数据结构,以及进行物理回溯,来系统性地探索未知环境。

最后,我们介绍了在线局部搜索的思路以及更高效的LRTA*算法。LRTA*通过维护和不断更新每个状态的代价估计H(S),将启发式搜索与在线学习相结合,使得智能体能够在探索过程中越来越“聪明”地选择路径,从而有效地在未知环境中找到目标。

在线搜索是智能体在现实世界(往往部分未知或动态变化)中运作的基础,是连接规划与执行的关键环节。

016:对抗搜索

概述

在本节课中,我们将要学习对抗搜索。这是一种用于多智能体场景的搜索方法,其中智能体的目标可能相互冲突或对立。我们将从基本的极小极大算法开始,探讨其原理、优化方法,并扩展到包含随机因素的博弈场景。


对抗搜索简介

对抗搜索描述了多智能体场景,其中智能体的目标相互对立或不一致。这意味着存在目标冲突。

我们首先回顾一些基本假设。对抗搜索有不同的形式:确定性、随机性。典型的场景涉及两个玩家,但可以有更多。零和博弈意味着所有玩家的得分总和是常数。例如,你赢多少,对手就输多少。此外,还需要考虑信息是否完全,即所有玩家是否对游戏有完全了解。

典型的场景是双人博弈,我们称之为 MaxMin。我们通常从 Max 的视角出发,并假设 Max 先手,且双方轮流行动。

博弈的要素包括:

  • 初始状态:游戏开始时的状态。
  • 玩家函数:指明当前轮到哪位玩家行动。
  • 动作函数:给定一个状态,玩家可以执行的可能动作。
  • 转移模型:描述执行动作 A 从状态 S 出发后,会到达哪个新状态。
  • 终止测试:判断游戏是否结束。
  • 效用函数:在游戏结束时,根据胜利、失败或平局给出收益。

通过递归应用初始状态、动作和转移模型,可以构建出整个博弈树。博弈树通常非常庞大,难以在合理时间内完全探索。

对于双人零和博弈,我们可以只使用一个效用值,从第一个玩家(Max)的视角看是最大化问题,从第二个玩家(Min)的视角看则是最小化问题。


极小极大算法

上一节我们介绍了对抗搜索的一般概念。本节中我们来看看具体的解决方案:极小极大算法。

其核心思想是递归的:在每一步,Max 玩家试图最大化自己的收益,同时预测 Min 玩家会在其回合中最小化 Max 的收益。我们假设对手是理性的,总是做出对其最有利的决策。

这归结为以下递归模式:

公式:极小极大值计算

函数 极小极大(状态 S):
    如果 S 是终止状态:
        返回 效用(S)
    如果 轮到 Max 玩家在 S 行动:
        返回 max_{动作 a ∈ 动作(S)} [ 极小极大(结果(S, a)) ]
    否则 (轮到 Min 玩家在 S 行动):
        返回 min_{动作 a ∈ 动作(S)} [ 极小极大(结果(S, a)) ]

这个递归定义是自底向上分析的。首先对动作结果进行递归调用,然后基于这些结果做出选择。虽然搜索是自底向上的,但实际的行动执行顺序是自上而下的。

考虑一个例子:Max 需要从动作 A1、A2、A3 中选择。Max 会想:如果我选 A1,那么理性的 Min 会选择给 Max 收益为 3 的结果;如果我选 A2,Min 会选择收益为 2 的结果;如果我选 A3,Min 会选择收益为 2 的结果。因此,Max 的最佳选择是 A1,因为它能保证最坏情况下的最大收益(3)。这就是 最大最小 策略。


扩展到多人博弈

上一节我们介绍了双人零和博弈的极小极大算法。本节中我们来看看当玩家多于两个时的情况。

在多人博弈中,你不再有简单的“极小极大”对手。通常,你需要为每个节点维护一个价值向量,每个分量代表对应玩家的收益。每个玩家都会试图最大化自己的收益值。

你的策略选择需要考虑其他玩家会试图做什么。这可能导致联盟的形成。例如,玩家 A 可能和玩家 B 达成协议:“如果我走这一步,你承诺走那一步,这样我们俩的收益都会比原来高。”

这种分析仍然是自底向上的,而行动是自上而下执行的。重要的是,在这种确定性对抗搜索中,具体的效用值大小并不关键,关键在于值的相对顺序。任何单调变换都不会改变最终的策略选择。


Alpha-Beta 剪枝

极小极大算法虽然完备且在面对最优对手时能给出最优解,但其时间复杂度是指数级的,对于像国际象棋这样的游戏,搜索空间巨大,无法完全探索。因此,我们需要优化方法。

Alpha-Beta 剪枝是一种在不牺牲结果准确性的前提下,显著提高搜索效率的技术。其核心思想是:在搜索过程中,如果发现某个分支无论如何都不会比当前已知的最佳选择更好,就停止探索该分支。

这类似于整数规划中的 分支定界 法。我们引入两个值来跟踪搜索过程:

  • Alpha(α):Max 玩家在当前路径上所能保证的最佳(最大)值(下界)。
  • Beta(β):Min 玩家在当前路径上所能保证的最佳(最小)值(上界)。

算法是极小极大算法的扩展,在递归过程中增加剪枝条件:

代码:Alpha-Beta 剪枝伪代码

函数 Max-Value(状态 S, α, β):
    如果 S 是终止状态: 返回 效用(S)
    v = -∞
    对于 S 的每个后继状态 s':
        v = max(v, Min-Value(s', α, β))
        如果 v ≥ β: 返回 v // Beta 剪枝
        α = max(α, v)
    返回 v

函数 Min-Value(状态 S, α, β):
    如果 S 是终止状态: 返回 效用(S)
    v = +∞
    对于 S 的每个后继状态 s':
        v = min(v, Max-Value(s', α, β))
        如果 v ≤ α: 返回 v // Alpha 剪枝
        β = min(β, v)
    返回 v

// 初始调用
最佳值 = Max-Value(初始状态, -∞, +∞)

剪枝能否发生以及能剪掉多少分支,很大程度上取决于探索后继节点的顺序。如果能优先探索“看起来更好”的走法(使用启发式),就更可能触发剪枝,从而大幅提升效率。理论上,在最优排序下,时间复杂度可从 O(b^m) 降至 O(b^(m/2))。


不完美的实时决策

上一节我们介绍了能精确剪枝的 Alpha-Beta 算法。但在实际游戏中,即使经过剪枝,搜索空间仍然过于庞大。本节中我们来看看如何通过近似方法进行实时决策。

解决方案是进行 深度限制搜索。我们只搜索到一定的深度 D,然后对非终止节点使用一个 启发式评估函数 来估计其优劣,而不是继续搜索到终局。

这需要对极小极大或 Alpha-Beta 算法进行修改:

  1. 终止测试 替换为 截断测试(例如,深度是否达到 D)。
  2. 在截断节点,不再返回真实的效用值,而是返回启发式评估函数的值。

代码:深度限制搜索修改点

函数 深度限制-极小极大(状态 S, 深度 D):
    如果 截断测试(S, D) 为真:
        返回 评估函数(S)
    如果 S 是终止状态:
        返回 效用(S)
    ... // 其余部分与标准极小极大相同

评估函数的设计至关重要:

  • 易于计算:不能本身又包含大量搜索。
  • 与获胜概率强相关:评估值应能较好反映该位置的实际胜率。
  • 保持序关系:胜利状态的评估值应 > 平局状态 > 失败状态。

以国际象棋为例,一个简单的评估函数可能是双方棋子价值的加权差(如:后=9,车=5,象/马=3,兵=1)。更复杂的函数会考虑棋子位置、控制区域、王的安全度等因素。

单纯的深度限制可能不准确,特别是遇到“不稳定”局面时(例如,一个棋子正受到攻击,下一步价值可能剧变)。更稳健的方法是使用 静止搜索:对于不稳定的局面,继续搜索几步直到局面“静止”,再进行评估。

从深蓝到 AlphaGo,现代计算机博弈程序结合了深度搜索、强大的启发式评估(通常由机器学习从大量对局数据中训练得到)以及开局/残局数据库。


随机博弈

到目前为止,我们讨论的博弈都是确定性的。但在许多游戏中,存在随机因素,如掷骰子、抽牌等。本节中我们来看看如何将搜索方法扩展到包含随机性的博弈中。

在随机博弈中,我们需要区分 对手机会

  • 对手(对抗性):试图对你造成最坏结果(最坏情况分析)。
  • 机会(随机性):没有敌友之分,按概率随机发生(平均情况分析)。

此时,博弈树中会在玩家决策层之间加入 机会节点。在机会节点,我们不是取最小值或最大值,而是计算所有可能结果的 期望值(概率加权平均)。

公式:包含机会节点的期望极小极大值

函数 期望-极小极大(状态 S):
    如果 S 是终止状态: 返回 效用(S)
    如果 轮到 Max 在 S 行动:
        返回 max_{动作 a} [ 期望-极小极大(结果(S, a)) ]
    如果 轮到 Min 在 S 行动:
        返回 min_{动作 a} [ 期望-极小极大(结果(S, a)) ]
    如果 轮到 机会 在 S 行动:
        返回 ∑_{结果 r} [ P(r) * 期望-极小极大(结果(S, r)) ]

一个关键区别是:在纯对抗搜索中,只有值的顺序重要,任何单调变换不影响决策。但在随机博弈中,因为涉及概率加权平均,效用值的大小变得重要。只有线性变换才能保持决策的一致性。

这引出了一个重要观点:金钱并非线性的效用度量。人们对额外金钱的感知价值取决于他们已经拥有多少钱。例如,对于一个穷人,100 万是巨款;对于一个亿万富翁,100 万微不足道。因此,在涉及风险和金钱的决策中,不能简单用金钱金额作为效用值。

随机博弈的复杂度更高,因为机会节点大大增加了分支因子。例如,双陆棋有 21 种可能的掷骰结果。因此,在实践中也只能进行很浅的搜索,并严重依赖评估函数。


总结

本节课中我们一起学习了对抗搜索的核心内容。我们从基本的极小极大算法开始,理解了如何在双人零和博弈中通过递归进行最优决策。为了应对巨大的搜索空间,我们引入了 Alpha-Beta 剪枝技术,通过剪除无效分支来提高效率。对于更实际的场景,我们探讨了深度限制搜索和使用评估函数进行近似决策的方法。最后,我们将模型扩展到包含随机因素的博弈,引入了期望极小极大算法,并认识到在随机环境中效用值标度的重要性以及金钱作为效用的局限性。这些技术是许多经典游戏 AI 和更广泛的多智能体决策系统的基础。

017:约束满足问题(CSPs)🧩

在本节课中,我们将要学习约束满足问题。这是一种非常重要的问题表示和求解范式,它利用状态的结构化表示和通用启发式方法,能够高效地解决从数独到调度等众多实际问题。


状态表示与CSP的核心思想

在之前的章节中,我们介绍了状态的不同表示方法。我们通常将状态视为一个不可分割的原子标签,或者一个简单的索引。然而,在约束满足问题中,我们采用一种因子化表示

在这种表示中,一个状态由一组状态变量及其赋值构成。这些变量可以是布尔型、有界整数等不同类型。目标测试则是一组约束,只有当所有约束都被满足时,当前状态才是一个目标状态。

CSP搜索算法与之前看到的搜索算法有根本不同。之前的算法(如盲目搜索)将状态视为一个整体,只关注状态之间的转移关系,而不探究状态内部的结构。CSP算法则充分利用状态的内部结构,特别是变量的部分赋值。其核心思想在于,它们依赖于通用的启发式方法,而不是针对特定问题的启发式。你可以将具体问题编码为CSP,然后使用通用算法进行求解。主要思路是逐步消除变量的候选值


CSP的基本定义

一个约束满足问题是一个三元组 <X, D, C>

  • X 是一组变量 {X1, X2, ..., Xn}。
  • D 是一组非空域 {D1, D2, ..., Dn},其中 Di 是变量 Xi 可以取值的集合。我们通常假设域是有限的。
  • C 是一组约束 {C1, C2, ..., Cm}。每个约束限制了一个或多个变量可以取值的组合。

每个约束 C 包含两个部分:

  1. 作用域:该约束涉及的变量集合。
  2. 关系:定义了这些变量被允许的取值组合。关系可以显式列出所有合法元组,也可以通过算术表达式等声明式语言来描述。一个约束关系应该能够测试一个赋值是否满足它,并且在域有限时,能够枚举所有可能的解。

赋值、解与问题变体

给定一个CSP,一个状态就是给变量赋值。我们有以下术语:

  • 完全(或全部)赋值:为每个变量都赋予了一个值。
  • 部分赋值:只给一个变量子集赋予了值。
  • 如果一个(完全或部分的)赋值不违反任何约束,则称其为一致的合法的
  • 一个满足所有约束的完全赋值,称为该CSP的一个

CSP的基本问题是找到至少一个解。此外还有两个重要的变体:

  1. 找到所有解
  2. 约束优化问题:除了找到满足约束的解,还要优化某个目标函数(通常是关于变量值的函数),找到最优解。

CSP实例:数独与地图着色

为了更具体地理解CSP,我们来看两个经典例子。

数独是一个典型的CSP:

  • 变量:81个方格,每个是一个变量 Xij。
  • :每个变量的域是 {1, 2, ..., 9}。
  • 约束:每行、每列、每个3x3宫内的所有数字必须互不相同。这可以表示为27个“所有不同”的全局约束,也可以表示为大量两两不相等的二元约束。

地图着色是另一个经典问题,它是许多更复杂问题(如频率分配、课程安排)的隐喻:

  • 变量:地图上的每个区域(例如澳大利亚的各个州)。
  • :一组颜色,例如 {红, 绿, 蓝}。
  • 约束:任何两个相邻的区域不能着相同的颜色。

地图着色问题可以自然地用约束图来表示:

  • 节点代表变量。
  • 边连接两个存在约束(即相邻)的变量。
    利用约束图的结构信息,可以极大地提升求解效率。例如,孤立节点(如塔斯马尼亚州)的赋值不会影响其他变量,可以单独处理。

CSP的类型与编码的重要性

CSP可以根据变量的类型进行分类:

  • 离散变量:这是我们主要关注的类型。
    • 有限域:例如布尔可满足性问题(SAT),域大小为2。这是AI中极其重要的子领域。
    • 无限域:例如使用整数表示作业开始时间,约束为线性算术不等式。
  • 连续变量:例如实数域上的线性规划问题。

为问题找到一个好的CSP编码至关重要,它直接决定了求解效率。以N皇后问题为例:

  • 较差编码:为NxN棋盘的每个格子设置一个布尔变量,表示是否有皇后。这需要N²个变量和大量约束。
  • 较好编码:为每一行设置一个变量,其值表示皇后在该行的列位置。这只需要N个变量,约束也更紧凑(列不冲突、对角线不冲突)。

约束的类型

约束可以根据其涉及变量的数量进行分类:

  • 一元约束:只涉及一个变量。
  • 二元约束:涉及两个变量。许多算法假设约束是二元的,因为任何高阶约束理论上都可以转化为二元约束。
  • 高阶约束:涉及三个或更多变量,需要用超图表示。
  • 全局约束:涉及任意数量变量,但具有共同结构。例如“所有不同”约束、“恰好K个为真”约束等。求解器通常有针对全局约束的专用高效算法。

此外,约束还可分为:

  • 硬约束:必须被满足。
  • 软约束:表示偏好,不一定必须满足,但满足它会带来奖励或避免惩罚。包含软约束的优化问题称为约束优化问题。

CSP求解:搜索与约束传播

CSP的求解范式与我们之前学过的搜索有本质区别。之前是从一个完全状态转移到另一个完全状态。而在CSP求解中,我们在部分赋值的空间中进行搜索,并交织进行约束传播

搜索步骤:非确定性地选择一个变量并为其赋值。如果沿着当前路径无法找到解,则进行回溯,撤销之前的某些赋值。

约束传播(推理)步骤:利用当前的部分赋值和约束,来推导缩小未赋值变量的域。如果某个变量的域被缩小为空,则触发回溯;如果被缩小为单个值,则可以立即进行确定性赋值。

一个通用的元启发式规则是:优先进行确定性步骤,推迟非确定性选择。这通常能减少搜索分支,提升效率。

约束传播是CSP求解的新颖且核心的部分。它通过在每一步削减搜索空间,避免了许多徒劳的搜索分支。约束传播有多种技术,需要在效果(能多大幅度削减域)和效率(计算开销)之间进行权衡。


局部一致性与传播算法

约束传播的核心是建立并维护某种局部一致性。我们主要关注两种:

1. 节点一致性
一个变量是节点一致的,当且仅当其域中的所有值都满足所有作用于它的一元约束。使整个CSP节点一致,只需遍历所有变量,移除违反一元约束的值即可。这通常在搜索开始前完成。

2. 弧一致性
对于二元约束,变量 Xi 相对于 Xj 是弧一致的,当且仅当对于 Xi 域中的每一个值,在 Xj 的域中都存在至少一个值,使得这对值满足 Xi 和 Xj 之间的所有约束。如果某个值找不到这样的“支持”,它就可以被安全地从 Xi 的域中移除。
整个CSP是弧一致的,当且仅当每一对相邻变量都相互满足弧一致性。

以下是两种重要的传播算法:

前向检查
这是最简单、最廉价的传播形式。

  • 操作:当一个变量被赋值后,检查所有与之相邻的未赋值变量,从它们的域中删除与当前赋值冲突的值。
  • 局限:它只保证了从已赋值变量到未赋值变量的弧的一致性,而不处理未赋值变量之间的弧。因此,前向检查后的CSP可能不是弧一致的。

AC-3算法
这是一种建立完全弧一致性的算法。

  • 操作:维护一个待检查弧的队列。每次从队列中取出一条弧 (Xi, Xj),检查 Xi 的每个值在 Xj 中是否有支持。如果没有,则删除该值。如果 Xi 的域被修改了,那么所有指向 Xi 的弧(除了刚检查过的 Xj)都需要重新加入队列进行检查。
  • 效果:算法运行直到队列为空,此时整个CSP达到弧一致。
  • 对比:AC-3比前向检查更强大,能更早地发现不一致,从而减少搜索,但每次传播的计算成本也更高。

一个关键区别在于如何处理域被缩小到单个值的未赋值变量

  • 在前向检查中,它被视为未赋值变量,不会触发对其邻居的检查。
  • 在AC-3中,域的任何缩小都会触发对其所有邻居的重新检查,从而可能引发连锁推理,实现更多确定性赋值。

本节课总结

在本节课中,我们一起学习了约束满足问题的基础知识。我们首先了解了CSP如何利用因子化状态表示和通用启发式。然后,我们掌握了CSP的形式化定义、相关术语(变量、域、约束、赋值、解)以及各种问题变体(如约束优化)。通过数独和地图着色等实例,我们加深了对CSP建模的理解,并认识到好的问题编码至关重要。

我们详细探讨了CSP求解的核心范式:在部分赋值的空间中进行搜索,并交织进行约束传播。我们重点学习了两种局部一致性(节点一致性和弧一致性)以及对应的传播算法(前向检查和AC-3),理解了它们在效果和效率上的权衡。

CSP是一个强大的框架,能够建模并解决从逻辑谜题到工业调度等广泛领域的问题。掌握其基本原理是学习更高级人工智能技术的重要一步。

018:CSP搜索

在本节课中,我们将要学习约束满足问题中的搜索算法。我们将探讨如何通过选择变量和值来逐步构建解,以及如何利用一致性传播来减少搜索空间。我们还将介绍一种更智能的回溯方法,以显著提高搜索效率。

搜索与传播

上一节我们介绍了CSP的基本概念,本节中我们来看看解决CSP的两个核心组件:搜索和传播。

搜索意味着选择变量并为其赋值。这与之前的状态空间搜索有根本区别:你不是在选择一个完整的状态,而是在构建一个逐步扩展的部分赋值。你选择一个变量,赋予它一个值,然后选择另一个变量,如此反复,直到找到一个满足所有约束的完整赋值,或者发现当前部分赋值违反了约束。

传播(也称为一致性传播或值传播)是另一个核心组件。其基本思想是:一旦你通过搜索为变量赋值,就可以根据约束来减少其他变量的可能取值(即缩小其值域)。这可以提前发现不一致性,从而剪枝掉大量无效的搜索分支。

传播的强度各有不同。以下是两种主要方式:

  • 前向检查:这是最简单的形式。当一个变量被赋值后,检查所有与其有约束关系的未赋值变量,并删除那些与当前赋值不兼容的值。这种方法非常快,但不够强大,因为它不考虑未赋值变量之间的约束关系,结果可能不是全局弧一致的。
  • 弧一致性传播(如AC3算法):这种方法更强大。当一个变量的值域被缩减后,它会递归地检查并缩减其邻居变量的值域,直到没有变化为止。这能更有效地缩减值域,但计算成本也更高。

这里存在效率与效果之间的权衡。前向检查更快但剪枝能力弱;弧一致性传播更有效但更昂贵。在实际应用中,需要根据问题特点进行选择。

一个重要的区别是:一个被明确赋值的变量一个值域被缩减到只剩一个值但尚未被正式“赋值”的变量是不同的。后者很可能在下一步搜索中被赋值,但在前向检查中,它不会触发对其邻居的检查,而弧一致性传播则会处理这种情况。

一致性概念

我们可以将弧一致性的概念推广到更高级的一致性,例如路径一致性和K一致性。

路径一致性:对于任意三个变量Xi, Xj, Xk,如果对于Xi和Xj的每一个相容赋值组合,都能在Xk中找到至少一个值,使得涉及这三个变量的所有约束都得到满足,那么这对变量关于第三个变量是路径一致的。以数独为例,有时你会发现两个格子只能填两个特定的数字(比如1和2),那么你就可以从它们共同邻居的候选值中排除1和2。

K一致性:一个CSP是K一致的,如果对于任意一组K-1个变量和它们的一个相容赋值,总能为第K个变量找到一个相容的值。当K=1时是节点一致,K=2时是弧一致,K=3时是路径一致。

问题在于,确保K一致性的计算成本随K值呈指数增长。因此,在搜索中,我们需要权衡传播的强度(效果)与其计算开销。通常,结合智能的搜索启发式方法,可能不需要非常强的传播也能高效地解决问题。

回溯搜索

现在,我们重点讨论搜索部分,特别是回溯搜索。这与之前的状态空间搜索不同,它是在部分赋值上进行推理,逐步将其细化。

基本思想是每次选择一个变量并为其赋值。为了简化,我们最初可以假设一个固定的变量顺序。每次引入新变量时,只选择那些与当前部分赋值不冲突的值。这相当于一种增量式目标测试:一旦部分赋值违反了任何约束,就立即回溯,避免了继续扩展无效分支的搜索。

标准的回溯搜索算法框架如下(这是一个算法族,具体实现取决于其中的子过程):

function BACKTRACKING-SEARCH(csp, assignment) returns solution or failure
    if assignment is complete then return assignment
    var ← SELECT-UNASSIGNED-VARIABLE(csp, assignment)
    for each value in ORDER-DOMAIN-VALUES(var, assignment, csp) do
        if value is consistent with assignment then
            add {var = value} to assignment
            inferences ← INFERENCE(csp, var, value)
            if inferences ≠ failure then
                add inferences to csp
                result ← BACKTRACKING-SEARCH(csp, assignment)
                if result ≠ failure then return result
                remove inferences from csp
        remove {var = value} from assignment
    return failure

这个框架是通用的,可用于任何编码为CSP的问题。其性能关键取决于几个子过程的实现:

  1. SELECT-UNASSIGNED-VARIABLE: 选择下一个要赋值的变量的策略。
  2. ORDER-DOMAIN-VALUES: 为选定变量尝试赋值的顺序。
  3. INFERENCE: 进行值传播(如前向检查或弧一致性传播)的强度。

变量与值选择启发式

为了提高搜索效率,我们使用启发式方法来指导变量和值的选择。

变量选择启发式
以下是选择下一个变量的常用策略:

  • 最少剩余值(MRV)启发式:选择合法值最少的变量(即值域最小的变量)。这能最早导致失败(如果值域为空),从而尽快剪枝。如果某个变量值域只剩一个值,它实际上变成了一个确定性选择,应优先处理。这遵循了搜索的一个基本原则:优先处理确定性选择,推迟非确定性选择
  • 度启发式:选择涉及最多约束的未赋值变量。这通常作为MRV的平局决胜规则使用。约束越多的变量,其赋值对后续搜索的影响越大,优先处理可以更有效地通过约束传播缩减搜索空间。

值选择启发式
一旦选定变量,按什么顺序尝试其值也很重要。

  • 最少约束值(LCV)启发式:优先尝试那个能给邻居变量留下最多选择的值。其理念是保留更大的灵活性,以增加找到解的机会。

通常,组合使用MRV(配合度启发式打破平局)选择变量,再使用LCV选择值,能取得良好效果。

智能回溯:冲突导向的回溯

标准的时序回溯(遇到失败就回溯到最近的一个决策点)可能非常低效,因为它会重复大量相似的失败搜索。

考虑一个例子:早期做出了一个错误决策(例如,将两个相邻区域都赋值为红色),但直到很晚才检测到失败。在回溯时,如果仅仅回溯到最近的决策点(比如塔斯马尼亚的颜色),而该决策与导致失败的冲突完全无关,那么你会为塔斯马尼亚的每个颜色值重复几乎相同的失败搜索过程,浪费大量时间。

解决方案是冲突导向的回溯。其核心思想是识别导致失败的根源——一个“不良集”。

  • 不良集:一个部分赋值,它本身(无论如何扩展)都不可能成为任何解的一部分。
  • 冲突集/解释:导致某个变量值域(特别是变为空)的一组赋值。通过分析冲突集,并递归地将其中因传播而被迫产生的确定性赋值替换为导致它们的原始决策,我们可以追溯到一个由早期关键决策构成的不良集。

智能回溯的步骤:

  1. 当检测到失败(如某个变量值域为空)时,识别出导致该失败的不良集(冲突集)。
  2. 回溯时,不是回到最近的决策点,而是弹出搜索栈中的赋值,直到弹出不良集中的一个元素。然后为该变量尝试另一个值。
  3. 这通常允许我们跳过多层无关的决策,直接回到导致问题的关键决策点。

为了实现这一点,算法需要记录:每次值域缩减是由哪个(些)赋值导致的。当发生失败时,就可以从当前空值域变量出发,逆向替换,最终得到一个由原始决策构成的不良集。

通过使用冲突导向的回溯,可以避免大量无用搜索,使CSP求解器的性能得到数量级的提升。

本节课中我们一起学习了CSP的搜索策略。我们了解了如何通过结合搜索(变量/值选择)和传播(一致性检查)来求解CSP,并比较了不同强度传播的利弊。我们重点介绍了回溯搜索框架,以及MRV、度、LCV等启发式方法。最后,我们探讨了标准时序回溯的低效性,并引入了强大的冲突导向回溯技术,它通过分析失败原因并直接回溯到冲突根源,极大地提升了搜索效率。

019:CSP局部搜索与结构化

在本节课中,我们将学习约束满足问题的最后两个重要子主题:局部搜索和利用问题结构来提升求解效率。我们将看到如何将随机局部搜索技术应用于CSP,以及如何通过识别问题的树状结构或割集来将指数级复杂度问题转化为多项式级问题,从而大幅提升性能。

上一节我们介绍了冲突导向回溯搜索,它是一种能极大提升CSP求解效率的智能搜索方法。本节中,我们来看看如何将局部搜索技术应用于CSP,以及如何利用CSP的结构来进一步优化求解过程。

局部搜索在CSP中的应用

局部搜索的核心思想是从一个状态跳转到其“邻居”状态。在CSP中,一个最直接的邻居状态定义是:改变一个变量的赋值。也就是说,我们从某个完全赋值(可能是随机或启发式生成的)开始,然后每次通过改变一个变量的值来移动到相邻状态。

其基本直觉是为违反的约束赋予惩罚值。我们的目标是找到一个惩罚值为0的状态,即所有约束都得到满足的解。这可以归结为一种“最小冲突”启发式方法。

以下是实现该思想的一种简单爬山算法伪代码:

function MIN-CONFLICTS(csp, max_steps):
    current = 一个csp的完全赋值(随机或启发式生成)
    for i = 1 to max_steps:
        if current 是csp的解:
            return current
        从违反约束的变量中随机选择一个变量 var
        为变量 var 选择一个能最小化冲突数量的值 value
        将 current 中 var 的值设置为 value
    return failure

当然,你可以采用更复杂的策略,例如:

  • 选择导致最多冲突的变量进行修改。
  • 根据约束违反的频率为其赋予不同权重。
  • 结合模拟退火、随机游走或禁忌搜索等随机局部搜索技术来避免陷入局部最优。

在20世纪90年代,随机局部搜索(如模拟退火)在解决像N皇后这样的大规模、明显可满足的CSP问题时,表现出了远超传统搜索技术的效率,尤其是在规划等编码为CSP的问题中。

利用CSP的结构

分治法:识别独立子问题

如果一个CSP的约束图可以分解为几个互不连接的连通分量(即独立子问题),那么我们可以分别求解每个子问题。这能带来性能的极大提升。

复杂度分析:假设原问题有 N 个变量,域大小为 D,最坏情况复杂度为 O(D^N)。如果能将其分解为 k 个大小约为 N/k 的独立子问题,则总复杂度变为 k * O(D^(N/k)),这是指数级的降低。

操作方法:使用Tarjan等线性时间算法识别图的强连通分量,每个分量即为一个独立子问题。

树结构CSP的高效算法

如果CSP的约束图是一棵树(无环图),那么存在一个 O(n * d^2) 的算法可以求解,其中 n 是变量数,d 是最大域大小。这相当于从指数复杂度降到了线性复杂度。

算法步骤(定向弧相容 + 赋值)

  1. 选择根节点并排序:任选一个变量作为根,对变量进行从根到叶的拓扑排序(父节点在子节点之前)。
  2. 后向传播(从叶到根):从最后一个变量开始向前(即从叶向根)处理。对每个变量,使其值域与其所有子节点的值域保持弧相容(即删除父节点中与某个子节点所有值都不相容的值)。如果任何变量的值域变为空,则问题无解。
  3. 前向赋值(从根到叶):如果步骤2未发现不一致,则从第一个变量(根)开始向后(即从根向叶)赋值。为每个变量赋予一个与其父节点赋值相容的值。由于步骤2已保证相容性,这一步总能成功。

这个算法之所以高效,是因为树结构保证了信息(约束)可以无环地单向传播。

割集调节

对于许多非树的CSP,我们可能找到一个割集:一个变量集合,如果从图中移除这些变量,剩余部分将形成一棵树(或多个独立子树)。

求解思路

  1. 找到一个小规模的割集 S(寻找最小割集是NP难的,但存在高效的近似算法)。
  2. 枚举割集 S 中所有可能的相容赋值组合。假设割集大小为 c,域大小为 d,则有最多 d^c 种组合。
  3. 对于割集的每一种赋值,将其代入原问题。由于剩余部分是一棵树,我们可以用 O(n * d^2) 的高效树算法来求解这个简化后的CSP。

复杂度分析:总复杂度从 O(d^n) 降低到 O(d^c * (n-c) * d^2)。当割集 c 远小于总变量数 n 时,性能提升是巨大的。

对称性破缺

在许多CSP中,值域是对称的(例如图形着色问题中的颜色)。这会导致搜索空间中存在大量本质上相同的解(通过置换值即可得到),从而使得搜索算法浪费大量时间重复探索等价的失败路径。

对称性破缺通过添加额外的约束来排除对称解,从而缩减搜索空间。一个常见的方法是引入值的顺序

操作方法
假设有 n 个变量,值域是对称的。我们可以规定:分配给变量的第一个“不同值”必须遵循某种顺序。
例如,在着色问题中,我们可以添加约束:如果 i < j,那么变量 V_i 的赋值必须小于或等于变量 V_j 的赋值(这里“小于”是基于值的一个任意但固定的顺序,例如红<蓝<绿)。这确保了在所有等价的着色方案中,只有一种满足这个顺序的会被找到。

识别和利用对称性可以带来数量级的性能提升,是设计高效CSP求解器时需要考虑的一个重要方面。


本节课中我们一起学习了约束满足问题的最后两个关键技巧。我们了解了如何将局部搜索的随机优化思想应用于CSP,通过定义邻居状态和最小冲突启发式来寻找解。更重要的是,我们探讨了如何通过分析CSP的结构来设计高效算法:通过分治处理独立子问题;利用树结构实现线性时间算法;以及通过识别割集将复杂问题分解为树结构子问题来求解。最后,我们还介绍了对称性破缺技术,通过消除搜索空间中的冗余来提升效率。这些结构化方法能带来性能的极大提升,是解决实际大规模CSP问题的有力工具。

020:布尔逻辑

在本节课中,我们将要学习命题逻辑,也称为布尔逻辑。这是人工智能中用于表示和推理知识的基础工具。我们将从基本概念开始,逐步深入到公式表示、语义解释以及一些重要的实用技巧。

概述

布尔逻辑是处理真(True)和假(False)两种值的逻辑系统。它构成了许多计算和推理系统的基础。本节我们将学习其语法、语义以及如何有效地表示逻辑公式。


布尔逻辑的语法 🧱

上一节我们介绍了布尔逻辑的基本概念,本节中我们来看看如何构建布尔逻辑公式。

一个命题公式布尔公式句子由以下成分构成:

  • 符号 TrueFalse(有时写作 ⊤ 和 ⊥)是公式,它们是布尔常量。
  • 命题原子。我将使用大写字母(可能带下标)来表示原子,例如 A1, B
  • 任何布尔公式的布尔组合本身也是一个布尔公式。

布尔组合由以下运算符定义:

  • 否定: ¬ (not)
  • 合取: (and)
  • 析取: (or)
  • 蕴含: (implies)
  • 反蕴含: (implied by)
  • 双条件(当且仅当): (if and only if)
  • 异或: (exclusive or)

例如,(¬A1 → (A2 ∧ A3)) ∧ (¬A1 ∨ ¬A4) 是一个布尔公式。

给定一个公式 φ,我们称出现在 φ 中的原子集合为 φ原子集

另一个重要概念是文字

  • 一个文字要么是一个命题原子(称为正文字),要么是一个原子的否定(称为负文字)。
  • 我们约定,如果一个文字已经是负的(如 ¬A),那么它的否定(¬¬A)就简化为原子 A

以下是基于文字构建的两个结构:

  • 一个子句是若干文字的析取,例如 (A1 ∨ ¬A2 ∨ A3)
  • 一个立方是若干文字的合取,例如 (A1 ∧ ¬A2 ∧ A3)

布尔运算符的语义 📖

上一节我们学习了如何构建公式,本节中我们来看看这些公式的含义,即它们的真值。

布尔运算符的含义由以下真值表定义。假设 αβ 是公式:

α β ¬α α ∧ β α ∨ β α → β α ↔ β α ⊕ β
T T F T T T T F
T F F F T F F T
F T T F T T F T
F F T F F T T F

核心概念解释:

  • 否定 (¬):翻转真值。
  • 合取 ():仅当两者都为真时为真。
  • 析取 ():至少一个为真时为真。
  • 蕴含 (α → β):如果前件 α 为假,或者后件 β 为真,则为真。特别注意:当前件 α 为假时,无论 β 是什么,蕴含式都为真。
  • 双条件 ():当两者真值相同时为真。
  • 异或 ():当两者真值不同时为真。

关于自然语言与逻辑蕴含的注意事项:
在自然语言中,“如果…那么…”常常暗示因果关系或时间顺序。但在逻辑中,α → β 仅表示一种真值关系,不要求 αβ 之间有实际关联。例如,“5是奇数 → 东京是日本的首都”在逻辑上为真(因为两者都为真),尽管两者没有因果关系。同样,“5是偶数 → 萨姆很聪明”也为真(因为前件为假)。理解这一点对于用逻辑准确建模至关重要。


运算符的性质与等价关系 🔄

布尔运算符具有一些基本性质,并且它们之间可以相互表达。

以下是运算符的基本性质:

  • , , , 满足交换律结合律。因此,在书写时可以省略括号,例如 A ∧ B ∧ C
  • 既不满足交换律也不满足结合律α → ββ → α 不同,(α → β) → γα → (β → γ) 也不同。

不同运算符之间可以通过等价关系相互转换,这使得它们在逻辑上是冗余的,但使用起来很方便。

以下是一些关键的等价关系:

  • 双重否定¬¬α ≡ α
  • 德摩根定律
    • ¬(α ∧ β) ≡ (¬α ∨ ¬β)
    • ¬(α ∨ β) ≡ (¬α ∧ ¬β)
  • 蕴含的转化(α → β) ≡ (¬α ∨ β)这是一个极其重要的等价式,将蕴含转化为析取,常常能简化推理。
  • 蕴含的否定¬(α → β) ≡ (α ∧ ¬β)
  • 双条件的表达(α ↔ β) ≡ (α → β) ∧ (β → α)
  • 异或的表达(α ⊕ β) ≡ ¬(α ↔ β)

从这些等价关系可以看出,实际上只需要否定 (¬)合取 ()(或者否定和析取)就可以表达所有其他布尔运算符。其他运算符可以看作是为了方便而引入的“语法糖”。


公式的内部表示(树与DAG)🌳➡️🕸️

在计算机中表示逻辑公式时,选择不同的数据结构对效率有巨大影响。

最直观的表示方式是语法树

  • 内部节点和根节点标记为布尔运算符。
  • 叶子节点标记为布尔原子。
  • 例如,公式 (A ∧ B) ∨ (C ∧ D) 可以表示为一棵树。

然而,在实现自动推理工具时,我们通常使用有向无环图

  • DAG 允许共享相同的子公式。
  • 这可以指数级地减少表示所需的空间,特别是当公式中包含大量 (双条件)或 (异或)运算时。

考虑以下例子:
(A1 ↔ (A2 ↔ (A3 ↔ A4)))
如果将其展开为只使用 , , ¬ 的表达式(通过 (P ↔ Q) ≡ (P → Q) ∧ (Q → P)),子公式 A2 ↔ (A3 ↔ A4) 会被重复。随着链的加长,重复会呈指数增长。使用 DAG 表示可以避免这种重复,所有相同的子公式只存储一次。

实现 DAG 表示的一种常用算法是哈希消元算法,它自底向上遍历表达式,并在哈希表中缓存每个唯一的子表达式。


真值指派与语义(续)🎯

上一节我们讨论了公式的语法和基本语义,本节我们更正式地定义真值指派及其满足关系。

一个全真值指派 μ 是一个函数,它为公式中出现的每个原子分配一个真值(True 或 False)。

  • 在计算机中,世界的状态(或问题的相关部分)可以用一个巨大的全真值指派来表示,其中每个比特对应一个布尔变量。

一个部分真值指派 η 只为一部分原子分配真值。

  • 一个部分指派 η 隐式地代表了一组全真值指派,即所有与 η 已分配值一致的全指派。
  • 如果 ηn 个变量中的 n-k 个分配了值,那么它隐式代表了 2^k 个可能的全指派。

为了方便,我们常用文字的集合立方(合取式)来表示真值指派。

  • 例如,指派 {A1, ¬A2} 或立方 (A1 ∧ ¬A2) 表示原子 A1 为真,A2 为假。
  • 这与公式 (A1 ∧ ¬A2) 被满足的概念是一致的:任何满足该公式的全指派都必须使 A1 为真,A2 为假。

我们定义全真值指派 μ 满足一个公式 φ(记作 μ ⊨ φ)如下:

  • μ ⊨ A(原子)当且仅当 μ(A) = True
  • μ ⊨ ¬φ 当且仅当 μ ⊭ φ(即 μ 不满足 φ)。
  • μ ⊨ (φ ∧ ψ) 当且仅当 μ ⊨ φμ ⊨ ψ
  • μ ⊨ (φ ∨ ψ) 当且仅当 μ ⊨ φμ ⊨ ψ(或两者)。
  • μ ⊨ (φ → ψ) 当且仅当如果 μ ⊨ φμ ⊨ ψ。根据真值表,这等价于 μ ⊨ ¬φμ ⊨ ψ
  • μ ⊨ (φ ↔ ψ) 当且仅当 μ ⊨ φμ ⊨ ψ 的真值相同。
  • μ ⊨ (φ ⊕ ψ) 当且仅当 μ ⊨ φμ ⊨ ψ 的真值不同。

我们用 M(φ) 表示所有满足公式 φ 的全真值指派的集合。

关于部分指派 η 的满足关系,我们将在后续课程中详细讨论。


总结

本节课中我们一起学习了布尔逻辑的核心内容:

  1. 语法:我们学习了如何用原子、常量和运算符(¬, , , , , )构建布尔公式,并了解了文字、子句和立方的概念。
  2. 语义:我们通过真值表明确了每个运算符的含义,并特别强调了逻辑蕴含()与自然语言中“如果…那么…”的区别,指出它不表示因果关系。
  3. 性质与等价:我们回顾了运算符的交换律、结合律,以及一系列重要的逻辑等价式,特别是 (α → β) ≡ (¬α ∨ β),这是简化推理的关键。
  4. 公式表示:我们比较了公式的树表示和DAG表示,指出在实现自动推理系统时,使用DAG可以避免子公式的指数级重复,极大地提升效率。
  5. 真值指派:我们定义了全指派和部分指派,以及指派满足公式的归纳定义,并介绍了用文字集合或立方来表示指派的简便方法。

布尔逻辑是人工智能中知识表示与推理的基石,掌握其基本概念和细节对后续学习至关重要。

021:布尔推理

概述

在本节课中,我们将要学习布尔逻辑推理的基础知识。我们将从真值指派、公式满足性等核心概念开始,逐步深入到命题逻辑的推理方法,特别是归结法和DPLL算法。课程将重点解释如何将复杂的逻辑问题转化为可计算的满足性问题,并介绍现代自动推理中使用的关键技术。

真值指派与满足性

上一节我们介绍了布尔逻辑的语法和基本运算符。本节中,我们来看看其语义的核心——真值指派。

一个全真值指派是一个从布尔原子集合到真值集合 {True, False} 的函数。在人工智能的上下文中,如果我们把所有信息都布尔化,一个真值指派可以看作是世界(或相关部分)状态的一种表示。

一个部分真值指派只将部分原子映射到真值。关于部分指派的一个基本事实是:一个部分指派隐式地代表了大量的全指派。具体来说,它代表了 2^k 个全指派,其中 k 是未被赋值的原子数量。这个看似简单的概念是大多数布尔逻辑推理算法的基础。

在表示上,我们通常不使用函数,而是使用文字序列、文字的合取(称为立方体)或集合来表示真值指派。例如,一个指派可以表示为 {a1, ¬a2}a1 ∧ ¬a2,其含义是原子 a1 为真,a2 为假。

公式的满足性

我们说一个全真值指派 τ 满足公式 φ(记作 τ ⊨ φ),如果它满足递归定义的标准布尔运算符语义。需要特别注意:τ ⊨ ¬φ 当且仅当 τ 不满足 φ。这个定义仅适用于全指派。对于一个部分指派,如果它不足以满足公式 φ,这并不意味着它满足 ¬φ。这个区别至关重要。

我们用 M(φ) 表示公式 φ 的模型集合,即所有满足 φ 的全真值指派的集合。

部分指派的满足性

我们可以说一个部分真值指派 μ 强满足公式 φ(记作 μ ⊨ φ),如果它的所有全扩展都满足 φ。这个定义在逻辑学中略有争议,但我们将采用此定义。

为什么这很重要?考虑公式 a1 ∨ a2。部分指派 {a1} 强满足该公式,因为无论 a2 取何值,a1 为真都足以使整个析取式为真。这类似于编程语言中的短路求值(惰性求值)。

可满足性、有效性及蕴涵

基于满足性,我们定义几个核心概念:

  • 可满足性:公式 φ 是可满足的,如果存在某个全真值指派 τ 满足 φ。这等价于 M(φ) ≠ ∅
  • 蕴涵:公式 α 蕴涵公式 β(记作 α ⊨ β),如果 α 的每一个模型也是 β 的模型。用集合表示为 M(α) ⊆ M(β)
  • 有效性:公式 φ 是有效的(记作 ⊨ φ),如果它的每个全真值指派都是其模型,即 M(φ) 是所有可能指派的集合。

基本性质与演绎定理

以下是几个基本性质:

  1. 一个公式是有效的,当且仅当其否定是不可满足的。
  2. 演绎定理:对于布尔逻辑,α ⊨ β 当且仅当公式 α → β 是有效的。这个定理并非对所有逻辑都成立(例如某些时序逻辑),但在布尔逻辑和谓词逻辑中成立。

结合这两个性质,我们可以得出一个关键推论:α ⊨ β 当且仅当 α ∧ ¬β 是不可满足的。

这个推论极其重要,因为它意味着有效性检查和蕴涵检查都可以直接归约为(不可)满足性检查。在现代布尔推理中,满足性检查是核心步骤,我们将所有推理问题都转化为对某个公式进行(不可)满足性检查。

等价性与可满足性保持

我们通常说两个公式 αβ等价的,如果它们具有相同的模型集合,即 M(α) = M(β)

一个较弱但同样有用的概念是可满足性等价αβ 是可满足性等价的,当且仅当 α 是可满足的 iff β 是可满足的。显然,如果两个公式等价,那么它们必然可满足性等价,但反之则不成立。

以下是可满足性等价但不等价的一个例子:

  • α = a1 ∨ a2
  • β = (a1 ∨ ¬a3) ∧ (a3 ∨ a2)

为什么这个概念在计算机科学中比在纯数学中更受重视?因为在自动推理中,我们经常对公式进行变换。有些变换是有效性保持的(保持模型不变),例如应用德摩根律。但有些变换仅仅是可满足性保持的:如果原公式可满足,则变换后的公式也可满足;如果原公式不可满足,则变换后的公式也不可满足。

由于我们进行推理的最终目标通常是检查可满足性,因此我们可以自由地使用可满足性保持的变换来简化公式,即使它们会改变模型的集合。这允许我们引入更多、更有效的简化手段,从而极大地提升推理效率。关键在于,我们必须清楚所使用的变换是哪种类型。

计算复杂性与合取范式

对于包含 n 个布尔变量的公式,存在 2^n 个可能的真值指派。因此,判定布尔公式可满足性的问题是 NP 完全的。事实上,可满足性问题(SAT)是第一个被证明的 NP 完全问题。有效性检查、蕴涵检查和等价性检查都可以多项式时间归约为(不可)满足性检查,因此它们分别是 co-NP 完全和 NP 完全的。这意味着,在经典计算框架下,普遍认为不存在解决所有实例的多项式时间算法。

合取范式

一个非常重要的公式表示形式是合取范式。一个公式是 CNF,如果它可以表示为一系列子句的合取,其中每个子句是文字的析取。

CNF 之所以重要,是因为它将任意复杂结构的公式转化为一个子句的集合。这使得推理可以集中在处理这些子句上,而无需考虑公式原始的递归结构,从而简化了算法设计。

将任意公式转化为 CNF 的标准逻辑学方法是应用德摩根律和分配律。然而,这种方法在最坏情况下可能导致公式大小指数级膨胀(例如,当处理析取式的合取时,会产生笛卡尔积式的展开)。这种转化是有效性保持的,但由于效率问题,在实践中很少直接使用。

线性时间转化:Tseitin 编码

实践中广泛使用的是 Tseitin 编码(或称标记法)。其核心思想是引入新的布尔变量来标记复杂的子公式。

以下是自底向上的策略:

  1. 假设公式已化为否定范式(即否定符号只出现在原子前)。
  2. 对于每个形如 (文字 运算符 文字) 的子公式,引入一个新的布尔变量 b 来代表它。
  3. 在原公式中,用 b 替换该子公式的所有出现。
  4. 添加一个定义子句:b ↔ (文字 运算符 文字)。这个双蕴含可以很容易地转化为少量(3或4个)CNF 子句。

对公式中的每个运算符重复此过程。这种方法的优点是:

  • 线性复杂度:产生的 CNF 公式大小相对于原公式是线性的。
  • 可满足性等价:新公式与原公式是可满足性等价的(但不等价)。
  • 引入新变量:代价是引入了额外的布尔变量。

由于我们通常只关心可满足性,Tseitin 编码是实践中将公式转化为 CNF 的首选方法。

命题推理:归结法

现在,我们来看看如何进行实际的布尔推理。一个最基本、最重要的算法是归结法

归结法的基本规则是:给定两个子句 C1 = P ∨ AC2 = ¬P ∨ B,其中 P 是一个文字,AB 是文字析取式,我们可以推导出归结式 A ∨ B

从语义上看,这对应着简单的逻辑推理。例如,从 A¬A ∨ B,通过归结可以得到 B,这正好是假言推理 (A ∧ (A → B)) → B

如何使用归结法进行推理?我们从一个 CNF 公式(即子句集合 S)开始。为了证明 S 是不可满足的,我们反复应用归结规则,从 S 中的子句推导新的子句,并将新子句加入集合。如果最终我们推导出了空子句(记为 False),则证明 S 是不可满足的。因为空子句代表矛盾,说明从 S 可以推出逻辑假。

然而,纯粹的归结法效率不高,因为它可能生成大量无关的子句。现代求解器使用更高效的算法。

现代 SAT 求解基础:DPLL 算法

DPLL 算法是大多数现代 SAT 求解器的理论基础。它本质上是一种深度优先搜索算法,结合了单元传播和纯文字消除等简化规则。

以下是 DPLL 算法的核心框架:

  1. 简化:反复应用单元传播和纯文字消除,简化子句集合。
    • 单元传播:如果一个子句是单元子句(只有一个文字 L),那么 L 必须为真。我们将 L 设为真,并简化所有包含 L¬L 的子句。
    • 纯文字消除:如果一个文字 L 在所有子句中都以同一极性出现(总是正或总是负),那么可以安全地将其设为真(如果总是正)或假(如果总是负),而不会影响可满足性。
  2. 判断
    • 如果子句集合为空,则找到可满足赋值。
    • 如果子句集合包含空子句,则当前分支不可满足,需要回溯。
  3. 决策:如果无法再简化,则选择一个未赋值的变量(决策变量),为其假设一个真值(例如 True),并递归调用 DPLL。如果递归调用返回不可满足,则回溯并尝试另一个赋值(例如 False)。

DPLL 算法通过聪明的分支决策启发式、子句学习(记录导致冲突的原因以避免重复搜索)和非时序回溯等技术,已经变得极其高效,能够处理包含数百万子句的问题。

一个特例:Horn 子句

Horn 子句是至多含有一个正文字的子句。例如:¬A ∨ ¬B ∨ C(等价于 A ∧ B → C)或 ¬A ∨ ¬B(等价于 A ∧ B → False)。

对于只由 Horn 子句构成的公式,存在一个非常高效的多项式时间算法来判断可满足性。算法很简单:

  1. 将所有变量初始化为假。
  2. 反复寻找一个前提(负文字)都已满足的 Horn 子句,则将其结论(正文字)设为真。
  3. 如果推导出矛盾(某个变量同时被要求为真和假),则公式不可满足;否则,最终得到的赋值就是一个模型。

这个算法比通用的 DPLL 简单得多,效率也高得多。

总结

本节课中我们一起学习了布尔推理的核心内容。我们从真值指派和满足性的基本定义出发,理解了可满足性、有效性和蕴涵之间的关系,并认识到将推理问题归约为可满足性检查的重要性。我们学习了两种将公式转化为合取范式的方法,并理解了为什么 Tseitin 编码在实践中更受欢迎。最后,我们探讨了命题推理的基本算法:归结法和作为现代 SAT 求解器基石的 DPLL 算法框架,以及 Horn 子句这一可高效处理的特例。这些概念是自动推理和许多人工智能应用的基础。

022:基于布尔逻辑的智能体

在本节课中,我们将学习如何将逻辑推理能力赋予智能体,使其能够基于知识进行决策。我们将从智能体的基本概念出发,探讨知识表示与推理的重要性,并重点介绍如何使用布尔逻辑来构建一个简单的逻辑智能体。


概述:逻辑智能体与知识库

上一章我们讨论了命题逻辑本身。本节中,我们来看看如何将其应用于智能体。具备逻辑推理能力的智能体,在许多场景下都至关重要。

人工智能的发展经历了不同阶段。早期(直到20世纪90年代末)主要是符号人工智能,专注于自动推理、知识表示与规划。如今,我们见证了神经人工智能(如深度学习、生成式AI)的繁荣。当前的研究趋势是尝试将两者融合。因此,将推理能力融入智能体上下文变得非常重要。

知识表示与推理是人工智能的一个完整领域,致力于让计算机系统能够表示知识,并利用它来推断新知识以完成任务。这构成了基于知识的系统的核心。此类智能体维护一个知识库,用以表示世界(或与之相关的部分)的状态。知识使用形式化语言(如逻辑)进行表示,以确保语义清晰、无歧义。

知识库包含背景知识(智能体初始拥有的或从以往经验中学到的知识),并随着智能体通过感知获取新信息而不断更新。推理的基本概念是逻辑蕴涵,即从已有知识和感知到的事实中推导出新结论。为此,智能体需要一个推理引擎

推理引擎的关键特性是领域无关性。这意味着引擎本身并不了解你正在解决的具体问题;领域特定的信息被编码到某种形式化语言(如CSP、布尔逻辑、一阶逻辑)中,然后引擎基于这些形式化表示进行推理。


什么是推理?

推理是对代表一系列信念的符号进行形式化操作,以根据给定的语义产生新的信念表示。通常,我们使用逻辑或可归结为逻辑的系统。

一个基本步骤是逻辑蕴涵逻辑演绎。这是一个非常古老的概念。例如:

  • 已知:病人X对药物M过敏。
  • 已知:任何对药物M过敏的人,也对药物M‘过敏。
  • 可推出:病人X对药物M’过敏。
  • 因此,医生知道不应给病人X开药物M‘。

这个演绎步骤的有趣之处在于:如果你推断所依据的表达式真实地反映了现实世界,那么你推断出的结论在现实世界中同样成立。这是因为逻辑规则符合表达式的语义。这与数学推理非常相似。会计师相信计算出的现金余额是准确的,正是因为数学运算符合现实世界的语义。

智能体可能进行的查询是:“我应该给X开药M‘吗?”,答案是“否”。这就是逻辑蕴涵推理。此外,还有概率推理(在不能100%确定时,以一定概率推断事实)和溯因推理(根据观察结果推断最可能的原因,如诊断),但本课程不涵盖后者。


逻辑智能体如何工作?

一个逻辑智能体将领域知识与当前感知结合起来,以推断它无法直接评估的现实方面(因为我们通常不能假设环境是完全可观察的)。

知识库需要能够:

  1. 表示状态和动作。
  2. 获取新的感知。
  3. 更新其对世界的内部表示。
  4. 从其他事实中推断出新事实。
  5. 推断隐藏属性(例如,病人也对药物M‘过敏),从而推导出适当的动作(例如,不开M‘这个药)。

一个实例:Wumpus世界

为了直观理解,我们使用一个简化版的Wumpus世界游戏作为例子。这是一个简单的视频游戏环境:

  • 智能体目标:找到金子。
  • 移动方式:每次可水平或垂直移动一格。
  • 危险:陷阱(掉入则死亡)和怪兽Wumpus(遇到则被杀死)。
  • 感知:在陷阱相邻格会感到“微风”;在Wumpus相邻格会闻到“恶臭”。
  • 限制:智能体只能感知当前所在格的情况,视野受限。

假设我们的英雄(智能体)从位置(1,1)开始,感知到“无微风,无恶臭”。根据规则,他可以推断(1,2)和(2,1)既没有陷阱也没有Wumpus,因此可以安全向上或向右移动。

他选择向上移动到(1,2),感知到“有微风,无恶臭”。根据规则,微风意味着相邻格(1,1), (1,3), (2,2)中至少有一个是陷阱。但已知(1,1)没有陷阱,所以陷阱在(1,3)或(2,2)中。这是一个危险情况,因此他选择返回(1,1)。

接着,他向右移动到(2,1),感知到“无微风,无恶臭”。现在他可以推断:

  • 关于陷阱:之前知道陷阱在(1,3)或(2,2)。现在(2,1)无微风,意味着其相邻格(2,2)和(3,1)没有陷阱。因此,陷阱一定在(1,3)。
  • 关于Wumpus:在(1,2)时感知到“无恶臭”,意味着其相邻格(1,1), (1,3), (2,2)没有Wumpus。结合已知信息,可以推断Wumpus在(2,3)或(3,2)(具体位置需进一步探索)。

通过这种基本的命题逻辑推理(例如,知道“A或B”并且“非B”,则可推出“A”),智能体逐步构建了对世界的认知,并做出安全移动的决策。在更复杂的情况下(例如,在两个位置都感到微风),可能需要概率推理来评估最安全的移动方向。


知识表示的形式化

我们可以使用不同复杂度的逻辑来构建知识库:

  1. 霍恩逻辑:非常基础,推理高效但表达能力有限。
  2. 布尔逻辑(命题逻辑):更通用,是本节课的重点。
  3. 一阶逻辑:能够表达对象及其关系,更强大。
  4. 模态逻辑:可以处理“可能”、“必然”、“知道”等概念。
  5. 时序逻辑:可以处理与时间相关的概念(如“最终”、“始终”)。
  6. 描述逻辑:用于表示概念、分类和本体。

让我们聚焦于最简单的形式之一:布尔逻辑


基于布尔逻辑的智能体 🧠

一个逻辑智能体使用某种逻辑语言作为形式化基础,并利用逻辑演绎进行推理。其思想是:

  • 将知识库表示为一组命题公式。
  • 将感知和动作表示为命题原子或它们的集合。
  • 在实践中,由于我们通常使用合取范式进行推理,这意味着我们将所有知识收集为一组子句
  • 然后执行命题逻辑推理(如蕴涵、模型检查)。现代技术主要使用SAT求解器(尤其是支持增量添加/删除公式的求解器)。

逻辑作为世界的表示,其句子与现实世界之间的联系是语义。语义将逻辑符号映射到现实世界的对象和关系。如果推理规则在语义上是可靠的,并且初始句子真实地反映了世界,那么所有推断出的结论也必然在现实世界中成立。这类似于我们信任数学计算的结果,因为数学运算符合现实世界的语义。这种保证是抽象推理能够应用于具体问题的基石。


用布尔逻辑形式化Wumpus世界

让我们用布尔逻辑原子来形式化之前的例子:

  • P_{i,j}:表示位置(i, j)有陷阱。
  • W_{i,j}:表示位置(i, j)有Wumpus。
  • B_{i,j}:表示在位置(i, j)感知到微风。
  • S_{i,j}:表示在位置(i, j)感知到恶臭。
  • OK_{i,j}:表示位置(i, j)是安全的(即没有陷阱也没有Wumpus)。这是一个语法糖,定义为 OK_{i,j} ⇔ ¬P_{i,j} ∧ ¬W_{i,j}

我们有一些全局知识规则(已转换为CNF形式):

  1. 微风规则:B_{i,j} ⇔ (P_{i-1,j} ∨ P_{i+1,j} ∨ P_{i,j-1} ∨ P_{i,j+1})
  2. 恶臭规则:S_{i,j} ⇔ (W_{i-1,j} ∨ W_{i+1,j} ∨ W_{i,j-1} ∨ W_{i,j+1})
  3. 安全定义:OK_{i,j} ⇔ ¬P_{i,j} ∧ ¬W_{i,j}

初始状态 (1,1)

  • 已知事实:¬P_{1,1}, ¬W_{1,1}
  • 感知事实:¬B_{1,1}, ¬S_{1,1}(初始感知)
  • 推理:通过单元传播,可以立即推断出 OK_{1,1},进而根据微风和恶臭规则,推断出 ¬W_{1,2}, ¬W_{2,1}, ¬P_{1,2}, ¬P_{2,1},从而得到 OK_{1,2}OK_{2,1}

移动到 (1,2) 后

  • 感知事实:B_{1,2}, ¬S_{1,2}
  • 推理:结合微风规则和已知的 ¬P_{1,1},可以推断出子句 P_{1,3} ∨ P_{2,2}。同时,根据恶臭规则和 ¬S_{1,2},可以推断出 ¬W_{1,1}, ¬W_{1,3}, ¬W_{2,2}(有些已知,有些是新信息)。

返回并移动到 (2,1) 后

  • 感知事实:¬B_{2,1}, ¬S_{2,1}
  • 推理:
    • 无微风意味着 ¬P_{2,2}, ¬P_{3,1}
    • 结合之前推断出的子句 P_{1,3} ∨ P_{2,2} 和新的 ¬P_{2,2},通过单元传播(解析)可以推断出 P_{1,3}
    • 无恶臭意味着 ¬W_{2,2}, ¬W_{3,1}
    • 结合之前关于Wumpus位置的信息,可以逐步缩小范围。
  • 最终,可以推断出 OK_{2,2} 等事实,从而安全移动。

通过将环境知识和感知编码为布尔子句,并应用单元传播解析等推理规则,智能体可以自动推导出隐藏信息,指导其行动。


总结

本节课中,我们一起学习了如何构建基于逻辑的智能体。我们首先了解了知识表示与推理在人工智能中的核心地位,以及逻辑智能体的基本架构。然后,我们通过Wumpus世界的例子,直观地展示了智能体如何通过逻辑推理在部分可观察的环境中做出决策。最后,我们深入探讨了如何使用布尔逻辑来形式化这个世界,将知识、感知和推理步骤转化为命题公式和子句,并利用自动推理技术(如SAT求解器)来模拟智能体的思考过程。

布尔逻辑是逻辑推理中最基础的形式,为我们理解更复杂的一阶逻辑及其他知识表示方法奠定了坚实的基础。

023:一阶逻辑

概述

在本节课中,我们将要学习一阶逻辑。一阶逻辑是一种结构化的知识表示方法,它允许我们表示对象、对象的属性以及对象之间的关系,并能对对象进行量化。我们将从一阶逻辑的基本概念开始,逐步深入到其语法和语义,并探讨其在人工智能代理中的应用。

一阶逻辑简介

上一节我们介绍了命题逻辑,它是一种强大的形式化语言,具有清晰的语义和独立的推理机制。然而,命题逻辑的表达能力有限,每个事实都需要一个独立的原子命题来表示,这在实际应用中非常不便。

本节中我们来看看一阶逻辑。一阶逻辑通过引入对象、谓词、函数和量词,极大地增强了表达能力。它允许我们表示诸如“所有人都是会死的”或“存在一个数是素数”这样的通用陈述。

逻辑的一般定义

逻辑通常被定义为一个三元组,包含语言、语义和推理系统。

以下是逻辑的三个核心组成部分:

  • 语言:一组符号和构建合法句子的语法规则。
  • 语义:一种形式化的规范,用于为每个句子在现实世界中赋予意义。
  • 推理系统:一组形式化的推导规则或算法,用于从已知句子推导出新句子。

一阶逻辑的核心要素

一阶逻辑是一种结构化表示。我们假设世界由对象组成,这些对象具有属性和相互关系。

以下是构成一阶逻辑世界的基本要素:

  • 对象:可以是具体或抽象的实体,如人、房子、数字、理论。
  • 关系:描述对象之间的联系。例如,“兄弟”是一个二元关系,“黑色”是一个一元关系(描述属性)。
  • 函数:将对象映射到另一个对象。例如,“父亲”是一个函数。
  • 量词:允许我们对对象进行量化陈述,如“对于所有对象”或“存在某个对象”。

一阶逻辑的语法

现在,我们开始详细学习一阶逻辑的语法,即如何构建合法的表达式。

基本符号

一阶逻辑的语法包含多种符号。

以下是构建一阶逻辑表达式所需的基本符号类型:

  • 常量符号:表示特定的对象。例如:KingJohn2Trento
  • 谓词符号:表示关系或属性。它们可以是一元、二元或n元的。例如:Brother(二元), Black(一元), >(二元)。
  • 函数符号:表示函数。例如:leftLegmotherOf+
  • 变量符号:表示泛指的对象。例如:xy
  • 逻辑连接词:与命题逻辑相同,包括 ¬(非), (与), (或), (蕴含), (当且仅当)。
  • 量词(全称量词,“对于所有”)和 (存在量词,“存在”)。
  • 等词:一个特殊的二元谓词 =,表示相等。

项用于指代世界中的对象。

以下是项的递归定义:

  • 常量是项。
  • 变量是项。
  • 任何n元函数符号应用于n个项的结果也是项。

例如:

  • KingJohn 是一个项(常量)。
  • x 是一个项(变量)。
  • leftLeg(Richard) 是一个项(函数应用)。
  • 2 * log(2) 也是一个项(嵌套的函数应用)。

原子公式

原子公式是构成更复杂公式的基本构件,其值为真或假。

以下是原子公式的构成方式:

  • 命题常量 TrueFalse
  • 将n元谓词符号应用于n个项。例如:Brother(Richard, John)
  • 项之间的相等关系。例如:leftLeg(Richard) = rightLeg(John)

公式

公式通过组合原子公式和逻辑连接词来构建。

以下是公式的归纳定义:

  • 每个原子公式都是一个公式。
  • 如果 φψ 是公式,那么 ¬φφ ∧ ψφ ∨ ψφ ⇒ ψφ ⇔ ψ 也是公式。
  • 如果 φ 是一个公式,x 是一个变量,那么 ∀x φ∃x φ 也是公式。

闭公式与基公式

理解公式中变量的作用范围很重要。

以下是两个关键概念:

  • 基公式:不包含任何变量的公式。例如:Brother(Richard, John)
  • 闭公式:所有变量都被量词绑定的公式。例如:∀x ∃y (x > y)。闭公式在给定解释后可以直接判断真假。

总结

本节课中我们一起学习了一阶逻辑的基础知识。我们首先了解了为什么需要比命题逻辑更具表达能力的工具。然后,我们探讨了逻辑的一般定义。接着,我们深入分析了一阶逻辑的语法,包括常量、谓词、函数、变量等基本符号,并学习了如何递归地构建项和公式。最后,我们区分了基公式和闭公式。一阶逻辑为表示具有结构和关系的复杂知识提供了强大的框架,是人工智能中知识表示和推理的重要基础。在接下来的课程中,我们将学习一阶逻辑的语义及其推理方法。

024:一阶逻辑

概述

在本节课中,我们将学习一阶逻辑的语法和语义。我们将介绍一阶逻辑的基本构成元素,包括项、原子公式、逻辑连接词和量词。此外,我们还将探讨公式的极性概念,并详细解释一阶逻辑的语义解释,即模型和解释。最后,我们将了解一阶逻辑的一些基本性质及其局限性。

语法回顾

上一节我们开始介绍一阶逻辑。到目前为止,我们已经基本介绍了其语法。除了这里展示的语法规则外,我们还有项的概念。

项可以是常量、变量,或者是应用于任何项的任何函数。这是一个递归定义。

然后我们有原子公式,它可能是应用于某些项的谓词。

项和公式的区别在于,项代表一个对象,例如“理查德国王的母亲”或“国王理查德”或数字“三”,或者“三乘以x的对数”。而谓词则陈述关于某些项的某种属性或事实。它代表一个真值,根据解释的不同,它可能为真或为假。

当然,原子公式可以使用逻辑连接词进行组合,其含义与命题逻辑完全相同。

此外,我们还有量词。我们有全称量词∀x,表示属性P(x)对所有可能的项实例都成立;以及存在量词∃x,表示属性P(x)对某些实例成立。我们将详细讨论这一点。

在此之前,我想介绍另一个非常重要的语法定义,即一阶推理中的极性概念。我不确定之前上过逻辑课程的人是否见过这个概念。这是一个在逻辑课程中经常被忽视的概念,所以请大家注意。

每个公式都有一些子公式。对于每个子公式的实例,我们可以说它是正极性出现、负极性出现,或者同时具有正负极性。本质上,极性的直觉是子公式出现所在作用域内的否定数量的模2。

这是一个递归定义:一个公式在自身中出现时是正极性的。否定会切换极性:如果ψ在φ中出现是正极性,那么¬ψ中的ψ就是负极性的。合取(∧)和析取(∨)会保留子公式的极性。

有点棘手的是蕴含(→):如果蕴含式在φ中是正极性出现,那么蕴含式的前件(ψ1)是负极性出现,后件(ψ2)是正极性出现。这可能听起来很奇怪,但如果你记得ψ1 → ψ2等价于¬ψ1 ∨ ψ2,然后你将其代入定义,你就会明白。

双条件(↔)的奇怪之处在于,无论它在φ中的极性如何,ψ1和ψ2都同时以正极性和负极性出现。为什么会这样?这不应该奇怪,因为如果你记得ψ1 ↔ ψ2等价于(ψ1 → ψ2) ∧ (ψ2 → ψ1)。这只是两个方向相反的蕴含式的合取的简写,然后你可以递归地应用定义。

同样,全称量词(∀)和存在量词(∃)会复制子公式的极性。

那么极性的作用是什么?这个递归定义的意义何在?如果你尝试展开公式,将蕴含式转换为否定和析取,极性本质上就是子公式出现所在作用域内的嵌套否定数量的模2。你知道双重否定会相互抵消。因此,极性告诉你:正极性意味着子公式的满足对主公式的满足有正面贡献。子公式满足得越多,主公式满足的可能性就越大。相反,如果一个公式是负极性出现,子公式满足得越多,主公式满足的可能性就越小。

你可以将其类比为数学表达式中的符号。考虑一个只有加号和减号的算术表达式。如果你看子表达式,并且增加一个子表达式的值,如果该子表达式出现在偶数个负号的作用域内,那么增加它会增加整个表达式的值;如果它出现在奇数个负号的作用域内,增加它会减少整个表达式的值。这非常相似,只不过我们这里说的“值”是指真值。增加正极性子公式满足的可能性,实质上会增加满足主公式的模型集合;反之则会减少。

极性对于自动定理证明者来说非常重要,尽管许多数理逻辑学家对此不太感兴趣,因此在许多逻辑课程中你不会找到这个概念,但它对自动化工具至关重要。

一阶逻辑的语义

好了,关于一阶逻辑的语法就讲到这里,现在我们来谈谈语义。在开始之前,我想说明一下术语。你可能会在不同的书籍中遇到模型、解释等概念,它们的含义可能略有不同。我将采用你们教材中的概念。

本质上,当你有一个句子时,关键是要根据你赋予符号的某种内在含义的解释,来确定这个句子是真还是假。语义的基本要素是所谓的模型,它由论域和解释组合而成。

论域是一个元素集合,我们将一阶逻辑中的对象映射到这个集合。虽然看起来显而易见,但从数学上讲,我们假设论域是非空的。无论怎样,论域至少包含一个元素。论域包含一些对象以及这些对象上的关系和函数。你可以把论域想象成任意的领域,比如数学领域、数字领域、人的领域等等。你可以将符号与你头脑中的对象关联起来。

解释则将句子中的符号映射到论域中的某些东西。具体来说,它将变量映射到对象,将常量符号映射到对象,将谓词符号映射到对象之间的关系,将函数符号映射到函数关系。

本质上,一个原子谓词P(t1, ..., tn)在一个解释下为真,当且仅当由项t1, ..., tn所指称的对象满足关系P。

例如,句子“兄弟(约翰国王,理查德国王)”为真,如果在论域中,由常量项“约翰国王”和“理查德国王”所指称的对象满足“兄弟”关系。

这是高层次、直观层面的解释。让我们更形式化一些。

一个模型M是一个对偶(论域,解释),其中论域D是一个非空的对象集合,称为论域元素。解释I是一个映射(注意,它不一定是单射,所以多个符号可以映射到同一个对象),它将常量符号映射到元素。我们使用方括号加下标I来表示指称。例如,常量符号“理查德国王”被映射到论域中我们称为“狮心王理查”的特定对象。

谓词符号将谓词符号P映射到具有相同元数的论域关系P^I ⊆ Dk。记住,关系是一个子集。k元关系是Dk的一个子集。这是关系的数学定义,与逻辑无关。例如,谓词“兄弟”被映射到“兄弟”的概念,或者更准确地说,映射到所有是兄弟的对象对(或动物对等)的集合。

函数符号被关联到论域函数。一个k元函数符号f被映射到一个论域函数f^I: D^k → D。我们假设f是全函数,所以每个输入值集合都有对应的输出值。

这是基本的要素。在此基础上,我们可以理解如何为变量、项和公式分配论域值。变量值被映射到论域元素。区别在于,这个值可能会根据某些特征而变化,它不是固定的。

现在我们有一个如何映射项的递归定义。项f(t1, ..., tk)在解释I下的指称值,记作 [f(t1, ..., tk)]I,是通过将f对应的论域函数fI应用于项t1, ..., tk在I下的值[t1]^I, ..., [tk]^I而得到的结果。即 [f(t1, ..., tk)]^I = fI([t1]I, ..., [tk]^I)。这看起来有些复杂,但相当直观。

另一个例子是“+”、“-”、“×”被视为二元函数符号,“0”、“1”、“2”、“3”等被解释为通常的数字。那么,表达式“(3 - 1) × (0 + 2)”会被递归解释。注意,函数“×”应用于两个项,这两个项本身分别是函数“-”应用于常量“3”和“1”,以及函数“+”应用于“0”和“2”的结果。这与算术无关,这是逻辑。在给定的解释下,它被解释为算术运算。假设这个解释,我将其解释为3,这个解释为1,减法解释为减法运算,所以这部分解释为数字2。同样,这部分解释为0和2的和,即2。因此整体上,这部分是2,这部分也是2。由于我将“×”解释为乘积函数,我将乘积函数应用于值2和2,得到值4。

不要被它看起来很像代数表达式所误导。这是逻辑,而我赋予它的解释使其看起来像算术表达式。

一旦定义了项的解释,我就可以定义公式的解释,特别是原子公式。原子公式P(t1, ..., tk)在给定解释I下为真,当且仅当项t1, ..., tk在I下映射到的对象满足P所映射到的关系。即P(t1, ..., tk)在I下为真,当且仅当 ([t1]^I, ..., [tk]^I) ∈ P^I。这看起来又有些复杂,但非常直观。

例如,“已婚(我的母亲,我的父亲)”在通常的解释下为真(就我而言)。对于某些人来说,这可能不为真。

另一个例子,使用之前的解释,但增加谓词“>”(严格大于),它被解释为算术上的“严格大于”关系。那么表达式“4 - 0 > 1 + 2”被解释为真。因为“4 - 0”解释为4,“1 + 2”解释为3。由于“>”被解释为“严格大于”的谓词关系,元素4和3满足“大于”关系。

我说过,有一个在所有上下文中通用的谓词,即等词“=”。t1 = t2在解释I下为真,当且仅当项t1和t2映射到同一个论域元素。例如,如果“母亲”按通常解释,“理查德”和“约翰”是兄弟,那么“母亲(理查德)= 母亲(约翰)”解释为真。同样,如果“+”、“-”按通常解释,“4 - 1 = 1 + 2”解释为真,因为两边都计算为3,这个有序对(3,3)属于等词关系。

所以现在这是逻辑。解释允许你表示许多非常不同的东西。

布尔运算符的解释方式与命题逻辑完全相同。

示例与注意事项

好了,这是一个来自你们教材的例子。论域由这些人表示:理查德和约翰。理查德是狮心王理查,约翰是他的兄弟约翰国王(无地王约翰)。他们分别是兄弟。每人有一条左腿,目前约翰是国王,他头上戴着王冠。

从语义上看,这是真的。关于这个例子有趣的是,狮心王理查在故事中常被描绘成好人,而无地王约翰则常被描绘成坏人。想想罗宾汉的故事。但有趣的是,狮心王理查本质上是个十字军战士,屠杀了许多人,而无地王约翰却是历史上第一个制定宪法的人。小说与历史的对比很有趣。

这是一个重要的备注。记住函数是全函数的假设。这意味着你应该为函数的每次应用提供一个输出值。那么“王冠的左腿”呢?人们用两种方式解决了这个问题。首先,在一种称为“空值”的通用逻辑中,为无意义的东西分配一个通用值。实际上,有一个更复杂、更有效的版本,称为“排序逻辑”的概念。排序非常类似于编程中的类型概念,可以为每个常量、变量、函数和谓词定义论域类型,以连贯的方式为每个函数和谓词指定精确的定义域和值域。它们被称为排序逻辑,是一阶逻辑的一个重要分支,但我们这里不讨论它们,因为它们更复杂。我只是想告诉你们这个问题是可以解决的。

量词

现在来到一阶逻辑最有趣的部分。全称量词和存在量词。

∀x α(x, ...) 的含义是“对于所有x,α成立”。例如,∀x (King(x) → Person(x)) 表示“所有国王都是人”。这给出了一个粗略的“国王”概念(狮子王可能不同意,但无论如何)。

本质上,∀x α(x) 在给定模型M下为真,当且仅当对于x可能被映射到的每个可能的论域值,α在M下都为真。所以如果你说∀x (King(x) → Person(x)),意味着你映射x到的每个对象,要么不是国王,要么是人。

我给你们一个关于全称量词的直觉,因为这能帮助我们理解很多事情。你可以把∀看作是一个可能无限多的公式实例的合取。例如,这里的表达式可以看作是 (King(John) → Person(John)) ∧ (King(Richard) → Person(Richard)) ∧ (King(Crown) → Person(Crown)) ∧ ... 的合取。非常重要的因素是,这是一个无限的合取。因此,它的行为就像一个合取。如果你记住全称量词隐含着一个无限的合取,这将允许你理解许多其他事情。

顺便注意,这意味着一阶逻辑表达能力极强,因为它允许你用一个小小的句子表达涉及大量(甚至是无限多)命题逻辑句子才能编码的概念。

让我们考虑对偶量词。我忘了说一点。通常,单独的∀x P(x)没有太多应用,因为很难找到一个对所有你想到的对象都成立的属性。所以,典型的用法是,当你使用全称量词时,通常会使用多次,或者你想限制论域。所以你希望说,某个给定子论域的所有元素都满足某个属性。因此,最终有一种方法可以做到这一点,典型的模式是你说 ∀x (P(x) → ...),其中P(x)是你想要限制的论域。典型的,我想说99%的全称量词使用都是这种形式:∀x (谓词于x或谓词的组合 → 你想要陈述的实际属性)。我们倾向于理解为:所有属于P(x)所表示类别的x元素都满足属性α。所以P(x)可以是King(x),Integer(x)等等。你也可以说更多,比如∀x∀y (Integer(x) ∧ Integer(y) → 关于x和y的某个属性)。

注意,不要犯错误。你不应该写 ∀x (King(x) ∧ Person(x))。这意味着每个人既是国王又是人,这不是真的。∀x (King(x) → Person(x)) 意味着每个是国王的人也是人,而不是说所有人都是国王。记住,对于不满足前件的元素,蕴含式自动为真。所以它不影响公式。

一个重要因素。我忘了说,在你们的教材中,要注意符号表示。因为我想使用全称量词的表示法,即∀x (公式)。你们的教材倾向于写∀x P(x),而不带括号,将全称量词与限制论域的谓词隐含地关联起来。我认为这是一种危险的做法,因为有时你不仅仅有一个简单的蕴含,它本身可能是更复杂公式的结果。所以我建议使用完整的括号,并且我假设在考试中你们使用完整的括号。

全称量词的一个基本性质是,它对于合取可分配,但对于析取不可分配。例如,∀x (P(x) ∧ Q(x)) 等价于 (∀x P(x)) ∧ (∀x Q(x))。例如,“每个人既是国王又是人”等同于说“每个人都是国王并且每个人都是人”。有一个简单的方法来记住这一点。如果你记得全称量词隐含着一个无限的合取,并且合取对于合取是可结合和可分配的,那么全称量词对于合取可分配。

记住,全称量词对于析取不可分配。∀x (P(x) ∨ Q(x)) 不等价于 (∀x P(x)) ∨ (∀x Q(x))。第一个弱得多。例如,“每个人是国王或是农民”比“每个人都是国王或者每个人都是农民”弱得多。后者说这两个完整句子中有一个为真。所以一般来说,(∀x P(x)) ∨ (∀x Q(x)) 蕴含 ∀x (P(x) ∨ Q(x)),但反之则不成立。因为如果前者为真,显然后者也为真,但反过来不一定,可能的情况是一些元素满足这个,一些元素满足那个,总体上每个元素都满足其中之一,但并非所有元素都满足同一方。

全称量词清楚了吗?有任何问题吗?好的,我们来看对偶量词存在量词。

∃x α(x, ...)。我忘了说一点,x当然要在α中出现。即使没有,本质上∃x α与α相同。这个量词读作“存在x使得”或“对于某个x”。这就是为什么我更喜欢“对于某个x”,因为翻译成英语时不需要复杂的“使得”。例如,∃x (King(x) ∧ Evil(x)) 可以表述为“对于某个x,x是国王并且x是邪恶的”,我不需要“使得”。如果你尝试用英语发音复杂的表达式,包含全称和存在量词,你可能会注意到区别。但这是我个人的偏好。你可以使用任何一种。

这表示∃x (King(x) ∧ Evil(x)),翻译成自然语言就是“存在一个邪恶的国王”,意思是存在某人既是国王又是邪恶的。

句法上,∃x α(x) 在模型M下为真,当且仅当存在至少一个x可能被映射到的论域值,使得α成立。你可能注意到∃x P(x) 本身通常信息量不大。

你可以把存在量词看作是一个无限的析取。所以你可以说 (King(Richard) ∧ Evil(Richard)) ∨ (King(John) ∧ Evil(John)) ∨ (King(Crown) ∧ Evil(Crown)) ∨ ... 的析取。根据罗宾汉故事,约翰国王是邪恶的,所以至少有一个实例满足。王冠当然不是邪恶的,无关紧要。

同样,如果你把存在量词看作隐含的无限析取,那么它允许你理解许多事情。同样,我们想限制论域,所以∃x something 通常是平凡的。存在一个实例,没有对象使得...这通常信息量不大。所以你想限制它。存在一个国王,存在一个数字,存在一个人,存在一个教授,等等。你如何用合取来做到这一点?典型的场景是,存在量词与∃x关联,然后是一些限制x论域的谓词,以及你想要陈述的实际内容。所以∃x (King(x) ∧ ...),∃x (Integer(x) ∧ ...)。例如,∃x (Real(x) ∧ derivative(ln(x)) = 1/x) 是数学中的一个已知定理,这个数字是e(顺便说一下,e实际上是无理数)。

注意,一个典型的错误是不要使用蕴含而不是合取。∃x (King(x) → Evil(x)) 意味着存在某人不是国王或者是邪恶的,这没有意义。而∃x (King(x) ∧ Evil(x)) 意味着存在某个国王是邪恶的。所以字面意思是“某个国王是邪恶的”。

与全称量词对偶,存在量词对于析取可分配,但对于合取不可分配。原因与之前完全相同,因为∃x blah blah 是其实例的隐含的无限析取。所以存在量词对于析取可分配,但对于合取不可分配。例如,∃x (P(x) ∨ Q(x)) 等价于 (∃x P(x)) ∨ (∃x Q(x))。但∃x (P(x) ∧ Q(x)) 不等价于 (∃x P(x)) ∧ (∃x Q(x))。存在某物既是国王又是邪恶的,比存在一个国王并且存在一个邪恶的人要强得多。通常,国王并不都是邪恶的。所以前者通常不成立,而后者成立(因为肯定有国王,也肯定有邪恶的人)。

所以存在合取比合取存在要强。

更多示例

让我们举一些例子。“兄弟是兄弟姐妹”:∀x∀y (Brother(x, y) → Sibling(x, y))。这定义了兄弟关系比兄弟姐妹关系更强。我们没有意大利语的“兄弟姐妹”一词。在英语中,sibling指的是有相同父母的人,不分男女。我们说“兄弟姐妹是对称的”:∀x∀y (Sibling(x, y) ↔ Sibling(y, x))。在这种情况下,我们不需要限制论域,因为谓词Sibling和Brother已经隐含了论域限制。

“一个人的母亲是他的女性父母”:∀x∀y (Mother(x, y) ↔ (Female(x) ∧ Parent(x, y)))。注意,这是用已知谓词定义某个谓词的典型模式。你用Parent谓词和Female限制来定义Mother。在这种情况下,你不需要在全称量词中加限制,因为你从谓词继承了论域。

更复杂一点的:“定义堂/表兄弟”。FirstCousin(x1, x2)。这很有趣,因为在定义中你需要存在量词。你如何定义堂兄弟关系?你需要中间元素。FirstCousin(x1, x2) 成立当且仅当存在元素P1和P2,使得Sibling(P1, P2),并且Parent(P1, x1),Parent(P2, x2)。注意,这里的Parent是一个二元关系。你需要存在量词,因为你需要第三个元素来定义x1和x2之间的关系。堂兄弟的概念依赖于其他元素,即存在分别的父母是兄弟姐妹。所以有时你需要存在量词来定义某些东西。

“狗是哺乳动物”:∀x (Dog(x) → Mammal(x))。这是定义某个类别子类的典型蕴含。

一个特殊的子案例是等词。我们知道等词是一个谓词。注意等词。等词是一个特殊的谓词,使得项1 = 项2在解释下为真,当且仅当t1和t2指称同一个对象。这是非常令人惊讶的事情。

注意记住,要注意那些你倾向于赋予隐含解释的符号。“1 = 2”你说错了?它可以是可满足的。为什么?因为这取决于你赋予这个符号和这个符号的解释。在逻辑中,我们通常说我们给数字一个标准解释。我们简单地假设数学中使用的所有常量、谓词和函数都有一个标准解释,即我们在学校或大学学到的那种。我只是想提醒你们,逻辑或一阶逻辑最初是被构想为元数学的,所以古代哲学家称之为元数学,一种语言和解释框架,在此基础上推理数学事实。

所以我们有一个标准解释,通常我们隐含地假设数学符号的标准解释。但记住,我们有一个额外的假设。如果你没有这个假设,这是可满足的。相反,“x = x”是有效的,无论解释如何,因为两个表达式句法相同,所以它们自动映射到同一个对象。所以即使是在数学标准解释之外解释,这也是有效的。

注意,例如,等词允许我们做一些有趣的事情。如果我们想用Parent来定义Sibling,我们必须使用等词和否定。Sibling(x, y) 成立当且仅当 x ≠ y(即¬(x = y))并且存在P1和P2,使得P1 ≠ P2,并且Parent(P1, x),Parent(P2, x),Parent(P1, y),Parent(P2, y)。所以他们有父母1和父母2。我们需要这个,因为否则,如果我们不指定P1和P2不同以及x和y不同,我会是我自己的兄弟姐妹,这是无意义的。而且,如果P1等于P2,这将意味着他们有一个共同的父母,这是兄弟或连襟的定义,而不是兄弟姐妹。所以我们有时需要指定不等性,定义中经常使用不等性,所以当我们量化对象时,我们需要量化不同的对象。

量词的性质

好了,让我们休息一下。我们继续看文件。好了,我们继续上课,举一些例子。

这些是一些你可以建模的例子。例如,“没有人是自己的兄弟姐妹”。这很简单。所以兄弟姐妹关系不是自反的。∀x ¬Sibling(x, x)。这意味着每个人都不是自己的兄弟姐妹。记住,“没有人”意味着∀x ¬。当你说“从不”、“没有人”、“没有”等时,就像是说∀x ¬ something。

“姐妹是女性,兄弟是男性”:Sister(x, y) 意味着Female(x) ∧ Female(y),而Brother(x, y) 意味着Male(x) ∧ Male(y)。每个已婚的人都有一个配偶。注意,这本质上是一个性质。有一些表达式通常定义某个谓词,而另一些则陈述性质。这是我们的性质,这不定义Sister或Brother。定义通常使用“当且仅当”。你说,谓词(参数)当且仅当用先前定义的谓词描述的公式组合。这通常是定义。然后你可以添加性质,比如姐妹都是女性,兄弟都是男性。

“每个已婚的人都有一个配偶”,这又是一个性质:∀x (Person(x) ∧ Married(x) → ∃y Spouse(x, y))。注意,在英语中,我们有完全相同的句子映射到两个不同的句子,但映射到相同的表达式。这里的趋势是,有时我们用“每个...”,有时我们用复数。“已婚的人”隐含地意味着每个已婚的人,这里有一个隐含的“所有”。所以要注意自然语言。自然语言非常模糊,不仅在这种情况下不是单射,而且有时不完全清楚,并不总是容易将英语表达式编码成公式。

举个例子,我教这门课的第一年,我给学生一些练习,给出一些意大利语/英语句子,让学生尝试编码。结果往往是学生能找到我没想到的表达式。这只是因为英语的措辞本质上是误导人的。一般来说,第一年我倾向于在考试中也出这样的题。然后我倾向于避免它们,因为用英语想出一个从逻辑角度看完全明确、完全清晰的非平凡英语句子真的非常困难。所以如果我给你们出题,会是我非常确定的表达式。

注意区别。“只有已婚的人才有配偶”与“已婚的人有配偶”非常不同。“只有已婚的人有配偶”意味着,如果你有配偶,你必须是已婚的。“人们不能与自己的兄弟姐妹结婚”意味着Spouse(x, y) → ¬Sibling(x, y),这也等同于写Sibling(x, y) → ¬Spouse(x, y),因为记住A → ¬B等价于B → ¬A。用析取的方式思考。

同样,“不是每个人都有配偶”可以看作“不是每个人都有配偶”。所以不是每个人都有配偶,即¬∀x (Person(x) → ∃y Spouse(x, y))。但常识也告诉你,不是每个人都有配偶意味着存在某人没有配偶,这与∃x (Person(x) ∧ ¬∃y Spouse(x, y))相同。所以存在某个没有配偶的人。

或者“每个人都有母亲”:∀x (Person(x) → ∃y Mother(y, x))。“每个人都有一个且仅有一个母亲”,哦,这很有趣。因为说“唯一性”意味着你必须使用更复杂的表达式。∀x (Person(x) → (∃y (Mother(y, x) ∧ ¬∃z (z ≠ y ∧ Mother(z, x)))))。你必须说唯一性意味着所有其他人都相等。所以没有不同的另一个。你明白吗?

英语句子的细微变化会导致非常不同的含义和非常不同的逻辑公式来表示它。

逻辑相对于语言的好消息是什么?是一旦你理解了逻辑,一旦你理解了语义,每个学过逻辑的人对所有句子都会给出完全相同的解释。即使来自不同文化、不同背景的人,对自然语言句子可能给出不同的解释,但对逻辑公式,只要他们有相应的背景知识,就会给出相同的解释。

量词的更多性质

再次强调量词的一些性质。首先,文献中可能有多种不同的符号表示。当你嵌套量词时,有时你可以写∀x (∀y ...),或者你可以简单地写,没有歧义。我通常使用点号。有些人用逗号来分解量词:∀x, y α。其他人就写∀x y α,只要你假设变量名只有一个字符,这是可以的。否则,你使用逗号。所以你可能会遇到几种不同的符号表示。

一些事实,如我之前提到的,如果x不在φ中出现,∀x φ等价于φ,∃x φ也一样。所以本质上,x的出现是无关紧要的。全称量词和存在量词只有在x出现在公式内部时才有意义。否则,它们是无关紧要的。

相同类型的量词可以颠倒顺序。∀x∀y P(x, y) 等同于 ∀y∀x P(x, y)。所以如果量词类型相同,你可以颠倒量化变量的顺序。例如,∀x∀y (x > y) 等同于 ∀y∀x (x > y)。存在量词也一样。所以只要量化方式相同,你可以交换变量的顺序。为什么?因为全称量词是合取,你可以随意交换合取项的顺序;存在量词是析取,你可以随意交换析取项的顺序。合取和析取都是可结合和可交换的,对吧?

你不能做的是交换存在量词和全称量词的顺序。∃x∀y P(x, y) 不等价于 ∀y∃x P(x, y)。它们不等价。所以∀x∃y Father(x, y)(每个人都有一个父亲)比∃y∀x Father(x, y)(存在一个人是所有人的父亲)弱得多。在“父亲”的通常解释下,第一个是合理的(虽然并非总是已知),但第二个是错误的,除非你给“父亲”一种形而上的解释,比如上帝。所以从生理父亲的意义上讲,它们显然不等价。事实上,第一个(每个人都有一个父亲)是合理的,但每个人都有一个父亲并不意味着存在一个共同的父亲。如果这是一个定理,你可以将其视为证明上帝存在的尝试。如果你给“父亲”一个形而上学的解释,说每个人都有一个父亲,那么这很合理,这蕴含着存在上帝。所以这是上帝存在的一个证明。

再次强调,变量名是无关紧要的。∀x P(x) 等同于 ∀y P(y),只要没有名称冲突。∀x∃y P(x, y) 不等同于 ∀y∃y P(y, y)。你看到,存在量词消除了变量的依赖性。所以如果你有像∀x P(x, y)这样的东西,这就变成了关于y的性质,因为y在这里是自由的。我可以给你一个类比,就像积分。你可以说f(y)定义为∫_0^1 f(x, y) dx。你可以把积分看作是一种量化,对吧?积分变量保留在积分内部。所以意味着,特别是当你量化一个变量时,这个表达式不再依赖于那个变量。这更清楚了吗?这非常类似于积分。

从语义陈述中你可能已经理解了这一点。∀和∃是对偶的。¬∀x α 等价于 ∃x ¬α。¬∃x α 等价于 ∀x ¬α。或者可能的组合。所以一般来说,存在量词和全称量词并非都是必需的。你可以只使用全称量词(或只使用存在量词)来写整个逻辑,并使用否定量词公式。但通常使用两者更方便,也更直观,更易读。你可能注意到,在逻辑中,没有冗余。在布尔逻辑中,你只需要“与非”或“或非”就可以表示一切,尽管使用所有符号非常方便。类似地,这里存在冗余,所以全称量词和存在量词是冗余的。但你可以只使用一个,但像数学中一样使用两者,因为方便。例如,“严格大于”是“不大于等于”的否定,但两者都使用,因为方便。

所以∀x Likes(x, IceCream) 等同于说 ¬∃x ¬Likes(x, IceCream)。当我说“每个人都喜欢冰淇淋”时,等同于说“不存在不喜欢冰淇淋的人”。注意,这很微妙,因为当你用自然语言写这个时,双重否定会削弱一些语气。在意大利语中,有一种称为“litote”的修辞形式,是双重否定,原则上逻辑上应该与肯定句相同,但实际上是一种弱化的表达。我猜英语中也有类似的逻辑含义。

还要注意否定量词。当你说∀x (P(x) → α)时,否定它,你得到¬∀x (P(x) → α) 等价于 ∃x (P(x) ∧ ¬α)。所以这告诉你,为什么当你限制论域时,对于全称量词你使用蕴含,对于存在量词你使用合取。这是一个明显的理由。所以你可能惊讶为什么会用不同的连接词,因为记住,蕴含门可以写成这种形式。这应该匹配你之前关于为什么对存在量词使用合取、对全称量词使用蕴含的理由。

例如,“并非所有国王都是邪恶的”等同于“有些国王不邪恶”。所以¬∀x (King(x) → Evil(x)) 等同于 ∃x (King(x) ∧ ¬Evil(x))。你明白吗?这再次不奇怪,因为∀和∃是对偶的。如果你记住这个表达式,这允许你理解关于量词的许多事情,无需记忆。

公式的基本性质

我希望通过给出一些基于所有这些语义定义的基本宏观性质来结束这部分。以下是这些性质。

我们说模型M满足φ,如果它的解释将φ映射为真。我们称M(φ)为满足φ的模型集合。注意,与布尔逻辑不同,M(φ)可以是无限的。我之前没提到,但某些领域的论域可以是无限的,比如整数域。

我们说φ是可满足的,如果存在某个模型满足它,或者换句话说,模型集合非空。我们说α蕴含β,记作α ⊨ β,如果所有满足α的模型也满足β,即α的模型集合是β的模型集合的子集。

我们说φ是有效的,如果所有模型(所有可能的解释对)都属于M(φ),即每个可能的解释、每个可能的论域、每个可能的解释映射都使公式为真。

我们说α和β是等价的,如果α蕴含β且β蕴含α,或者换句话说,如果它们共享相同的模型集合。与布尔逻辑本质上相同,等价的公式是表示相同模型集合的不同方式。

有时我们表示一组公式而不是合取。我们说一组公式是可满足的(除非另有说明,我们隐含地将一组公式视为其公式的合取),意味着如果这组公式的合取是可满足的,那么存在一个解释使所有公式为真。同样,我们说一组公式蕴含一个公式,如果这组公式的合取蕴含该公式。我们说Γ是有效的,当且仅当这个合取是有效的。

让我看看你们是不是已经睡着了。我可以说一组公式是可满足的当且仅当其所有元素都是可满足的吗?不,为什么?注意,一组公式可满足并不意味着每个公式单独可满足。因为公式集合可满足意味着存在一个模型满足所有公式。说所有公式都是可满足的,并不意味着存在某个模型满足所有公式,这与说每个公式都有一个模型满足它是不同的。这又是我们之前见过的“存在∀”与“∀存在”的变体。所以不要犯错误,说一组公式可满足当且仅当每个公式可满足。不。

我们有一些基本性质,与布尔逻辑的性质完全相同。我们说一个性质是有效的,当且仅当它的否定不可满足。如果你看到这表示所有模型都满足,这表示没有模型满足否定,显然这是。

我怀疑我收到了消息,提示我们可能丢失了录音的部分内容。我可以重讲这节课。

再次,音频有问题。我们看看去年的资深版本。再次,我们有演绎定理。这是非常重要的,它说α ⊨ β 当且仅当 α → β 是有效的。这引出一个重要的推论:α ⊨ β 当且仅当 α ∧ ¬β 是不可满足的。这非常重要,因为再次,这告诉我们一种将蕴含归约为可满足性或不可满足性的方法。所以当我们想检验蕴含时,我们通常这样做。所以有效性检查和蕴含检查可以直接归约为不可满足性检查。

不幸的是,一切并不像布尔逻辑那样。这里有一些事情。那些是,我想说,直观的。为什么那些是可满足的?这是显然的,对吧?哪些是可满足的?“所有x都大于等于0”是可满足的吗?是的,例如在自然数域中,这是成立的。可满足意味着存在对符号的解释(包括变量和这里的符号)使句子为真。例如,在自然数中成立。

那些本质上是不可满足的。P(x) ∧ ¬P(x),¬(x = x),∀x∀y Q(x, y) ∧ ¬Q(a, b)(其中a, b是常量值),这说Q(x, y)总是对x, y为真,而这说对于某些实例a和b不为真,所以这显然不成立。

这显然是有效的。好的,所以这些,我相信这只是。这是有效的,这个蕴含,那个再次有效。这是本质不可满足的,但因为这是无效的,这个相同。

“一大于二”是可满足的,为什么?注意这类问题。“一大于二”我可以把这个符号解释为我,这个符号解释为我的兄弟,这个符号解释为“是...的兄弟”。这取决于你的解释,可满足意味着我给符号赋予解释。清楚了吗?

我留给你们一些练习。我想讲到某一点,所以我结束这节课。我能枚举模型吗?对于从1到无限的每个宇宙元素数量,记住你可能有无限多的解释。对于每个k元谓词P,在句子中,对于每个可能的k元关系对象;对于句子中的每个常量符号c,对于每个对象,c被映射到...等等。在监狱里,这是不可行的。但注意,即使你有一个有限论域,如果你上面还有函数,这也是不可行的,因为你有f(x)。所以如果你有f^1,例如,考虑一个只有后继函数和符号0的论域。我有项0,但我也有项后继(0),后继(后继(0)),即0, 1, 2, 3, 4, 5, 6, ... 等等。所以我甚至有可数无限的元素。所以我可能有句子的可能实例的无限枚举。

这导致我们得出一些非常糟糕的结果。这就是图灵和丘奇已知的结果。图灵说,蕴含(即有效性、不可满足性)只是半可判定的。你可以检查,如果Γ蕴含某个公式,这可以在有限时间内检查。有限时间意味着你的计算机上运行的算法对于某些句子可以做到。但是,你不能保证在有限时间内检查出Γ不蕴含公式。因为可能的反例可能是无限的,所以你应该枚举所有反例,这是无限的。这是一阶逻辑的可怕部分。它只是半可判定的。所以你没有一阶逻辑的证明器,能保证找到蕴含或属性查询的解。你只能运行它并希望。

今天就到这里。我们周五见。非常感谢。

025:一阶逻辑

在本章中,我们将学习一阶逻辑,并了解如何将其应用于知识库智能体。我们将看到一阶逻辑如何比命题逻辑更强大、更简洁地表示知识,并通过一些具体例子来理解其应用。

从命题逻辑到一阶逻辑知识库智能体

上一章我们介绍了命题逻辑。本节中,我们来看看如何将基于命题逻辑的知识库智能体,升级为基于一阶逻辑的智能体。

其核心思想与基于知识的智能体相同。你拥有一个知识库 KB。但这次,KB 不是布尔语句的集合,而是一阶语句的集合。这样做的一大优势是,知识表示可以更加紧凑。因为一个一阶语句可以比一个布尔语句通用得多。一个一阶逻辑语句可以拥有无限多个可能的实例化。

我们假设智能体具备一阶逻辑的推理能力(具体推理方法将在下一章学习)。知识库可以通过类似 TELL 的操作来添加语句。例如:

  • TELL(KB, King(John)):告诉知识库“约翰是国王”。
  • TELL(KB, Person(Richard)):告诉知识库“理查德是人”。

这些是事实,通常是关于具体对象(常元)的断言。此外,还有一些通用陈述,通常是全称量词公式,例如:

  • ∀x King(x) ⇒ Person(x):每个国王都是人。

然后,我们可以向知识库提问或设定目标。例如:

  • ASK(KB, King(John)):询问“约翰是国王吗?”。这只是对已知事实的查询。
  • ASK(KB, Person(John)):询问“约翰是人吗?”。这需要进行演绎推理,因为知识库中并没有直接存储 Person(John)。但通过已知的 King(John) 和通用规则 ∀x King(x) ⇒ Person(x),我们可以推出 Person(John)。这类似于亚里士多德的三段论:所有人都会死,苏格拉底是人,所以苏格拉底会死。
  • ASK(KB, ∃x Person(x)):询问“存在一个人吗?”。答案通常是“是”,因为根据推导出的 Person(John),我们可以推断至少存在一个人。

当询问存在性问题时,我们不仅可以问真假,还可以要求找出满足查询的变量值(即绑定列表)。绑定列表是一个替换列表,将变量替换为项,以匹配存在性查询。例如:

  • ASKVARS(KB, Person(x)) 可能返回绑定 {x/John}{x/Richard}

需要注意的是,这种确定性的值返回通常适用于霍恩子句。对于析取式,情况则不同。例如,如果知识库包含 King(John) ∨ King(Richard),那么 ASK(KB, ∃x King(x)) 的答案是“真”。但 ASKVARS(KB, King(x)) 不会返回任何绑定,因为我们不知道具体是约翰还是理查德。我们知道存在某个国王,但不知道具体是谁。

示例领域:家族关系

现在,我们来看一个具体的例子:如何用一阶逻辑表示家族关系。

假设我们有一些二元谓词符号来表示关系,如 Parent(父母)、Sibling(兄弟姐妹)、Brother(兄弟)、Sister(姐妹)等。同时,我们还可以有函数符号,如 mother(母亲)、father(父亲),它们将一个人映射到其(唯一的)母亲或父亲。

我们可以构建一个知识库,用公理来形式化这些谓词。公理通常是全称量词形式的规则,用于根据其他谓词定义新谓词或断言某些谓词的性质。

以下是部分公理示例:

  • 定义母亲函数∀x ∀y (x = mother(y) ⇔ (Female(x) ∧ Parent(x, y)))
    • 注意:mother 是函数(返回唯一个体),而 Parent 是关系(一个人有两个父母)。
  • 定义兄弟关系∀x ∀y (Brother(x, y) ⇔ (Male(x) ∧ Sibling(x, y)))
  • 定义祖父母关系∀x ∀y (Grandparent(x, y) ⇔ ∃z (Parent(x, z) ∧ Parent(z, y)))
    • 这里需要一个存在量词来引入中间代(父母)。
  • 定义兄弟姐妹关系∀x ∀y (Sibling(x, y) ⇔ (x ≠ y ∧ ∃p1 ∃p2 (p1 ≠ p2 ∧ Parent(p1, x) ∧ Parent(p1, y) ∧ Parent(p2, x) ∧ Parent(p2, y))))
    • 两个人是兄弟姐妹,当且仅当他们有两个不同的共同父母。

定义了这些公理后,我们就可以进行查询,例如 Sibling(John, Richard) 是否为真,或者利用公理4推断出 Sibling 关系是对称的(即 ∀x ∀y (Sibling(x, y) ⇒ Sibling(y, x)))。

示例领域:皮亚诺算术形式化

另一个极其重要的例子是皮亚诺算术的形式化。这不仅仅是示例,它展示了如何用一组公理来形式化算术的基本元素。

我们需要以下初始符号:

  • 一个一元谓词:NatNum(x)(x是自然数)
  • 一个常元符号:0
  • 一个一元函数符号:S(x)(后继函数,即 x+1)

利用这些初始符号,我们可以通过归纳定义整个自然数域:{0, S(0), S(S(0)), ...}。其他符号(如1, 2, +, *)可以作为“语法糖”来定义。

以下是部分皮亚诺公理:

  1. NatNum(0):0是自然数。
  2. ∀x (NatNum(x) ⇒ NatNum(S(x))):如果x是自然数,则x的后继也是自然数。
  3. ∀x (S(x) ≠ 0):0不是任何自然数的后继。
  4. ∀x ∀y ((NatNum(x) ∧ NatNum(y) ∧ x ≠ y) ⇒ S(x) ≠ S(y)):后继函数是单射的。

我们可以用归纳法定义加法:

  • ∀x (Plus(0, x) = x)
  • ∀x ∀y (Plus(S(x), y) = S(Plus(x, y)))

基于这些公理,我们可以进行推理,例如证明 Plus(4, 5) = 9。虽然这种方法在证明大规模计算时不实用(需要极多推理步骤),但它从根本上表明了算术可以在逻辑中被形式化。

在实际的自动推理中,我们通常将逻辑与专门的算术理论(如线性算术)结合起来,这构成了可满足性模理论(SMT)这一重要领域,它能极大地增强推理能力。

一阶逻辑版 Wumpus 世界

最后,我们回顾一下 Wumpus 世界,看看如何用一阶逻辑对其进行更紧凑的形式化。

在命题逻辑版本中,我们需要为每个方格、每个时间步单独编写公理。对于一个4x4的网格,这已经需要很多语句。对于一个1000x1000的网格,则需要数百万条语句,这非常不现实。

一阶逻辑的强大之处在于,它可以使用量词来概括这些模式。例如,定义“相邻”关系:

∀x ∀y ∀a ∀b (Adjacent([x, y], [a, b]) ⇔
    ( (x = a ∧ y = b-1) ∨
      (x = a ∧ y = b+1) ∨
      (y = b ∧ x = a-1) ∨
      (y = b ∧ x = a+1) )
)

仅用这一条语句,我们就定义了所有方格之间所有可能的相邻关系实例。这正是因为 (全称量词)允许我们表达适用于所有方格的通用规则。

我们还可以形式化智能体的感知、位置、时间演变以及行动效果。例如:

  • 将感知与位置关联:∀t ∀s (At(Agent, s, t) ∧ Percept([Breeze], t) ⇒ Breezy(s))
  • 定义“安全”位置:∀s (OK(s) ⇔ ¬Pit(s) ∧ ¬Wumpus(s))
  • 描述行动规则:∀t (At(Agent, s, t) ∧ Gold(s) ⇒ BestAction(Grab, t))

通过这种形式化,我们可以像在命题逻辑中一样进行推理,但知识库的编码却极其紧凑。在命题逻辑中,“对于每个方格……”是一种元语言指令,要求我们人工添加无数条具体语句。而在一阶逻辑中,“对于所有方格……”本身就是一条逻辑语句,由推理机自动处理。这是一阶逻辑表达能力的核心优势。

总结

在本章中,我们一起学习了一阶逻辑的基本概念及其在知识表示中的应用。我们看到,通过引入对象关系函数量词,一阶逻辑能够以非常紧凑的方式表达复杂的知识,远超命题逻辑的能力。我们探讨了家族关系和皮亚诺算术这两个形式化示例,并重新审视了Wumpus世界,对比了一阶逻辑编码与命题逻辑编码在简洁性上的巨大差异。一阶逻辑的这种强大表达能力,为将其与专门领域理论(如算术)结合,构建更强大的自动推理系统(如SMT求解器)奠定了基础。在接下来的章节中,我们将深入探讨如何在一阶逻辑中进行实际的自动推理。

026:一阶逻辑推理

在本节课中,我们将要学习如何在一阶逻辑中进行自动推理。我们将从一些基本的推理步骤开始,然后讨论如何处理特定的子情况,最后介绍在一般情况下的核心推理方法——合一。

基本概念与替换

上一节我们介绍了推理的目标,本节中我们来看看推理的基础操作:替换。

替换是逻辑中的一个基本操作,记作 E[a1/E2] 或简写为 E[a1/E2]。给定一个表达式 E,将其中所有出现的 a1 同时替换为 E2,就得到了替换后的表达式。

替换有两种形式:

  • 项替换a1E2 都是项。例如,表达式 y+1 = 1+y,将 y 替换为 S(x),得到 S(x)+1 = 1+S(x)
  • 公式替换a1E2 都是子公式。例如,公式 Even(x) ∨ Odd(x),将 Even(x) 替换为 Odd(S(x)),得到 Odd(S(x)) ∨ Odd(x)

通常,替换可以同时进行多个。例如,在公式 P(x, y) ⇒ Q(x, y) 中,同时将 x 替换为 1y 替换为 2,得到 P(1, 2) ⇒ Q(1, 2)

重要提示:多个替换是同时进行的,没有先后顺序。这一点在处理共享元素时尤其关键。例如,在 P(x) ∧ Q(y) 中同时替换 xyyf(b),结果是 P(y) ∧ Q(f(b)),而不是先替换 x 得到 P(y) ∧ Q(y),再替换 y 得到 P(f(b)) ∧ Q(f(b))

基本推理规则

了解了替换之后,我们来看看基于替换的几个基本推理规则。

等价替换规则

如果已知两个项或公式等价,则可以在更大的表达式中自由替换它们。

  • 项等价替换:如果 t1 = t2 是已知的,并且有公式 α 包含 t1,那么可以推出将 αt1 替换为 t2 后的新公式 α[t1/t2]。例如,已知 S(x) = x+10 ≠ S(x),可以推出 0 ≠ x+1。替换前后的公式在逻辑上是等价的。
  • 公式等价替换:如果 β1 ⇔ β2 是已知的,并且有公式 α 包含 β1,那么可以推出将 αβ1 替换为 β2 后的新公式 α[β1/β2]。例如,已知 Even(x) ⇔ ¬Odd(x) 和公式 Even(x) ∨ Odd(x),可以推出 ¬Odd(x) ∨ Odd(x)。同样,替换保持逻辑等价。

全称实例化与存在实例化

以下是两个处理量词的核心规则。

  • 全称实例化 (Universal Instantiation, UI):如果有一个全称量词公式 ∀x α(x),那么对于任意项 t,都可以推出 α(t)(即 α[x/t])。其含义是,如果某个性质对一切事物都成立,那么它对任何一个具体事物也成立。例如,从 ∀x (King(x) ∧ Greedy(x) ⇒ Evil(x)),可以实例化得到 King(John) ∧ Greedy(John) ⇒ Evil(John),或者 King(Richard) ∧ Greedy(Richard) ⇒ Evil(Richard)。这个规则是保真的(有效性保持)。
  • 存在实例化 (Existential Instantiation, EI):如果有一个存在量词公式 ∃x α(x),那么可以引入一个新的(即之前未出现过的)常元 c,并用 α(c) 替换原公式。其含义是,如果存在某个事物满足性质 α,我们就给它起个新名字 c。例如,从 ∃x (Crown(x) ∧ OnHead(x, John)),可以引入新常元 C1,得到 Crown(C1) ∧ OnHead(C1, John)。这个规则不保真,但保可满足性。也就是说,原公式集可满足当且仅当新公式集可满足。这对于基于可满足性的推理(如验证蕴含)已经足够。

关于存在实例化的注意事项

  1. 对同一个存在量词公式只能应用一次。
  2. 引入的常元必须是全新的(“新鲜的”)。
  3. 该规则通常只适用于作为合取支出现的存在量化公式(即形如 ... ∧ ∃x α(x) ∧ ...)。如果量词前有否定,需要先利用等价式 ¬∀x α(x) ≡ ∃x ¬α(x)¬∃x α(x) ≡ ∀x ¬α(x) 将其转化。

命题化与合一

我们曾希望将一阶逻辑问题转化为命题逻辑问题来解决,但这种方法存在局限性。

命题化的思想是:将知识库 Γ 和查询 ¬α 中所有全称量词变量用所有可能的地面项(不含变量的项)实例化,生成所有地面原子,然后将每个不同的地面原子视为一个命题符号。这样,一阶逻辑问题就变成了一个(可能非常大的)命题逻辑问题。

然而,命题化有两个主要问题:

  1. 生成大量无关子句:实例化会产生许多与当前证明目标无关的公式,导致搜索空间爆炸。
  2. 处理函数符号时可能无限:如果知识库包含函数符号(如 father(x)),则会生成无限的地面项序列(John, father(John), father(father(John)), ...)。根据 Herbrand 定理,如果查询确实被知识库蕴含,我们总能在某个有限的嵌套深度内找到证明;但如果查询不被蕴含,这个过程可能永不终止。这正反映了一阶逻辑是半可判定的。

因此,命题化并非通用的好方法。我们需要更高效的技术——合一

广义肯定前件式

在介绍合一前,我们先看一个结合了全称实例化和命题推理的常用规则:广义肯定前件式

假设我们有一条规则:∀... (α1 ∧ α2 ∧ ... ∧ αk ⇒ β),并且我们有一些已知事实:α1‘, α2‘, ..., αk‘。如果存在一个替换 θ,使得对于所有 i,有 αi‘θ = αiθ(即应用替换 θ 后,事实与规则前提中的对应子公式变得完全相同),那么我们就可以推出 βθ

示例

  • 规则:∀x (King(x) ∧ Greedy(x) ⇒ Evil(x))
  • 事实:King(John), Greedy(John)
  • 替换:θ = {x/John}
  • 应用规则后,前提变为 King(John) ∧ Greedy(John),与事实匹配。
  • 结论:推出 Evil(John)

这个规则是许多早期专家系统和生产式规则系统的推理基础。

合一算法

合一是一阶逻辑自动推理的核心步骤。它要解决的问题是:给定两个表达式(项、原子公式或列表),是否存在一个替换,使得应用该替换后,两个表达式变得完全相同。

定义:一个替换 θ 是表达式 αβ合一子,当且仅当 αθ = βθ。如果存在这样的 θ,则称 αβ可合一的

示例

  • Knows(John, x)Knows(John, Jane) 可合一,合一子为 θ = {x/Jane}
  • Knows(John, x)Knows(y, OJ) 可合一,合一子为 θ = {y/John, x/OJ}
  • Knows(John, x)Knows(x, OJ) 直接合一会失败,因为要求 x 同时等于 JohnOJ。但变量名可以标准化以避免冲突。我们将第二个表达式重命名为 Knows(z, OJ),然后就能合一:θ = {z/John, x/OJ}

最一般合一子:合一子通常不唯一。例如,Knows(John, x)Knows(y, z) 有多个合一子,如 θ1 = {y/John, x/z}θ2 = {y/John, x/John, z/John}。我们说 θ1θ2 更一般,因为存在另一个替换 σ = {z/John},使得 θ2 = θ1σ。更一般的合一子包含更少的约束(用变量替换变量比用具体项替换变量更一般),因此信息量更大。对于任意一对可合一的表达式,都存在一个最一般合一子,它比任何其他合一子都更一般(在变量重命名的意义下唯一)。

以下是合一算法的高层描述,它递归地寻找最一般合一子:

算法 UNIFY(x, y, θ)

  • 输入:待合一的表达式 xy,当前替换 θ(初始为空)。
  • 输出:最一般合一子 θ,或失败。
  • 过程
    1. 如果 θ 已经是失败标志,则返回失败。
    2. 如果 xy 完全相同,返回 θ
    3. 如果 x 是一个变量,调用 UNIFY-VAR(x, y, θ)
    4. 如果 y 是一个变量,调用 UNIFY-VAR(y, x, θ)
    5. 如果 xy 都是复合表达式(如 P(args)f(args)):
      • 检查运算符是否相同。如果不同,返回失败。
      • 递归调用 UNIFY 来合一参数列表(逐个合一第一个参数,然后将得到的替换用于剩余参数的合一)。
    6. 否则,返回失败。

子过程 UNIFY-VAR(var, x, θ)

  • 如果 θ 中已包含替换 var/val,则递归调用 UNIFY(val, x, θ)
  • 如果 θ 中已包含替换 x/val,则递归调用 UNIFY(var, val, θ)
  • 如果 var 出现在 x 中(出现检查),返回失败(避免无限循环,如 xf(x) 合一)。
  • 否则,将替换 var/x 加入 θ 并返回。

总结

本节课中我们一起学习了一阶逻辑自动推理的基础。

  1. 我们首先介绍了替换操作及其两种形式:项替换和公式替换。
  2. 接着,我们学习了两个基本的量词推理规则:全称实例化存在实例化,并了解了它们的性质和应用条件。
  3. 然后,我们探讨了通过命题化将一阶问题转化为命题问题的思路,并指出了其在实际应用中的局限性(无关子句和无限域问题)。
  4. 之后,我们介绍了结合实例化与命题推理的广义肯定前件式规则。
  5. 最后,我们深入讲解了一阶逻辑推理的核心技术——合一。我们定义了合一子、最一般合一子,并概述了寻找最一般合一子的递归算法。合一算法通过变量替换、出现检查、递归处理复合表达式等步骤,高效地判断两个表达式是否可合一,并给出最一般的合一结果。

掌握这些基本概念和规则,是理解和设计更复杂的一阶逻辑自动定理证明器(如下节课将介绍的归结原理)的关键。

027:一阶逻辑推理

在本节课中,我们将学习一阶逻辑推理的核心技术。我们将从一些基础概念开始,逐步深入到更复杂的推理方法,特别是针对特定可判定子类的推理,以及通用的归结方法。

概述

我们将首先回顾一阶逻辑推理的基本步骤,包括代换、全称与存在实例化,以及广义假言推理。接着,我们会探讨如何将一阶逻辑命题化,并指出其局限性。然后,我们会介绍合一这一关键概念,它是自动推理的基础步骤。之后,我们将专注于两个可判定的逻辑子类:Horn子句Datalog,并学习其对应的推理策略:前向链接后向链接。最后,我们将学习如何通过斯科伦化将任意一阶逻辑公式转换为可进行归结推理的子句形式。


一阶逻辑推理基础

上一节我们介绍了推理的基本概念。本节中,我们来看看一阶逻辑推理的一些基本步骤。

我们提到了代换的概念,这引出了全称实例化和存在实例化等基本步骤。然后我们介绍了广义假言推理,它实质上是实例化与假言推理的结合。

我们还讨论了如何将一阶逻辑命题化,并指出除了极少数情况外,这通常是不可行的。


合一:自动推理的核心步骤

在进入主要的推理模块之前,我们需要理解一个关键元素:合一

当你有两个公式或项时,合一的目标是找到一个共同的变量代换(称为合一子),使得应用这个代换后,两个项变得完全相同。这是大多数自动推理系统的基本步骤。


处理可判定的子类

由于一阶逻辑推理是半可判定的,人们将注意力集中在一些受限的子类上。本节中,我们来看看这些子类及其推理技术。

我们首先讨论Horn子句Datalog的定义。我们特别关注两种技术:前向链接后向链接,这里只给出其核心思想。

我们在此子类中假设没有函数符号,因为函数符号是导致不可判定的根源之一。同时,我们假设不存在在全称量词辖域内的存在量词,因为这本质上会引入函数。

Horn子句与定子句

回忆一下定子句的概念:它是恰好有一个正文字的闭式。如果我们把子句看作蕴含式,那么唯一的正文字就是被蕴含的原子,而负文字是前提。

对于这类公式,我们可以省略全称量词,假设所有变量都是隐式全称量化的。我们通过存在实例化提前处理掉存在量词。

定子句倾向于表示原子公式之间的蕴含关系,是我们在布尔逻辑中看到的对应物。例如,公式 ∀x (King(x) ∧ Greedy(x) ⇒ Evil(x)) 可以写成子句 ¬King(x) ∨ ¬Greedy(x) ∨ Evil(x)。正文字扮演被蕴含项的角色,负文字扮演前提的角色。

这很重要,因为这是Datalog中使用的形式。Datalog是一组没有函数符号的一阶定子句,最初用于在关系数据库上进行推理。如果限制在这个特定子类,推理会变得容易得多。


示例:形式化一个推理问题

为了理解如何应用这些概念,我们来看一个例子。

假设有以下陈述:

  1. 向敌对国出售武器的美国人是罪犯。
  2. 国家Nono是美国的敌人,拥有一些导弹。
  3. Nono所有的导弹都是由美国人Colonel West出售给它的。

目标:证明Colonel West是罪犯。

这是一个可以用逻辑形式化的法律推理案例。但需要注意,我们需要添加一些对人类显而易见但对机器并非立即可知的常识性假设,例如“犯下罪行的人是罪犯”。

以下是形式化过程:

  1. 规则1∀x, y, z American(x) ∧ Weapon(y) ∧ Hostile(z) ∧ Sells(x, y, z) ⇒ Criminal(x)
  2. 事实2a(Nono有导弹)∃x Missile(x) ∧ Owns(Nono, x)。通过存在实例化,引入新常量M1:Missile(M1) ∧ Owns(Nono, M1)
  3. 事实2b(Nono是敌人)Enemy(Nono, America)
  4. 事实3∀x (Missile(x) ∧ Owns(Nono, x) ⇒ Sells(West, x, Nono))
  5. 常识1(导弹是武器)∀x (Missile(x) ⇒ Weapon(x))
  6. 常识2(敌人是敌对国)∀x (Enemy(x, America) ⇒ Hostile(x))
  7. 事实4(West是美国人)American(West)

前向链接推理

上一节我们形式化了问题。本节中,我们来看看如何使用前向链接进行推理。

前向链接的直观思想是:你从已知的原子事实开始,尝试将它们与定子句(蕴含式)进行匹配。只要你能匹配一个子句的所有前提,你就可以推出其结论。这实质上是广义假言推理的迭代应用。

我们从已知的原子事实开始:

  • American(West)
  • Missile(M1)
  • Owns(Nono, M1)
  • Enemy(Nono, America)

以下是推理步骤:

  1. Missile(M1) 和常识1,推出 Weapon(M1)
  2. Missile(M1)Owns(Nono, M1) 和事实3,推出 Sells(West, M1, Nono)
  3. Enemy(Nono, America) 和常识2,推出 Hostile(Nono)

现在,我们有了应用主规则(规则1)所需的所有前提:

  • American(West) (X=West)
  • Weapon(M1) (Y=M1)
  • Hostile(Nono) (Z=Nono)
  • Sells(West, M1, Nono)

应用广义假言推理,我们得到结论:Criminal(West)。这就证明了目标。

这种技术是可靠的,因为每个推理步骤都是广义假言推理的应用。对于Datalog(无函数符号,无非全称量词辖域内的存在量词),该技术也是完备的,并且总能终止。其复杂度与谓词数量呈线性关系,与谓词元数呈指数关系,但在实际应用中,谓词元数通常很小(2或3),因此可以认为是高效的。

前向链接是一种从已知事实出发,逐步推导直到达到目标的搜索策略。


后向链接推理

除了从前提出发,我们也可以从目标出发进行推理,这就是后向链接。

后向链接沿相反方向工作。你从目标开始,检查是否有可以推出该目标的子句。如果有,则找出产生该目标所需的前提,然后尝试证明这些前提。这类似于人类制定计划时的逆向思维。

我们从目标 Criminal(West) 开始。

  1. 如何推出 Criminal(West)?只能匹配规则1。我们需要匹配其前提:

    • American(West) —— 已有。
    • Weapon(Y) —— 新子目标。
    • Sells(West, Y, Z) —— 新子目标。
    • Hostile(Z) —— 新子目标。
      我们实例化变量:X=West。
  2. 处理子目标 Weapon(Y)。匹配常识1,需要 Missile(Y)。我们记录 Y 可能被实例化为 M1(通过其他匹配)。

  3. 处理子目标 Sells(West, Y, Z)。匹配事实3,需要 Missile(Y) ∧ Owns(Nono, Y)。我们记录 Z=Nono,并且需要 Missile(Y)Owns(Nono, Y)

  4. 处理子目标 Hostile(Z),其中 Z=Nono。匹配常识2,需要 Enemy(Nono, America) —— 已有。

  5. 现在,检查为证明 Sells 而需要的子目标 Missile(Y)Owns(Nono, Y)。结合步骤2,Y 被实例化为 M1。我们已有 Missile(M1)Owns(Nono, M1)

所有子目标均得到满足,因此原始目标 Criminal(West) 得证。

后向链接从目标开始,递归地将目标分解为子目标。其搜索空间与证明大小呈线性关系。但若存在函数符号,可能产生无限循环。解决方法包括缓存已证明的目标,这类似于图搜索与树搜索的区别。

后向链接是逻辑编程语言(如Prolog)所使用的核心机制。


归结:通用自动推理

前面我们讨论了受限子类的推理。本节中,我们来看看适用于一般一阶逻辑的通用自动推理方法:归结

其核心思想是:我们将知识库和查询都转换为合取范式,然后应用归结规则。首要步骤是斯科伦化,以消除量词。

转换为合取范式

CNF是一组子句的合取,每个子句是文字(原子或其否定)的析取,且所有变量都是隐式全称量化的。处理通用一阶公式的步骤如下:

  1. 消除蕴含和双蕴含:与命题逻辑相同,将 A ⇒ B 替换为 ¬A ∨ B
  2. 将否定内移,得到否定范式:利用德摩根律和量词否定律(¬∀x P(x) 等价于 ∃x ¬P(x)¬∃x P(x) 等价于 ∀x ¬P(x)),将否定符号直接移到原子公式前。最终公式只包含 ,且否定只出现在原子前。
  3. 标准化变量:重命名量词变量,使得每个量词都使用不同的变量符号,避免后续步骤产生混淆。
  4. 斯科伦化:消除存在量词:这是最关键的一步。对于每个存在量词 ∃y
    • 如果 ∃y 不在任何全称量词 ∀x1...∀xn 的辖域内,则用一个新的常量(斯科伦常量)替换 y
    • 如果 ∃y 在全称量词 ∀x1...∀xn 的辖域内,则用一个新的函数 f(x1, ..., xn)(斯科伦函数)替换 y。这个函数表示 y 依赖于 x1, ..., xn 的值。
    • 每次使用的斯科伦常量或函数必须是新的(未在公式中出现过的)。
  5. 删除全称量词:此时公式中只剩下全称量词,且它们分布在所有变量上。我们可以删除它们,约定所有变量都是全称量化的。
  6. 转换为CNF:使用分配律 over ,将公式化为子句合取的形式。为了避免指数级膨胀,通常使用命题化技术(引入新命题变量标记子公式)。
  7. 标准化分离(建议步骤):在归结前,重命名每个子句中的变量,确保不同子句不使用相同的变量名。这可以避免归结时产生意外的变量统一。

斯科伦化的性质

斯科伦化保持可满足性,但不保持逻辑等价。因为引入的新函数符号限制了可能的模型。然而,对于推理(特别是证明不可满足性)来说,保持可满足性就足够了:

  • 如果斯科伦化后的公式是可满足的,则原公式也是可满足的。
  • 如果斯科伦化后的公式是不可满足的,则原公式也是不可满足的。
  • 由于逻辑蕴涵 KB ⊨ α 等价于 KB ∧ ¬α 不可满足,因此斯科伦化也可用于证明蕴涵关系。

示例:斯科伦化

考虑句子:“所有爱动物的人都会被某人所爱。”
形式化为:∀x [∀y (Animal(y) ⇒ Loves(x, y))] ⇒ [∃z Loves(z, x)]

  1. 消除蕴含:∀x [¬∀y (¬Animal(y) ∨ Loves(x, y))] ∨ [∃z Loves(z, x)]
  2. 否定内移(¬∀y 变为 ∃y ¬):∀x [∃y ¬(¬Animal(y) ∨ Loves(x, y))] ∨ [∃z Loves(z, x)]
    = ∀x [∃y (Animal(y) ∧ ¬Loves(x, y))] ∨ [∃z Loves(z, x)]
  3. 标准化变量(已满足)。
  4. 斯科伦化:
    • 第一个 ∃y∀x 辖域内:用 F(x) 替换 y
    • 第二个 ∃z∀x 辖域内:用 G(x) 替换 z
      得到:∀x [(Animal(F(x)) ∧ ¬Loves(x, F(x))) ∨ Loves(G(x), x)]
  5. 删除全称量词:(Animal(F(x)) ∧ ¬Loves(x, F(x))) ∨ Loves(G(x), x)
  6. 转换为CNF(分配律):[Animal(F(x)) ∨ Loves(G(x), x)] ∧ [¬Loves(x, F(x)) ∨ Loves(G(x), x)]
  7. 标准化分离(两个子句变量独立):[Animal(F(x1)) ∨ Loves(G(x1), x1)] ∧ [¬Loves(x2, F(x2)) ∨ Loves(G(x2), x2)]

常见错误

务必遵循上述步骤顺序:

  • 不要在转换为NNF和标准化变量之前进行斯科伦化或删除量词。因为量词的极性(正出现/负出现)在NNF中才完全确定,错误的极性会导致斯科伦化出错(例如,应将全称量词变为存在量词时却未变)。
  • 务必在消除量词前进行标准化变量,否则可能错误地将本应不同的变量映射到同一个斯科伦函数。

总结

本节课中我们一起学习了一阶逻辑推理的完整路径。

我们首先回顾了推理的基本步骤,并引入了合一这一核心概念。接着,为了处理可判定的情况,我们聚焦于Horn子句Datalog,并学习了对应的前向链接后向链接算法,它们分别从事实出发和从目标出发进行高效推理。

最后,为了处理任意的一阶逻辑公式,我们学习了归结方法的前置步骤:将公式转换为合取范式。其中最关键的是斯科伦化过程,它通过引入新的函数或常量来消除存在量词,从而得到一个所有变量都是全称量化的子句集合,为后续的归结推理做好了准备。我们强调了转换步骤的顺序和常见错误,以确保推理的正确性。

028:一阶逻辑中的推理

在本节课中,我们将学习如何将一阶逻辑公式转换为合取范式,并应用归结推理规则进行自动证明。我们将详细讲解斯科伦化过程、归结步骤以及实用的求解策略。

概述:斯科伦化与归结推理

上一节我们介绍了将一阶逻辑公式转换为合取范式的基本概念。本节中,我们来看看如何具体执行斯科伦化,并应用归结法进行推理。

斯科伦化是将一阶逻辑公式转化为不含存在量词的合取范式的过程。其核心在于用斯科伦函数替换存在量词变量。

斯科伦化步骤回顾

以下是执行斯科伦化的关键步骤:

  1. 转换为否定范式:消除蕴含词,将否定词向内推,直到只出现在原子公式前。
  2. 变量标准化:重命名变量,避免不同量词作用域内的变量名冲突。
  3. 消除存在量词:用斯科伦函数替换存在量词变量。
  4. 删除全称量词:删除剩余的全称量词,所有变量默认为全称量化。
  5. 转换为合取范式:将公式化为子句的合取。

斯科伦函数替换规则

  • 如果存在量词变量 y 不在任何全称量词 ∀x 的辖域内,则用一个新的常元(零元函数)替换 y
  • 如果存在量词变量 y 在全称量词 ∀x 的辖域内,则用一个新的一元函数 f(x) 替换 y
  • 更一般地,如果 yn 个全称量词 ∀x1, ..., ∀xn 的辖域内,则用一个 n元函数 f(x1, ..., xn) 替换 y

公式表示∃y φ(y)∀x1...∀xn 辖域内,替换为 φ(f(x1, ..., xn)),其中 f 是新函数符号。

斯科伦化的性质

斯科伦化保持可满足性,但不一定保持逻辑等价性或有效性。这对于基于反驳(证明不可满足性)的自动推理方法(如归结法)已经足够。

归结推理规则

现在,我们来看如何在一阶逻辑子句上应用归结规则。

基本思想

给定两个子句 C1C2,如果它们包含可以合一的一对互补文字,则可以进行归结。

定义:文字 L¬M可合一的,如果存在一个最一般合一子 θ,使得 Lθ = Mθ。最一般合一子是能使得两个表达式相等的、最具一般性的变量替换。

归结规则
如果子句 C1 包含文字 L,子句 C2 包含文字 ¬M,且 LM 可通过最一般合一子 θ 合一,则可以得到归结式 Resolvent(C1, C2)
(C1θ - {Lθ}) ∪ (C2θ - {¬Mθ})

代码描述(伪代码):

function resolve(C1, C2):
    for each literal L in C1:
        for each literal ¬M in C2:
            θ = unify(L, M) // 计算最一般合一子
            if θ exists:
                resolvent = (C1 - {L})θ ∪ (C2 - {¬M})θ
                remove duplicate literals from resolvent
                return resolvent
    return None // 无可归结的文字

归结证明过程

要证明 Γ ⊨ α(在前提 Γ 下结论 α 成立):

  1. Γ¬α 的合取进行斯科伦化,得到子句集 S
  2. 反复对 S 中的子句应用归结规则,将新产生的子句加入 S
  3. 可能出现三种结果:
    • 生成空子句 :这意味着 Γ ∧ ¬α 不可满足,从而证明 Γ ⊨ α 成立。
    • 无法再生成新的子句:这意味着 Γ ∧ ¬α 是可满足的,因此 Γ ⊨ α 不成立(在可判定的情况下)。对于一阶逻辑,此过程可能永不终止。
    • 过程无限进行或超出资源限制:一阶逻辑是半可判定的,对于不成立的蕴含关系,归结过程可能永远找不到矛盾。

实用策略与示例

在实际应用中,盲目归结会产生大量无用子句。以下是一些有效的控制策略。

归结策略建议

  • 优先进行单元归结:首先尝试用单元子句(只包含一个文字的子句)进行归结。这能更快地缩短子句长度。
  • 优先归结较短子句:在可能的情况下,优先归结较短的两个子句,产生的归结式通常更短、信息量更大。
  • 子句标准化:在归结前,确保不同子句中的变量是标准化 apart 的(即使用不同的变量名)。这是许多错误的根源。
  • 支持集策略:将目标 ¬α 的子句及其后代作为“支持集”,只允许支持集内的子句之间或支持集子句与原始子句之间进行归结。这类似于反向链接。

示例:Curiosity 杀死了猫

让我们用归结法分析一个经典例子。

已知事实

  1. 每个爱所有动物的人都会被某人所爱。
  2. 任何杀死动物的人都不会被任何人所爱。
  3. Jack 爱所有动物。
  4. 要么 Jack,要么 Curiosity 杀死了名叫 Tuna 的猫。
  5. Tuna 是一只猫。
  6. (隐含知识)猫是动物。

目标:证明 Curiosity 杀死了 Tuna (kills(Curiosity, Tuna))。

证明思路(归结法)

  1. 将上述所有语句(以及目标的否定)形式化为一阶逻辑公式,并进行斯科伦化,得到子句集。
  2. 将目标 kills(Curiosity, Tuna) 取反,加入子句集。
  3. 应用归结规则。一个高效的策略是:
    • 利用单元子句 cat(Tuna) 和隐含知识 ¬cat(x) ∨ animal(x),得到 animal(Tuna)
    • 利用子句 ¬animal(y) ∨ ¬kills(x, y) ∨ ¬loves(z, x)animal(Tuna) 归结,得到关于杀死 Tuna 者的新子句。
    • 与关于 Jack 爱动物的子句以及“爱动物者被爱”的子句进行归结。
    • 最终与“杀动物者不被爱”的子句产生矛盾,从而推导出 kills(Curiosity, Tuna)

关键教训:在归结步骤中,必须注意对变量进行标准化 apart,否则可能导致错误的合一和无效的归结。

高级归结策略

  • 有序归结:为原子公式定义一个全序。在归结时,只归结每个子句中在该序下“最大”的文字。这可以大幅减少搜索空间。
  • 超归结:将子句分为电子(只含正文字)和(至少含一个负文字)。超归结步骤允许一个核与多个电子同时归结,一次性消去核中的多个负文字。这可以看作是一个宏推理步骤。

总结

本节课中我们一起学习了一阶逻辑自动推理的核心技术。

我们首先深入探讨了斯科伦化,这是将带量词公式转化为无存在量词子句集的关键步骤,其核心在于用函数项替换存在量词变量。接着,我们介绍了一阶归结规则,它通过寻找文字间的最一般合一子来生成新子句,是自动证明的基础。

为了高效应用归结法,我们强调了几点:务必进行变量标准化;在搜索策略上,应优先使用单元子句和较短子句进行归结,以控制子句集的爆炸性增长。我们还通过“Curiosity 杀死了猫”的实例,演示了如何将自然语言描述的问题形式化,并运用归结策略完成证明。

最后,我们简要提及了有序归结超归结等高级控制策略,它们在实际的定理证明器中用于提高效率。理解这些基本原理和策略,是掌握逻辑人工智能中自动推理能力的重要一步。

029:经典规划

在本节课中,我们将要学习人工智能中的一个核心领域——经典规划。我们将了解什么是规划问题,如何形式化地描述它,以及它与我们之前学过的搜索问题有何联系与区别。

概述

规划是人工智能中一个重要的研究领域,它关注如何为智能体合成一系列动作,使其能从世界的初始状态出发,达到一个期望的目标状态。这与我们在第三章学习的搜索智能体有密切关系,但规划通常处理更结构化、因子化的状态表示。

规划问题定义

上一节我们介绍了规划的基本概念,本节中我们来看看如何形式化地定义一个规划问题。

一个经典规划问题包含以下几个核心组成部分:

  • 初始状态:世界开始时的状态。通常表示为一系列基本事实(ground literals)的合取。
  • 目标状态:我们希望达到的状态。它被表示为一个可能包含变量的文字合取式。
  • 动作集合:智能体可以执行的操作。每个动作由前提条件和效果定义。

状态与闭世界假设

在规划中,我们通常采用闭世界假设。这意味着,任何在状态描述中没有明确声明为真的事实,都被假定为假。这简化了状态的表示。

状态 S 可以形式化地表示为基本事实的合取:
S = fact₁ ∧ fact₂ ∧ ... ∧ factₙ

动作的模式

动作是规划的核心。一个动作模式 Action 通常包含以下部分:

  • 动作名称和参数:例如 Fly(p, from, to)
  • 前提条件:执行该动作前必须为真的条件。它是一个文字的合取。
  • 效果:执行该动作后世界发生的变化。它包含一个添加列表(执行后变为真的事实)和一个删除列表(执行后变为假的事实)。

一个动作模式可以形式化地描述为:

Action: Fly(plane, from, to)
Precondition: At(plane, from) ∧ Plane(plane) ∧ Airport(from) ∧ Airport(to)
Effect: ¬At(plane, from) ∧ At(plane, to)

规划即搜索

规划问题可以被视为一个搜索问题。初始状态是搜索的起点,目标状态是搜索的终点,而动作定义了状态之间的转换。

前向搜索与后向搜索

解决规划问题主要有两种搜索策略:

  • 前向(前进)状态空间搜索:从初始状态开始,应用所有适用的动作,生成新的状态,直到达到一个满足目标的状态。
  • 后向(回归)状态空间搜索:从目标状态开始,反向思考哪些动作能导致该目标,并考虑这些动作的前提条件作为新的子目标,直到所有子目标都被初始状态满足。

前向搜索直观,但分支因子可能很大。后向搜索可能更高效,因为它专注于与目标相关的动作。

规划与一般搜索的区别

虽然规划可以看作搜索,但它有其特殊性:

  1. 因子化表示:规划中的状态和动作使用逻辑符号进行结构化、因子化表示,而不是像一般搜索中那样使用原子化的状态编码。这使得表示更加紧凑,并能利用领域知识。
  2. 大量动作:规划领域通常定义了许多动作模式,通过实例化可以生成海量的具体动作。直接在前向搜索中应用所有这些动作效率很低。
  3. 启发式信息:规划器可以利用领域结构的启发式信息(如忽略某些前提条件)来更有效地指导搜索,这比通用搜索中的启发式更强大。

规划领域描述语言(PDDL)

为了标准化规划问题的描述,研究者开发了规划领域描述语言。PDDL 提供了一种描述动作模式、谓词和类型的语法。

以下是 PDDL 中一个动作定义的示例:

(:action fly
 :parameters (?p - plane ?from ?to - airport)
 :precondition (and (at ?p ?from))
 :effect (and (not (at ?p ?from)) (at ?p ?to))
)

规划实例:货物运输

让我们通过一个简单的例子来巩固概念。假设我们有一个规划问题:将一件货物 C1 从机场 A 运送到机场 B

  • 初始状态At(C1, A) ∧ At(P1, A) ∧ Cargo(C1) ∧ Plane(P1) ∧ Airport(A) ∧ Airport(B)
  • 目标状态At(C1, B)
  • 可用动作Load(c, p, a), Unload(c, p, a), Fly(p, from, to)

一个可行的规划(解决方案)序列可能是:

  1. Load(C1, P1, A)
  2. Fly(P1, A, B)
  3. Unload(C1, P1, B)

规划中的挑战

在实际规划中,我们会遇到一些挑战:

  • 框架问题:需要明确说明动作不会改变哪些事实。PDDL 和大多数规划模型通过“默认不变”的约定(即只有效果中提及的事实会改变)来处理此问题。
  • 分支因子大:由于动作众多,前向搜索的分支因子可能非常大。
  • 目标的可满足性:目标可能是一个合取式,需要同时满足多个子目标,这可能需要交错执行多个子目标的达成步骤。

总结

本节课中我们一起学习了经典规划的基础知识。我们了解到规划是为达成特定目标而合成动作序列的问题。我们学习了如何用初始状态、目标状态和动作集合来形式化定义规划问题,并探讨了规划与搜索之间的关系,特别是前向与后向搜索策略。我们还简要介绍了 PDDL 语言,并通过一个运输例子演示了规划过程。理解这些经典规划的概念是学习更复杂、更真实的规划方法(如下一章将涉及的内容)的重要基础。

030:经典规划

概述

在本节课中,我们将学习经典规划中的核心搜索策略、启发式方法以及一种重要的数据结构——规划图。我们将探讨前向搜索与后向搜索的区别,如何构建有效的启发式函数来指导搜索,并深入了解规划图的构建原理及其在规划算法中的应用。


前向搜索与后向搜索

上一节我们介绍了规划问题的描述和PDDL语言,并指出规划本质上是一个搜索问题。本节中,我们来看看规划中两种主要的搜索策略。

规划主要有两种策略:

  1. 前向搜索:从初始状态出发,通过不断应用动作,直到达到一个满足目标测试的状态。
  2. 后向搜索(回归):从目标状态(通常代表一组状态)出发,反向寻找哪些动作可能导致当前目标,并计算其前提条件作为新的子目标。

前向搜索是经典搜索技术(第三章)最直接的实例化。而后向搜索可以映射到标准搜索算法中。通常,我们使用深度优先或广度优先搜索,它们在不考虑函数符号的经典规划中,通过实现循环检查程序可以变得完备,并且只需要多项式内存。

盲目搜索的主要问题是分支因子巨大。因此,无论采用哪种策略,都需要一个好的启发式方法来指导搜索方向。


后向搜索详解

为了进行更好的后向搜索,我们需要反向选取候选动作。

假设目标 G 是应用动作 A 的结果。在什么条件下这是可能的?这意味着我们需要计算一个子目标 G‘,从该子目标应用动作 A 后,通常能达到当前目标 G

我们需要区分目标中的正文字和负文字。新子目标 G‘ 的正文字由当前目标 G 的正文字集合,去掉动作 A 的效果列表(add list)中的文字,再加上动作 A 的正前提条件构成。类似地,G‘ 的负文字由 G 的负文字集合,去掉动作 A 的删除列表(delete list)中的文字(因为这些文字被动作设为假),再加上动作 A 的负前提条件构成。

这样计算的是最弱前提条件,即最一般、约束最少的子目标。这使我们有更高的机会在后续步骤中与初始状态或其他动作匹配。

GG‘ 都可能代表许多状态,它们只是一组文字,可能只给部分原子或流(fluent)赋值,而无关的文字则保持未指定状态。

实例说明

考虑一个目标:At(C1, SanFrancisco) ∧ At(C2, JFK)

假设我们有一个具体动作(ground action):Unload(C1, P1, SanFrancisco)。其前提条件是:In(C1, P1), At(P1, SanFrancisco), Cargo(C1), Plane(P1), Airport(SanFrancisco)。效果是:At(C1, SanFrancisco), ¬In(C1, P1)

应用后向规则,子目标 G‘ 的正文字包括所有前提条件,以及从原目标正文字中去掉已被该动作满足的 At(C1, SanFrancisco) 后剩下的 At(C2, JFK)。类型谓词(如 Cargo, Plane, Airport)是固定的。因此,子目标要求 In(C1, P1), At(P1, SanFrancisco)At(C2, JFK)

保持一般性

在实际操作中,我们通常处理动作模式(schema),而非具体动作。我们应尽可能少地实例化变量,以保持子目标的一般性。

规则是:只实例化必要的部分。使用最一般合一子(most general unifier),在经典规划中,这意味着如果能保持变量为变量,就不要将其实例化为常量。必要时可以重命名变量以避免冲突。

在目标中,变量是存在量化的;在动作中,变量是全称量化的。因此,我们通常希望子目标表达为“存在某个飞机...”,而不是“必须是飞机P1...”,这样更弱,也更容易匹配。

保持目标尽可能一般,是后向搜索乃至所有从目标状态反向推理(如软件静态分析)的通用经验法则。


相关动作选取

在后向搜索中,我们需要一个标准来选取可能导出当前目标的动作。

一个动作被称为与目标 G 相关,需要满足两个条件:

  1. 做有益的事:该动作的至少一个正效果能与目标 G 中的某个文字合一。即,它必须产生了目标所需的某些东西。
  2. 不做有害的事:该动作不能破坏(即其删除列表中的文字不能与)目标 G 中的任何期望文字冲突。

直观上,一个可能是最终步骤的动作,必须做了些好事(促成目标)且没做坏事(破坏目标)。仅满足条件2的动作称为一致的,但不一定是相关的。

实例说明

对于目标 At(C1, SFO) ∧ At(C2, JFK)

  • Unload(C1, P1, SFO) 是相关的,因为它产生了 At(C1, SFO) 且不破坏任何目标。
  • Load(C3, P1, SFO) 是一致的(不破坏目标),但不是相关的,因为它对实现当前目标没有直接贡献。
  • Load(C1, P1, JFK) 是不一致的,因为它会破坏 At(C1, SFO) 这个目标。

后向搜索的优势在于,由于有“相关性”概念,其分支因子通常比前向搜索小,搜索更聚焦于目标。然而,现代规划器认识到启发式搜索的重要性,而前向搜索更适合与启发式结合,因此大多数现代规划器采用 前向搜索 + 启发式 的策略。


启发式方法

我们需要启发式函数 h(s) 来估计从状态 s 到达目标的乐观代价。标准方法是放松原规划问题。放松后的问题更容易解决,且原问题的解也是放松问题的解(反之不一定),因此得到的代价估计是乐观的(可采纳的)。

以下是几种常见的放松技术:

忽略某些前提条件

通过忽略一些前提条件,使得动作更容易执行,从而得到乐观的代价估计。如果忽略所有前提条件,会过于乐观(每个目标文字可能一步达成)。更实用的方法是忽略那些根据领域知识或结构分析判断为“次要”或“容易满足”的前提条件。

忽略删除列表启发式

假设目标和动作前提只包含正文字。通过忽略动作的删除列表,我们得到一个放松问题,其中动作只会增加(而不会删除)文字。这使得问题具有单调进展性,易于用贪心或局部搜索快速找到放松解,然后尝试在原问题中修复因忽略删除而引入的冲突。

状态抽象(聚类)

通过忽略一些不太相关的流(fluent),将多个具体状态聚类成抽象状态,在抽象空间中进行规划。一旦在抽象层面找到计划,再将其具体化。这依赖于领域知识:被忽略的流应该是容易实现的。

分治

将复杂问题分解为若干子问题,独立解决后再组合。


规划图

规划图是一种数据结构,它能提供丰富的启发信息,并能驱动一个称为 Graphplan 的算法家族。

规划图是一个有向图,按层级组织,交替包含状态层(命题文字)和动作层(具体动作)。

  • 层 S0:包含初始状态中所有成立的基础文字(根据闭世界假设,未出现的文字取其否定)。
  • 层 A0:包含所有前提条件能在 S0 中得到满足的具体动作。
  • 层 S1:包含所有 A0 层动作的所有效果(正效果和负效果)。
  • 以此类推,递归构建。

此外,规划图还包含 互斥(mutex) 关系,表示两个元素不能在同一层级同时成立。

  1. 动作间互斥:如果两个动作满足以下任一条件,则它们互斥:
    • 不一致的效果:一个动作的效果与另一个动作的效果(或持久动作的效果)互斥。
    • 不一致的前提:一个动作的前提与另一个动作的前提互斥。
    • 竞争需求:一个动作破坏了(删除)另一个动作的前提。
  2. 文字间互斥:如果所有能产生这两个文字的动作对都是互斥的,则这两个文字互斥。一个文字与其否定自然互斥。

规划图具有以下性质:

  • 文字和动作集合随层级增加单调递增(由于持久动作的存在),并在有限步后达到固定点。
  • 互斥关系随层级增加单调递减(因为产生文字的方式增多,可能打破互斥)。
  • 规划图的大小是问题规模的多项式级别,构建速度很快。
  • 规划图是原问题状态空间的一个超集近似。所有可行解路径都体现在规划图中,但图中的路径不一定是可行解。如果目标文字未出现在某层,或目标文字间在该层互斥,则问题在该层深度内无解。

启发式信息提取

规划图提供了宝贵的启发式信息:

  • 层级代价:一个文字 L 首次出现的层级 i,是对从初始状态达到 L 所需最小步骤数的乐观估计。这是一个可采纳的启发式估计。
    • 例如,在“蛋糕问题”中,HaveCake 的层级代价为0(初始状态),EatenCake 的层级代价为1。
  • 对于目标(一组文字的合取),有多种方式组合单个文字的层级代价:
    • 最大层级启发式:取所有目标文字层级代价的最大值。可采纳,但可能过于乐观。
    • 求和层级启发式:取所有目标文字层级代价之和。通常不可采纳,但实践中可能有效。
    • 集合层级启发式:目标文字集合首次共同出现且彼此不互斥的层级。这被认为是更准确的估计。

Graphplan 算法框架

Graphplan 不只是一个算法,而是一个算法框架:

  1. 扩展图:从初始状态开始,逐步构建规划图层级。
  2. 检查目标:在最新的状态层中,检查所有目标文字是否都已出现且彼此不互斥。如果不是,则继续扩展图;如果是,则进入下一步。
  3. 解提取:尝试从当前规划图中提取一个有效的计划。这是一个独立的搜索过程(如回溯搜索),利用规划图中的动作、效果和互斥关系进行约束。
  4. 循环:如果在当前层提取解失败,则记录这个“失败”信息(称为“nogood”),然后要么继续扩展图(增加深度),要么回溯。
  5. 终止:当图扩展到固定点(连续两层完全相同)仍未找到解,则判定问题无解。

该框架将多项式时间的规划图构建与一个可能是指数时间的解提取搜索过程分离,并通过记录“nogood”和增量式图扩展来避免重复搜索。


总结

本节课中,我们一起学习了经典规划中的核心高级技术。我们深入探讨了后向搜索的原理与优势,理解了通过放松问题来构建可采纳启发式函数的各种方法。重点介绍了规划图这一强大工具,它不仅能高效地提供优质的启发式估计,还构成了Graphplan算法系列的基础。规划图通过层级结构和互斥关系,对状态空间进行紧凑的多项式级近似,从而极大地优化了规划搜索过程。掌握这些概念对于理解和设计高效的自动规划系统至关重要。

031:经典规划(下)

在本节课中,我们将继续学习经典规划,重点介绍规划图算法及其应用,特别是如何利用规划图进行启发式搜索,以及如何将规划问题编码为可满足性问题(SAT)来求解。

上一节我们介绍了规划图的基本概念。本节中,我们来看看如何利用规划图来高效地搜索解决方案。

规划图算法:Graphplan

规划图本身不是一个完整的算法,而是一个元算法或框架。它主要包含两个核心步骤:如何扩展规划图,以及如何在图中搜索解决方案(称为“提取解决方案”)。

以下是Graphplan算法的主要流程:

  1. 初始化:从初始状态(第0层)开始构建规划图。同时初始化一个空的好表(记录目标首次出现的层数)。
  2. 迭代扩展与搜索
    • 检查当前最高层是否包含所有目标文字,并且这些目标文字之间不存在互斥关系。
    • 如果满足条件,则调用“提取解决方案”函数,尝试在当前规划图限制的范围内搜索一个可行的计划。
    • 如果找到了解决方案,算法成功结束。
    • 如果未找到解决方案,且规划图和好表都已达到“稳定点”(即无法再扩展出新信息),则算法返回失败。
    • 否则,为规划图添加一个新的动作层和状态层,然后重复此过程。

这个过程类似于迭代加深搜索,从较浅的层开始尝试,逐步增加深度,直到找到解或证明无解。

规划图示例:轮胎问题

让我们回顾一下轮胎问题的例子,以理解规划图如何工作。

  • 初始状态:轮胎是瘪的,备胎在行李箱里。
  • 目标状态:备胎装在车轴上。
  • 关键动作:从行李箱取出备胎、从车轴上卸下瘪胎、将备胎装到车轴上。

在规划图中:

  • 第0层包含初始状态文字,如 at(spare, trunk)flat(axle)。目标 at(spare, axle) 不在这一层,因此需要扩展。
  • 第1层引入了可执行的动作,如 remove(spare, trunk)remove(flat, axle),以及它们产生的效果,如 at(spare, ground)at(flat, ground)。同时,互斥关系也被标记出来。
  • 在第2层,由于有了 at(spare, ground),动作 put-on(spare, axle) 变得可执行,其效果 at(spare, axle) 正是我们的目标。

此时,规划图包含了目标,且目标间无互斥,因此可以调用“提取解决方案”搜索。搜索过程会利用图中已有的动作和互斥约束,极大地剪枝无效的搜索分支,从而高效地找到计划序列:remove(spare, trunk) -> remove(flat, axle) -> put-on(spare, axle)

规划图的核心优势在于它作为一种预处理,提前计算了可能的动作路径和大量的约束(互斥关系),从而使得后续的搜索过程更加高效。

串行与并行规划

规划可以分为串行规划和并行(偏序)规划。

  • 串行规划:计划是一个严格的线性动作序列。例如,先做A,再做B,然后做C。
  • 偏序规划:计划定义了一组动作间的偏序关系(先后约束),而不是一个完整的线性顺序。例如,动作A必须在动作C之前完成,动作B必须在动作D之前完成,但A和B之间可以任意顺序执行,甚至并行。

偏序规划通常更高效,因为它压缩了那些可以独立或并行执行的动作组,减少了规划图中的层数。一个经典的例子是“穿袜穿鞋”问题:左脚穿袜、右脚穿袜、左脚穿鞋、右脚穿鞋。其中,每只脚的“穿袜”必须在对应的“穿鞋”之前,但左右脚的动作可以任意交错执行。一个偏序规划只需要2层动作,而任何串行规划都需要至少4层动作。找到偏序规划后,可以很容易地将其转化为一个满足偏序约束的串行计划。

规划图的性质与SAT编码

规划图有两个重要性质:

  1. 状态文字集随着层数增加单调递增(或保持不变),由于文字总数有限,最终会达到一个固定点。
  2. 互斥关系和好表的大小单调递减,最终也会稳定。

当规划图和好表都达到固定点,而目标仍未(无互斥地)出现时,就可以安全地断定该问题在该深度下无解。

规划图的“提取解决方案”步骤可以借助多种搜索方法,其中一种非常有效的方法是将有界规划问题编码为命题可满足性问题(SAT)

其基本思想是:给定一个最大步数 T,为每一步 t 中的每个基本动作和每个状态文字创建一个布尔变量。然后,构造一个庞大的布尔公式,这个公式由三部分组成:

  1. 初始状态编码:描述第0步时哪些文字为真。
  2. 转移关系编码:对于每一步 t,编码从状态 t 到状态 t+1 的所有合法转移。这包括:动作执行需满足其前提条件;动作执行会带来其效果;对于未受动作影响的文字,其值保持不变(框架公理)。
  3. 目标状态编码:描述在第 T 步时,目标文字必须成立。

此外,规划图中计算出的互斥关系可以非常方便地编码为SAT子句(例如,not Action_A_t OR not Action_B_t 表示两个动作在步 t 不能同时执行)。这些互斥子句能在SAT求解过程中进行高效的单元传播,从而极大地减少搜索空间。

如果这个布尔公式是可满足的,那么满足赋值就对应了一个有效的计划(为真的动作变量序列即为计划)。如果公式不可满足,则说明在 T 步内无解。

总结

本节课中我们一起学习了经典规划的高级主题。

  • 我们深入探讨了Graphplan算法,它通过迭代构建规划图并利用图中的约束来高效搜索计划。
  • 我们比较了串行规划偏序规划,认识到偏序规划在表示和效率上的优势。
  • 最后,我们介绍了如何将规划问题编码为SAT问题来求解,特别是利用规划图提供的互斥约束来加速SAT求解过程。

规划图及其相关算法是经典规划中连接启发式搜索与逻辑推理的重要桥梁,它显著提升了规划器的求解能力。

032:现实世界规划

在本节课中,我们将要学习经典规划的扩展——现实世界规划。我们将探讨如何将时间、资源等复杂因素纳入规划过程,并了解规划与调度、搜索等概念的结合。


概述

上一节我们讨论了经典规划。本节中,我们将扩展视野,探讨现实世界中的规划问题。规划远不止选择一系列原子动作,它还需要考虑动作的持续时间、资源约束以及动作之间的时序关系。我们将介绍两个核心扩展方向:时间资源,并引入调度的概念。此外,我们还会看到如何将之前学过的无传感器搜索(即一致性规划)和非确定性步骤规划(即应急规划或条件规划)的思想融入规划领域。


时间与规划

在经典规划中,动作被视为瞬间发生的原子事件。然而,在现实中,每个动作都有持续时间。动作在特定时间点开始和结束,并且后续动作必须等待前置动作完成后才能开始。

例如,如果动作A需要在动作B之前执行,且动作A耗时10秒,那么动作B最早只能在动作A开始10秒后启动。这个约束在计算上很重要,尤其是当多个动作可以并行执行时。如果我们能合理安排动作间的依赖关系,就能优化整个计划的执行时间。

公式表示: 若动作 A 在时间 t_start 开始,持续时间为 D,则其结束时间为:

t_end = t_start + D

若动作 B 必须在动作 A 之后执行,则需满足:

t_start(B) >= t_end(A)

资源与规划

除了时间,资源是另一个关键约束。资源决定了哪些动作可以并行执行。资源主要分为两类:

  • 可重用资源: 使用后不会被消耗,可以再次使用。例如,一个检查员、一台机器人或一个引擎吊架。
  • 可消耗资源: 使用后即被消耗,无法重复使用。例如,安装轮胎时使用的螺栓。

如果某项资源(如一个轮胎安装工位)只有一个可用实例,那么需要该资源的两个动作就无法同时进行。因此,规划时必须考虑资源的可用性,以生成可行的计划。


规划与调度

一种常见的处理复杂约束的方法是采用 “先规划,后调度” 的策略。

以下是该策略的两个主要步骤:

  1. 生成偏序计划: 首先,忽略时间和资源的精确数值,只找出动作之间的逻辑依赖关系,生成一个偏序计划。这个计划确定了哪些动作必须在其他动作之前执行,但并未指定具体的开始时间。
  2. 调度优化: 然后,在偏序计划的基础上,结合动作的持续时间资源约束,为每个动作分配具体的开始时间,目标是优化某个指标(如总完成时间)。这个过程就是调度

通过这种方式,我们将符号化的逻辑规划与数值化的优化计算结合了起来。


示例:汽车装配问题

让我们通过一个汽车装配的例子来具体理解这些概念。假设我们需要组装两辆汽车(C1和C2),涉及安装引擎(E1, E2)、安装轮胎组(W1, W2)和最终检查。

初始状态

  • 有底盘 C1, C2。
  • 引擎 E1 适合 C1, E2 适合 C2。
  • 轮胎组 W1 适合 C1, W2 适合 C2。

目标状态

  • C1 和 C2 都完成组装(即 done(C1)done(C2) 为真)。

动作(经典规划部分)

  • add_engine(E, C): 将引擎E安装到底盘C上。
    • 前提条件: 引擎E适合底盘C,且引擎E不在底盘C上。
    • 效果: 引擎E在底盘C上。
  • add_wheels(W, C): 将轮胎组W安装到底盘C上。
    • 前提条件: 轮胎组W适合底盘C,且轮胎组W不在底盘C上。
    • 效果: 轮胎组W在底盘C上。
  • inspect(C): 检查底盘C。
    • 前提条件: 引擎已在底盘C上,且轮胎组已在底盘C上。
    • 效果done(C) 为真。

引入时间和资源
现在,我们为动作添加持续时间和资源需求信息。

  • add_engine(E1, C1): 耗时 60 秒,需要 1 个引擎吊架。
  • add_wheels(W1, C1): 耗时 30 秒,需要 1 个轮胎安装工位。
  • add_engine(E2, C2): 耗时 90 秒,需要 1 个引擎吊架。
  • add_wheels(W2, C2): 耗时 150 秒,需要 1 个轮胎安装工位。
  • inspect(C): 每个检查耗时 10 秒,需要 1 个检查员。

资源约束

  • 引擎吊架: 只有1个。
  • 轮胎安装工位: 只有1个。
  • 检查员: 只有1个。

偏序计划
一个可能的偏序计划是:为每辆车先安装引擎,再安装轮胎,最后进行检查。即:

add_engine(E1, C1) → add_wheels(W1, C1) → inspect(C1)
add_engine(E2, C2) → add_wheels(W2, C2) → inspect(C2)

但两组动作之间的顺序未定。

调度挑战
由于资源有限(例如只有一个轮胎安装工位),add_wheels(W1, C1)add_wheels(W2, C2) 不能同时进行。调度器需要决定它们的执行顺序以及具体的开始时间,以最小化总装配时间(即完工时间),同时满足所有资源约束。


符号推理与数值计算

在规划中处理时间和资源时,我们面临一个核心挑战:如何将符号逻辑推理与数值计算结合起来

经典规划器主要在符号层面操作(如命题逻辑)。然而,持续时间(如60秒)和资源数量(如1个)是数值信息。计算总时间或检查资源冲突需要进行算术运算。

代码示例: 检查两个动作是否在时间上重叠。

# 假设 action1 从 start1 开始,持续 duration1
# action2 从 start2 开始,持续 duration2
def actions_overlap(start1, duration1, start2, duration2):
    end1 = start1 + duration1
    end2 = start2 + duration2
    # 如果两个时间段有交集,则重叠
    return not (end1 <= start2 or end2 <= start1)

解决这种“跨界”问题的一种强大工具是可满足性模理论,它允许在逻辑公式中嵌入算术等理论,从而进行联合推理。这通常是更高级课程的内容。


总结

本节课中我们一起学习了现实世界规划的基础。我们了解到,真实的规划问题需要超越经典的原子动作序列,必须整合时间资源这两大要素。我们介绍了 “先规划,后调度” 的方法论,即先通过逻辑推理生成动作间的偏序关系,再通过数值优化解决资源约束下的时序安排。最后,我们指出了将符号推理与数值计算相结合的重要性,这是处理复杂规划问题的关键。下一讲,我们将继续深入探讨调度算法和更复杂的规划场景。

posted @ 2026-03-26 13:18  布客飞龙V  阅读(0)  评论(0)    收藏  举报