- 全名
- Artificial Intelligence: Principles and Techniques。
- 主讲
- 2025 Autumn:Percy Liang 等;
- 2026 Spring:Moses Charikar、Zachary Robertson;
- 往年还有 Sanmi Koyejo、Dorsa Sadigh 等,名单随学期变化。
- 形式
- 教师讲座、Problem Session(习题课)与 homework party(集中答疑写作业);
- 每周一份作业:书面推导加 Python 实现,每份作业围绕一个具体应用展开;
- 闭卷考试;可选的课程项目或 AI Product Deep Dive 作为额外加分。
- 先修
- 编程:CS106A、CS106B;
- 离散数学:CS103;
- 概率:CS109;
- 线性代数:MATH51;
- 建议另修:CS161(算法)、CS107(计算机系统)。
- 入口
▸版本范围
Explore Courses / Bulletin 对 CS221 的定位是:在信息不完整(因而需要概率)与计算资源有限(因而需要算法)的条件下做决策的数学。官方列出的主题包括搜索(search)、约束满足(constraint satisfaction)、博弈(game playing)、马尔可夫决策过程(Markov decision processes)、图模型(graphical models)、机器学习(machine learning)与逻辑(logic)。
算法与记号以 Percy Liang 一系的模块讲义为准,即反射 / 状态 / 变量 / 逻辑四条建模轴,以及“建模-推断-学习”三步拆题法。正文按 2026 Spring 课表的顺序组织:学习、搜索、MDP、博弈、因子图与束搜索、贝叶斯网、逻辑。2025 Autumn 另外讲了语言模型、AI 与社会、AI 供应链三个专题,按“课堂扩展”补入。之后的学期即使微调讲座题目,四条建模轴以及 UCS / A* / 值迭代 / minimax / 变量消去这些核心算法通常仍会保留。
课程总览:四类模型与贯穿全课的主线
CS221 覆盖的主题看起来很散:从情感分类、路径规划、Pac-Man 博弈,到排课、车辆跟踪、逻辑推理。初学者容易把它当成一门“AI 算法大杂烩”,每章背一个算法。实际上这些主题由一条统一的线索串起来:先决定用哪一类模型来表示问题,再问在这个模型上要计算什么量,最后决定模型里的参数是人写的还是从数据学的。模型的“类”按表示方式划分为四条轴——反射、状态、变量、逻辑——它们大体从简单到复杂排列,表达能力逐步增强,推断通常也越来越昂贵。
这一章先给出全课的地图:每条轴包含哪些模型和算法、每个模型回答什么问题、复杂度大致是多少、实现时有哪些数值和编程上的坑。读者在后面任何一章迷路时,都可以回到这里定位“当前处在哪条轴、在算哪个量”。
四条建模轴的内容清单
按讲义的划分,全课内容如下。
- 反射模型(reflex-based models):输入一来,一次计算直接给出输出,不考虑未来。
- 核心组件是线性预测器(linear predictor)、特征(feature)、损失(loss)与随机梯度下降(SGD);
- 分类与回归中常用的损失有铰链(hinge)、逻辑(logistic)与平方损失;
- 线性模型不够用时,用非线性特征或浅层神经网络扩展表达能力;无论哪种,推断都只是一次前向计算。
- 状态模型(state-based models):输出不再是一个标签,而是一串动作,需要考虑每一步动作把世界带到什么状态。
- 搜索:定义后继函数与动作代价,用回溯、DFS / BFS / UCS / A* 求路径;A* 依赖启发式(heuristic),而好的启发式常通过松弛(relaxation)原问题得到;
- MDP:动作的结果是随机的,用转移概率、回报与策略描述问题,用值迭代求解,在模型未知时用 Q 学习;
- 博弈:存在对手或随机事件,用 expectimax、minimax 求博弈值,用 α-β 剪枝加速,用估值函数截断深度。
- 变量模型(variable-based models):不关心“先做什么后做什么”,只关心一组变量最终取什么值。
- 因子图(factor graph)与约束满足问题(CSP);
- 求解方法有回溯、弧一致(arc consistency)、束搜索(beam search)、变量消去(variable elimination);
- 贝叶斯网(Bayesian network)把因子换成条件概率,涉及条件独立、精确推断与粒子滤波(particle filter)。
- 逻辑模型(logic-based models):用公式表达知识,用推理得出必然成立的结论。
- 命题逻辑的语法、语义、蕴含与消解(resolution);
- 一阶逻辑(first-order logic),以及把自然语言转写成逻辑公式。
- 课堂扩展
- 语言模型(language models);
- AI 与社会、AI 供应链与评估协议。
每道题的分析顺序
笔记对每个主题都按同一顺序记录:先说任务是什么,再选模型族,然后写出要解的推断问题,接着说明参数是学出来还是人工指定的,最后讨论复杂度以及算不动时用什么近似。
$$ \text{任务} \rightarrow\text{模型族} \rightarrow\text{推断问题} \rightarrow\text{学习或指定参数} \rightarrow\text{复杂度与近似} $$
这个顺序的意义在于避免“拿到题就写代码”:同一个任务换一个模型族,要解的推断问题、可用的算法和复杂度都会完全不同。
容易混淆的几组指标
不同模型优化的量各不相同,不能互相替代,评估时需要分清楚:
- 训练损失下降,既不等于搜索得到的路径代价最优,也不等于策略的期望回报最大。损失是反射模型的学习目标,后两者是状态模型的推断目标。
- UCS 展开的结点数只是墙钟时间的一部分(优先队列操作、后继生成、哈希都要花时间)。另外,启发式必须先满足一致性(consistency),保证 A* 结果正确,之后才谈得上加速多少。
- 贝叶斯网中的边缘概率(某个变量单独取各值的概率)与 MAP 赋值(整体最可能的一组取值)回答的是不同问题;把每个变量各自取边缘最大的值拼起来,一般不等于 MAP 赋值。
学完应具备的实现能力
课程的作业要求能从零实现以下算法:SGD、UCS / A*、值迭代、minimax 与 α-β 剪枝、回溯加 AC-3、变量消去。光会调用还不够,还要能讲清楚几个关键量的实际含义:优先队列的键代表什么、为什么启发式要一致、探索率控制的是什么权衡、粒子的权重表示什么。
全课主线:从一次动作到一条路径,再到随机、对手与分解
全课可以分成前后两半。
- 前半:从一次动作到一条路径。 反射模型对每个输入输出单个标签;搜索则输出一整条动作序列,并对整条序列的总代价负责。
- 后半:随机、对手与分解。 如果后继状态不再确定,搜索升级为 MDP;如果有对手参与决定后继,升级为博弈;如果状态空间大到无法枚举,就把状态拆成若干变量,改用因子图等变量模型。
四条主要的“输入 → 算法 → 输出”链条如下:
$$ \begin{aligned} \text{特征与损失} &\rightarrow\text{SGD} \rightarrow\text{反射预测},\\ \text{后继与代价} &\rightarrow\text{UCS / A*} \rightarrow\text{最小代价路径},\\ \text{转移与回报} &\rightarrow\text{值迭代 / Q 学习} \rightarrow\text{策略},\\ \text{因子} &\rightarrow\text{回溯 / 变量消去} \rightarrow\text{赋值或边缘}. \end{aligned} $$
▸概念链:每类模型在求什么
- 建模-推断-学习把每个题目拆成三步,先想清楚三步各是什么,而不是先写代码。
- 反射:一次前向计算,$f(x)$ 直接给出标签。
- 搜索:后继确定,求最小代价路径。
- MDP:后继随机,求期望回报最大的策略。
- 博弈:后继由对手或机会决定,求博弈值。
- 因子图 / CSP:给变量赋值,求权重最大的赋值或一个可行解。
- 贝叶斯网:因子带有概率含义,求边缘概率或 MAP 赋值。
- 逻辑:由公式与证明构成,求蕴含关系。
- 语言模型与社会专题讨论的是部署问题,不改变前面各类推断问题的定义。
▸材料分工:讲义与课堂各管什么
- 模块讲义负责定义、递推式、正确性证明、启发式需要满足的条件;作业里的公式与状态表示以讲义为准来核对。
- 课堂负责贯穿全课的应用例子:情感分类、路径规划、Mountain Car、Pac-Man、排课、车辆跟踪、从语言到逻辑。
- 做实验时,“换模型族”与“换启发式 / 换探索率”是两件独立的事,不宜放在同一次实验里同时改,否则无法判断效果来自哪一处改动。
数学预备:全课反复出现的六类对象
后面各章看上去用的数学各不相同,但反复出现的其实只有六类对象。先把它们集中列出,读者在后文遇到时能马上认出“这是哪一类”。
预测器与损失
反射模型是一个从输入空间到输出空间的函数 $f_w:\mathcal{X}\rightarrow\mathcal{Y}$,其中 $w$ 是参数(权重向量)。损失 $\mathrm{Loss}(x,y,w)$ 衡量参数为 $w$ 时,模型在样本 $(x,y)$ 上预测得有多差,结果是一个标量。经验风险(训练损失)是训练集上所有样本损失的平均值,学习就是让它变小。
梯度
SGD 每一步只用一个样本的梯度 $\nabla_w\mathrm{Loss}$ 来更新 $w$,计算量与数据集大小无关。不同损失的梯度行为差别很大,例如铰链损失在间隔大于 $1$ 时梯度为 $0$,也就是“已经分得足够好的样本不再贡献更新”。这一点在反射模型一章详细推导。
图上的路径
搜索问题可以看作在图上找路:状态是图的顶点,动作是边,每条边有代价。$\mathrm{FutureCost}(s)$ 定义为从状态 $s$ 出发到达任一终点的最小剩余代价。需要注意两点:
- 搜索里的 $\mathrm{Cost}$ 越小越好,而 MDP 里的回报 $R$ 越大越好,两者符号相反。
- UCS 的正确性依赖一个不变量(出队时代价已是最优),优先队列的键必须与这个已证明的不变量一致;随意改键(例如只按启发式排序)会让 UCS 不再正确。
期望与策略
MDP 中一个状态的值,是在随机转移和给定策略下得到的期望回报。贝尔曼方程(Bellman equation)把这个期望拆成“走一步得到的即时奖励”加上“后继状态的值”,从而可以递推求解。它和搜索的关系是:当转移是确定的、$R=-\mathrm{Cost}$、折扣 $\gamma=1$ 时,MDP 退化为搜索,此时 $V_{\mathrm{opt}}=-\mathrm{FutureCost}$。
因子与边缘
变量模型把联合分布(或一组赋值的权重)写成若干个因子的乘积,每个因子只依赖少数几个变量。在此基础上,边缘化(marginalization)是对一部分变量求和,MAP 是对它们取最大。求和与取最大的区别,就是“边缘概率”与“MAP 赋值”回答不同问题的根源。
蕴含
逻辑一章的核心关系是蕴含:$\mathrm{KB}\models\phi$ 表示所有满足知识库 KB 的赋值(模型)都满足公式 $\phi$,即只要 KB 为真,$\phi$ 就必然为真。消解是一条完备的证明规则(以反证法使用时,对命题逻辑中的蕴含判定是可靠且完备的:把 $\neg\phi$ 加入 KB,推出空子句即证明 $\mathrm{KB}\models\phi$),它是严格的推理过程,而不是一种启发式搜索。
复杂度:先问哪一个维度在增长
分析一个算法的代价时,首先要问“随着什么变大,计算量变大”:样本维度、状态数、搜索深度,还是因子宽度。下面汇总各章算法的时间与空间复杂度,具体推导放在对应章节。
时间复杂度
| 算法 | 时间复杂度 | 增长来源 |
|---|---|---|
| 线性模型一步 SGD | $O(d)$ | 特征维数 $d$,一次点乘加一次向量更新 |
| UCS / A* | 与展开的状态数及优先队列操作有关,最坏指数 | 状态空间大小 |
| 值迭代(每轮) | $O(\lvert S\rvert^{2}\lvert A\rvert)$(稠密转移)或 $O(\lvert S\rvert\lvert A\rvert b)$(分支因子 $b$,即每个 $(s,a)$ 最多 $b$ 个后继) | 状态数 $\lvert S\rvert$、动作数 $\lvert A\rvert$ |
| minimax | 深度 $d$、分支 $b$ 时 $O(b^{d})$ | 博弈树规模 |
| α-β 剪枝 | 最好情况约 $O(b^{d/2})$ | 走法排序理想时,相当于同样时间内搜索深度翻倍 |
| 变量消去 | 指数于最大中间因子的宽度(induced width) | 消元顺序产生的最大因子 |
| HMM 前向算法 | $O(T\lvert Z\rvert^{2})$ | 序列长 $T$、隐状态数 $\lvert Z\rvert$ |
| 粒子滤波 | $O(TK)$,另加样本方差 | 粒子数 $K$;用随机误差换计算量 |
值迭代的 $O(|S|^{2}|A|)$ 来自:每轮对每个状态 $s$、每个动作 $a$,都要对所有可能的后继 $s'$ 求加权和;转移稀疏、每个 $(s,a)$ 只有 $b$ 个后继时,最内层从 $|S|$ 降为 $b$。α-β 的 $O(b^{d/2})$ 只在走法排序最理想时成立,排序很差时退回 $O(b^{d})$。
空间复杂度
- 反射模型只需存权重向量 $w$。
- UCS 需要存 frontier(待展开的优先队列)与 visited(已展开集合),两者都可能随状态数增长。
- 值迭代需要存每个状态的值,即 $|S|$ 个数;若存 Q 值则是 $|S||A|$ 个数。
- 贝叶斯网精确推断时,变量消去产生的中间表可能比输入的因子大几个数量级,这往往比时间更早成为瓶颈。
数值计算:作业中常见的数值陷阱
以下几条都来自“算法理论上正确,但实现出来结果不对”的典型情况:
- 优先队列的平局规则。 键相等时必须有确定的打破规则(例如按插入顺序或按状态本身比较),否则结点的展开次序不确定,自动评分会因展开次序不同而判错。
- 折扣与吸收态。 折扣 $\gamma=1$ 且没有吸收态(终止状态)时,回报可以无限累加,值迭代可能发散。
- log 域计算。 很多小概率连乘会下溢为 $0$;应改为在 log 域把对数概率相加。
- α-β 的初始窗口。 窗口初值必须是真正的 $\pm\infty$(例如 Python 的
float('inf')),不能用一个“很大的数”冒充;当估值函数的取值恰好超过这个数时,剪枝就会出错。 - 粒子权重归一化。 粒子滤波的权重必须归一化;有效样本量(effective sample size)过低、即少数粒子占了几乎全部权重时,再进行重采样。
Python 实现基础:状态表示与路径回溯
搜索类作业的大部分 bug 不在算法本身,而在状态的表示方式。需要遵守三条约定:
- 状态必须可哈希,才能放进 visited 集合或作为字典键。用元组、冻结集合(
frozenset)表示状态,不要用可变的 list 当字典键。 - 后继函数应返回新的状态对象,不要原地修改当前状态;否则已经放进队列或集合里的状态会被悄悄改掉。
- 作业的自动评分对浮点数和展开结点数都检查得很严格,建议先在手画的小图上与手算结果对拍,再跑完整数据。
下面的函数用于搜索结束后回溯出动作序列。搜索过程中对每个状态记录它的父状态和到达它所用的动作,即 prev[s] = (parent, action);约定起点的父状态是 None。回溯时从终点沿父指针一路走回起点,再把收集到的动作反转。
1 | import heapq |
▸小结
- 全课按四条建模轴组织:反射、状态、变量、逻辑;每道题按“任务 → 模型族 → 推断问题 → 学习或指定参数 → 复杂度与近似”分析。
- 损失、路径代价、期望回报、边缘概率是不同的量,不能互相替代。
- 复杂度先问哪个维度在增长:$d$、$|S|$、$b^{d}$ 还是因子宽度。
- 实现上注意平局规则、log 域、真正的 $\pm\infty$、可哈希且不可变的状态。
建模框架:建模、推断与学习
拿到一个 AI 任务,最常见的错误是直接挑一个算法或一个库开始写代码,写到一半才发现要算的根本不是这个量。CS221 用“建模-推断-学习”(modeling-inference-learning)三步来避免这种情况:先把真实任务写成一个模型,再明确在模型上要回答的推断问题,最后(如果模型里有未知参数)再考虑如何从数据中学习它们。同一个任务可以放在反射、状态、变量或逻辑四条轴中的任意一条上,选择不同,要算的量和代价也不同。
这一章固定全课使用的这套术语:输入是任务描述,输出是“选哪个模型族、要算哪个量”。本章本身不引入具体算法;下一章(反射模型)会用最简单的模型把三步完整走一遍。
三个步骤各做什么
- 建模(modeling):选择用什么状态、变量或公式来描述世界。好的模型要满足两个条件:在计算上可行(推断算得动),在任务上够用(保留了任务关心的信息)。
- 推断(inference):给定一个已经完全确定的模型,回答问题,例如“这个输入的标签是什么”“最短路径是哪条”“这个变量取 1 的概率是多少”。
- 学习(learning):给定数据,填上模型里的未知量,例如线性分类器的权重、MDP 的转移概率。
三者的关系可以这样理解:建模决定“问什么”,推断负责“怎么答”,学习负责“模型里的数字从哪来”。
▸建模-推断-学习
这是 CS221 贯穿全课的拆题方法。它源于把 AI 定义为“在有限计算下做决策”,而不是先选一个库函数。
- 与直接调用求解器相比:先写清楚问的是路径、策略、赋值还是蕴含,再选择算法。
- 与 CS229 的“假设类加损失”相比:CS229 中推断默认就是算一次 $h_{\theta}(x)$;而这里同一个损失可以搭配完全不同的推断方式,可能是一次前向计算,也可能是一次搜索。
- 实践提示:作业失败的常见原因是模型写错了,而不是 SGD 步长没调好。
四条建模轴
四条轴按“模型里的基本对象是什么”来区分:
| 轴 | 对象 | 推断问什么 | 典型算法 |
|---|---|---|---|
| 反射 | 输入 $x$ | $f(x)$ | 前向、$\arg\max$ 分数 |
| 状态 | 状态与动作 | 路径或策略 | UCS、值迭代、minimax |
| 变量 | 变量与因子 | 赋值或边缘 | 回溯、变量消去 |
| 逻辑 | 公式 | 蕴含 | 模型检测、消解 |
从上到下,模型能表达的结构越来越丰富:反射模型只看当前输入;状态模型考虑动作的后果;变量模型不需要规定决策顺序,可以同时处理变量之间任意的约束;逻辑模型能表达“对所有……都成立”这类知识,并给出必然为真的结论。
同一任务放在不同轴上:排课的例子
同一个任务放在不同的轴上,要问的量也随之改变。以“给一组课程安排时段”为例:
- 反射:把(课程、教室、学期)编码成特征 $\phi$,模型一次输出一个时段标签。速度很快,但每门课是独立决定的,不能保证教室不冲突。
- 状态:状态是“已经排好的课程集合,加上当前要排的课”,动作是为当前课选一个时段,代价是冲突带来的惩罚,推断用 UCS。问题在于状态数随课程数指数增长。
- 变量:每门课对应一个变量,取值是时段;冲突关系写成因子,推断用回溯或变量消去(VE)。变量数就等于课程数,不再需要枚举“已排集合”。
- 逻辑:把“先修课必须排得更早”“同一位教师不能同时上两门课”写成公式,问知识库 KB 是否蕴含某种排法合法。输出是“是 / 否”或一个反例模型,而不是一张最优课表。
如何选轴
判断标准是任务要求的输出形式:是单个标签、一条路径、一组赋值,还是一句必然为真的陈述。选定轴之后如果计算预算不够,应该在同一条轴上做近似(例如束搜索、粒子滤波、截断搜索深度),而不是把问题硬塞成反射模型、假装它只需要一次前向计算。
▸例0 送货问题在四条轴上的不同写法
任务:从仓库出发,送完一组包裹后返回仓库。
- 反射:把当前坐标输入一个网络,输出下一步往哪个方向走。简单,但不保证能送完所有包裹。
- 搜索:状态写成 $(\mathrm{loc},S)$,即当前位置加上尚未送达的包裹集合;UCS 给出总代价最小的路径。
- MDP:如果路况是随机的,同一个动作可能得到不同的结果,此时需要的是一个策略(每个状态下怎么走),而不是一条固定路径。
- 变量:把每件包裹的送达顺序或送达时段当作变量,车辆容量写成因子。
- 逻辑:问“是否必然存在一条不超时的路线”,回答的是存在性,而不是求出那条路线。
这几种题面措辞相近,但对应的模型族完全不同。
▸四条轴的意义
课程用这四条轴来限定“智能体”一词的外延:AI 按表示方式划分,而不是按应用领域划分。
- 与“先选神经网络”相比:神经网络只覆盖反射轴上特征 $\phi$ 的那一部分,搜索与消解不会因为换成神经网络就自动出现。
- 与 CS229 相比:CS229 默认推断是一次 $h_{\theta}(x)$;这里的推断本身可能就是 NP 难的搜索或消去问题。
反射模型(Reflex-Based Models):线性预测、损失与 SGD
先看一个具体任务:给定一条电影评论,判断它是正面还是负面。这个任务的输出只是一个标签,而且这个决定不会影响之后的输入,不需要考虑“这一步之后世界会变成什么样”。像这样“看到输入就立即作答”的模型称为反射模型:输入 $x$ 可以是邮件、评论文本、图像,输出是单个动作或标签 $y$,输出时不会搜索未来的状态。
反射模型是四条轴里最简单的一条,用它可以把“建模-推断-学习”三步完整走一遍:建模是选择特征 $\phi$ 与损失函数;推断是一次点乘或一次前向计算,几乎没有代价;学习用 SGD 从 $(x,y)$ 训练集得到权重 $w$。因此这一章的难点不在推断,而在两件事上:损失怎么定义才可优化,特征怎么设计才能表达任务。本章内容依次为线性预测器、损失、SGD、特征模板与浅层网络。
与前后章节的关系:上一章给出了三步拆法;下一章的搜索把输出变成一整条动作序列,推断不再是一次点乘。
线性预测器:先打分,再决策
讲义把智能体统一写成一个函数 $f$。反射模型在看到 $x$ 的当下就给出 $y$,不展开后继状态。最基本的反射模型是线性预测器(linear predictor),它分两步工作:先把输入变成特征向量 $\phi(x)\in\mathbb{R}^{d}$(一个 $d$ 维实向量,每一维描述输入的一个方面,例如“评论里出现了几次 good”),再用权重向量 $w\in\mathbb{R}^{d}$ 算一个分数(score),按分数的正负做决策:
$$ s=w\cdot\phi(x),\qquad f_w(x)=\mathrm{sign}(s). $$
这里标签取 $y\in\{-1,+1\}$。分数 $s$ 是各维特征的加权和;$w_j$ 越大,表示第 $j$ 维特征越强烈地把分数往正类推($w_j\lt0$ 则往负类推)。分数的绝对值可以理解为置信程度,正负号决定预测的类别。
上面是二分类的情形。另外两种常见情形写法如下:
- 回归:去掉 $\mathrm{sign}$,直接输出实数分数 $s$ 作为预测值。
- 多类分类:对每个候选标签 $y$ 定义一份特征 $\phi(x,y)$(可以理解为“输入 $x$ 与标签 $y$ 搭配时的特征”),对所有标签打分后取最高者:
$$ f_w(x)=\arg\max_{y}w\cdot\phi(x,y). $$
使用中要注意:特征映射 $\phi(x)$ 在训练与测试时必须使用同一套定义,否则学到的 $w$ 在测试时作用在含义不同的维度上,结果没有意义。
▸反射模型
当前输入一经给出,输出由一次计算确定,不展开后继。它的来源是感知机与线性分类,课程把它当作最简单的智能体。
- 与状态模型相比:反射模型不能处理“这一步的后果要几步之后才结算”的任务。
- 与直接使用深度网络相比:线性模型加上好的特征已经足以完成情感分类作业;神经网络只是把 $\phi$ 换成可以学习的部分。
损失函数:把预测好坏变成可优化的标量
为什么不能直接优化错误率
分类任务真正关心的指标通常是错误率,也就是零一损失(zero-one loss)$\mathbf{1}\{f_w(x)\ne y\}$:分错记 1,分对记 0。问题在于,零一损失作为 $w$ 的函数是分段常数——稍微改动 $w$,只要没有让某个样本跨过决策边界,损失就完全不变。因此它对 $w$ 几乎处处梯度为 $0$,梯度无法告诉优化器往哪个方向调,也就不能直接用 SGD 优化。
课程的做法是改用代理损失(surrogate loss):选一个与零一损失“方向一致”、但连续且(几乎处处)可导的函数来代替它进行优化。学习的目标是经验风险(empirical risk),即训练集上的平均损失:
$$ \mathrm{TrainLoss}(w)=\frac1n\sum_{i=1}^{n}\mathrm{Loss}(x^{(i)},y^{(i)},w). $$
其中 $n$ 是训练样本数,$(x^{(i)},y^{(i)})$ 是第 $i$ 个样本。
间隔:统一描述分类损失的量
标签 $y\in\{-1,+1\}$、分数 $s=w\cdot\phi(x)$ 时,乘积 $ys$ 称为间隔(margin)。它的含义很直观:$ys\gt0$ 表示分对了,$ys\lt0$ 表示分错了,$|ys|$ 越大表示越“确信”。分类损失都可以写成间隔 $ys$ 的函数;回归则用残差(residual)$s-y$,即预测超出目标的量。
| 损失 | 公式 | 梯度 $\nabla_w$ | 何时用 |
|---|---|---|---|
| 零一 | $\mathbf{1}\{ys\le 0\}$ | 几乎处处 $0$ | 报告准确率 |
| 感知机 | $\max(0,-ys)$ | $ys\lt0$ 时 $-y\phi$ | 只改错分 |
| 铰链 | $\max(0,1-ys)$ | $ys\lt1$ 时 $-y\phi$ | 还要间隔 |
| 逻辑 | $\log(1+e^{-ys})$ | $-\frac{y}{1+e^{ys}}\phi$ | 要概率 |
| 平方 | $(s-y)^{2}$ | $2(s-y)\phi$ | 回归 |
表中梯度一栏未写出的区域梯度为 $0$;例如感知机损失在 $ys\ge 0$ 时为 $0$,铰链在 $ys\ge 1$ 时为 $0$。所有梯度都通过链式法则 $\nabla_w\mathrm{Loss}=\frac{\partial\mathrm{Loss}}{\partial s}\cdot\phi(x)$ 得到,因为 $\partial s/\partial w=\phi(x)$。
代理损失与零一损失的关系
零一、感知机、铰链、逻辑这四种损失都是间隔 $ys$ 的单调非增函数:间隔越大,损失越小(或不变)。其中铰链与逻辑损失是零一损失的凸上界:
- $\mathrm{Loss}_{\mathrm{0-1}}\le\mathrm{Loss}_{\mathrm{hinge}}$:在 $ys\le 0$ 时铰链为 $1-ys\ge 1$,在 $ys\gt0$ 时铰链非负,而零一为 $0$。
- $\mathrm{Loss}_{\mathrm{0-1}}\le\mathrm{Loss}_{\mathrm{logistic}}/\log 2$(差一个正常数):在 $ys=0$ 处逻辑损失恰为 $\log 2$,且随 $ys$ 减小而增大,所以除以 $\log 2$ 后在 $ys\le 0$ 一侧至少为 $1$。
这意味着把上界压低,零一损失也被压在下面。但需要注意:最小化上界并不自动最小化零一损失,它只是提供了一个可以用梯度优化的凸目标。
铰链损失的梯度
▸铰链梯度
设 $s=w\cdot\phi(x)$,$\mathrm{Loss}=\max(0,1-ys)$。分两段讨论:
- 在 $ys\gt1$ 处,损失为 $0$,梯度为 $0$。
- 在 $ys\lt1$ 处,$\mathrm{Loss}=1-ys$,对 $s$ 求导得 $-y$,再乘以 $\partial s/\partial w=\phi(x)$:
$$ \nabla_w\mathrm{Loss}=-y\,\phi(x). $$
在 $ys=1$ 这个折点处函数不可微,次梯度(subgradient)可取 $0$ 或 $-y\phi$(两者之间的凸组合也可以)。代入梯度下降,更新为 $w\leftarrow w+\eta y\phi(x)$,并且只发生在间隔不足的点上。这一更新的方向是把 $w$ 往 $y\phi(x)$ 方向推,使该样本的分数 $w\cdot\phi(x)$ 朝 $y$ 的符号移动。
感性理解:已经离开间隔带的点不再拉动 $w$。零一损失只看对错;铰链还要求分对且分数至少达到 $1$。逻辑损失则对所有点都给一点梯度,间隔很大时梯度按 $e^{-ys}$ 的速度变小,但永远不会精确等于 $0$。
逻辑损失的梯度与概率解释
▸逻辑损失梯度
$\mathrm{Loss}=\log(1+e^{-ys})$。令 $u=-ys$,则 $\mathrm{Loss}=\log(1+e^{u})$,由链式法则
$\frac{\partial\mathrm{Loss}}{\partial s}=\frac{e^{u}}{1+e^{u}}\cdot\frac{\partial u}{\partial s}$,而 $\frac{\partial u}{\partial s}=-y$,于是
$$ \frac{\partial\mathrm{Loss}}{\partial s} = \frac{-y\,e^{-ys}}{1+e^{-ys}} = -\frac{y}{1+e^{ys}}. $$
最后一步是分子分母同乘 $e^{ys}$。再乘以 $\partial s/\partial w=\phi(x)$ 即得表中的梯度。看两个极端:$ys\rightarrow+\infty$(分得非常对)时该系数 $\rightarrow 0$;$ys\rightarrow-\infty$(错得非常离谱)时该系数 $\rightarrow -y$,与铰链在错分一侧的梯度 $-y\phi$ 同阶。所以逻辑损失可以看作铰链的“平滑版本”。
概率读法:令 $P(y=1\mid x)=\sigma(s)$,其中 $\sigma(z)=(1+e^{-z})^{-1}$ 是 sigmoid 函数。由于 $1-\sigma(s)=\sigma(-s)$,两类可以统一写成 $P(y\mid x)=\sigma(ys)$,取负对数恰好是 $\log(1+e^{-ys})$。也就是说,逻辑损失等于 Bernoulli 负对数似然(与 $\{0,1\}$ 标签的交叉熵写法相比只差一个与 $w$ 无关的改写)。需要校准过的概率输出时用逻辑损失;不需要概率时,铰链损失通常更稀疏(大量样本梯度恰为 $0$)。
▸损失
损失把一次预测变成可优化的标量。零一损失是真正的目标指标,但不能直接 SGD。铰链与逻辑都是零一的凸上界,思路来自统计学习中“用代理损失换取可微性”。
为什么分类不宜直接把平方损失套在 $\pm 1$ 标签上:分类关心的是间隔,而不是分数离 $\pm 1$ 有多近。平方损失在 $|s|$ 很大且符号已经正确时仍然给出很大的梯度(例如 $y=1$、$s=5$ 时损失为 $16$),梯度下降会把这类已经分对、且离边界很远的点的分数硬拉回 $1$ 附近,为此不惜移动决策边界,反而可能让别的点分错。
随机梯度下降(SGD)
有了可导的训练损失,下一个问题是怎样找到让它变小的 $w$。最直接的办法是梯度下降:沿负梯度方向走一小步,重复多次。问题是训练集很大时,每一步都要遍历全部 $n$ 个样本,代价太高。SGD 的想法是:每一步只用一个随机样本的梯度来近似全体梯度——这个估计有噪声,但平均来说方向是对的,而且每步代价与 $n$ 无关。
批量梯度下降与随机梯度下降
批量梯度下降(batch gradient descent)每步使用全体样本:
$$ w\leftarrow w-\eta\nabla_w\mathrm{TrainLoss}(w) = w-\frac{\eta}n\sum_{i=1}^{n}\nabla_w\mathrm{Loss}(x^{(i)},y^{(i)},w). $$
第二个等号成立,是因为 $\mathrm{TrainLoss}$ 是各样本损失的平均,而梯度是线性运算,平均的梯度等于梯度的平均。
随机梯度下降(SGD)每次只抽一个样本(或一小批,即 mini-batch),用它的梯度更新:
$$ w\leftarrow w-\eta_t\nabla_w\mathrm{Loss}(x^{(t)},y^{(t)},w). $$
如果样本是均匀随机抽取的,单样本梯度的期望恰好等于全体平均梯度,这就是“平均方向是对的”的含义。
步长与收敛性
$\eta_t$ 是第 $t$ 步的步长(step size / learning rate)。收敛性取决于损失是否为凸:
- 铰链与逻辑损失对 $w$ 是凸的,取适当衰减的 $\eta_t$(例如 $\eta_t=\eta_0/\sqrt{t}$)可以使 $\mathrm{TrainLoss}$ 收敛到全局最小值的邻域。步长需要衰减,是因为单样本梯度有噪声,固定步长会让 $w$ 在最优点附近持续抖动。
- 神经网络的损失非凸,只能保证收敛到驻点附近,不保证全局最优。
实践中的经验:作业里步长过大,参数会发散,损失变成 nan;步长过小,几十轮下来损失几乎不动。
感知机算法
感知机更新可以看作铰链 SGD 的一个特例:取 $\eta=1$,并且只在 $ys\le 0$(已经分错)时更新。它有两个经典性质:
- 数据线性可分时,感知机在有限步内到达某个能把数据完全分开的超平面,但这个平面不一定是间隔最大的那个。
- 数据线性不可分时,感知机不会收敛,权重会一直振荡。
▸SGD
SGD 用单个样本的梯度代替全训练集的梯度。它源自 Robbins-Monro 随机逼近,并不是为深度学习新造的方法。
- 与批量梯度相比:$n$ 很大时每一步很便宜,而且在读完全部数据之前就可以开始更新 $w$。
- 代价是梯度有噪声,需要衰减 $\eta$ 或改用小批量。
- 作业里步长过大会出现
nan,过小则几十轮损失不动。
正则化与过拟合
只最小化训练损失,模型可能把训练集记得太细,在新数据上反而变差。正则化(regularization)的做法是在损失上加一项 $\frac\lambda2\|w\|_{2}^{2}$ 惩罚大的权重,相应地梯度多出一项 $\lambda w$(因为 $\nabla_w\frac\lambda2\|w\|_2^2=\lambda w$)。每步更新时这一项都会把 $w$ 按比例往 $0$ 拉,因此也称权重衰减(weight decay)。
- $L_2$ 正则把权重拉向 $0$,降低模型的有效容量。
- $\lambda$ 要用验证集来选,不能看训练损失(训练损失总是在 $\lambda=0$ 时最小)。
- 通常不惩罚截距那一维,因为截距只反映类别先验,压缩它没有降低复杂度的作用。
- 情感分类里如果 $\lambda$ 过大,那些出现次数少但极性很强的词(如某个罕见的强烈贬义词)的权重会被压没。
过拟合(overfitting)的典型症状是:训练集上的零一损失持续下降,验证集上的零一损失却开始上升。对策包括加大 $\lambda$、早停(early stopping,在验证误差最低时停止训练)、减少特征,或增加数据。课程不把双重下降(double descent)现象作为作业要求。
多类铰链损失
二分类的铰链要求“正确类的分数比 $0$ 高出间隔 $1$”。推广到多类时,要求变成:正确标签 $y$ 的分数要比每一个错误标签 $y'$ 的分数都高出至少 $1$。对每个错误标签各算一份铰链,再求和:
$$ \mathrm{Loss}(x,y,w)=\sum_{y'\ne y}\max\big(0,1+w\cdot\phi(x,y')-w\cdot\phi(x,y)\big). $$
对某个错误标签 $y'$,如果它的分数还没比正确类低出间隔 $1$,这一项就是正的,其梯度是 $\phi(x,y')-\phi(x,y)$;梯度下降沿这个方向的反方向更新 $w$,从而提高正确类的分数、压低 $y'$ 的分数,把两者推开。(另一种常见写法是对 $y'$ 取最大而不是求和,只惩罚得分最高的错误标签。)
结构化感知机(structured perceptron)可以看作这一思想在 $y$ 是整条路径等结构化输出时的特例:此时 $\arg\max_y$ 无法枚举所有标签,推断改成一次搜索。作业中的情感分类如果只有正负两类,不需要用到这个式子。
▸感知机是铰链的特例
铰链在 $ys\lt1$ 时更新,感知机在 $ys\lt0$ 时更新。把铰链的间隔从 $1$ 改成 $0$,并取 $\eta=1$,就得到感知机。
- 线性可分时,感知机有限步到达某个分隔超平面,但该平面不一定是最大间隔的;间隔越大的铰链解,对特征扰动越稳健。
- 不可分时,感知机振荡;而铰链加 $L_2$ 正则的目标是强凸的,仍有唯一的最小点。
感性理解:感知机只在已经分错时才动手;铰链在“分对了但太险”时也会动手;逻辑损失对分得很对的点仍保留一点梯度。三者优化的都是 $ys$ 的某种上界,而不是直接数错分的个数。
特征设计:特征模板与情感分类
线性模型的表达能力完全取决于特征 $\phi$:模型只能对特征做加权求和,特征里没有的信息,模型无论怎么调 $w$ 都学不到。因此在反射模型里,大部分建模工作其实是设计特征。手工逐维定义特征不现实(词表可能有几万个词),课程用特征模板(feature template)来批量生成特征:先写一个模式,如“单词 ___ 出现的次数”,再在数据上展开,每个具体的词就成为一维。
情感分类中常见的模板有:
- 词袋(bag of words):每个词出现的次数,如 $\phi_{\mathrm{good}}(x)=\mathrm{count}(\mathrm{good})$。
- 二元语法(bigram):相邻两个词组成的短语出现的次数,如 $\phi_{\mathrm{not_good}}(x)=\mathrm{count}(\mathrm{not}\ \mathrm{good})$。它能捕捉单个词看不出来的组合含义。
- 否定范围:从 not / n’t 开始到下一个标点为止,把其中极性词的特征取反(或加上否定标记),使“not good”不再被当作 good。
- 截距:常数特征 $\phi_0=1$,它的权重吸收正负类的先验比例(例如训练集中正面评论更多)。
实现时有几条必须遵守的约定:
- 词表必须在训练集上确定后冻结。测试时出现的新词归入
UNK桶,不能在测试时给 $w$ 增加新的维度。 - 大小写、标点、HTML 标签是否归一化,训练与测试必须保持一致。
▸例1 只有两个词的情感分类
假设评论里只含 good / bad 两个词。特征取 $\phi=(\mathbf{1}\{\mathrm{good}\},\mathbf{1}\{\mathrm{bad}\},1)$,三维分别是“是否出现 good”“是否出现 bad”和截距。
- 初值 $w=0$ 时,所有样本的分数都是 $0$,间隔 $ys=0\lt1$,铰链在所有点上都会更新。
- 正面评论(含 good,$y=+1$)的更新把 $w_1$ 往上推,负面评论(含 bad,$y=-1$)把 $w_2$ 往下推;若干步后 $w_1\gt0$、$w_2\lt0$。
- 问题出现在 “not good” 这样的评论上:如果它仍被编码成“出现了 good”,正权重 $w_1$ 就会被错误触发,把一条负面评论判成正面。
这是特征问题,不是步长问题,调步长解决不了。加上二元语法或否定范围特征之后,模型可以学到 $w_{\mathrm{not_good}}$ 为负。
从线性模型到神经网络
线性模型的局限:XOR
线性模型的决策面是 $\phi$ 空间中的一个超平面 $w\cdot\phi(x)=0$。如果正负样本在特征空间里不能用一个超平面分开,线性模型就无能为力。经典反例是 XOR:四个点 $(0,0),(1,1)$ 为一类、$(0,1),(1,0)$ 为另一类,在原始坐标上不存在任何一条直线能把它们分开。解决办法有两种:手工换特征(例如加入乘积项 $x_1x_2$),或者加一个隐层,让模型自己学特征。
浅层网络
浅层网络在输入与输出之间加一层隐藏单元:
$$ h=g(Wx+b),\qquad s=v\cdot h. $$
其中 $W$ 是 $k\times d$ 的权重矩阵($k$ 为隐藏单元数),$b\in\mathbb{R}^{k}$ 是偏置,$g$ 是逐元素作用的非线性激活函数,$h\in\mathbb{R}^{k}$ 是隐层输出,$v\in\mathbb{R}^{k}$ 是输出层权重。与线性模型对照:$h$ 就是“学出来的特征”,最后一步 $s=v\cdot h$ 仍是线性预测器。
$g$ 常用 $\mathrm{ReLU}(z)=\max(0,z)$ 或 sigmoid 函数 $\sigma$。如果没有非线性 $g$,两层线性变换的复合仍是线性的,加隐层就毫无意义。
反向传播与初始化
训练网络仍然用 SGD,只是梯度要用链式法则逐层往回传,即反向传播(backpropagation):先算出损失对分数的导数 $\partial\mathrm{Loss}/\partial s$(就是前面各损失表中对 $s$ 的导数),由 $s=v\cdot h$ 得到对 $v$ 的梯度 $\frac{\partial\mathrm{Loss}}{\partial s}h$;再经过激活函数的导数 $g'$ 传到 $W$。课程只要求会把链式法则接到损失上,不要求更深入的网络训练技巧。
一个必须注意的细节:初始化不能全为零。若所有隐藏单元的初始权重相同,它们在前向时算出相同的输出,反向时收到相同的梯度,更新之后仍然相同——这种对称性永远无法打破,多个隐藏单元退化成一个。因此需要用小的随机数初始化。
实现:铰链与逻辑梯度、带 L2 的 SGD
下面的代码把本章的公式落成实现:hinge_grad 与 logistic_grad 分别对应前面推导的两个梯度,sgd_hinge 是带 $L_2$ 正则的铰链 SGD,predict 按分数正负给出标签。
1 | def hinge_grad(w, phi, y): |
注意线性模型零初始化没有问题:它没有隐藏单元之间的对称性需要打破。上面的 sgd_hinge 按固定顺序遍历样本、使用固定步长;实际使用时通常每轮打乱样本顺序,必要时让步长衰减。
▸标签约定
讲义与情感分类作业都用 $y\in\{-1,+1\}$。若数据给的是 $\{0,1\}$,必须先映射成 $\pm 1$;否则铰链里的 $ys$ 符号就是错的:本应取 $-1$ 的负类样本变成 $y=0$,其间隔恒为 $0$、梯度 $-y\phi$ 恒为 $0$,这一整支本应把 $w$ 往负方向推的梯度不是反号就是消失。多类情形使用 $\arg\max_y w\cdot\phi(x,y)$ 时,不要再在外面套 $\mathrm{sign}$。
▸对照:反射与搜索
反射模型一次就算完 $f(x)$。搜索则要返回整条动作序列,并比较整条路径的代价。情感分类用反射模型就足够;但迷宫或路径规划如果用反射模型,在每个岔路口只能贪心地选当前看起来最好的方向,全局代价可能任意差。
▸衔接:从反射到搜索
反射模型的推断只是一次点乘。下一章把输出改成一条路径:必须先定义状态、后继与代价,再用图搜索而不是 SGD 来完成推断。
状态空间搜索(Search)
反射模型(reflex model)对一个输入只输出一个标签:看一眼图片,说“猫”或“狗”,事情就结束了。很多任务却不是一次判断能完成的。导航软件要给出从家到机场的一串转弯;解 8 数码(8-puzzle)要给出一串滑块移动;快递员要决定先送哪家、再送哪家、最后回仓库。这类任务的共同点是:每一步动作会改变“处境”,前面的选择会被后面的选择补偿或放大,所以必须把整条动作序列一起考虑,不能每一步单独贪心。
搜索问题的核心想法一句话就是:把任务写成一张加权图上的最短路问题——结点是状态,边是动作,边权是代价,要找从起点到某个终点、总代价最小的一条路。本章假定动作的后果是确定的:在状态 $s$ 做动作 $a$,只会落到唯一一个后继 $\mathrm{Succ}(s,a)$。在这个前提下,本章依次给出搜索问题的形式定义,再讲回溯、DFS / BFS / 迭代加深、一致代价搜索(UCS)和 A*,最后讲用动态规划求同一个方程,以及如何从数据中学习边代价(结构化感知机)。算法的输入是“后继函数 + 代价”,输出是路径、路径代价以及展开过的结点数(作业常按展开数评分)。
和相邻主题的关系:反射模型输出单个标签,本章输出动作序列,推断(inference)变成一次图搜索;下一章(MDP)里后继变成一个概率分布,一条事先写好的路径就不够用了,必须换成“每个状态该怎么做”的策略。
搜索问题的形式定义
五个组成部分
搜索问题(search problem)由输入 $x$(例如一张地图加起点终点,或一个 8 数码初始棋盘)构造出以下几个对象:
- $\mathrm{Start}(x)$:起始状态。
- $\mathrm{Actions}(s)$:在状态 $s$ 可以执行的动作集合。
- $\mathrm{Succ}(s,a)$:在 $s$ 执行 $a$ 后到达的后继状态(本章中唯一确定)。
- $\mathrm{Cost}(s,a)\ge 0$:在 $s$ 执行 $a$ 的代价,非负。
- $\mathrm{IsEnd}(s)$:判断 $s$ 是否为终点状态。
一条动作序列 $a_1,\ldots,a_T$ 会诱导出一串状态:$s_0=\mathrm{Start}$,$s_t=\mathrm{Succ}(s_{t-1},a_t)$。搜索的目标是在所有满足 $\mathrm{IsEnd}(s_T)$ 的序列中,最小化总代价
$$ \sum_{t=1}^{T}\mathrm{Cost}(s_{t-1},a_t). $$
下文简写为 $\sum_t\mathrm{Cost}(s_{t-1},a_t)$。
状态必须包含未来决策所需的全部信息
“状态是什么”是建模时最关键、也最容易出错的一步。原则是:状态必须包含未来决策所需的全部信息,过去怎么走到这里的细节如果不影响将来,就不要放进去(这就是马尔可夫性,Markov property)。几个例子:
- 路径规划只需要把一个包裹送到某地:状态就是当前位置。
- 还要送完一组包裹再回仓库:只记位置不够,因为“哪些包裹还没送”会影响下一步该去哪。状态应写成 $(\mathrm{loc},S)$,其中 $S$ 为尚未送达的地点集合。
- 8 数码:只需存当前棋盘,因为未来能做什么只取决于棋盘布局。
漏掉必需的字段,搜索就会在一张“错误的图”上求最短路,得到“看起来每步都合法、但全局不可行”的路径;多存无关字段则会让状态数无谓膨胀。
▸搜索问题在课程中的位置
搜索把任务写成加权图上的最短路,来源是路径规划与谜题求解,课程把它当作“基于状态的模型”(state-based model)这条主线的第一站。相对反射模型:动作的后果会被后续动作补偿或放大,必须展开整条序列。相对 MDP:后继没有随机性,讨论的是一条确定路径的代价,而不是期望。
最小剩余代价 FutureCost
在讲具体算法之前,先写出“最优路径代价”满足的方程,后面所有算法都是在用不同方式求它。定义最小剩余代价(future cost)$\mathrm{FutureCost}(s)$ 为从 $s$ 出发走到任一终点所需的最小代价:
$$ \mathrm{FutureCost}(s) = \begin{cases} 0 & \mathrm{IsEnd}(s)\\ \min_{a}\big[\mathrm{Cost}(s,a)+\mathrm{FutureCost}(\mathrm{Succ}(s,a))\big] & \text{否则.} \end{cases} $$
从起点看,$\mathrm{FutureCost}(\mathrm{Start})$ 就是最优路径代价。与之对应,从起点走到 $s$ 已经付出的最小代价记为 $\mathrm{PastCost}(s)$。对任意一条经过 $s$ 的前缀,“已付 + 剩余最优”都不可能低于全局最优,即 $\mathrm{PastCost}(s)+\mathrm{FutureCost}(s)$ 不小于最优总代价;恰好在最优路径上的状态取等号。
▸FutureCost 递推的含义与适用条件
终点处已经不用再走,剩余代价为 $0$。非终点必须先走一步 $a$,付出 $\mathrm{Cost}(s,a)$,然后从后继出发再付最优剩余代价;对所有 $a$ 取最小就是最优。这是动态规划(最优子结构:最优路径的后缀也是最优的),不是新的搜索启发式。
若图有环且某个环的总代价为负,沿环无限转圈可使代价趋向 $-\infty$,该递推没有有限解。因此 UCS / A* 要求边代价非负。零代价边允许,但不能有负边。
感性理解:从终点往回填表,每个格子写“从这里走出去还要至少花多少”。搜索算法则从起点往前走,用已付代价去逐步逼近这张表。
▸例2 送货问题的状态设计
地图上有三个点 $A,B,C$,任意两点间边代价为 $1$。要从 $A$ 出发经过 $B$ 与 $C$(顺序不限)再回到 $A$。
若状态只存位置,搜索看到“已在 $A$”就认为到了终点,或者以为到过 $B$ 一次即可,可能漏掉 $C$。正确的状态是 $(p,S)$:$p\in\{A,B,C\}$ 为当前位置,$S\subseteq\{B,C\}$ 为尚未访问的点。起点为 $(A,\{B,C\})$,终点为 $(A,\emptyset)$。状态数是 $3\cdot 2^{2}=12$,不是 $3$。这里可以看到:状态空间的大小由“需要记住什么”决定,而不是由地图大小决定。
树搜索:回溯、DFS、BFS 与迭代加深
最朴素的想法是把所有可能的动作序列都试一遍。这一节的算法都属于这种“穷举型”思路,区别在于展开顺序与内存占用,它们都不利用边权的差异来指导搜索方向。
搜索树与状态图
先分清两个概念。状态图的结点是状态,边是 $(\mathrm{Succ},\mathrm{Cost})$;搜索树的结点是从起点出发的一条路径。同一状态经由两条不同路径到达,在状态图里是一个结点,在搜索树里是两个结点。
- 树搜索(tree search)逐条路径展开,可以处理负代价(只要加深度上限,或保证无负环),但同一状态可能被反复展开,代价是指数级。
- 图搜索(graph search)用
visited集合丢掉重复状态,省时间;但只有在“第一次见到该状态时它的代价已是最优”成立时才能丢,否则会丢掉更优的路径。
下面用 $b$ 表示分支因子(每个状态的动作数),$d$ 表示解的深度(动作数)。
回溯搜索
回溯(backtracking)递归地选择动作,走到终点时记录整条路径代价,然后撤回最后一步继续尝试别的动作。它完整展开搜索树,因此一定能找到最优解。时间 $O(b^{d})$,空间 $O(d)$(只需保存当前路径)。边代价可以任意;但若有负环,必须另加深度或代价下限,否则递归不终止。
1 | def backtracking(s, is_end, successors, path=None, cost=0.0, best=None): |
深度优先搜索(DFS)
DFS 用栈或递归,总是先往深处走。它只在找到“某个”解时停止,不保证最短。有环时必须记住当前路径上的状态,否则会无穷递归。若做成图搜索版、在入栈时就标记 visited,一个状态会被最先到达它的那条路径“占住”,之后经更优路径再到达时被直接剪掉,留下的是“先到但不优”的路径:单位代价时有时碰巧正确,边权不等时可能出错。
广度优先搜索(BFS)
BFS 用队列按层扩展。边代价全为 $1$ 时,第一次出队的终点就是步数最少、因而代价最小的解。边权不等时,“层数最少”可以不是代价最小。空间 $O(b^{d})$:浅层很宽时,内存会先于时间耗尽。
迭代加深(ID-DFS)
迭代加深(iterative deepening, ID-DFS)依次以深度上限 $0,1,2,\ldots$ 反复做 DFS,直到找到解。它兼有两者的优点:空间同 DFS 为 $O(d)$,在单位代价下最优性同 BFS。代价是同一浅层结点会被多次展开,常数更大;但由于搜索树越深结点越多,浅层的重复只占一小部分。内存紧且边权为单位时适合使用。
各算法对比
| 算法 | 最优对象 | 条件 | 时间(最坏) | 空间 |
|---|---|---|---|---|
| 回溯 | 任意代价路径 | 无负环或有深度帽 | $O(b^{d})$ | $O(d)$ |
| DFS | 不保证 | 有环须记路径 | $O(b^{d})$ | $O(d)$ |
| BFS | 最少步 | 边权全为 $1$ | $O(b^{d})$ | $O(b^{d})$ |
| ID-DFS | 最少步 | 边权全为 $1$ | $O(b^{d})$ | $O(d)$ |
| UCS | 最小代价 | 边权 $\ge 0$ | 见下文 | frontier |
| A* | 最小代价 | 一致 $h$ | 随 $h$ 降 | frontier |
表中“frontier”指优先队列中待展开结点的规模;UCS 与 A* 的细节见后两节。
▸回溯与 BFS 小结
回溯枚举所有完整路径;BFS 在单位代价图上按层扩展。二者都来自穷尽搜索,不带启发信息。相对 UCS:二者都不看边权差异,边权不等时 BFS 的“层数最少”可以不是代价最小。相对动态规划:树搜索可以把同一状态经不同路径展开多次,重复计算是其指数代价的来源。
一致代价搜索(Uniform Cost Search, UCS)
BFS 的问题在于它按“步数”而不是“代价”排序。UCS 的想法很直接:把 BFS 的普通队列换成按“已付代价”排序的优先队列,每次总是处理当前最便宜的那个状态。只要边权非负,一个状态第一次被取出时,它的代价就已经是最优的,可以放心地“结算”。这正是 Dijkstra 最短路算法,只是课程从搜索问题(后继函数)而不是从显式邻接表出发描述它。
算法
UCS 维护一个优先队列(称为 frontier),键是从起点已付代价 $g(s)=\mathrm{PastCost}(s)$。每次弹出键最小的状态,展开它的后继并入队。弹出的第一个终点即对应最优路径。
图搜索实现还需另维护 best[s]:目前已知的到达 $s$ 的最小 $g$。实现细节如下:
- 同一状态可以多次入队(惰性删除,lazy deletion),弹出时若其 $g$ 已大于
best[s],说明是过时的旧条目,直接丢弃。这样不需要优先队列支持 decrease-key 操作。 - 真正的“展开”(expand)发生在弹出时,而不是入队时。作业中统计的展开结点数按弹出计。
关键不变量:边权非负时,$s$ 第一次被弹出时,$g(s)$ 已是从起点到 $s$ 的最小代价,此后不必再打开 $s$。
1 | def uniform_cost_search(start, is_end, successors): |
▸为什么 UCS“弹出即最优”
前提:边权 $\ge 0$。对弹出次序做归纳。起点以 $g=0$ 弹出,显然最优。设此前弹出的状态都已拿到最优 $g$。令 $s$ 是下一个弹出、键为 $g(s)$ 的状态。假设存在更短路径 $P$ 到达 $s$,考察 $P$ 上第一个尚未弹出的点 $u$。$u$ 在 $P$ 上的前驱已经弹出(且其 $g$ 最优),展开时已把 $u$ 以不超过 $P$ 上代价的键放入队列,于是
$$ g(u)\le\mathrm{PastCost}_{P}(u)\le\mathrm{PastCost}_{P}(s)\lt g(s). $$
第一个不等式来自前驱已用最优值展开;第二个不等式来自非负边权(从 $u$ 沿 $P$ 继续走到 $s$ 代价不会减少);第三个是“$P$ 更短”的假设。这样优先队列应先弹出 $u$,与“下一个弹出的是 $s$”矛盾。因此 $g(s)$ 最优。由此,终点第一次弹出时即可停止,不必清空队列。
感性理解:优先队列总是先结算“已经确定不会更便宜”的点。非负边权保证后面再加上去的边不会让旧点突然变便宜。负边权破坏这一点:一条尚未走完的弯路可以在最后一步用负代价反超,弹出不再等于最优。零代价边是允许的,零代价环不会让 $g$ 下降,但可能让队列里堆积很多等价路径,必须靠状态哈希(best 表)挡住。
▸UCS 小结
UCS 按已付代价从小到大结算状态,来源是 Dijkstra 最短路,课程把它写成搜索问题而不是邻接表算法。相对 BFS:边权不必为 $1$,键是代价和而不是步数。相对 A*:相当于启发式恒为 $0$。相对动态规划:UCS 只保证“碰到的”状态最优,不必预先列出全部状态,也能处理有环图。
复杂度
设最优代价为 $C^{\ast}$,最小正边权为 $\varepsilon$,分支因子为 $b$。树版 UCS 会展开所有代价小于 $C^{\ast}$ 的路径,这些路径的步数至多约 $C^{\ast}/\varepsilon$,因此最坏展开约 $O(b^{1+C^{\ast}/\varepsilon})$ 个结点。图版 UCS 至多展开每个状态一次,时间由状态数 $|S|$、边数与堆操作(每次 $O(\log)$)决定。作业中的图通常稀疏,展开结点数随启发式(见下一节 A*)的变化更明显。
▸例3 四结点加权图上的 UCS
状态 $S,A,B,G$。边 $S\xrightarrow{1}A$,$S\xrightarrow{5}B$,$A\xrightarrow{1}B$,$A\xrightarrow{6}G$,$B\xrightarrow{1}G$。UCS 的弹出次序:
- 弹出 $S$,$g=0$。入队 $A$ 键 $1$、$B$ 键 $5$。
- 弹出 $A$,$g=1$。入队 $B$ 键 $2$(比原来的 $5$ 更好,更新
best)、$G$ 键 $7$。旧的 $B$ 键 $5$ 仍在堆里,之后弹出时会因过时而被丢掉。 - 弹出 $B$,$g=2$。入队 $G$ 键 $3$。
- 弹出 $G$,$g=3$。停止。路径为 $S\rightarrow A\rightarrow B\rightarrow G$。
若用 BFS 按步数,会先找到 $S\rightarrow A\rightarrow G$(两步,代价 $7$),不是最优。这说明边权不等时不能用 BFS 冒充 UCS。
A* 搜索
UCS 只看已经走过的代价,对终点在哪个方向一无所知:它会像水波一样向四周均匀扩散,包括背离终点的方向。A* 的想法是给每个状态额外加上一个“还剩多远”的估计,让搜索优先往看起来离终点近的方向推进。只要这个估计不高估(更准确地说,满足一致性),A* 仍然保证最优,同时展开的结点更少。
优先队列的键与两种性质
A* 的优先队列键是
$$ f(s)=g(s)+h(s), $$
其中 $g(s)$ 是已付代价,$h(s)$ 是启发式(heuristic),估计从 $s$ 到终点的剩余代价。$h\equiv 0$ 时 A* 退回 UCS。
启发式有两种常用性质:
- 可采纳(admissible):对一切 $s$,$h(s)\le\mathrm{FutureCost}(s)$,即从不高估剩余代价。
- 一致(consistent,也称 monotone):
$$ h(s)\le\mathrm{Cost}(s,a)+h(\mathrm{Succ}(s,a)),\qquad h(\mathrm{end})=0. $$
一致性是“启发式版的三角不等式”:从 $s$ 估计的剩余,不能比“先真实走一步再估计”还大。一致蕴含可采纳(证明见下)。另一种理解:若定义修改后的边代价 $\mathrm{Cost}'(s,a)=\mathrm{Cost}(s,a)+h(\mathrm{Succ}(s,a))-h(s)$,一致性恰好保证 $\mathrm{Cost}'\ge 0$,而 A* 就等价于在这张新代价图上跑 UCS——所以 UCS 的正确性证明可以直接搬过来。
由此得到的结论:
- $h$ 一致时,A* 弹出 $s$ 时 $g(s)$ 仍是最优,第一次弹出终点即可停止,图搜索不必重新打开已弹出的状态。
- $h$ 只可采纳、不一致时,树版 A*(不丢弃结点)仍最优;图版 A* 必须允许同一状态在获得更小 $g$ 时再次入队(重新打开)。
下面的实现采用惰性删除,只要找到更小的 $g$ 就重新入队,因此对“只可采纳”的启发式也能给出最优代价(代价是可能多次展开同一状态)。
1 | def astar(start, is_end, successors, h): |
▸一致推出可采纳
取从 $s$ 出发的一条最优剩余路径 $s=s_0\rightarrow\cdots\rightarrow s_T$($s_T$ 为终点,第 $t$ 条边代价为 $c_t$),对每条边写一致性:
$$ h(s_t)\le c_t+h(s_{t+1}). $$
从 $t=0$ 依次代入到 $t=T-1$(每次把右边的 $h(s_{t+1})$ 用下一个不等式放大):
$$ h(s)\le c_0+c_1+\cdots+c_{T-1}+h(s_T). $$
由 $h(s_T)=0$,右边正好是 $\mathrm{FutureCost}(s)$。故一致 $\Rightarrow$ 可采纳。逆命题不成立:可采纳只约束每个点与终点之间的关系,中间可以起伏。
感性理解:一致性说启发式沿真实边下降得不会比真实代价快,不会“跳过”真实代价。可采纳只比较当前点与终点,中间可以先低估再高估。UCS 式的“弹出即最优”不变量需要更强的一致性:否则队列键 $g+h$ 的次序不再与真实 $g$ 的次序同向。
▸只可采纳时,“弹出即关闭”的图 A* 可能出错
先看一个可采纳但不一致、却碰巧不出错的例子。边 $S\xrightarrow{1}A\xrightarrow{1}G$,$S\xrightarrow{3}G$。取 $h(S)=2$,$h(A)=0$,$h(G)=0$。
- 可采纳:$\mathrm{FutureCost}(S)=2$,$h(S)=2$;$\mathrm{FutureCost}(A)=1$,$h(A)=0$。
- 不一致:$h(S)=2\not\le 1+h(A)=1$。
键:$S$ 的 $f=0+2=2$。展开后 $A$ 的 $f=1+0=1$,直达的 $G$ 的 $f=3+0=3$。接着弹出 $A$,再弹出经 $A$ 到达的 $G$($f=2$),代价 $2$,结果仍碰巧正确。
真正出错需要“一条先到达但 $g$ 较大的路径把中间点关掉”。原笔记对此只给了含糊的描述(且其中“把 $h(A)$ 改成 $0$”与上例中已有的 $h(A)=0$ 相同,不构成新例子),这里补一个具体反例。边:$S\xrightarrow{1}A$,$A\xrightarrow{1}C$,$S\xrightarrow{1}B$,$B\xrightarrow{2}C$,$C\xrightarrow{3}G$。真实剩余代价 $\mathrm{FutureCost}(C)=3$,$\mathrm{FutureCost}(A)=4$,$\mathrm{FutureCost}(B)=5$,$\mathrm{FutureCost}(S)=5$(经 $A$)。取 $h(A)=4$,其余 $h=0$:处处可采纳,但 $h(A)=4\not\le 1+h(C)=1$,不一致。
- 弹出 $S$($f=0$),入队 $A$($g=1,f=5$)、$B$($g=1,f=1$)。
- 弹出 $B$($f=1$),入队 $C$($g=3,f=3$)。
- 弹出 $C$($f=3$,$g=3$)。若实现“弹出即关闭、禁止再打开”,$C$ 就此关闭。入队 $G$($g=6,f=6$)。
- 弹出 $A$($f=5$),发现 $C$ 的更短 $g=2$,但 $C$ 已关闭,被忽略。
- 弹出 $G$,返回代价 $6$,而最优是 $5$。
上面的 astar 代码在第 4 步会因 $2\lt\mathrm{best}[C]=3$ 把 $C$ 重新入队,随后以 $g=2$ 再展开 $C$,最终返回正确的代价 $5$(展开数多 $1$)。因此:作业若只保证可采纳,必须允许 decrease-key 或惰性再入队;若要求“弹出即最优、不再打开”,必须证明 $h$ 一致。
感性理解:不一致的 $h$ 会让算法以为某条已经很贵的前缀“看起来更有希望”(这里的 $B$),先把它下面的点($C$)关掉。可采纳保证第一次弹出终点时,不存在更短的完整路径(树搜索);图搜索多了“关掉中间点”这一步,需要一致性来撑住。
▸A* 小结
A* 在 UCS 的键上加剩余估计 $h$,少展开明显偏离终点的点。来源是 Hart、Nilsson 与 Raphael 的启发式搜索。相对 UCS:$h=0$ 退回 UCS;$h$ 越紧(越大但仍可采纳),展开越少。相对只看 $h$ 的贪心最佳优先搜索(greedy best-first search):贪心不把已付代价算进去,即使在单位代价图上也可能不最优。$h$ 不可采纳时,第一次弹出的终点不一定最优。加权 A*(weighted A*)用 $g+\lambda h$($\lambda\gt1$)进一步减少展开,但放弃最优性,课程作业一般不准用。
用松弛构造启发式
好的启发式从哪里来?标准方法是松弛(relaxation):丢掉原问题的一部分约束,得到一个更容易精确求解的问题,用它的最优代价作为原问题的 $h$。
为什么松弛得到的代价自动可采纳:原问题的每条合法路径在松弛问题里仍然合法(约束只少不多),所以松弛问题的最优代价 $\le$ 原问题最优代价。进一步,若松弛问题与原问题共享状态,且松弛后的边代价满足 $\mathrm{Cost}_{\mathrm{rel}}(s,a)\le\mathrm{Cost}(s,a)$,则 $h=\mathrm{FutureCost}_{\mathrm{rel}}$ 自动一致:它在松弛问题里满足 $h(s)\le\mathrm{Cost}_{\mathrm{rel}}(s,a)+h(\mathrm{Succ}(s,a))$,再用 $\mathrm{Cost}_{\mathrm{rel}}\le\mathrm{Cost}$ 放大即可。原笔记的说法“若松弛后的代价仍满足三角不等式,则一致”可以理解为这一条件的简写。
常见构造:
- 路径规划:丢掉障碍或单行限制,启发式变成直线距离,或无视拥堵的最短路。欧氏距离在四向网格上可采纳(真实走的是折线,不会比直线短),但在只准四向移动时不是最紧的。曼哈顿距离 $|x_G-x|+|y_G-y|$ 在四向、单位代价、无障碍时恰好等于 $\mathrm{FutureCost}$;有障碍时仍可采纳且一致。
- 8 数码:允许棋子重叠或瞬移。允许重叠、每块独立移动时,得到曼哈顿距离启发式(各瓦片到目标位置的曼哈顿距离之和),可采纳。线性冲突(linear conflict)在曼哈顿距离上修正:同一行(或列)里两块瓦片都已在目标行(列)但相对顺序颠倒时,其中一块必须先让开再回来,每对这样的冲突额外加 $2$。它仍可采纳,且支配曼哈顿距离。(原笔记写作“在曼哈顿上再加 $2$”,准确说法是每对冲突加 $2$。)
- 送货问题:丢掉“必须送完再回去”中的一部分约束,例如只考虑剩余包裹。可用的下界包括剩余包裹(连同当前位置与仓库)的最小生成树(MST)总长,或到最近未送包裹的距离。需要注意:贪心最近邻巡回路线的长度是上界而不是下界,不能直接当启发式。
两个可采纳启发式 $h_1,h_2$ 取点态最大 $h(s)=\max(h_1(s),h_2(s))$ 仍可采纳;二者都一致,则最大也一致。若对所有 $s$ 有 $h_1(s)\ge h_2(s)$(且都可采纳),称 $h_1$ 支配(dominate)$h_2$:$h$ 越大(仍可采纳),展开越少。
▸例4 网格与非单位边权
$3\times 3$ 网格,四向移动代价 $1$,从 $(0,0)$ 到 $(2,2)$。UCS 与 BFS 都给出代价 $4$。曼哈顿 $h=|2-x|+|2-y|$ 一致:向目标走一步,$h$ 减 $1$、代价加 $1$;走回头,$h$ 加 $1$、代价仍加 $1$。A* 不展开明显远离终点的格子,展开数小于 UCS。
补充一个细节:在这个无障碍网格里,从角到对角,每个格子都在某条最短路上,所有格子的 $f=g+h$ 都等于 $4$,所以 A* 少展开多少完全取决于平局打破规则。若键相等时优先弹出 $g$ 较大的结点,A* 沿一条最短路只展开 $5$ 个格子;若键相等时按入队先后(FIFO)弹出,A* 同样要展开全部 $9$ 个格子,与 UCS 无异。UCS 要先展开所有 $g\lt4$ 的格子($1+2+3+2=8$ 个)再弹出终点,共 $9$ 个。地图更大或有障碍时,A* 的优势会真正来自“远离终点的格子 $f$ 更大”。
边权不等时必须离开“步数”直觉。另作一张小图:边 $S\xrightarrow{10}G$,$S\xrightarrow{1}A\xrightarrow{1}B\xrightarrow{1}G$。BFS(按步)先走到直达的 $G$,代价 $10$。UCS 走 $S\rightarrow A\rightarrow B\rightarrow G$,代价 $3$。
此时把“剩余步数”(单位曼哈顿)直接当启发式要小心。它假设每步代价恰为 $1$:本图各边代价都 $\ge 1$,它只会低估(例如把直达边的代价 $10$ 估成 $1$),仍可采纳,只是不够紧;真正的风险在于某些边代价小于 $1$ 时,它会高估而失去可采纳性。(原笔记写“会把直达边估得过低”所以不能用,低估本身并不破坏可采纳性,这里改正。)可采纳的改法是用最小边权缩放($h=\varepsilon_{\min}\cdot$ 剩余步数),或在丢掉部分边之后再求松弛最短路。
动态规划求 FutureCost
UCS 和 A* 从起点往前推;另一种思路是直接求解开头写下的 FutureCost 方程,从终点往回算。这就是动态规划(dynamic programming, DP):把每个状态的最优剩余代价记下来,同一状态只算一次。
FutureCost 递推本身就是动态规划。状态图无环(或已按拓扑逆序排列)时,按该顺序填表,每个状态算一次,时间与边数成正比。有环且边权非负时,递推的不动点仍存在,但简单的记忆化递归会在环上循环(求 $s$ 需要先求 $s$ 自己);此时应改用 UCS / A*,或先检测环。
1 | def future_cost(s, is_end, successors, memo=None): |
该写法默认后继图无环。以送货状态 $(p,S)$ 为例:若规定取(送)包裹后 $|S|$ 严格下降、且在 $|S|$ 不变时不允许回到同一状态(例如把移动折叠成“直接去下一个包裹点”),则图自然无环。表的大小为 $|\mathrm{Loc}|\cdot 2^{|P|}$($|\mathrm{Loc}|$ 为位置数,$|P|$ 为包裹数),指数在包裹个数上,而不在地图边数上。这是作业里“状态设计决定复杂度”的典型来源。
从起点做 UCS 与从终点填 FutureCost,在确定性、非负、无折扣时给出同一最优值。差别在于:UCS 不必列举全部状态,碰到终点就停;DP 一次给出所有状态的剩余代价,适合之后还要从许多不同起点查询的场景。
▸动态规划与搜索的关系
同一套 FutureCost 方程,可以用记忆化递归、拓扑序填表或 UCS 求解。来源是最短路的最优子结构,不是新的启发式。相对树回溯:同一状态只算一次。相对下一章的值迭代:这里没有随机后继,也没有折扣,方程里是“$\min$ 加代价”,而不是“$\max$ 加期望回报”。
结构化感知机:从数据中学习代价
到这里为止,搜索都假定 $\mathrm{Cost}$ 是已知的。实际中边代价往往需要调:导航中“走高速”与“走小路”各该算多少代价?结构化感知机(structured perceptron, Collins 2002)给出一个学习方法:给定一些正确的示范路径,调整代价,使得在当前代价下搜索出的最优路径与示范一致。
推断即一次搜索
设输入为 $x$,真实路径(或更一般的标签结构)为 $y$,特征为 $\phi(x,y)$(例如路径中各类动作出现的次数)。代价写成特征的线性函数 $w\cdot\phi(x,y')$,当前权重 $w$ 下的推断为
$$ \hat y=\arg\min_{y'}w\cdot\phi(x,y'), $$
这等价于把每条边的代价写成该边特征的线性函数,再跑 UCS / A*。
更新规则
更新与反射模型中的感知机同形:比较“真实结构”与“预测结构”的特征差。在“代价越小越好”($\arg\min$)的约定下,应当降低真实路径的代价、抬高错误预测路径的代价,因此更新为
$$ w\leftarrow w-\phi(x,y)+\phi(x,\hat y). $$
$\hat y=y$ 时两项抵消,不更新。原笔记写作 $w\leftarrow w+\phi(x,y)-\phi(x,\hat y)$,那是“得分越大越好”($\hat y=\arg\max_{y'}w\cdot\phi(x,y')$)约定下的形式;与上面的 $\arg\min$ 推断搭配时符号相反,这里已改正。两种写法只差把 $w$ 换成 $-w$,本质相同。CS221 讲义里的表述正是:真实路径上每个动作的代价减 $1$,预测路径上每个动作的代价加 $1$。
需要注意:推断必须(近似)最优。否则 $\hat y$ 不是当前 $w$ 下的最小代价结构,更新方向可能指向更差的 $w$。这部分在模块讲义中标为可选,考试可能只问更新式以及“推断是一次搜索”这一点。
▸结构化感知机小结
结构化感知机把感知机从单个标签推广到整条结构(路径或赋值)。来源是结构化预测,不是另写一套搜索。相对反射模型的感知机:推断不再是看点乘的符号,而是一次最小代价推断。相对独立地手工调每条边的代价:它用正确路径与错误路径的特征差来修改 $w$。相对下一章 MDP:这里没有随机后继,损失仍是感知机式的“对或错”,不是期望回报。
实现注意事项
下面是作业中最常见的错误来源:
- 状态表示错误比算法写错更常见。 漏掉必须记住的信息,等于定义了另一张图,UCS 会在那张错图上给出“正确”的最短路。
- 结算时机。
visited/best在 UCS 与一致 A* 里按“弹出时结算”处理。DFS 若在入栈时标记 visited,会剪掉合法但后到的更优路径。 - 状态必须可哈希。 用可变
list当状态会无法作为字典键;若为此临时转换或用对象身份比较,同一格子会被当成不同对象,展开数爆炸。应使用tuple、frozenset等不可变类型。 - 后继函数必须返回新对象,不能原地修改当前状态再入队,否则队列中的多个条目会指向同一个被改坏的对象。
- 平局打破。 作业按展开结点数计分时,优先队列键相等必须有确定的打破规则(再比较状态元组、再比较动作名),并与参考实现一致。
- 启发式不可采纳时,自动评分会在最优代价上直接失败,而不只是展开数差一点。
▸负代价与零代价环
UCS / A* 的弹出不变量依赖边权 $\ge 0$。出现负边时应改用其他算法,或先证明图无负环再把问题改写成 DP。全零代价环不会让 $g$ 下降,但会让不记 best 的树搜索无限走下去;图搜索用 best 挡住即可。
▸对照:树搜索、UCS 与 A*
树回溯保证看见所有路径,代价是指数级依赖深度。UCS 在非负边上按 $g$ 结算,第一次弹出终点即最优。A* 用一致的 $h$ 少展开结点,不改变最优性。三者求的都是一条确定路径的最小代价。只看 $h$ 的贪心与只看步数的 BFS,优化的是另一个目标。
▸小结与衔接
- 搜索问题由 $\mathrm{Start}$、$\mathrm{Actions}$、$\mathrm{Succ}$、$\mathrm{Cost}$、$\mathrm{IsEnd}$ 定义,状态要包含未来决策所需的全部信息。
- 最优路径代价满足 FutureCost 递推;DP、UCS、A* 是求它的不同方式。
- UCS 要求边权非负;A* 要求 $h$ 一致才能“弹出即最优”,只可采纳时需允许重新打开。
- 启发式可由松弛得到,松弛最优代价自动可采纳(通常也一致)。
- 结构化感知机把“推断 = 搜索”用于学习边代价。
搜索假定 $\mathrm{Succ}(s,a)$ 唯一。如果下一步动作按概率落到若干后继之一,最小代价路径就不再是合适的决策对象:随机性把智能体打到预定路径之外以后,预先写死的动作序列就没有定义了。必须改成对每个状态规定动作,并对期望回报取最优。这就是下一章的策略与贝尔曼方程。
马尔可夫决策过程(Markov Decision Process, MDP)
搜索假定世界是确定的:在路口左转,就一定到左边那条街。现实中动作的后果常常带有随机性:机器人在冰面上想往前走,可能打滑到侧面;玩骰子游戏时,选择“继续掷”之后会落到哪里由骰子决定。这时“规划一条最优路径”这件事本身就失去了意义——一旦被随机性推离预定路线,后面写好的动作就不知道该怎么用了。
MDP 的核心想法一句话:不再求一条路径,而是求一张策略表 $\pi$,告诉智能体在每个状态该做什么,使期望的折扣累计回报最大。本章先给出 MDP 的定义和贝尔曼方程,再讲已知模型时的三种求解方法(策略评估、值迭代、策略迭代),最后讲模型未知、只能从经验轨迹中学习的情形(TD 学习、Q 学习、SARSA)。算法的输入是模型 $(T,R)$ 或交互得到的轨迹,输出是值函数 $V$、动作值 $Q$ 或策略 $\pi$。
和相邻主题的关系:上一章(搜索)后继确定;本章后继随机,路径换成策略;下一章(博弈)的随机性来自对手而不是自然。
MDP 的定义
五元组
马尔可夫决策过程(Markov decision process, MDP)是五元组 $(S,A,T,R,\gamma)$:
- $S$:状态集;$A$:动作集,或每个状态一份可用动作集 $A(s)$。
- $T(s,a,s')=\Pr(s_{t+1}=s'\mid s_t=s,a_t=a)$:转移概率(transition probability),即在 $s$ 做 $a$ 之后落到 $s'$ 的分布;对每个 $(s,a)$,$\sum_{s'}T(s,a,s')=1$。
- $R(s,a,s')$:该步获得的回报(reward),有的讲义写成只依赖 $(s,a)$ 的 $R(s,a)$。
- $\gamma$:折扣因子(discount)。本笔记取 $\gamma\in[0,1)$。CS221 讲义允许 $0\le\gamma\le 1$,但 $\gamma=1$ 只在几乎必然会进入终点(被吸收)时才有有限的值。
马尔可夫性:下一状态的分布只依赖当前的 $(s,a)$,不依赖更早的历史。和搜索中一样,这要求状态设计得足够完整。
策略(policy)$\pi$ 对每个状态给出一个动作(确定性策略 $\pi(s)$),或一个动作分布 $\pi(a\mid s)$(随机策略)。搜索是 MDP 的一个特例:$T$ 退化为确定后继,回报取代价的相反数,并且只关心从起点出发的一条路径。
▸MDP 在课程中的位置
MDP 在随机后继下最大化期望回报,来源是最优控制与 Bellman 的动态规划,课程把它写成搜索的概率版本。相对搜索:必须对每个状态规定动作,因为随机性可能把智能体打到路径之外。相对监督学习:没有现成的“正确动作” $a^{\ast}$ 作为标签,数据分布还依赖正在执行的 $\pi$。相对 CS229 中同一套方程:CS221 的记号从搜索的 $\mathrm{Cost}$ 切换到回报 $R$,值是“越大越好”,不要把 UCS 的最小代价直接抄进贝尔曼方程。
策略值与动作值
从 $s_0=s$ 出发、一直按 $\pi$ 行动,折扣累计回报的期望称为策略值(value):
$$ V_{\pi}(s)=\mathbb{E}_{\pi}\Big[\sum_{t=0}^{\infty}\gamma^{t}R(s_t,a_t,s_{t+1})\ \Big|\ s_0=s\Big]. $$
折扣 $\gamma^{t}$ 有两个作用:一是表达“近处的回报比远处的更重要”,二是在无限长过程中保证求和有限(回报有界时,总和不超过 $R_{\max}/(1-\gamma)$)。
动作值(Q 值)$Q_{\pi}(s,a)$ 指先强制在 $s$ 走动作 $a$,此后再按 $\pi$ 行动的期望回报。最优值定义为所有策略中最好的那个:$V_{\mathrm{opt}}(s)=\max_{\pi}V_{\pi}(s)$,$Q_{\mathrm{opt}}(s,a)=\max_{\pi}Q_{\pi}(s,a)$。确定性最优策略可以直接从 $Q_{\mathrm{opt}}$ 读出:
$$ \pi_{\mathrm{opt}}(s)=\arg\max_a Q_{\mathrm{opt}}(s,a). $$
有限 MDP、$\gamma\lt1$ 时最优值唯一,但达到它的最优策略可以不止一个(例如两个动作的 $Q_{\mathrm{opt}}$ 相等时)。
贝尔曼方程
把回报拆成“这一步”与“下一步以后”,用全期望定律对第一步的动作与后继取平均:
$$ V_{\pi}(s)=\sum_{a}\pi(a\mid s)\sum_{s'}T(s,a,s')\big[R(s,a,s')+\gamma V_{\pi}(s')\big]. $$
这是贝尔曼期望方程(Bellman expectation equation)。最优值满足贝尔曼最优方程(Bellman optimality equation):
$$ V_{\mathrm{opt}}(s)=\max_{a}\sum_{s'}T(s,a,s')\big[R(s,a,s')+\gamma V_{\mathrm{opt}}(s')\big]. $$
$Q$ 的对应形式更常用于学习,因为取最大的动作就在表里,不需要模型:
$$ Q_{\mathrm{opt}}(s,a)=\sum_{s'}T(s,a,s')\Big[R(s,a,s')+\gamma\max_{a'}Q_{\mathrm{opt}}(s',a')\Big], $$
且 $V_{\mathrm{opt}}(s)=\max_a Q_{\mathrm{opt}}(s,a)$。在终点(或吸收且 $R=0$ 的状态)上 $V=0$、$Q=0$。
▸贝尔曼方程的来源与压缩性
令 $G_t=\sum_{k=0}^{\infty}\gamma^{k}R_{t+k}$ 为从时刻 $t$ 起的折扣回报。把求和的第一项单独拿出来,剩下的恰好是 $\gamma$ 乘以从 $t+1$ 起的回报:$G_t=R_t+\gamma G_{t+1}$。两边在 $s_t=s$、按 $\pi$ 行动的条件下取期望,并利用马尔可夫性($\mathbb{E}[G_{t+1}\mid s_{t+1}]=V_{\pi}(s_{t+1})$):
$$ V_{\pi}(s)=\mathbb{E}[R_t+\gamma V_{\pi}(s_{t+1})\mid s_t=s]. $$
把期望按 $a\sim\pi(\cdot\mid s)$、$s'\sim T(s,a,\cdot)$ 展开,就得到期望方程。最优方程把对 $\pi$ 的平均换成 $\max_a$:在 $s$ 选使右端最大的动作,此后假设继续最优地走。这不是新的假设,而是最优子结构。
$\gamma\lt1$ 时,贝尔曼最优算子 $(T_{\mathrm{opt}}V)(s)=\max_a\sum_{s'}T[R+\gamma V(s')]$ 是压缩映射(contraction):
$$ \|T_{\mathrm{opt}}V-T_{\mathrm{opt}}W\|_{\infty}\le\gamma\|V-W\|_{\infty}. $$
证明分两步。记 $Q^{V}(s,a)=\sum_{s'}T(s,a,s')[R+\gamma V(s')]$。第一步,两个函数的最大值之差不超过它们差的最大绝对值:$\lvert\max_a Q^{V}(s,a)-\max_a Q^{W}(s,a)\rvert\le\max_a\lvert Q^{V}-Q^{W}\rvert$。第二步,回报项相消,$\lvert Q^{V}(s,a)-Q^{W}(s,a)\rvert=\lvert\sum_{s'}T\cdot\gamma(V-W)\rvert\le\gamma\|V-W\|_{\infty}$,因为 $T(s,a,\cdot)$ 是概率分布,加权平均不超过最大值。由 Banach 不动点定理,存在唯一的不动点 $V_{\mathrm{opt}}$,且值迭代从任意初值出发都收敛到它。
感性理解:今天的值 $=$ 这一步的期望回报 $+$ $\gamma$ 乘“明天再按同一规则走下去的值”。$\gamma$ 接近 $1$ 时远期回报几乎同等计入,压缩变弱,迭代更慢。没有折扣且可以无限游荡时,值可以是 $+\infty$ 或 $-\infty$。
与搜索的对应
确定后继、$\gamma=1$、回报取代价相反数时,贝尔曼最优方程退化为 FutureCost 递推($\max$ 变成 $\min$,回报变成负代价),于是
$$ V_{\mathrm{opt}}(s)=-\mathrm{FutureCost}(s). $$
符号相反是因为搜索最小化代价,MDP 最大化回报。有随机性时,预先写死的路径在偏离后没有定义,必须改存一张策略表。
策略评估(Policy Evaluation)
在求最优策略之前,先解决一个更简单的问题:给定一个固定的策略 $\pi$,它到底有多好?这一步叫策略评估,它是策略迭代的内层步骤,也是判断一个手写策略是否合理的工具。
固定 $\pi$ 之后,贝尔曼期望方程中没有 $\max$,是关于 $|S|$ 个未知数 $V_{\pi}(s)$ 的线性方程组。定义策略下的转移矩阵与期望回报向量:
$$ P_{\pi}(s,s')=\sum_a\pi(a\mid s)T(s,a,s'),\qquad r_{\pi}(s)=\sum_a\pi(a\mid s)\sum_{s'}T(s,a,s')R(s,a,s'), $$
其中 $P_{\pi}$ 是 $|S|\times|S|$ 的行随机矩阵,$r_{\pi}$ 是长度 $|S|$ 的向量。则方程组写成矩阵形式并可直接求解:
$$ V_{\pi}=r_{\pi}+\gamma P_{\pi}V_{\pi}, \qquad V_{\pi}=(I-\gamma P_{\pi})^{-1}r_{\pi}. $$
$\gamma\lt1$ 时 $I-\gamma P_{\pi}$ 可逆($\gamma P_{\pi}$ 的谱半径不超过 $\gamma\lt1$)。直接求逆的代价约为 $O(|S|^{3})$,状态多时改用迭代:反复执行 $V\leftarrow r_{\pi}+\gamma P_{\pi}V$,直到 $\max_s\lvert\Delta V(s)\rvert$ 小于容差。由于这个更新同样是 $\gamma$-压缩,迭代必然收敛。
1 | def policy_evaluation(states, pi, trans, gamma=0.95, tol=1e-8): |
值迭代与策略迭代
策略评估只能评价给定策略。要找最优策略,有两条路:值迭代直接迭代贝尔曼最优方程,把 $V$ 逼近 $V_{\mathrm{opt}}$ 后再读出策略;策略迭代则在“评估当前策略”和“根据评估结果贪心改进”之间交替。两者都要求已知 $T$ 和 $R$。
值迭代
值迭代(value iteration)从任意 $V_0$(常用全 $0$)出发,反复套用最优算子:
$$ V_{k+1}(s)=\max_{a}\sum_{s'}T(s,a,s')\big[R(s,a,s')+\gamma V_k(s')\big]. $$
由压缩性,$\|V_{k+1}-V_{\mathrm{opt}}\|_{\infty}\le\gamma\|V_k-V_{\mathrm{opt}}\|_{\infty}$,所以误差按 $\gamma^{k}$ 几何收缩。$V_k$ 也可以理解为“只剩 $k$ 步可走时的最优值”。收敛后对 $Q$ 取贪心即得策略 $\pi$。同步更新用一张旧表算出整张新表;原地更新(算出一个就立刻覆盖)也能收敛,而且常常更快,但作业对拍数值时以同步为准。
1 | def q_value(s, a, V, trans, gamma): |
策略迭代
策略迭代(policy iteration)任取初始策略 $\pi_0$,然后交替执行两步:
- 策略评估:求出当前策略的 $V_{\pi}$(解线性方程组或内层迭代)。
- 贪心改进:$\pi'(s)=\arg\max_a Q_{V}(s,a)$,其中 $Q_{V}$ 用刚求得的 $V_{\pi}$ 计算。
有限 MDP 上策略是单调改进的(点态 $V_{\pi'}\ge V_{\pi}$,这是策略改进定理的结论);而确定性策略只有有限个,策略不变时即满足最优方程,因此有限步就能到达最优。与值迭代相比,策略迭代单轮更贵(要解线性方程组或做内层迭代),但轮数通常少得多。截断策略迭代(modified / truncated policy iteration)每轮只做少数几次评估 sweep,介于二者之间:只做 $1$ 次 sweep 时就接近值迭代。
▸值迭代小结
值迭代就是反复套贝尔曼最优算子,来源是动态规划,不是梯度下降。相对 UCS:它更新的是整张状态表,而不是沿一条路径展开。相对 Q 学习:必须知道 $T$ 与 $R$。$\gamma$ 接近 $1$ 时压缩变慢,需要更多轮。相对策略迭代:值迭代不显式存 $\pi$,每轮都对所有动作计算期望。
▸例5 单状态两动作的 MDP
状态 $s$ 与吸收终点 $t$,$V(t)=0$,$\gamma=0.9$。
- 动作 $A$:以概率 $1$ 到 $t$,回报 $2$,故 $Q(s,A)=2$。
- 动作 $B$:以概率 $0.5$ 留在 $s$、回报 $0$;以概率 $0.5$ 到 $t$、回报 $4$。
按定义对两个后继加权:
$$ Q(s,B)=0.5\big(0+\gamma V(s)\big)+0.5\cdot 4=2+0.45\,V(s). $$
最优方程是 $V=\max(2,\ 2+0.45V)$。若取右边,$0.55V=2$,$V=40/11\approx 3.636$,且大于 $2$,自洽,故最优动作是 $B$(取左边得 $V=2$,但此时 $2+0.45\cdot 2=2.9\gt2$,与“左边是最大值”矛盾)。直觉上,$B$ 失败了还能重来,相当于多次以 $0.5$ 的概率抽取 $4$ 的回报,只是每等一步要打 $0.9$ 折。
值迭代从 $V_0=0$ 开始:$V_1=\max(2,2)$,即 $V_1=2$;$V_2=\max(2,\,2+0.45\cdot 2)$,即 $V_2=2.9$;$V_3=2+0.45\cdot 2.9$,即 $V_3=3.305$。每一轮与极限的差乘以 $0.45$,逐步靠近 $40/11$。(这里的收缩因子 $0.45=0.5\gamma$ 小于 $\gamma$,因为只有一半概率留在 $s$;一般结论 $\le\gamma$ 依然成立。)
常见错误:把 $B$ 的期望写成“先加 $4$ 再乘 $0.5$”,却漏掉留在 $s$ 的那条分支,会得到错误的 $Q$。
无模型学习:TD 学习、Q 学习与 SARSA
前面的方法都假设 $T$ 和 $R$ 已知,可以直接对后继求期望。现实中往往不知道模型:机器人不知道冰面上打滑的概率,游戏玩家不知道规则的全部细节。这时只能通过与环境交互得到轨迹 $(s,a,r,s')$,用样本代替期望。这就是强化学习(reinforcement learning)的设定。核心想法:贝尔曼方程右侧的期望无法计算,就用一次真实转移的样本去估计它,然后让当前估计朝这个样本目标挪一小步。
蒙特卡洛与 TD(0)
蒙特卡洛(Monte Carlo)方法等整条回合结束,用实际得到的折扣回报作为 $V(s)$ 的目标。它是无偏的,但方差大(整条回合的随机性都累积在一个目标里),而且必须等回合结束。
TD(0)(temporal difference,时序差分)不等回合结束,而是用自举(bootstrapping)目标 $r+\gamma V(s')$——用当前对下一状态的估计代替真实的未来回报:
$$ V(s)\leftarrow V(s)+\alpha\big(r+\gamma V(s')-V(s)\big). $$
其中 $\alpha\in(0,1]$ 为步长,括号里的量称为 TD 误差。TD 的偏差来自当前 $V$ 不准确;但它的方差更小,也不需要完整回合。表格型 TD 在每个状态被充分访问(visitation 充分)且 $\alpha$ 满足 Robbins-Monro 条件时收敛到 $V_{\pi}$。在函数逼近(function approximation)下 TD 可能发散,课程作业使用表格表示。
Q 学习
TD 学的是 $V_{\pi}$,但没有模型时,从 $V$ 读出策略仍需要 $T$。Q 学习直接估计 $Q_{\mathrm{opt}}$,于是策略可以直接从表中读出:
$$ Q(s,a)\leftarrow(1-\alpha)Q(s,a)+\alpha\Big(r+\gamma\max_{a'}Q(s',a')\Big). $$
即旧估计与新样本目标的加权平均。若 $s'$ 是终点,目标中没有后续项,只取 $r$。
Q 学习是离策略(off-policy)的:产生数据的行为策略(behavior policy)可以随意探索,而更新目标中的 $\max_{a'}$ 始终对应最优贝尔曼方程,学到的是最优策略的值。SARSA 把 $\max_{a'}$ 换成回合中实际采取的下一个动作 $a'$(名字来自所用的五元组 $s,a,r,s',a'$),估计的是行为策略自身的 $Q_{\pi}$,属于在策略(on-policy)方法。
探索
只做贪心会出问题:如果从来不尝试某个动作,就永远不知道它好不好。常用两种探索方法:
- $\varepsilon$-贪心($\varepsilon$-greedy):以概率 $\varepsilon$ 均匀随机选动作,以 $1-\varepsilon$ 取 $\arg\max Q$。
- 乐观初值:对尚未访问的 $(s,a)$ 赋一个偏大的初始 $Q$,促使智能体去尝试。
$\varepsilon=0$ 且初值不乐观时,从未试过的动作永远看不到正确的目标。Mountain Car 作业中,若 $\varepsilon=0$,小车到不了坡顶,更新永远看不到正回报。
1 | def epsilon_greedy(Q, s, actions, eps, rng): |
▸Q 学习是一种随机近似
贝尔曼最优方程中对 $s'$ 的期望被一次样本 $(s,a,r,s')$ 代替,再向该样本目标走一步长 $\alpha$。这正是随机近似(stochastic approximation)的形式。若步长 $\alpha_t$ 满足 $\sum_t\alpha_t=\infty$(总步长足够大,能走到任何地方)、$\sum_t\alpha_t^{2}\lt\infty$(噪声的累积影响有限),且每个 $(s,a)$ 被访问无穷多次,则表格 Q 学习几乎必然收敛到 $Q_{\mathrm{opt}}$。函数逼近下这一保证一般不再成立。
SARSA 的目标是 $r+\gamma Q(s',a')$,$a'$ 来自行为策略,收敛到 $Q_{\pi}$ 而不是 $Q_{\mathrm{opt}}$。若 $\varepsilon$ 缓慢降到 $0$,二者的极限可以重合。作业若按最优回报评分,用 Q 学习;若按“实际执行的 $\varepsilon$-贪心策略”的回报评分,二者给出的数字不同。
感性理解:每一次转移都是对“若此处之后最优地走,下一步值该是多少”的一次带噪测量。$\max_{a'}$ 用的是当前估计,早期很不准,所以必须持续探索,否则从未走过的动作会永远停在初值。
▸例6 火山格子(volcano crossing)
火山格子是 CS221 讲义中的经典网格世界,具体参数各版本不同,这里取如下设定:到达风景点终点得回报 $+20$,掉进火山得 $-50$,每走一步 $-0.1$,$\gamma=0.99$;每个动作以 $0.8$ 的概率按意图移动、各以 $0.1$ 的概率滑向两侧。
值迭代给出的策略箭头会绕开火山:贴着悬崖的近路在滑行下期望为负(一次滑倒的 $-50$ 抵消了省下的几步)。把滑行概率改成 $0$,最优策略就会贴着悬崖走近路。这说明:模型一旦错了,求解的最优方程就是另一盘棋,得到的“最优策略”在真实环境中可能很糟。
在同一格子上跑 Q 学习:$\varepsilon=0$ 时,智能体若一开始不敢靠近风景点,就永远学不到 $+20$。$\alpha$ 过大则 $Q$ 振荡,$+20$ 与 $-50$ 交替污染邻近格子的估计。对拍时应先在 $T$ 已知的情况下跑值迭代,把得到的 $Q$ 表当参考答案。
实现注意事项
- 转移概率归一。 对每个 $(s,a)$,转移概率必须加总为 $1$。漏掉滑行分支等于改写了物理规律。
- 回报的位置。 $R$ 写在 $(s,a,s')$ 上还是写在进入终点时,必须与作业约定一致,否则 $V$ 会差一个常数或差一步。
- 折扣与环。 $\gamma=1$ 且存在可以无限循环的环时,值迭代可能发散。先确认终点必然被吸收,或改用 $\gamma\lt1$。
- 哈希。 表格 $Q$ 的键是
(s, a),状态不可哈希则整张表无法使用。 - 探索衰减。 先用较大的 $\varepsilon$,再逐渐减小。用训练回报来挑选 $\varepsilon$ 会对该随机种子过拟合。
▸对照:搜索与 MDP
搜索要一条路径;MDP 要一张策略表。确定后继且无折扣时,值迭代算出的 $V$ 与 $\mathrm{FutureCost}$ 只差一个符号。有随机性时,“预先写死的路径”在偏离后没有定义。UCS 的“弹出即最优”不变量在期望代价上不成立:必须对整张状态表 $S$ 迭代,或对样本做随机近似。
▸小结与衔接
- MDP 由 $(S,A,T,R,\gamma)$ 定义,解是策略而不是路径。
- $V_{\pi}$ 满足线性的贝尔曼期望方程,可直接求解或迭代;$V_{\mathrm{opt}}$ 满足带 $\max$ 的贝尔曼最优方程。
- $\gamma\lt1$ 时最优算子是压缩映射,值迭代按 $\gamma^{k}$ 收敛;策略迭代单调改进、有限步收敛。
- 模型未知时,TD 学 $V_{\pi}$,Q 学习(off-policy)学 $Q_{\mathrm{opt}}$,SARSA(on-policy)学 $Q_{\pi}$;都需要足够的探索。
MDP 的随机性来自自然,因此对后继取期望。下一章的随机性来自对手:除非对手策略已知,否则不能再对对手的动作取期望,而要考虑最坏情形,改用 $\min$。算法也从值迭代变成 minimax / expectimax。
博弈:对抗搜索与 minimax(Games)
前面的搜索与 MDP 都只有一个决策者:搜索问题里每个动作的后继是确定的,MDP 里后继虽然随机,但随机性来自一个“不怀好意也不怀善意”的自然,其分布已知,可以直接取期望。下棋、Pac-Man 躲幽灵这类问题多了一个新角色:对手。当前玩家走完一步之后,下一个状态由对手(或骰子)决定,而对手可能专门挑对当前玩家最坏的那一步。这时要问的不再是“单智能体的一条最优路径”,而是博弈值(game value)以及当前玩家此刻该走哪一步。
核心想法一句话:把博弈树上的节点按“谁在做选择”分类,max 节点取最大、min 节点取最小、chance 节点取期望,自底向上回传,就得到根节点的值与最优动作。本章依次讲 expectimax、minimax 与二者的混合 expectiminimax,再讲两种让它在大树上算得动的办法:α-β 剪枝(精确,少展开)与估值函数(截断深度,近似)。这些算法的输入是一棵博弈树,输出是根节点的值与当前应走的动作。
和前后章节的关系:上一章(MDP)只有一个决策者;本章引入 min 玩家或随机节点;下一章(因子图)则不再沿时间把状态一步步展开,而是把问题拆成一组变量。
博弈树的三类节点与状态
零和(zero-sum)回合制博弈的状态树分成三类节点:
- max 节点:轮到当前智能体(agent)走,它要让回报尽量大;
- min 节点:轮到对手走,在零和假设下对手的收益是当前玩家的负收益,所以对手要让回报尽量小;
- chance 节点:由骰子,或者一个已知的随机策略(例如随机乱走的幽灵)决定后继。
叶子(终局)给出回报(utility),约定从当前玩家视角越大越好。折扣 $\gamma$ 可有可无(大多数棋类终局才有回报,不需要折扣)。Pac-Man 作业通常按步数截断深度,在截断处的“叶子”上改用估值函数打分(见下文“估值函数”一节)。
状态仍然必须满足 Markov 性:后续的一切只能依赖当前状态。对 Pac-Man 而言,状态至少要包含 Pac-Man 与各幽灵的位置、剩余豆子、幽灵是否处于惊吓(scared)状态、轮到谁走。漏掉任何一个字段,等于换了一盘棋——例如漏了“轮到谁走”,同一个盘面在 max 层与 min 层的值完全不同,却被当成同一个状态。
▸博弈问题在框架中的位置
博弈问题要在对手或机会存在时计算根节点的值。思想来源是 von Neumann 的对抗搜索,CS221 把它写成“MDP 的对手版”。
- 相对 MDP:对手节点不能直接对后继取期望(除非对手策略已知),最坏情形要取 $\min$。
- 相对搜索:预先写死的一条路径会在对手偏离预期时崩溃;博弈的解是一个策略(对每种对手回应都给出应对),不是一条路径。
- 相对反射模型:不能看一眼棋盘就输出动作,而不展开对手的回应。
expectimax 与 minimax
expectimax:对手策略已知
如果对手(或自然)的策略 $\pi_{\mathrm{opp}}$ 是固定且已知的,例如 Pac-Man 作业里假设幽灵在合法动作中均匀随机选择,那么对手节点其实就是一个分布已知的随机节点,可以直接取期望。这就是 expectimax。记 $\mathrm{eval}(s)$ 为叶子的回报(或截断处的估值),$\mathrm{Succ}(s,a)$ 为在 $s$ 执行 $a$ 的后继,$T(s,s')$ 为 chance 节点 $s$ 转到 $s'$ 的概率(对对手节点就是 $\pi_{\mathrm{opp}}$ 选中对应动作的概率):
$$ V_{\mathrm{ex}}(s)= \begin{cases} \mathrm{eval}(s) & s\text{ 为叶}\\ \max_{a}V_{\mathrm{ex}}(\mathrm{Succ}(s,a)) & s\text{ 为 max}\\ \sum_{s'}T(s,s')V_{\mathrm{ex}}(s') & s\text{ 为 chance.} \end{cases} $$
这与 MDP 的值迭代(value iteration)同形:MDP 里 $Q(s,a)=\sum_{s'}T(s,a,s')[\ldots]$ 是“先选动作再由自然抽后继”,这里只是把动作层(max)与机会层(chance)拆成两层分开写。
minimax:对手最坏
如果对手不是随机的,而是会针对当前玩家挑最坏的一步,就把 chance 层换成 min 层。两人轮流走一步时,可以把两层合写成
$$ V_{\mathrm{mm}}(s)=\max_{a}\min_{a_{\mathrm{opp}}}V_{\mathrm{mm}}(\mathrm{Succ}(s,a,a_{\mathrm{opp}})). $$
这里 $\mathrm{Succ}(s,a,a_{\mathrm{opp}})$ 表示当前玩家走 $a$、对手接着走 $a_{\mathrm{opp}}$ 之后到达的状态。按节点逐层写,就是“max 节点取子节点最大、min 节点取子节点最小、叶子取回报”。
双方都最优时根值称为博弈值(game value)。对二人零和、有限、完美信息(perfect information)的博弈,这个值一定存在:一般博弈中需要允许随机化(混合)策略,但在没有同时行动的回合制博弈树上,纯策略已经足够(这就是 Zermelo / von Neumann 的经典结论,证明见下面的结构归纳)。
expectiminimax:三类节点混用
现实问题常常三类节点都有:例如部分幽灵按已知分布走(chance)、另一部分幽灵追着 Pac-Man 走(min),地图上还会随机刷出果实(chance)。这时用 expectiminimax:
$$ V(s)= \begin{cases} \mathrm{eval}(s) & s\text{ 为叶}\\ \max_{a}V(\mathrm{Succ}(s,a)) & s\text{ 为 max}\\ \min_{a}V(\mathrm{Succ}(s,a)) & s\text{ 为 min}\\ \sum_{s'}T(s,s')V(s') & s\text{ 为 chance.} \end{cases} $$
三类节点按各自的类型取值。选错节点类型等于解另一盘游戏:把应该是 min 的节点写成 chance,算出的是“对手随机”那盘游戏的最优策略。
递归实现
1 | def expectimax(state, depth, node, actions, succ, eval_fn, chances): |
需要注意,这段代码里“下一层是什么类型”是写死的:max 之后总是 chance,min 与 chance 之后总是 max。因此从 max 根出发时 min 分支永远走不到,它只是展示三种节点各自怎么取值。实际实现里,“下一层节点类型”必须按作业的回合顺序写死:Pac-Man 走完之后是否每个幽灵各走一步(有 $n$ 个幽灵就有 $n$ 个连续的 min/chance 层)、机会节点插在哪一层,都会改变这棵树。
▸minimax 在算什么,以及它和 expectimax 的强弱关系
设叶子回报确定。从叶子向上:min 层取子节点最小,max 层取最大。根值等于:max 先走、此后双方都最优时的回报。这不是对历史对局的统计,而是对整棵树的定义。
证明用结构归纳(structural induction):叶子的值就是回报,显然正确;假设某个 min 节点的各子树值都正确,那么对手在这里挑最小的子节点,得到的正是该玩家能逼对方接受的值;max 节点同理。归纳到根即得结论。
若对手并非最优,按 minimax 走仍有下界保证:结果不会比博弈值更差(前提是估值等于真实回报,即没有截断误差),但可能放过更高的回报。expectimax 在对手接近随机时平均回报更高,但在对手刻意针对时可以任意差。Pac-Man 里如果把一个会跟踪的幽灵写成均匀随机,整张最优动作表都会偏掉:智能体会从“本会被堵住的巷口”穿过去,以为幽灵只有一定概率堵它。
用 $V(\pi_{\mathrm{agent}},\pi_{\mathrm{opp}})$ 表示双方分别用这两个策略时的期望回报,$\pi_{\max},\pi_{\min}$ 是 minimax 策略,$\pi_{\mathrm{ex}}$ 是针对某个已知随机对手策略 $\pi$ 算出的 expectimax 策略。CS221 讲义把上述关系总结成一条不等式链:
$$ V(\pi_{\mathrm{ex}},\pi_{\min})\le V(\pi_{\max},\pi_{\min})\le V(\pi_{\max},\pi)\le V(\pi_{\mathrm{ex}},\pi). $$
中间的不等号说“对手偏离 minimax 策略只会让 max 更好”(下界保证);右边的不等号说“若知道对手是 $\pi$,minimax 不是最优的”;左边的不等号说“expectimax 策略遇到真正的对抗对手可能更差”。
感性理解:max 问“当前玩家能逼出的最好结果”,min 问“对手能逼出的最坏结果”,chance 问“骰子的平均”。三者不能互换。只看一层的贪心(只比较下一步的叶子)会忽略对手两步之后的反击。
▸例7 一层 expectimax
根是 max 节点,有两个动作。左动作进入 chance 节点:叶子 $10$ 与 $0$ 各占概率 $1/2$,期望 $5$。右动作进入 chance 节点:叶子 $4$ 与 $6$ 各 $1/2$,期望 $5$。根值 $5$,两个动作并列。
若右动作其实通向 min 节点(对手在 $4$ 与 $6$ 中挑选),则右值变成 $\min(4,6)=4$,根选左,根值仍为 $5$。
结论:同一棵树形状,节点类型不同,动作表就不同。自动评分(autograder)会同时核对根值与所选动作,而不只核对结果“看起来合理”。另外可以注意到,左动作在 min 解释下的值是 $\min(10,0)=0$,若两个动作都换成 min 节点,根会改选右(值 $4$)——方差大的动作在对抗设定下最吃亏。
▸例7′ CS221 讲义中的“三个盒子”游戏
讲义中有一个经典小例子:对手面前有三个盒子 A、B、C,各装两个数,A 为 $\{-50,50\}$,B 为 $\{1,3\}$,C 为 $\{-5,15\}$。智能体先选盒子,对手再从盒子里挑一个数作为智能体的回报。
- minimax(对手挑最小):三个盒子的值分别为 $-50,1,-5$,选 B,值 $1$。
- expectimax(对手均匀随机,记为 $\pi$):三个盒子的值分别为 $0,2,5$,选 C,值 $5$。
四种组合的回报为:$V(\pi_{\max},\pi_{\min})=1$,$V(\pi_{\max},\pi)=2$,$V(\pi_{\mathrm{ex}},\pi_{\min})=-5$,$V(\pi_{\mathrm{ex}},\pi)=5$,恰好满足上面的不等式链 $-5\le 1\le 2\le 5$。
α-β 剪枝(Alpha-Beta Pruning)
minimax 要展开整棵树,分支因子为 $b$、深度为 $d$ 时有 $O(b^{d})$ 个叶子,象棋这样的游戏根本算不完。但很多分支其实不必看:如果已经知道某个 min 子树的值不会超过 $2$,而 max 在别的分支上已经能拿到 $3$,那这棵子树剩下的叶子是什么都不影响根的选择。α-β 剪枝就是把这种“已经知道的界”系统地沿递归传下去。
α-β 在 minimax 递推里携带一个窗口 $[\alpha,\beta]$:
- $\alpha$:在通往根的路径上,max 已经保证能拿到的下界(已经能逼到至少这么好);
- $\beta$:在通往根的路径上,min 已经保证能压到的上界(已经能逼到至多这么好)。
当某个分支的值不可能落入 $(\alpha,\beta)$、从而不可能影响根值时,就把它剪掉。
正确性:被剪掉的子树即使展开,根的 minimax 值也不会变。理由是:在 min 节点上,一旦当前值 $v\le\alpha$,这个 min 节点最终的值只会 $\le v\le\alpha$,而上方某个 max 祖先已经有值 $\alpha$ 的选择,绝不会选这里;max 节点上 $v\ge\beta$ 对称。所以剪枝不改变值,只改变展开量,是精确算法而不是近似。
expectimax 一般不能剪:chance 层的值是期望,任何一个未展开的叶子都可能把期望拉动任意大小,除非事先知道叶子取值的上下界(例如回报落在 $[-1,1]$),否则无法断言“剩下的不重要”。
复杂度:最好情形(每层子节点恰好按从好到坏的顺序排)大约展开 $O(b^{d/2})$ 个叶子,有效分支因子约为 $\sqrt{b}$,相当于同样时间内能多搜一倍深度;随机次序时 AIMA 给出的经验量级约为 $O(b^{3d/4})$;最坏(次序完全相反)仍是 $O(b^{d})$。因此“先试看起来好的动作”(move ordering)对剪枝效率影响很大。
实现要点:必须在递推里传递并更新 $\alpha,\beta$,不能只在根做一次比较;窗口初始化必须是真正的 $\pm\infty$(不能用 $\pm 1000$ 之类的“足够大”的数,估值一旦超出就会错剪)。
1 | import numpy as np # 代码中用到 np.inf |
(原代码使用 np.inf 但未导入 numpy,这里补了一行 import numpy as np,其余不变。)
关于边界条件:alpha >= beta 在相等时也剪(tie 时剪枝)。原笔记把它称为“闭区间(fail-hard)”,这个说法不准确:fail-hard / fail-soft 指的是函数返回值是否被截断到 $[\alpha,\beta]$ 内,而上面的代码直接返回 $v$(未截断),属于 fail-soft 写法;“相等时剪不剪”是另一个独立的选择。相等时是否剪,会影响展开的状态集合,必须与参考实现一致——例如 Berkeley 系列(CS221 Pac-Man 作业即源自此)的说明要求不要在相等时剪枝(即改用严格不等号 alpha > beta),以便与 autograder 统计的展开状态数一致。
若还要返回最优动作,只能在根层记录使 $v$ 更新的那个 $a$;不要在内层被剪枝之后残缺的 $v$ 上乱选——被剪掉的子树返回的只是一个界,不是真值,不同动作之间的这种“值”不可比。
▸α-β 剪枝的定位
在 minimax 递推中携带当前窗口,剪掉不能更新根值的分支。思想来源是博弈树上的分支定界(branch and bound),不是近似。
- 相对完整 minimax:值相同,展开更少。
- 相对随便截断深度:截断改变的是叶子的定义,必须另配估值函数(evaluation function)。
- 相对 A*:二者都用“界”来少展开,A* 的界来自启发函数 $h$,α-β 的界来自兄弟节点已经算出的值。
▸例8 三层 α-β
根是 max,两个子节点都是 min。左 min 的叶子从左到右为 $3,12,8$;右 min 的叶子为 $2,4,6$。约定从左向右展开,根的初始窗口为 $[-\infty,+\infty]$。
- 左 min 看到 $3$,当前 $v=3$,$\beta=3$。再看 $12$、$8$,$\min$ 仍为 $3$(此时窗口下界 $\alpha=-\infty$,不会剪)。左子树值为 $3$。根更新 $\alpha=3$。
- 右 min 先看 $2$,当前 $v=2$,把 $\beta$ 降为 $2$。此时 $\alpha=3\ge\beta=2$,其余叶子 $4,6$ 不必看:min 已经能把该子树压到 $\le 2$,max 不会选它。
- 根值 $\max(3,2)=3$,展开的叶子为 $\{3,12,8,2\}$。
把最左叶子 $3$ 改成 $10$。左 min 得 $\min(10,12,8)=8$,根 $\alpha=8$。右 min 仍先见 $2$,$\beta=2$,$8\ge 2$,同样剪掉 $4,6$。根值 $\max(8,2)=8$。完整展开右子树也是 $\min(2,4,6)=2$,值不变。两种情形下展开的叶子集合都是“左子树三片全看 + 右子树只看 $2$”:左子树在根窗口仍为 $(-\infty,+\infty)$ 时被展开,无论左值是 $3$ 还是 $8$ 都不会在左子树内部剪枝,剪掉的都是右子树的 $4,6$。(原笔记称此时“左子树的比较次数不同”,按上述过程两种情形下左子树都完整展开三片叶子,比较次数相同,这里予以更正。)自动评分会核对展开过的叶子集合,不单核对根值。
若改为右子树先展开(先看到 $2$),此时根的 $\alpha$ 仍为 $-\infty$,还不能剪:右子树 $2,4,6$ 全看,值 $2$,根 $\alpha=2$;再展开左子树,看到 $3$ 时 $\beta=3\gt\alpha=2$,仍不能剪,$12,8$ 也要看。整棵树一片叶子都没剪。可见剪枝位置随展开次序变化。作业若指定了动作顺序(如按 getLegalActions 返回的顺序),不要按“看起来好”重排,除非题目允许启发式排序。
估值函数(Evaluation Function)
即使有了 α-β,象棋或 Pac-Man 的完整博弈树仍然太深。实际做法是限制搜索深度:搜到深度 $d$ 就停下,把那里的状态当成叶子,用一个可计算的分数 $\hat V(s)$ 代替“从这里一直下到终局”的真实值。CS221 Pac-Man 作业的深度约定是:一层深度(depth)包含 Pac-Man 与全部 $n$ 个幽灵各走一步,即 $n+1$ 个 ply。带深度的 minimax 写成
$$ V(s,d)= \begin{cases} \mathrm{Utility}(s) & s\text{ 为终局}\\ \hat V(s) & d=0\\ \max_{a}V(\mathrm{Succ}(s,a),d) & s\text{ 为 max}\\ \min_{a}V(\mathrm{Succ}(s,a),d') & s\text{ 为 min,} \end{cases} $$
其中 $d'$ 在最后一个幽灵走完时减一,否则保持 $d$(按半步计深度的实现则每层都减一,两种约定见下节“实现注意”)。
α-β 仍然精确计算这棵截断树上的 minimax 值;不精确的是树本身。 也就是说,误差全部来自估值函数。估值必须与终点回报同号、同尺度,否则 max / min 在“终局叶子”与“截断叶子”之间的比较就没有意义:若终点吃豆是 $+10$、估值却把“离豆近”写成 $+1000$,智能体会宁愿停在离豆近的位置,也不真的去吃豆。
Pac-Man 常用的特征有:距最近食物的距离、距幽灵的距离、惊吓剩余步数、剩余豆数、是否处在死巷。线性估值 $\hat V=w\cdot\phi(s)$ 与反射模型(线性分类器)同形,但这里的 $w$ 多半是手调的,而不是 SGD 训练出来的。调估值是建模,不是调 α-β。
如果希望自动学出 $w$,CS221 讲义介绍了时序差分学习(temporal difference learning, TD learning):在对局中每观察到一次 $(s,a,r,s')$,就让 $\hat V(s;w)$ 向“一步之后的目标” $r+\gamma\hat V(s';w)$ 靠拢,
$$ w\leftarrow w-\eta\big[\hat V(s;w)-(r+\gamma\hat V(s';w))\big]\nabla_w\hat V(s;w), $$
其中 $\eta$ 为步长。TD-Gammon(西洋双陆棋)是这一方法的经典成功案例。
水平效应(horizon effect):深度是奇数还是偶数,会改变“谁最后走”;估值若偏爱某一侧,就会出现水平效应——智能体通过一些拖延着法,把不可避免的损失推到搜索深度之外,估值看不见,于是误以为局面安全。对策包括:增加深度;静止搜索(quiescence search),即在“不平静”的局面(如正在交换棋子)继续往下搜,直到局面平静再估值;或者把必然的着法(如必吃)走完再估值。课程作业通常只要求固定深度加一组特征。
▸估值函数的定位
把截断点当成叶子,用可计算的分数代替整棵子树。思想来源是象棋程序的局面评价,不是新的博弈定义。
- 相对真实回报:改变的是叶子,不改变 minimax / α-β 的递推。
- 相对反射模型:$\phi(s)$ 可以很像情感分类里的特征,但推断仍是一层层的 $\max/\min$,而不是一次 $\mathrm{sign}(w\cdot\phi)$。
实现注意事项
博弈搜索的 bug 往往不在公式,而在“树长什么样”的细节上。下面几条是作业里最常见的坑:
- 状态拷贝:走一步必须返回新状态。如果原地修改棋盘再递归,回溯时盘面已经被改坏,兄弟分支看到的是错误的局面。
- 节点类型与“深度减一”的粒度:有的实现把 Pac-Man 加全部幽灵算作一步,深度按回合减;有的按半步(每个智能体一次)减。与作业约定不一致,展开数就全错。
- α-β 窗口必须沿递归传递:如果在子调用之外更新窗口,却给子调用传入旧窗口,等于没剪。
- 并列动作:根层应有确定的打破平局(tie-breaking)规则,否则所选动作与参考实现不一致(CS221 作业说明也提到,值必须与 minimax 一致,但动作可能因打破平局方式不同而不同)。
- expectimax 不要对 chance 层做 α-β,除非另外给出概率或取值的界。
▸对照:expectimax、minimax 与 expectiminimax
- 对手策略已知且随机,用 expectimax;对手对抗,用 minimax;两者混合,用 expectiminimax。选错节点类型等于解另一盘游戏。
- α-β 只对纯 minimax 合法。
- 估值函数错了,则整棵截断树也在解另一盘游戏。
▸小结与衔接
- 博弈树有 max / min / chance 三类节点,值自底向上按类型回传。
- minimax 给出对抗下的下界保证;已知对手随机时 expectimax 更好,但遇到真正的对手可能很差。
- α-β 剪枝精确、只省计算,效率依赖展开次序;估值函数截断深度,是误差的唯一来源。
状态轴沿时间展开,状态数随步数指数增长:棋盘、豆子、轮到谁,每多一个维度,状态数就成倍增加。而许多作业其实变量个数是固定的(时段、教室、位置坐标),不必把整条轨迹压进一个状态。下一章把问题拆成变量上的因子,推断改为求赋值或边缘。
因子图与约束满足(Factor Graphs and CSPs)
设想一个排课问题:有 $n$ 门课,每门课要分配一个时段和教室;同一教室同一时段不能排两门课,先修课要排在前面,同一位教师的课不能重叠。如果沿用搜索或博弈的“状态”视角,就得把“已经排好了哪些课、各排在哪”都塞进一个状态里,再一步步扩展——但这里的“步”毫无时间意义,先排哪门课都一样,真正重要的是最终整组赋值满足哪些约束、好不好。
因子图的核心想法一句话:把问题写成一组变量,再用许多只涉及少数变量的局部打分函数(因子)的乘积来评价一组完整赋值。即一组完整赋值的权为 $w(x)=\prod_j f_j(x_{C_j})$,要求的是最大权赋值或可行赋值。这样,“哪些变量之间有耦合”就直接写在图结构里,算法可以利用这种局部性。
和前后章节的关系:上一章(博弈)的状态随时间增长;本章变量个数固定,耦合写在因子里;下一章(贝叶斯网)把因子改成条件概率,于是权有了概率含义。本章依次讲因子图的定义、回溯与前向检查、弧一致(AC-3)、束搜索与变量消去;这些算法的输入是一组因子,输出是一组赋值或分割函数。
因子图的定义
设有变量 $X_1,\ldots,X_n$,每个 $X_i$ 取值于有限定义域 $\mathrm{Domain}_i$(记大小为 $|\mathcal{D}|$)。另有若干因子(factor)$f_1,\ldots,f_m$,每个 $f_j\ge 0$ 只依赖一小组变量 $X_{C_j}$($C_j\subseteq\{1,\ldots,n\}$ 称为它的作用域,$|C_j|$ 称为元数 arity:$1$ 元叫一元因子,$2$ 元叫二元因子)。
因子图(factor graph)是一个二部图:一侧是变量节点,一侧是因子节点,边表示因子依赖哪些变量。一组完整赋值 $x$ 的联合权为
$$ w(x)=\prod_{j}f_j(x_{C_j}),\qquad x=(x_1,\ldots,x_n). $$
乘积意味着“每个因子都要满意”:只要有一个因子为 $0$,整组赋值的权就是 $0$。
在因子图上,推断问的是两件事之一:
- 最大权赋值(MAP):$\arg\max_x w(x)$;
- 分割函数(partition function) $Z=\sum_x w(x)$,以及各变量的边缘(marginal)。
约束满足问题(constraint satisfaction problem, CSP) 是因子取值都在 $\{0,1\}$ 中的特例:因子为 $0$ 表示违反约束,可行(consistent)赋值即 $w(x)=1$。排课作业中,课程的时段是变量;教室冲突、先修关系、教师不重叠是因子。
马尔可夫网(Markov network) 则把权解释为概率:$P(x)=w(x)/Z$。推断算法与因子图完全相同,只是输出要除以 $Z$ 归一化成概率。无向图上的条件独立按“割集”读:给定一组把 $A$ 与 $B$ 隔开的变量后,二者条件独立;特别地,给定一个变量的全部邻居(Markov blanket),它与其余变量条件独立。无向图里不会出现“对撞”结构,因此下一章要讲的“解释掉(explaining away)”现象是有向网特有的。
▸因子图的定位
因子图把联合权分解成局部因子的乘积,思想来源是约束满足与无向图模型,CS221 用它接住“变量轴”。
- 相对状态模型:不需要把整条时间线压进一个状态;排 $n$ 门课是 $n$ 个变量,不是 $n$ 步路径。
- 相对贝叶斯网:这里先不谈概率,只谈权与约束。
- 相对反射模型:一次赋值要同时满足许多局部因子,不能对每个变量独立地取 $\mathrm{sign}$。
回溯搜索与前向检查
最直接的求解办法是回溯搜索(backtracking search):选一个尚未赋值的变量,按定义域逐个试值;若与已有赋值所涉及的因子矛盾,就撤回,换下一个值;所有值都失败则回退到上一个变量。最坏情况下要枚举 $O(|\mathcal{D}|^{n})$ 个赋值,所以关键在于尽早发现失败。
变量序与值序只影响展开量,不改变正确性。常用启发式有三个:
- 最少剩余值(minimum remaining values, MRV),也叫“最受约束变量”:先选当前定义域最短的变量。它最可能失败,越早失败,被剪掉的子树越大。
- 度启发式(degree heuristic):MRV 并列时,先选与其他未赋值变量约束最多的变量。
- 最少约束值(least constraining value, LCV):选定变量之后,先试“删掉邻居选项最少”的值,给后面的变量留下更大的空间。
直觉上,选变量时要“挑最难的先做”(反正每个变量都要赋值,早失败早回头),选值时要“挑最宽松的先试”(只需要找到一个解,宽松的值更可能成功)。
前向检查(forward checking):刚赋值 $X_i=v$ 后,只检查与 $X_i$ 有约束的未赋值邻居,从它们的定义域里删掉与 $v$ 不一致的值;若某个邻居的定义域被删空,立刻判定这一支失败。撤回 $X_i$ 时,要把这些邻居的定义域恢复原状。前向检查比纯回溯更早发现失败,但比弧一致弱:它只看一步,不在邻居之间继续传播。
1 | def backtrack(assign, domains, variables, consistent, neighbors): |
需要注意,这段代码按定义域原顺序试值,没有实现 LCV;若要实现,可以在 for val in ... 之前按“删掉邻居选项数”对 domains[x] 排序。
弧一致与 AC-3
前向检查只在刚赋值的变量周围删值。但删值本身会产生连锁反应:$Y$ 的定义域缩小后,$Z$ 中原本靠 $Y$ 的某个值来“配对”的值可能也不再可行。弧一致把这种传播做到底。
弧一致(arc consistency):对有序对(弧)$(X_i,X_j)$,若 $X_i$ 的每个值都能在 $X_j$ 的定义域里找到至少一个满足约束的搭档,就说 $X_i$ 关于 $X_j$ 弧一致;做法是删掉 $X_i$ 中找不到任何搭档的值。
AC-3 算法:把所有弧放入队列;每次取出一条弧 $(X_i,X_j)$ 并修剪 $X_i$(下面代码中的 revise);若 $X_i$ 的定义域缩小了,就把所有指向 $X_i$ 的弧 $(X_k,X_i)$($X_k\ne X_j$)重新入队,因为它们的搭档变少了;直到队列为空,达到不动点。时间复杂度为 $O(ed^{3})$,其中 $e$ 是(二元)约束数,$d$ 是定义域大小:每条弧最多因为对面删值而重新入队 $d$ 次,每次 revise 要比较 $d^2$ 对值。这是多项式时间,但不给出全局解。
使用方式有两种:
- 回溯之前先跑一遍 AC-3,若某定义域被删空,整支(即整个问题)直接判无解;
- MAC(maintaining arc consistency):每次赋值之后,对剩余的图再跑 AC-3。它比前向检查剪得更狠,但每步的常数开销更大。
1 | from collections import deque |
▸弧一致的定位
弧一致删掉“局部看起来已经不可能”的值,思想来源是约束传播(constraint propagation),而不是搜索。
- 相对纯回溯:许多失败在赋值之前就被发现。
- 相对前向检查:AC-3 会沿约束链传播,不限于刚赋值变量的邻居。
- 相对变量消去:AC-3 是多项式时间,但不给出全局解。
需要注意单向的含义:定义域被删空则一定无解;但所有定义域都非空,仍可能无解。例如奇数长度的环用两种颜色着色,每条边局部看都还有颜色可选,AC-3 删不掉任何值,但全局无解。
▸例9 双变量不等约束
$X,Y\in\{1,2\}$,约束 $X\ne Y$。AC-3 不删任何值:每个值在对面都能找到搭档($X=1$ 配 $Y=2$,$X=2$ 配 $Y=1$)。前向检查在赋值 $X=1$ 之后把 $Y$ 的 $1$ 删掉,剩下 $2$。
再加入 $Z\in\{1\}$ 与约束 $X\ne Z$、$Y\ne Z$:$Z$ 只能取 $1$,AC-3 处理弧 $(X,Z)$、$(Y,Z)$ 时删掉 $X=1$ 与 $Y=1$,剩下 $X=Y=2$;随后 $X$、$Y$ 定义域缩小触发弧 $(Y,X)$ 重查,$Y=2$ 在 $X$ 中找不到搭档($X$ 只剩 $2$),于是被 $X\ne Y$ 删空,报告无解。按 $X,Y,Z$ 的顺序做纯回溯,则要走到第三层(给 $Z$ 赋值)才发现失败。
束搜索(Beam Search)
回溯是完备的,但在大规模排课这类问题上可能慢到不可接受;而且当因子不是 $0/1$ 约束而是软打分时,往往只需要一个“足够好”的赋值。束搜索用“不完备”换“时间可控”。
做法:按变量序一层层扩展部分赋值,每层把当前所有前缀的所有扩展都打分,然后只保留权最高的 $K$ 个前缀($K$ 称为束宽,beam size)。$K=1$ 就是贪心;$K=\infty$ 就是完整的宽度优先搜索。每层要对 $K|\mathcal{D}|$ 个候选排序,总时间约为 $O(nK|\mathcal{D}|\log(K|\mathcal{D}|))$。束搜索不保证找到最优解,也不保证找到可行解。Spring 2026 单独讲它,是因为排课规模上完整回溯不可行。
1 | def beam_search(variables, domains, score, K): |
部分赋值的 score 只能使用已经触及的因子(即作用域内变量全部已赋值的因子)。如果用未赋值变量上的“乐观上界”当分数,那就变成了分支定界(branch and bound),不是普通束搜索。作业必须报告所用的 $K$ 以及是否找到可行解,不能只报分数。
▸束搜索的定位
束搜索是在“赋值的层”上做截断的宽度优先搜索,思想来源是语音识别与机器翻译的解码,不是 CSP 的完备算法。
- 相对回溯:时间可控,但可能漏掉唯一的可行解。
- 相对动态规划:不存整张表,只存 $K$ 条前缀。
- 相对 A*:束宽 $K$ 是硬截断,没有可采纳性(admissibility)保证。
变量消去(Variable Elimination)
回溯、AC-3、束搜索都是在“找一组赋值”。另一类问题要求对所有赋值求和或取最大:计算分割函数 $Z$(后面算概率时要用),或者求精确的 MAP。直接枚举是 $|\mathcal{D}|^{n}$ 项,不可行;变量消去利用“每个因子只涉及少数变量”这一点,把大求和拆成一串小求和。
目标是
$$ Z=\sum_{x_1}\cdots\sum_{x_n}\prod_j f_j(x_{C_j}) $$
或 MAP $\max_x\prod_j f_j$。关键观察是分配律:不含 $x_i$ 的因子可以提到 $\sum_{x_i}$ 外面去。于是每次挑一个变量,只把含它的因子乘起来,再对该变量求和(或取最大),得到一个不再含该变量的新因子,放回因子集合;重复直到所有变量都被消去。
消去顺序决定中间因子的大小。树宽(treewidth)定义为:对所有消去顺序,取“该顺序下最大中间宽度”(新因子所含变量数的最大值),再在所有顺序上取最小。树宽小,则精确推断可行。
▸变量消去的一步
设要消去 $X_i$。令 $F_i$ 为所有依赖 $X_i$ 的因子,其余因子不动。新因子
$$ f_{\mathrm{new}}(x_{S})=\sum_{x_i}\prod_{f\in F_i}f(\cdot), $$
其中 $S$ 是 $F_i$ 中除 $X_i$ 之外出现的所有变量。用 $f_{\mathrm{new}}$ 替换 $F_i$ 之后,联合乘积对其余变量的值不变,只是 $X_i$ 被“积掉”了:这一步正确,是因为 $\sum_{x_i}\big(\prod_{f\notin F_i}f\big)\big(\prod_{f\in F_i}f\big)=\big(\prod_{f\notin F_i}f\big)\sum_{x_i}\prod_{f\in F_i}f$。
MAP 把 $\sum$ 换成 $\max$(因为 $\max$ 对非负因子同样满足分配律),并记录每一步的 $\arg\max$,以便最后反向回填出完整的最优赋值。证据 $X_e=e$ 在开始时处理:把因子中与证据冲突的行置零(或直接删掉这些行)。
感性理解:先把“还连着 $X_i$ 的几张表”合成一张,再把 $X_i$ 这一列收掉。合成时出现的列数就是代价。消去顺序差一点,中间表就可能大到无法存储。链状图按端点往里消,中间表永远只涉及两个变量;从中间先消,立刻会把两端耦合进同一张表。
复杂度:$O(n|\mathcal{D}|^{w+1})$,其中 $w$ 是该消去顺序下的最大中间宽度(新因子的变量数 $|S|$);合成表要枚举 $S$ 与被消变量共 $w+1$ 个变量的取值,故指数为 $w+1$。找最优消去顺序是 NP-难的;常用启发式有最小度(先消邻居最少的变量)与最小缺边(min-fill,先消“把邻居连成团时需要补的边最少”的变量)。实现时可以把乘积放到 log 域改成加法,避免连乘下溢;MAP 同样适用($\max$ 在 log 域不变)。
1 | def eliminate(factors, var, domains, maximize=False): |
_product 枚举 others 上的全部赋值(例如可用 itertools.product 实现)。具体实现细节不重要,重要的是:中间表的键是 others,表的大小为 $|\mathcal{D}|^{|\mathrm{others}|}$,这正是复杂度中指数项的来源。
▸例10 三变量链上的数值消去
二值变量 $X,Y,Z$,链 $X-Y-Z$。因子
$$ f_{XY}(0,0)=2,\ f_{XY}(0,1)=1,\ f_{XY}(1,0)=1,\ f_{XY}(1,1)=3; $$
$$ f_{YZ}(0,0)=1,\ f_{YZ}(0,1)=4,\ f_{YZ}(1,0)=2,\ f_{YZ}(1,1)=1. $$
求 $Z$。先消 $Y$:对每组 $(x,z)$ 把 $y$ 加起来,
$$ f_{XZ}(x,z)=\sum_{y}f_{XY}(x,y)f_{YZ}(y,z). $$
$f_{XZ}(0,0)=2\cdot 1+1\cdot 2=4$,$f_{XZ}(0,1)=2\cdot 4+1\cdot 1=9$,$f_{XZ}(1,0)=1\cdot 1+3\cdot 2=7$,$f_{XZ}(1,1)=1\cdot 4+3\cdot 1=7$。再消 $X$:$g(z)=\sum_x f_{XZ}(x,z)$,$g(0)=4+7=11$,$g(1)=9+7=16$。分割函数 $\sum_z g(z)=27$。
可以用全枚举核对。八组赋值 $(x,y,z)$ 的权 $f_{XY}(x,y)f_{YZ}(y,z)$ 为:
| $(x,y,z)$ | 权 |
|---|---|
| $(0,0,0)$ | $2\cdot 1=2$ |
| $(0,0,1)$ | $2\cdot 4=8$ |
| $(0,1,0)$ | $1\cdot 2=2$ |
| $(0,1,1)$ | $1\cdot 1=1$ |
| $(1,0,0)$ | $1\cdot 1=1$ |
| $(1,0,1)$ | $1\cdot 4=4$ |
| $(1,1,0)$ | $3\cdot 2=6$ |
| $(1,1,1)$ | $3\cdot 1=3$ |
总和 $2+8+2+1+1+4+6+3=27$,与消去结果一致。
求 MAP。把每一步的求和换成最大:消 $Y$ 得 $m(x,z)=\max_y f_{XY}(x,y)f_{YZ}(y,z)$,即 $m(0,0)=\max(2,2)=2$,$m(0,1)=\max(8,1)=8$,$m(1,0)=\max(1,6)=6$,$m(1,1)=\max(4,3)=4$;再对 $(x,z)$ 取最大得 $8$。最大权为 $8$,回填得赋值 $(X,Y,Z)=(0,0,1)$(与表中一致)。
边缘众数不等于 MAP。$Z$ 的边缘 $g(1)=16\gt g(0)$,与 MAP 中 $Z=1$ 同侧,但这只是巧合。看 $X$ 的边缘:$\sum_z f_{XZ}(0,z)=4+9=13$,$\sum_z f_{XZ}(1,z)=7+7=14$,边缘众数是 $X=1$,而 MAP 中 $X=0$——这个例子本身就已经出现了不一致。若把各变量边缘的 $\arg\max$ 拼起来($Y$ 的边缘为 $15$ 对 $12$,取 $Y=0$),得到 $(1,0,1)$,其权只有 $4$。把边缘 $\arg\max$ 当成 MAP 会错。
▸对照:回溯、束搜索与变量消去
- 回溯与 AC-3 用于找可行赋值,若跑完则是完备的。
- 束搜索有时间上限,不完备。
- 变量消去计算 $Z$ 或 MAP,代价由树宽决定,与“好不好找到一组可行解”不是同一种复杂度。树宽高时精确变量消去不可行,此时改用束搜索或采样,并且必须在报告里写明这是近似结果。
▸小结与衔接
- 因子图把联合权写成局部因子之积;CSP 是 $0/1$ 因子的特例,马尔可夫网是归一化后的版本。
- 回溯配合 MRV / LCV 与前向检查、AC-3,可以大幅提前发现失败;AC-3 多项式时间但只做局部推理。
- 束搜索用束宽 $K$ 换时间,不完备;变量消去精确,代价指数依赖树宽。
因子图的权还没有概率语义。下一章把因子写成条件概率表,推断问的是边缘 $P(Q\mid E=e)$,并引入条件独立来少算。算法骨架(消去、采样)仍在,只是数字的含义变成了概率。
贝叶斯网(Bayesian Networks)
因子图的权是任意非负数,能说“这组赋值比那组好”,却说不出“这件事有多大可能”。很多问题本质上是在不确定性下推理:听到警报响,家里被盗的概率有多大?雷达读数一路传来,车现在最可能在哪?这需要一个联合概率分布,并且能在观测到部分变量(证据)之后,算出其余变量的条件分布。
困难在于:联合分布 $P(X_1,\ldots,X_n)$ 若直接存成表,$n$ 个二值变量的完整联合表有 $2^{n}$ 行,既存不下,也无从估计。贝叶斯网的核心想法一句话:用一张有向无环图声明“每个变量只直接依赖它的父节点”,把联合分布拆成一串小的局部条件概率表之积。拆开之后,它就是一张带概率含义的因子图,上一章的变量消去、采样等算法可以直接沿用。
和前后章节的关系:上一章的因子是任意非负权;本章的因子是条件概率 $P(X_i\mid\mathrm{Pa}(X_i))$,对 $x_i$ 求和为 $1$;下一章(逻辑)则离开数值权,改用真假公式。本章依次讲:分解与条件概率表、条件独立与 d-分离、精确推断、HMM 与前向算法、粒子滤波。要回答的问题包括条件边缘 $P(Q\mid E=e)$、MAP 或最可能解释;输入是网与证据,输出是概率或一组粒子。
从链式法则到贝叶斯网
任意联合分布都满足链式法则(chain rule):
$$ P(x_1,\ldots,x_n)=P(x_1)P(x_2\mid x_1)\cdots P(x_n\mid x_1,\ldots,x_{n-1}). $$
这个式子总是成立,但没有省任何东西:最后一项的条件集包含了前面全部变量。贝叶斯网(Bayesian network)用一张有向无环图(DAG)声明一个假设:按拓扑序排列后,每个变量在给定其父节点 $\mathrm{Pa}(i)$ 的条件下,与其余更早的变量无关。于是每项的条件集缩成父节点:
$$ P(x_1,\ldots,x_n)=\prod_{i=1}^{n}P(x_i\mid x_{\mathrm{Pa}(i)}). $$
每个局部条件分布用一张条件概率表(conditional probability table, CPT)存储,它是一个因子,并且满足局部归一化:对每一种父节点配置,对 $x_i$ 的所有取值求和为 $1$,即 $\sum_{x_i}P(x_i\mid x_{\mathrm{Pa}(i)})=1$。(原笔记写作“对父配置求和为 $1$”,求和对象应是子变量 $x_i$ 而非父配置,这里改正。)局部归一化保证了整个乘积自动是一个合法分布,不需要再除以 $Z$。
两点常见误解:
- 没有边不等于独立。两个变量之间没有直接的边,仍可能通过别的路径相关;独立与否要用 d-分离读出(下一节)。
- 边是分解假设,不是自动的因果证明。同一个分布可以用方向不同的多张图表示;图的方向只有在额外假设下才有因果含义。
▸贝叶斯网的定位
贝叶斯网用有向无环图把联合分布分解成局部 CPT,思想来源是 Pearl 的信念网(belief network),CS221 把它当作“带概率的因子图”。
- 相对完整联合表:$n$ 个二值变量的参数量从 $2^{n}$ 量级降到各 CPT 大小之和(每个变量约 $2^{|\mathrm{Pa}(i)|}$ 个参数)。
- 相对马尔可夫网:CPT 已经各自归一化,整体无需求 $Z$;但询问 $P(Q\mid e)$ 时,代入证据后仍要再归一化一次。
- 相对因果口号:干预与 $do$ 演算不在本课考试范围,作业只问观测推断。
▸例11 报警网
变量:盗窃 $B$、地震 $E$、报警 $A$(均为二值)。边 $B\rightarrow A\leftarrow E$。CPT:$P(B=1)=0.01$,$P(E=1)=0.01$,$P(A=1\mid B,E)$ 在 $(B,E)=(1,1),(1,0),(0,1),(0,0)$ 上分别为 $0.95,0.94,0.29,0.001$。联合分布为
$$ P(b,e,a)=P(b)P(e)P(a\mid b,e). $$
报警的边缘概率是对两个父节点求和:$P(A=1)=\sum_{b,e}P(b)P(e)P(A=1\mid b,e)$。逐项计算:
| $(b,e)$ | $P(b)P(e)P(A=1\mid b,e)$ |
|---|---|
| $(1,1)$ | $0.01\times 0.01\times 0.95=0.000095$ |
| $(1,0)$ | $0.01\times 0.99\times 0.94=0.009306$ |
| $(0,1)$ | $0.99\times 0.01\times 0.29=0.002871$ |
| $(0,0)$ | $0.99\times 0.99\times 0.001=0.0009801$ |
合计 $P(A=1)\approx 0.01325$。数值上主项来自“只有盗窃”(约 $70\%$),其次是“只有地震”与“无盗窃无地震仍误报”,而不是两因同时发生(该项最小,因为两个小概率相乘)。(原笔记称主项来自“误报”与“只有盗窃”;按上表,“只有地震”一项 $0.002871$ 比误报一项 $0.00098$ 更大,这里更正。)由此 $P(B=1\mid A=1)=(0.000095+0.009306)/0.0132521\approx 0.709$。
需要注意:若 CPT 未归一(某一行对 $a$ 加总不为 $1$),整张网就不是一个分布。
条件独立与 d-分离
贝叶斯网的价值不只是省参数,还在于能从图上直接读出“哪些变量在给定证据后互不相关”,从而在推断时少算。读法是 d-分离(d-separation):考察两组变量之间的每一条无向路径,每条路径会被证据“开通”或“关闭”,取决于路径上每个中间节点的局部结构。若两组变量之间的所有路径都被证据关闭,则二者条件独立,联合分布中对应的因子化成立。
三种局部结构:
- 串行(链) $A\rightarrow B\rightarrow C$:$B$ 未观察时路径开通($A$ 的信息经 $B$ 传到 $C$);观察 $B$ 后阻断(知道了 $B$,$A$ 对 $C$ 不再提供额外信息)。
- 分叉(共同原因) $A\leftarrow B\rightarrow C$:$B$ 未观察时开通(两个结果因共同原因而相关);观察 $B$ 后阻断。
- 对撞(共同结果) $A\rightarrow B\leftarrow C$:恰好相反,$B$ 未观察时阻断;观察 $B$ 或 $B$ 的任一后裔时反而开通。
一条路径只要有一个中间节点处于阻断状态,整条路径就被关闭。
图上的 d-分离是条件独立的充分条件:图说独立,则任何按此图分解的分布都独立。在忠实(faithful)分布上它还是必要条件;对于具体数值的 CPT,可能出现图上不分离、但数值上恰好独立的巧合。作业按图读独立,不要用“看起来相关”代替。
▸对撞为何开通:解释掉(explaining away)
未观察 $B$ 时,$A$ 与 $C$ 可以独立(两个原因、一个结果,两个原因本身互不相干)。一旦知道了 $B$,关于 $A$ 的信息就会改变对 $C$ 的信念:如果已经确认 $A$ 发生了,它就足以解释 $B$,$C$ 的嫌疑随之下降。这种现象称为解释掉(explaining away),是贝叶斯网相对无向图多出来的结构。马尔可夫网中没有对撞,给定中间变量总是阻断。
报警网中,$B$ 与 $E$ 之间无边,且 $A$ 是对撞点,所以二者先验独立。听到报警后,$P(E=1\mid A=1,B=1)\lt P(E=1\mid A=1)$:已经用盗窃解释了报警,地震的后验随之下降。代入例11 的数值:
$$ P(E=1\mid A=1)=\frac{0.000095+0.002871}{0.0132521}\approx 0.224,\qquad P(E=1\mid A=1,B=1)=\frac{0.000095}{0.000095+0.009306}\approx 0.0101. $$
而先验 $P(E=1)=0.01$:仅听到报警,地震概率升到约 $0.224$;再确认是盗窃,就几乎回落到先验。
感性理解:车开不动有两个可能原因,引擎故障(引擎异响)与没油(油灯亮),二者事先独立。知道车开不动之后,若确认是引擎的问题,没油的嫌疑就下降。证据(车开不动)把两条原因连了起来。
精确推断
贝叶斯网上最常见的询问是:给定证据 $E=e$,查询变量 $Q$ 的后验 $P(Q\mid E=e)$。由于贝叶斯网就是一张 CPT 为因子的因子图,上一章的变量消去几乎可以原样使用,只多一步归一化:
- 把每个 CPT 当作一个因子;
- 代入证据:把与 $E=e$ 冲突的行置零(或删除);
- 对其余隐变量(既非查询也非证据的变量)做变量消去,得到只含 $Q$ 的因子 $f(q)$,它等于 $P(Q=q,E=e)$;
- 归一化:$P(q\mid e)=f(q)/\sum_{q'}f(q')$,分母正是 $P(E=e)$。
在消去之前还可以先做一步化简(CS221 讲义的一般策略):既不是查询变量或证据变量本身、也不是它们祖先的那些节点,可以直接从网中删掉(逐个删去这样的叶子)——对一个叶子节点求和,其 CPT 加总为 $1$,等于没有。
枚举(enumeration) 是变量消去的笨实现:对隐变量的全部赋值累加 $\prod_i\mathrm{CPT}_i$,复杂度为 $O(n|\mathcal{D}|^{n-|E|})$,不管图结构如何。变量消去利用因子的局部性,复杂度降到由树宽决定。
MAP / MPE(最可能解释,most probable explanation)把求和换成最大化,求最可能的完整赋值。需要区分:对每个变量单独取 $\arg\max P(X_i\mid e)$ 得到的“边缘众数”,可以不是 MAP 赋值(因子图一章的例10 中已有具体反例)。
车辆跟踪作业中,若把整条轨迹 $z_{1:T}$ 当成一张静态网做精确推断,变量数随 $T$ 增长,树宽也随轨迹变长而增大,算不动。必须改用 HMM 的前向递推(见下一节)或粒子滤波。
Gibbs 采样(Gibbs sampling) 是另一种近似推断:固定证据变量,轮流对每个非证据变量 $X_i$,从它的满条件分布 $P(X_i\mid X_{-i},e)$ 中抽样并更新。满条件只依赖 $X_i$ 的 Markov blanket(父节点、子节点、子节点的其他父节点),正比于“含 $X_i$ 的那些 CPT 之积”:
$$ P(X_i=v\mid X_{-i},e)\propto\prod_{j:\,X_i\in\{X_j\}\cup\mathrm{Pa}(j)}P(x_j\mid x_{\mathrm{Pa}(j)})\Big|_{X_i=v}. $$
这条马尔可夫链的平稳分布就是真实后验。但混合慢时相邻样本高度相关,不能把连续的状态当作独立样本来报误差条。燃烧期(burn-in)与间隔抽样(thinning)属于实现细节,作业通常只要求写出满条件正比于“含 $X_i$ 的那些 CPT 之积”。它与因子图上的迭代条件众数(ICM,每次取使局部因子之积最大的值)是一对:ICM 取最大容易困在局部最优,Gibbs 按比例抽样,通常能跳出来。
▸精确推断的定位
证据下的边缘 = 因子图上的变量消去 + 一次归一化。来源是同一套消去,不是新的概率公理。
- 相对 CSP:输出是数字 $P(q\mid e)\in[0,1]$,不是“有没有可行赋值”。
- 相对 MAP:边缘与最可能完整赋值回答的是不同问题。
- 相对 Gibbs:精确法没有方差,但树宽高时不可用。
隐马尔可夫模型与前向算法
很多跟踪问题有天然的时间结构:一辆车的真实位置 $z_t$ 看不见,只能看到每个时刻带噪的传感器读数 $x_t$;位置按动力学一步步演化,每个读数只依赖当时的位置。把这种结构画成贝叶斯网,就是一条链。
隐马尔可夫模型(hidden Markov model, HMM) 是贝叶斯网的时间链:隐状态 $z_t$ 只依赖 $z_{t-1}$,观测 $x_t$ 只依赖 $z_t$,
$$ P(z_{1:T},x_{1:T})=P(z_1)\prod_{t=2}^{T}P(z_t\mid z_{t-1})\prod_{t=1}^{T}P(x_t\mid z_t). $$
其中 $P(z_t\mid z_{t-1})$ 称为转移模型(transition model),$P(x_t\mid z_t)$ 称为观测(发射)模型(emission model)。车辆跟踪作业中,$z$ 是位置(或离散化的位姿),$x$ 是传感器读数。两类常见询问:
- 滤波(filtering):$P(z_t\mid x_{1:t})$,只用到当前为止的观测,用于在线跟踪;
- 平滑(smoothing):$P(z_t\mid x_{1:T})$,用上全部观测(包括未来的),用于事后回放。
前向递推。定义前向信息 $\alpha_t(z)=P(z_t=z,x_{1:t})$,即“在时刻 $t$ 处于 $z$ 并且看到了 $x_{1:t}$”的联合概率。它满足
$$ \alpha_1(z)=P(z_1=z)P(x_1\mid z), \qquad \alpha_{t}(z)=P(x_t\mid z)\sum_{z'}\alpha_{t-1}(z')P(z_t=z\mid z_{t-1}=z'). $$
推导:对上一时刻状态 $z'$ 展开,$\alpha_t(z)=\sum_{z'}P(z_{t-1}=z',z_t=z,x_{1:t})$;按 HMM 的分解,这一项等于 $P(z_{t-1}=z',x_{1:t-1})\cdot P(z_t=z\mid z_{t-1}=z')\cdot P(x_t\mid z_t=z)$——第二个因子成立是因为给定 $z_{t-1}$ 后 $z_t$ 与过去观测无关(链结构被 $z_{t-1}$ 阻断),第三个因子成立是因为 $x_t$ 只依赖 $z_t$。把与 $z'$ 无关的 $P(x_t\mid z)$ 提到求和外面,即得上式。
把 $\alpha_t$ 归一化即得滤波分布:$P(z_t=z\mid x_{1:t})=\alpha_t(z)/\sum_{z''}\alpha_t(z'')$。离散状态数 $|Z|$ 较小时这是精确的,时间 $O(T|Z|^{2})$(每步对每个 $z$ 求一个 $|Z|$ 项的和),不随树宽爆炸。若 $|Z|$ 是连续网格或高维位姿,前向表存不下,就要改用粒子滤波。
平滑还需要对称的后向递推 $\beta_t(z)=P(x_{t+1:T}\mid z_t=z)=\sum_{z''}P(z_{t+1}=z''\mid z_t=z)P(x_{t+1}\mid z'')\beta_{t+1}(z'')$,$\beta_T\equiv 1$,然后 $P(z_t=z\mid x_{1:T})\propto\alpha_t(z)\beta_t(z)$(前向-后向算法,forward-backward)。
▸前向递推的含义
$\alpha_t(z)$ 把“走到 $z$ 并且看到 $x_{1:t}$”的概率拆成三步:先以任意 $z'$ 结束上一步($\alpha_{t-1}$),再乘转移概率,再乘本步的观测似然。这是变量消去按时间顺序消去 $z_{t-1}$ 的特化:链的树宽为 $1$,所以永远只有两个变量的中间表。它不是新公式。
感性理解:每个格子存“有多少概率质量经过这里,并且没和传感器打架”。下一步先把质量按动力学推移,再按新观测打折。归一化之后才是后验。若漏掉归一化,连续乘似然会让数值下溢到 $0$;实现时通常每步都归一化(或在 log 域计算)。
粒子滤波(Particle Filtering)
当状态空间很大(例如车辆位置是细网格甚至连续坐标)时,前向算法每步 $O(|Z|^{2})$ 的代价不可承受。粒子滤波的想法是:不存整张分布表,而是用 $K$ 个“粒子”(具体的状态样本)来代表分布,概率大的地方粒子多、权重大。每一步只需要移动、打分、重新分配这 $K$ 个粒子。
粒子滤波维护一组加权粒子 $\{z^{(k)},w^{(k)}\}_{k=1}^{K}$,其中 $z^{(k)}$ 是第 $k$ 个粒子所在的状态,$w^{(k)}\ge 0$ 是它的权重。每个时间步做三件事:
- 提议(proposal):按转移核 $P(z_t\mid z_{t-1})$,让每个粒子从上一时刻的位置随机前进一步;
- 加权(weighting):用观测似然 $P(x_t\mid z^{(k)})$ 乘到权上;
- 归一化与重采样(resampling):把权归一化;若有效样本量 $\mathrm{ESS}=1/\sum_k (w^{(k)})^{2}$ 过低,则按权有放回地重采样 $K$ 个粒子,并把权复位为均匀。
ESS 的取值在 $1$(全部权集中在一颗粒子上)与 $K$(权完全均匀)之间,衡量“相当于多少颗有效粒子”。CS221 讲义中的版本每一步都重采样;下面的代码只在 ESS 低于 $K/2$ 时重采样,是常见的折中。
1 | def particle_filter_step(particles, weights, transition, likelihood, rng): |
▸粒子权为何乘似然
这是序贯的重要性采样(importance sampling),以转移核 $P(z_t\mid z_{t-1})$ 作为提议分布。目标是滤波后验
$$ P(z_t\mid x_{1:t})\propto P(x_t\mid z_t)P(z_t\mid x_{1:t-1}). $$
前一步的粒子已经近似 $P(z_{t-1}\mid x_{1:t-1})$;按转移核推移一步后,它们近似的是预测分布 $P(z_t\mid x_{1:t-1})=\sum_{z_{t-1}}P(z_t\mid z_{t-1})P(z_{t-1}\mid x_{1:t-1})$。目标与提议之比正是 $P(x_t\mid z_t)$,故权乘以 $P(x_t\mid z_t)$。可以看到,这与前向递推“先推移、再乘似然”的两步完全对应。
重采样按权有放回地复制粒子,并把权复位成均匀。ESS 低表示少数粒子占了大部分权,如果不重采样,下一步几乎只在更新那几颗粒子,其余粒子白白浪费计算(权退化,weight degeneracy)。但过于频繁的重采样会增加方差(每次重采样都引入额外随机性,并减少粒子多样性)。当所有权都变成 $0$(似然模型与粒子完全不相容)时必须重启,不能除以 $0$。
感性理解:粒子先按动力学撒到下一时刻,再看谁更像当前的传感器读数。像的权变大,不像的权变小。权都集中到少数粒子时,重采样把计算预算重新摊开到概率高的区域。观测模型写错,权就会把粒子赶到错误的位置。
▸粒子滤波的定位
粒子滤波用有限样本近似随时间变化的后验,思想来源是序贯蒙特卡洛(sequential Monte Carlo),不是卡尔曼滤波的非线性补丁。
- 相对精确前向:时间长度不再进入树宽,每步代价只与 $K$ 有关,但有样本方差。
- 相对卡尔曼滤波(Kalman filter):不要求线性高斯。
- 粒子数不足会导致退化,这不是调学习率能修好的问题。
- 相对 Gibbs:粒子滤波沿时间向前走,Gibbs 在一张静态网上反复扫描变量。
▸例12 一维跟踪
位置在 $\{1,\ldots,10\}$ 中取值。转移模型:以 $0.8$ 的概率留在原格、各以 $0.1$ 的概率向左右相邻格移动(端点反射)。(原笔记写作“以 $0.8$ 留在邻格”,按概率和为 $1$ 应理解为留在原格,这里改正。)观测是带噪的距离读数,似然在真位置附近呈峰。用 $20$ 个粒子,从均匀分布出发。连续两步观测都偏向 $8$ 附近之后,粒子应集中到右侧。
几种典型错误及其表现:
- 若似然写成均匀,权永远不更新,滤波就退化为开环仿真(只按动力学乱走,与观测无关)。
- 若转移写成“永远停在原地”,粒子只能留在初始位置,若初始没有粒子落在 $8$ 附近,就永远到不了 $8$。
- 若 $K=3$ 且从不重采样,两步之后权会堆在一颗粒子上,后验方差被严重低估。
调试方法:$|Z|=10$ 时精确前向算法完全算得动,可以当对拍:粒子直方图应接近归一化的 $\alpha_t$。
▸对照:CSP 与贝叶斯网
- CSP 问有没有可行赋值,或最大权赋值;贝叶斯网问概率。
- 同一张图,$0/1$ 因子换成 CPT 之后,算法骨架(消去、采样)仍在,只是输出数字的含义不同。把边缘概率当成“最可能的完整赋值”会错。
- HMM 前向是变量消去在时间链上的特化;粒子滤波是前向算法在大 $|Z|$ 上的样本版。
▸小结与衔接
- 贝叶斯网 = DAG + 局部归一化的 CPT,联合分布为 CPT 之积。
- d-分离按链、分叉、对撞三种结构读条件独立;对撞在观察后开通,产生“解释掉”。
- 精确推断是变量消去加归一化;Gibbs 采样按 Markov blanket 上的满条件抽样。
- HMM 前向递推 $O(T|Z|^{2})$ 精确滤波;粒子滤波以“提议—加权—重采样”近似大状态空间上的滤波。
变量轴处理的是有限定义域上的数值。下一章把“真与假”写成逻辑公式,推断问的是蕴含(entailment),不再对定义域求和。概率 $0.99$ 不是蕴含;逻辑作业若出现反模型(counter-model),不能用“通常成立”来搪塞。
逻辑(Logic):命题逻辑、消解与一阶逻辑
前面几章的模型输出的都是“程度”:反射模型输出分数,搜索与 MDP 输出代价或回报,贝叶斯网输出概率,CSP 输出一个满足约束的赋值。可是有一类问题要的不是“有多可能”,而是“是否必然”。例如知识库里写着“下雨则地湿”“今天下雨”,问“地湿吗”——答案应当是一个有保证的“是”,而不是“0.99 的概率是”。逻辑(logic)就是为这种需求设计的:用公式描述世界,再问某个查询公式是否在所有与知识一致的世界里都成立。
核心想法一句话:把知识写成公式,推断就是判断蕴含 $\mathrm{KB}\models\phi$——知识库的每一个模型是否都满足查询。 推断的输出是一个证明(说明蕴含成立),或者一个反模型(counter-model,一个让 KB 为真而 $\phi$ 为假的赋值,说明蕴含不成立)。这和上一章的输出(概率或赋值)是不同类型的对象。课程作业 From Language to Logic 正是要把自然语言句子翻译成一阶公式,再交给推断引擎。
本章的路线是:先讲命题逻辑的语法与语义(模型、知识库、蕴含、可满足性),再讲推断规则(modus ponens 与消解,以及它们各自的完备性条件),然后把同样的思路推广到一阶逻辑(量词、替换、合一、Skolem 化),最后列出实现与作业中的常见坑。本章之后,课程扩展部分会回到统计语言模型——二者都处理“语言”,但回答的是不同问题。
命题逻辑的语法与语义
语法:公式怎么写
CS221 把一种逻辑拆成三部分:语法(syntax,哪些符号串是合法公式)、语义(semantics,公式在什么情况下为真)、推断规则(inference rules,如何从已有公式机械地推出新公式)。语法只管形式,语义才赋予意义。
命题逻辑的语法如下:
- 命题符号(propositional symbol)$P,Q,\ldots$(如 $P,Q,R$)是原子公式,每个代表一个可真可假的陈述,如 $\mathrm{Rain}$、$\mathrm{Wet}$。
- 若 $f,g$ 是公式,则 $\neg f$、$f\wedge g$、$f\vee g$、$f\rightarrow g$(以及 $f\leftrightarrow g$)也是公式。四个基本连接词 $\neg,\wedge,\vee,\rightarrow$ 依次读作非、与、或、蕴涵(如果……则……)。
- 常用的结合优先级约定是 $\neg$ 最高,其次 $\wedge$、$\vee$,再其次 $\rightarrow$、$\leftrightarrow$。拿不准时一律加括号。
语义:模型与真值
语义的基本对象是模型(model),也叫赋值(assignment):给每个命题符号指定真(1)或假(0)。$n$ 个符号共有 $2^n$ 个模型。给定模型 $w$,公式 $f$ 的真值按真值表递归定义(记作解释函数 $\mathcal{I}(f,w)\in\{0,1\}$):$\neg f$ 与 $f$ 相反;$f\wedge g$ 两者都真才真;$f\vee g$ 至少一个真就真;$f\rightarrow g$ 只在 $f$ 真而 $g$ 假时为假,即等价于 $\neg f\vee g$。
把一个公式 $f$ 看成它所允许的模型集合 $\mathcal{M}(f)=\{w:\mathcal{I}(f,w)=1\}$,是理解后面所有概念的关键:公式越“强”,排除的世界越多,$\mathcal{M}(f)$ 越小。
知识库、蕴含、可满足性与有效性
知识库(knowledge base, KB)是一组公式,视为它们的合取;因此 $\mathcal{M}(\mathrm{KB})$ 是各公式模型集合的交集。每加入一条知识,交集就收缩一点。
蕴含(entailment)
$$ \mathrm{KB}\models\phi $$
当且仅当每个使 KB 为真的赋值也使 $\phi$ 为真,即 $\mathcal{M}(\mathrm{KB})\subseteq\mathcal{M}(\phi)$。直觉上:$\phi$ 没有带来新信息,它已经被 KB 隐含了。
与蕴含配套的几个概念:
- 可满足(satisfiable):存在至少一个模型使公式为真,即 $\mathcal{M}(f)\neq\varnothing$。
- 有效(valid,重言式):在所有赋值下都为真,例如 $P\vee\neg P$。
- 矛盾(contradiction):KB 的模型中没有一个满足 $\phi$,即 $\mathcal{M}(\mathrm{KB})\cap\mathcal{M}(\phi)=\varnothing$,等价于 $\mathrm{KB}\models\neg\phi$。
- 偶然(contingency):$\phi$ 与 KB 相容但不被蕴含,交集非空且是真子集——加入它会带来真正的新信息。
蕴含可以归约为可满足性判定,这是本章最重要的一个等价:
$$ \mathrm{KB}\models\phi\iff \mathrm{KB}\wedge\neg\phi\ \text{不可满足}. $$
理由很直接:若存在模型同时满足 KB 和 $\neg\phi$,它就是 KB 真而 $\phi$ 假的反模型,蕴含不成立;反之没有这样的模型,KB 的每个模型都必须满足 $\phi$。这意味着只要有一个可满足性检查器(SAT 求解器、CSP 求解器或消解),就能回答蕴含问题。
CS221 用 Ask / Tell 接口描述一个逻辑系统怎样与用户交互:
| 操作 | 情形 | 系统回应 |
|---|---|---|
| Ask $\phi$ | $\mathrm{KB}\models\phi$(蕴含) | 是(Yes) |
| Ask $\phi$ | $\mathrm{KB}\models\neg\phi$(矛盾) | 否(No) |
| Ask $\phi$ | 偶然 | 不知道(I don’t know) |
| Tell $\phi$ | 蕴含 | 已经知道了(I already knew that) |
| Tell $\phi$ | 矛盾 | 不相信(I don’t believe that) |
| Tell $\phi$ | 偶然 | 学到了新东西,把 $\phi$ 加入 KB |
注意“不知道”是一个合法且常见的答案:逻辑不会像概率那样说“大概是”。
▸逻辑蕴含与前几章的区别
蕴含的含义是:知识库的每个模型都满足查询。它来自经典的证明论与模型论,不是“概率大于某个阈值”。
- 相对贝叶斯网:没有“有多可能”,只有“是否必然”。(若给模型加上均匀权重,$P(\phi\mid\mathrm{KB})$ 等于 1 恰好对应蕴含,但逻辑推断不需要也不计算这个数。)
- 相对检索:公式可以推出从未写过的句子,只要它在所有模型里为真。
- 相对 CSP:CSP 找一个满足赋值;蕴含要求所有满足 KB 的赋值都满足 $\phi$。
模型检测:把真值表走完
最直接的推断方法是模型检测(model checking):枚举所有模型,检查有没有 KB 真而 $\phi$ 假的行。
▸例13 三个符号的真值表
$\mathrm{KB}=\{P\vee Q,\ \neg P\vee R\}$,问是否 $\mathrm{KB}\models Q\vee R$。
枚举 $(P,Q,R)$ 共 $8$ 行。先找使 KB 为真的行:$P\vee Q$ 排除 $P=Q=0$ 的两行;$\neg P\vee R$ 排除 $P=1,R=0$ 的两行。剩下四行,逐一检查查询:
- $(0,1,0)$ 给出 $Q\vee R=1$;
- $(0,1,1)$ 为 $1$;
- $(1,0,1)$ 为 $1$;
- $(1,1,1)$ 为 $1$。
没有 KB 真而 $Q\vee R$ 假的行,故蕴含成立。若查询改成 $R$,则 $(0,1,0)$ 是反模型:KB 真而 $R$ 假,所以 $\mathrm{KB}\not\models R$。
模型检测就是把这张表走完。$n$ 个符号 $2^{n}$ 行,符号少时够用。
实际中不会真的枚举 $2^n$ 行,而是把“$\mathrm{KB}\wedge\neg\phi$ 是否可满足”交给 SAT 求解器:DPLL 用回溯加单元传播(本质就是 CSP 里的回溯搜索加约束传播),WalkSat 用局部搜索。需要注意局部搜索找不到解并不证明不可满足,它只能找到模型,不能证明“没有模型”。
推断规则:modus ponens 及其完备性条件
模型检测在语义层面工作;另一条路是在语法层面工作:不看模型,只按规则把公式变形。推断规则(inference rule)写成“前提 $f_1,\ldots,f_k$ 推出结论 $g$”。反复应用规则得到的公式记作 $\mathrm{KB}\vdash g$(可推导)。
评价一组规则有两个标准:
- 可靠性(soundness):推导出的都被蕴含,$\mathrm{KB}\vdash g\Rightarrow\mathrm{KB}\models g$。不可靠的规则会“证明”假话,不能用。
- 完备性(completeness):被蕴含的都能推导出,$\mathrm{KB}\models g\Rightarrow\mathrm{KB}\vdash g$。不完备的规则会漏掉真结论。
Modus ponens(肯定前件)是最常见的规则:
$$ \frac{p_1,\ \ldots,\ p_k,\qquad (p_1\wedge\cdots\wedge p_k)\rightarrow q}{q}. $$
它显然可靠,但一般不完备。它完备的条件是:KB 只由 Horn 子句(Horn clause)组成,且查询是单个命题符号。Horn 子句分两类:确定子句(definite clause)$(p_1\wedge\cdots\wedge p_k)\rightarrow q$($k=0$ 时就是事实 $q$),以及目标子句(goal clause)$(p_1\wedge\cdots\wedge p_k)\rightarrow\mathrm{false}$。写成析取形式,Horn 子句就是“至多含一个正文字”的子句。在这个限制下,前向推理(forward chaining,不断用 modus ponens 把新事实加入 KB 直到不再变化)是完备的,而且可以在与 KB 大小成线性的时间内完成。
▸例 modus ponens 能做什么、不能做什么
Horn KB:$\mathrm{Rain}$,$\mathrm{Weekday}$,$\mathrm{Rain}\wedge\mathrm{Weekday}\rightarrow\mathrm{Traffic}$,$\mathrm{Traffic}\wedge\mathrm{Careless}\rightarrow\mathrm{Accident}$。前向推理一步得到 $\mathrm{Traffic}$;第二条规则缺 $\mathrm{Careless}$,不再触发。$\mathrm{Accident}$ 不被推出,它也确实不被蕴含(偶然)。
非 Horn KB:$\mathrm{Rain}\vee\mathrm{Snow}$,$\mathrm{Rain}\rightarrow\mathrm{Traffic}$,$\mathrm{Snow}\rightarrow\mathrm{Traffic}$。在每个模型里要么下雨要么下雪,两种情况都堵车,所以 $\mathrm{KB}\models\mathrm{Traffic}$。但 modus ponens 需要先单独得到 $\mathrm{Rain}$ 或 $\mathrm{Snow}$,而 KB 只给出它们的析取,规则一次也触发不了。这说明 modus ponens 对一般命题逻辑不完备,需要更强的规则——消解。
CNF 与消解(Resolution)
消解只有一条规则,却对整个命题逻辑(以反驳的方式)完备。代价是它要求公式先写成统一的形状:合取范式。
合取范式及其转换
合取范式(conjunctive normal form, CNF):公式写成子句的合取,子句(clause)是文字(literal,命题或其否定)的析取。例如 $(\neg P\vee R)\wedge(\neg Q\vee R)$。任何命题公式都能等价地化成 CNF,步骤是:
- 消去 $\rightarrow$:$A\rightarrow B$ 改成 $\neg A\vee B$($\leftrightarrow$ 先拆成两个方向的 $\rightarrow$);
- 把 $\neg$ 内移(德摩根:$\neg(A\wedge B)=\neg A\vee\neg B$,$\neg(A\vee B)=\neg A\wedge\neg B$),消去双否定 $\neg\neg A=A$;
- 分配 $\vee$ 过 $\wedge$:$A\vee(B\wedge C)=(A\vee B)\wedge(A\vee C)$,直到顶层只有 $\wedge$。
每一步都是逻辑等价变换,所以模型集合不变。
▸例 化 CNF
$(P\vee Q)\rightarrow R$
$\;\Rightarrow\;\neg(P\vee Q)\vee R$(消去 $\rightarrow$)
$\;\Rightarrow\;(\neg P\wedge\neg Q)\vee R$(德摩根)
$\;\Rightarrow\;(\neg P\vee R)\wedge(\neg Q\vee R)$(分配)。
结果是两个 Horn 子句:“$P$ 推出 $R$”与“$Q$ 推出 $R$”,与直觉一致。
分配会使公式指数变长,这是代价,不是可选步骤:例如 $(A_1\wedge B_1)\vee\cdots\vee(A_n\wedge B_n)$ 展开后有 $2^n$ 个子句(每个析取项选 $A_i$ 或 $B_i$ 之一)。(实践中的 SAT 求解器会引入辅助变量做只保持可满足性的线性转换,课程不要求。)
子句常写成文字集合;$\{ \}$ 表示空子句,即没有任何文字的析取,等价于 $\mathrm{false}$,不可满足。
消解规则
消解(resolution)取含互补文字 $P$ 与 $\neg P$ 的两子句,合并其余文字并去掉互补对。推出空子句则不可满足。
▸消解一步
子句 $C\vee P$ 与 $D\vee\neg P$ 推出 $C\vee D$:
$$ \frac{C\vee P,\qquad D\vee\neg P}{C\vee D}. $$
语义上:任一赋值使 $P$ 为真则须满足 $D$(第二个子句里 $\neg P$ 已假),为假则须满足 $C$(第一个子句里 $P$ 已假),故 $C\vee D$ 在两子句同时真时为真——这就是规则可靠的理由。重复直到空子句或无法再产生新子句(命题情形子句集合有限,会停)。
因子(factoring):同一子句里重复文字合成一个,否则永远消不干净。消解不是把两个子句的所有文字无条件并起来;必须先选一对互补文字。(一次只消一对:$P\vee Q$ 与 $\neg P\vee\neg Q$ 不能同时消去两对得到空子句,正确结果是重言式 $Q\vee\neg Q$ 或 $P\vee\neg P$。)
感性理解:两句话分别说“$P$ 或左边”“非 $P$ 或右边”,合在一起至少一边成立。空子句表示两边都被抽空,知识库加查询的否定走不通。
modus ponens 是消解的特例:$P$ 与 $\neg P\vee Q$(即 $P\rightarrow Q$)消解得 $Q$。
反驳完备性与消解证明
命题消解是反驳完备(refutation complete)的:不可满足的 CNF 一定能推出空子句。它不是证明完备到“任何重言式都能用几步消解直接写出”,标准用法是反驳:要证 $\mathrm{KB}\models\phi$,对 $\mathrm{KB}\wedge\neg\phi$ 的 CNF 做消解。完整流程是:
- 把 $\neg\phi$ 加入 KB;
- 把所有公式化成 CNF,拆成子句集合;
- 反复对含互补文字的子句对做消解,把新子句加入集合;
- 得到空子句 → 蕴含成立;不再产生新子句而没有空子句 → 蕴含不成立(此时 $\mathrm{KB}\wedge\neg\phi$ 可满足,存在反模型)。
消解在最坏情况下是指数时间的——这并不意外:命题可满足性(SAT)是 NP 完全的,而蕴含判定等价于判定 $\mathrm{KB}\wedge\neg\phi$ 不可满足(其补问题,coNP 完全),目前没有已知的多项式时间算法。相比之下,Horn KB 上的 modus ponens 是线性时间;这正是“表达能力换效率”的取舍。
▸例14 消解反驳
KB:$P\vee Q$,$\neg Q\vee R$,$\neg R$。查询 $P$。把 $\neg P$ 加入。子句:$(P\vee Q)$,$(\neg Q\vee R)$,$(\neg R)$,$(\neg P)$。
- $P\vee Q$ 与 $\neg P$ 消解得 $Q$;
- $Q$ 与 $\neg Q\vee R$ 得 $R$;
- $R$ 与 $\neg R$ 得空子句。
故 $\mathrm{KB}\models P$。若漏加 $\neg P$,只从 KB 消解,得不到空子句(KB 本身可满足),不能据此否定蕴含。
▸例 用消解补上 modus ponens 的漏洞
回到非 Horn KB:$\mathrm{Rain}\vee\mathrm{Snow}$,$\mathrm{Rain}\rightarrow\mathrm{Traffic}$,$\mathrm{Snow}\rightarrow\mathrm{Traffic}$,查询 $\mathrm{Traffic}$。加入 $\neg\mathrm{Traffic}$ 并化 CNF,子句为 $(\mathrm{Rain}\vee\mathrm{Snow})$,$(\neg\mathrm{Rain}\vee\mathrm{Traffic})$,$(\neg\mathrm{Snow}\vee\mathrm{Traffic})$,$(\neg\mathrm{Traffic})$。
- $\neg\mathrm{Traffic}$ 与 $\neg\mathrm{Snow}\vee\mathrm{Traffic}$ 得 $\neg\mathrm{Snow}$;
- $\neg\mathrm{Traffic}$ 与 $\neg\mathrm{Rain}\vee\mathrm{Traffic}$ 得 $\neg\mathrm{Rain}$;
- $\mathrm{Rain}\vee\mathrm{Snow}$ 与 $\neg\mathrm{Snow}$ 得 $\mathrm{Rain}$;
- $\mathrm{Rain}$ 与 $\neg\mathrm{Rain}$ 得空子句。
所以 $\mathrm{KB}\models\mathrm{Traffic}$。第 3 步对非 Horn 子句 $\mathrm{Rain}\vee\mathrm{Snow}$ 做了“情况分析”,这正是 modus ponens 做不到的。
一阶逻辑(First-Order Logic)
命题逻辑的符号是不可分的整体。要表达“每个学生都知道算术”,只能为每个学生各写一个符号 $\mathrm{AliceKnowsArithmetic}$、$\mathrm{BobKnowsArithmetic}$……既冗长,又无法表达“所有人”这种对未知个体的断言。一阶逻辑把世界看成对象及其关系,并引入量词,用一条公式概括整个模式。
语法:项、原子与量词
一阶逻辑(first-order logic, FOL)加对象、谓词、函数与量词 $\forall,\exists$。
- 项(term)指代对象,是常量(如 $\mathrm{John}$)、变量(如 $x$)或函数作用在项上(如 $\mathrm{Mother}(\mathrm{John})$)。
- 原子公式(atomic formula)是谓词作用在项上,如 $\mathrm{Knows}(\mathrm{John},x)$,取值真或假。
- 公式由原子经连接词与量词组合而成:$\forall x\,f$(对所有 $x$),$\exists x\,f$(存在 $x$)。
两种量词搭配的连接词有固定习惯:$\forall$ 通常配 $\rightarrow$,$\exists$ 通常配 $\wedge$。“所有学生都知道算术”写成 $\forall x\,\mathrm{Student}(x)\rightarrow\mathrm{Knows}(x,\mathrm{Arithmetic})$;若误写成 $\forall x\,\mathrm{Student}(x)\wedge\mathrm{Knows}(x,\mathrm{Arithmetic})$,意思变成“所有东西都是学生且都知道算术”。“有学生知道算术”写成 $\exists x\,\mathrm{Student}(x)\wedge\mathrm{Knows}(x,\mathrm{Arithmetic})$;若误写成 $\rightarrow$,只要存在一个非学生就平凡为真。量词次序也有意义:$\forall x\,\exists y\,\mathrm{Knows}(x,y)$(人人都认识某个人)与 $\exists y\,\forall x\,\mathrm{Knows}(x,y)$(有一个人被所有人认识)是不同的命题。
语义:解释
赋值变成论域上的解释(interpretation):论域非空,常量映射到对象,谓词映射到关系(对象元组的集合),函数映射到论域上的函数。蕴含仍是“所有解释、所有变量赋值”下 KB 真则 $\phi$ 真。与命题逻辑不同,解释可以有无限多个、论域可以无限大,所以没法再枚举真值表。
替换与合一
一阶推断的核心操作是让带变量的公式“对上”具体事实。替换(substitution)$\theta$ 把变量映射到项,$\mathrm{Subst}[\theta,f]$ 表示把 $\theta$ 应用到 $f$。
合一(unification)找使两文字相同的最一般替换(most general unifier, MGU),不存在时返回失败。“最一般”指不做多余的绑定,其他可行替换都能由它再细化得到。
- $\mathrm{Knows}(\mathrm{John},x)$ 与 $\mathrm{Knows}(\mathrm{John},\mathrm{Jane})$ 的 MGU 是 $\{x/\mathrm{Jane}\}$。
- $\mathrm{Knows}(\mathrm{John},x)$ 与 $\mathrm{Knows}(y,\mathrm{Mother}(y))$ 的 MGU 是 $\{y/\mathrm{John},\ x/\mathrm{Mother}(\mathrm{John})\}$。
- $\mathrm{Knows}(\mathrm{John},x)$ 与 $\mathrm{Knows}(x,\mathrm{Jane})$ 直接合一失败($x$ 不能同时是 John 和 Jane);但两条公式里的 $x$ 本是各自量化的独立变量,先改名(standardizing apart)成 $x_1,x_2$ 后即可合一为 $\{x_2/\mathrm{John},\ x_1/\mathrm{Jane}\}$。
出现检查(occur check):$x$ 与 $f(x)$ 不能合一,否则会得到无限项 $f(f(f(\cdots)))$。
一阶 modus ponens 与消解
一阶 modus ponens:对 Horn 子句 $\forall x_1\cdots(a_1\wedge\cdots\wedge a_k)\rightarrow b$ 与事实 $a'_1,\ldots,a'_k$,找替换 $\theta$ 使每个 $a'_i$ 与 $a_i$ 合一,然后推出 $\mathrm{Subst}[\theta,b]$。例如事实 $\mathrm{Takes}(\mathrm{alice},\mathrm{cs221})$、$\mathrm{Covers}(\mathrm{cs221},\mathrm{mdp})$ 与规则 $\forall x,y,z\ \mathrm{Takes}(x,y)\wedge\mathrm{Covers}(y,z)\rightarrow\mathrm{Knows}(x,z)$,在 $\theta=\{x/\mathrm{alice},y/\mathrm{cs221},z/\mathrm{mdp}\}$ 下推出 $\mathrm{Knows}(\mathrm{alice},\mathrm{mdp})$。它对一阶 Horn 子句 KB 完备。
一阶消解先合一互补文字,再把替换应用到其余文字。例如子句 $\neg\mathrm{Human}(x)\vee\mathrm{Mortal}(x)$(即“人都会死”)、$\mathrm{Human}(\mathrm{Socrates})$,以及查询的否定 $\neg\mathrm{Mortal}(\mathrm{Socrates})$:在 $\{x/\mathrm{Socrates}\}$ 下前两项中的 $\mathrm{Mortal}$ 互补,得 $\neg\mathrm{Human}(\mathrm{Socrates})$;再与 $\mathrm{Human}(\mathrm{Socrates})$ 消解得空子句,故 Socrates 会死。
另一种思路是还原为命题逻辑(propositionalization):若没有函数符号、常量有限,就把每条全称公式对所有常量实例化,得到一个(可能很大的)命题 KB,再用命题方法求解。有函数符号时实例化会无穷无尽,这引出下面的限制。
一阶推断有一个命题逻辑没有的根本限制:它是半可判定(semi-decidable)的。若 $\mathrm{KB}\models\phi$,完备的推断过程(如消解)一定会在有限步内找到证明;若不蕴含,算法可能永远跑下去,无法在有限时间内确认。根源是函数符号可以无限嵌套出新项($f(c),f(f(c)),\ldots$),推导永不饱和。
一阶 CNF 与 Skolem 化
一阶公式要用消解,也得先化成子句。步骤是在命题 CNF 转换的基础上增加:先把 $\neg$ 推过量词($\neg\forall x\,f=\exists x\,\neg f$,$\neg\exists x\,f=\forall x\,\neg f$),给不同量词的变量改名,再消去存在量词,最后丢掉全称量词(子句中的变量默认全称)。
Skolem 化:把 $\exists$ 换成新函数,依赖所有外层 $\forall$ 变量。$\forall x\,\exists y\,R(x,y)$ 变成 $\forall x\,R(x,f(x))$,不能换成同一个常量 $c$,否则语义变成“所有 $x$ 共享同一个 $y$”。$\exists y\forall x$ 才能 Skolem 成常量。量词外移必须先改成前束形(prenex form,所有量词都在最外层),改名避免捕获(两个同名的约束变量被提到一起后指向同一对象)。Skolem 化后的公式与原式一般不等价,但保持可满足性,这对反驳证明已经足够。
▸Skolem 依赖
$\forall x\,\exists y$ 的 $y$ 可以随 $x$ 变,必须引入一元函数 $f(x)$。$\exists y\forall x$ 的 $y$ 先被选定,再用全称,Skolem 常量即可。把二者写反,得到的 CNF 证明的是另一句话。
感性理解:每个学生都有一张学生证,证号可以不同,不能先造一个全局常量 ID0 塞给所有人。存在一个校长管理所有系,才是常量。
▸一阶逻辑与其他模型的区别
一阶逻辑在对象与关系上写全称 / 存在命题。它来自谓词演算,课程用它接自然语言作业。
- 相对命题逻辑:同一模式不必为每个常元复制一份子句。
- 相对语言模型:公式的真假可检查,句子的流畅不能代替模型论语义。
- 相对贝叶斯网:没有 CPT,失败模式是反模型,不是低概率。
From Language to Logic:从句子到公式
作业 From Language to Logic 的难点不在推断引擎,而在翻译。把“每门课有一位教师”写成 $\forall c\,\mathrm{Course}(c)\rightarrow\exists t\,\mathrm{Teaches}(t,c)$。量词范围写错是最常见失败,不是合一实现错。存在量词不要先换成某个具体常量,除非做 Skolem 化并记录依赖。
▸例15 排课语句
“CS221 与 CS229 不能同一时段”写成 $\neg\mathrm{SameSlot}(\mathrm{CS221},\mathrm{CS229})$,或用时段函数 $\mathrm{Slot}(\mathrm{CS221})\ne\mathrm{Slot}(\mathrm{CS229})$。若写成 $\exists t\,\neg\mathrm{At}(\mathrm{CS221},t)\vee\neg\mathrm{At}(\mathrm{CS229},t)$,语义变成“存在某个时段使至少一门不在”,与意图不符——只要有一个空闲时段,这句话就平凡成立。意图是
$$ \forall t\,\neg\big(\mathrm{At}(\mathrm{CS221},t)\wedge\mathrm{At}(\mathrm{CS229},t)\big). $$
由德摩根,它等价于 $\forall t\,\big(\neg\mathrm{At}(\mathrm{CS221},t)\vee\neg\mathrm{At}(\mathrm{CS229},t)\big)$:与错误写法只差一个量词,$\exists$ 换成 $\forall$,含义完全不同。
“每门课恰好一位教师”需要两个方向:存在一位,以及若两位都教则是同一人:
$$ \forall c\,\mathrm{Course}(c)\rightarrow\Big(\exists t\,\mathrm{Teaches}(t,c)\ \wedge\ \forall t_1\,\forall t_2\,\big((\mathrm{Teaches}(t_1,c)\wedge\mathrm{Teaches}(t_2,c))\rightarrow t_1=t_2\big)\Big). $$
只写 $\exists$ 不禁止两位。自然语言里的“一位”常被漏写成存在而不写唯一。
实现注意与常见错误
- 先手写小论域的反模型,再交给求解器。量词范围错误无法靠求解器“报个错”发现:求解器证的是写下去的那句。例如对例 15 的错误写法,构造“两门课都在时段 1、时段 2 空闲”的小论域,就能看到它仍然为真。
- 合一时出现检查不能省。作业若允许函数嵌套,漏检查会接受非法替换。
- 命题消解的子句用集合,不要保留重复文字。
- 按常用约定 $\rightarrow$ 的优先级低于 $\vee$,所以 $P\vee Q\rightarrow R$ 的标准读法是 $(P\vee Q)\rightarrow R$;漏括号(或解析器优先级设错)会把它读成 $P\vee(Q\rightarrow R)$。两者不等价:取 $P=Q=1,R=0$,前者为假,后者为真。课程代码用
Implies(Or(P, Q), R)这类显式构造函数,嵌套结构就是括号,最稳妥。
▸对照:概率与逻辑
贝叶斯网给出 $P(\phi\mid e)$。逻辑给出 $\mathrm{KB}\models\phi$。把 $0.99$ 当成蕴含,或把“通常为真”写成 $\forall$,都会在作业里得到反模型。需要程度就留在概率轴;需要必然就留在逻辑轴。CSP 的可行赋值是逻辑可满足的特例(约束写成子句);蕴含比“找到一个模型”更强。
▸小结
- 逻辑 = 语法 + 语义 + 推断规则;公式的意义是它允许的模型集合。
- $\mathrm{KB}\models\phi\iff\mathrm{KB}\wedge\neg\phi$ 不可满足,蕴含问题归约为可满足性问题。
- Modus ponens 可靠,在 Horn 子句 KB 上完备且线性时间;对一般 KB 不完备。
- 消解对命题逻辑反驳完备,但需要 CNF,最坏指数时间。
- 一阶逻辑靠合一(含出现检查)与 Skolem 化(注意依赖外层 $\forall$)把命题方法推广,推断是半可判定的。
- 作业中最常见的错误是翻译时量词范围与连接词搭配写错,而不是推断实现错。
▸衔接
核心四条轴到此结束。2025 Autumn 用语言模型处理同一语言接口,但推断是 next-token 似然,不是消解。社会专题处理评价指标与供应链,不改前面的定义。流畅的句子可以不满足 KB;满足 KB 的公式可以读起来不像人话。
语言模型(Language Models)
逻辑一章把自然语言翻译成公式,靠模型论保证结论正确;但翻译需要人工,而且大量知识根本写不成干净的公式。语言模型走的是另一条路:不写规则,而是从海量文本中拟合“下一个词是什么”的条件分布。输入文本,输出下一 token 的分布,或者据此生成一段序列。当代大语言模型(基于 Transformer)在问答、翻译、写代码上都表现出色,因此 2025 Autumn 为它新增了讲座;它不是 2026 Spring 核心作业的内容。
核心想法一句话:用链式法则把序列概率拆成逐个 token 的条件概率,用数据拟合这些条件分布。 和上一章对照:逻辑用公式保证蕴含;语言模型用数据拟合条件分布,它给出的是“在训练数据分布上有多可能”,所以流畅的句子不必满足 KB。下文依次讲自回归分解、训练目标与困惑度、Transformer 如何计算条件分布、解码(提示、温度、束搜索),以及语言模型与课程前半各模型的接口。
自回归分解
文本先被切成 token(词或子词片段)序列 $x_1,\ldots,x_T$,每个 $x_t$ 取自一个有限词表 $\mathcal{V}$。自回归(autoregressive)语言模型把联合分布拆成逐步预测
$$ P(x_1,\ldots,x_T)=\prod_{t=1}^{T}P(x_t\mid x_1,\ldots,x_{t-1}). $$
简记为 $P(x_1,\ldots,x_T)=\prod_t P(x_t\mid x_{\lt t})$。这一步没有任何近似,就是概率的链式法则。它与贝叶斯网的链式法则相同,父集是全部前缀——相当于一张按时间顺序全连接的贝叶斯网。难点在于如何表示 $P(x_t\mid x_{\lt t})$:前缀可能任意长,不可能为每个前缀存一张条件概率表。
- $n$-gram 把父集截成最近 $n-1$ 个 token(马尔可夫假设),条件概率用计数估计,如二元模型 $P(x_t\mid x_{t-1})=\mathrm{count}(x_{t-1}x_t)/\mathrm{count}(x_{t-1})$。它简单,但看不到远处的上下文,且未出现过的组合概率为 0,需要平滑。
- 神经模型(含 Transformer)用网络近似完整条件:把前缀编码成向量,经线性层得到每个 token 的分数(logits),再 softmax 成分布。参数在所有位置、所有前缀间共享,因此能泛化到没见过的前缀。
训练目标与困惑度
训练就是极大似然估计:让训练语料在模型下的概率尽量大。取负对数后,训练最小化平均负对数似然
$$ J=\frac1N\sum_{i=1}^{N}\sum_{t=1}^{T_i}-\log P_{\theta}(x_t^{(i)}\mid x_{\lt t}^{(i)}). $$
其中 $N$ 是序列条数,$T_i$ 是第 $i$ 条序列长度,$\theta$ 是网络参数。每个位置的项 $-\log P_\theta(x_t\mid x_{\lt t})$ 恰是以真实下一 token 为标签的交叉熵损失,所以训练语言模型就是在每个位置上做一次 $|\mathcal{V}|$ 类的多分类,用 SGD 优化——与反射模型一章的分类训练同形,只是标签取自文本本身,无需人工标注(自监督)。
困惑度(perplexity)是 $\exp(J)$(按 token 平均,即上式除以总 token 数 $\sum_i T_i$ 而非序列数)。它可以理解为“模型每一步平均在多少个候选中犹豫”:对大小为 $|\mathcal{V}|$ 的词表做均匀猜测,困惑度恰为 $|\mathcal{V}|$;完美预测则为 1。它是似然的单调变换,不是准确率,也不能代替“是否满足 KB”。比较困惑度时要用同一分词器与同一测试集,否则数字不可比。
Transformer 如何计算条件分布
课程中的当代语言模型基于 Transformer 解码器。其计算流程可以概括为:
- 每个 token 查表得到嵌入向量(embedding),再加上位置信息(位置编码),否则模型分不出词序。
- 经过若干层,每层包含自注意力(self-attention)和逐位置的前馈网络(MLP),并配有残差连接与层归一化。
- 最后一层在位置 $t$ 的向量经线性层得到 $|\mathcal{V}|$ 维 logits,softmax 后就是 $P(x_{t+1}\mid x_{\le t})$。
自注意力让每个位置从所有允许看到的位置汇集信息。把各位置的向量分别线性变换成查询 $Q$、键 $K$、值 $V$(每行对应一个位置,维度 $d$),则
$$ \mathrm{Attention}(Q,K,V)=\mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt d}+M\right)V, $$
其中 $QK^\top$ 的第 $(t,s)$ 元衡量位置 $t$ 对位置 $s$ 的“关注程度”,除以 $\sqrt d$ 防止点积过大使 softmax 饱和。$M$ 是因果掩码(causal mask):$s\gt t$ 时取 $-\infty$,保证位置 $t$ 看不到未来 token,这正是自回归分解所要求的条件结构。有了掩码,一条长度 $T$ 的序列可以在一次前向中同时算出全部 $T$ 个位置的条件分布,训练高效。
解码:提示、温度与束搜索
训练好模型后,生成文本就是在这个分布上做推断。
提示(prompt)是推理期条件:固定前缀,对后续 token 取样或取 $\arg\max$。提示不是另一次梯度更新——参数不变,变的只是条件 $x_{\lt t}$。
温度 $\tau$ 把 logits 除以 $\tau$ 再 softmax:
$$ P_\tau(x_t=v\mid x_{\lt t})=\frac{\exp(z_v/\tau)}{\sum_{u\in\mathcal{V}}\exp(z_u/\tau)}. $$
$\tau\rightarrow 0$ 近贪心(概率集中到最大 logit),$\tau$ 大则更平($\tau\to\infty$ 时趋于均匀分布),$\tau=1$ 就是原分布。
贪心解码每步取 $\arg\max$,不保证整句联合概率最大。束搜索在 token 层保留 $K$ 条前缀,与排课作业的束搜索同形,同样不保证最优完整序列。
▸例16 两个 token 的玩具
词表 $\{a,b\}$。设 $P(x_1=a)=0.6$,$P(x_2=a\mid x_1=a)=0.9$,$P(x_2=a\mid x_1=b)=0.2$。四种序列的联合概率:
- $P(aa)=0.6\cdot 0.9=0.54$;
- $P(ab)=0.6\cdot 0.1=0.06$;
- $P(ba)=0.4\cdot 0.2=0.08$;
- $P(bb)=0.4\cdot 0.8=0.32$。
四者之和为 1。贪心先取 $a$,再取 $a$,得到 $aa$,联合 $0.54$,是最大(在这个例子里贪心恰好最优,一般不保证)。
若把第二步温度开得很大,样本会经常落到 $ab$,联合更小。具体地,$x_1=a$ 后两个 logits 之差为 $\ln(0.9/0.1)=\ln 9$;取 $\tau=2$ 时差变为 $\tfrac12\ln 9=\ln 3$,分布从 $(0.9,0.1)$ 变成 $(0.75,0.25)$,$\tau\to\infty$ 时趋于 $(0.5,0.5)$。
困惑度按 $-\log P$ 平均,不看“读起来像不像句子”。例如序列 $aa$ 的按 token 困惑度为 $\exp\!\big(-\tfrac12(\ln 0.6+\ln 0.9)\big)=0.54^{-1/2}\approx 1.36$,而 $ab$ 为 $0.06^{-1/2}\approx 4.08$。
▸语言模型与其他模型的区别
语言模型用 next-token 条件分布定义文本上的概率。它来自经典 $n$-gram 与当代 Transformer 语言模型。
- 相对一阶逻辑:可以生成从未写过的通顺句子,但不提供模型论保证。
- 相对贝叶斯网:变量是 token,因子由网络计算,树宽不再是讨论对象。
- 相对反射:生成是一条序列,每步条件依赖已写出的前缀,但推断仍是局部 $\arg\max$ 或采样,不是 UCS。
与课程前半的接口
语言模型并没有取代前面的模型,而是在它们之上加了一个“语言接口”。几条需要分清的边界:
- 检索加生成(retrieval-augmented generation, RAG)仍要分开报命中率与忠实度。检索是变量 / 反射轴上的最近邻,生成是自回归,二者的错误会叠加:检索没找到正确文档,生成再流畅也是错的;找到了,生成也可能不忠实于它。
- 把 Pac-Man 策略写成提示不会自动得到 minimax。语言模型没有展开对手树。
- 逻辑作业若改用语言模型翻译句子,必须再做符号检验。流畅的 $\forall$ 位置可以是错的(参见上一章例 15 那种只差一个量词的错误)。
- 情感分类仍是反射:把整句送进冻结 LM 取向量再训线性头,推断仍是一次前向,不是消解。
幻觉(hallucination)是高似然但与证据或世界冲突的续写。其根源在于训练目标只奖励“像训练数据”,不奖励“为真”。降低温度或加 RAG 可以减少一部分,不能变成蕴含。课程不把 RLHF(基于人类反馈的强化学习,用偏好数据进一步微调模型)细节当作考试核心。
▸小结
- 自回归分解 $P(x_{1:T})=\prod_t P(x_t\mid x_{\lt t})$ 是精确的链式法则;模型的差别在于如何表示条件分布($n$-gram 截断 vs. 神经网络)。
- 训练 = 每个位置上的交叉熵 = 极大似然;困惑度 $\exp(J)$ 是似然的单调变换。
- Transformer 用带因果掩码的自注意力,一次前向算出所有位置的条件分布。
- 解码时提示只改条件不改参数;温度控制分布的尖锐程度;贪心与束搜索都不保证全局最优。
- 高似然不等于真:幻觉不能靠调温度或 RAG 变成蕴含。
▸衔接
语言模型回答的是“下文在数据分布上有多可能”。社会专题问的是系统部署之后对谁造成伤害、指标有没有把伤害藏起来。二者都不改 UCS、贝尔曼或消解的定义。
AI 与社会(AI and Society):伤害、指标与供应链
前面各章都在优化一个标量:训练损失、路径代价、期望回报或边缘似然。这些标量是建模时为了可计算而选择的代理目标,它们都不自动等于社会代价。一个总体准确率 90% 的分类器,完全可能在某个少数群体上系统地失败;一个回报最高的策略,可能把风险集中转嫁给某一类人。这一章不引入新的推断公式,而是要求把系统放回数据从哪里来、被谁使用、出错时伤害谁的情境中去评价。2025 Autumn 课程为此安排了 AI & Society 作业与供应链讲座,课程整体也贯穿 Embedded Ethics(嵌入式伦理)模块。
核心想法一句话:平均指标高仍可能在分组上系统失败;先写伤害,再选指标。 本章的“输入”是一个系统描述,“输出”是该系统可能造成的伤害以及能暴露这些伤害的指标,而不是新的推断公式。涉及的环节包括数据、标注、评价、部署与供给链。
先写伤害,再选指标
指标是为了发现伤害而选的,所以顺序必须是先问“这个系统出错时谁受损、怎么受损”,再决定报告什么数字。伤害随作业变化:
- 情感分类:错分的评论被压(例如某种方言的正面评论被判为负面而被过滤);
- 搜索与路径规划:路径规划牺牲某类街区(例如把车流长期导向某片居民区);
- MDP 与强化学习:Q 学习策略在少数起始状态下冲进火山——平均回报高,个别情形却是灾难;
- 贝叶斯网与 HMM:跟踪系统的误报触发错误处置。
总体准确率、总体回报、总体边缘似然都可以把分组失败藏起来,因为它们是对所有样本或状态的平均,规模小的群体对平均值几乎没有影响。
分组报告
分组指标至少报:各组样本量、各组准确率或回报、最差组、以及总体。样本量极不均衡时,总体被多数组拖着走。最差组(worst-group)指标尤其重要,它直接回答“系统对最吃亏的那群人表现如何”。
▸例17 情感分组
总体准确率 $0.90$。按方言或身份相关用词切开,其中一组 $0.62$,样本占 $8\%$。由总体是各组按样本量加权平均可以反推多数组的准确率:
$$ 0.90=0.08\times 0.62+0.92\times a\ \Rightarrow\ a=\frac{0.90-0.0496}{0.92}\approx 0.924. $$
也就是说,两组相差约 30 个百分点,但总体数字几乎完全由多数组决定。
铰链损失仍在下降,因为多数组间隔在变大。加 $L_2$ 不会自动抬起该组。必须另报该组混淆矩阵,并检查词表是否把该组的极性词大量送进 UNK(未登录词被映射为同一个未知符号,特征全部丢失)。
公平性定义互不兼容
“公平”有多种形式化定义,常见的有:
- 人口统计均等(demographic parity):各组获得正预测的比例相同;
- 均等机会 / 均等几率(equal opportunity / equalized odds):各组的真正例率(以及假正例率)相同;
- 校准(calibration):在每个组内,模型说“70% 概率为正”的样本中,实际约 70% 为正。
公平性定义彼此不兼容(校准与均等机会一般不能同时精确成立)。已有结果(Kleinberg 等 2016;Chouldechova 2017)表明,当各组的基础比率(正例比例)不同、且分类器并非完美时,组内校准与各组错误率均衡不可能同时满足。这意味着选择哪种公平性是价值判断,而不是技术细节。作业要求写清选了哪一条,而不是写“已经公平”。
代理特征与数据偏差
情感分类作业的词表会放大语料里的刻板相关;这是特征问题,不是 SGD 实现错——优化器忠实地拟合了数据里的相关,相关本身就带偏差。一个自然的想法是删掉敏感词(如身份词),但这不够:删掉敏感词之后,邮政编码、用词长度、表情符号仍可作为代理特征(proxy feature),与敏感属性高度相关,模型照样能“间接”使用它们。
▸AI 与社会的定位
AI 与社会专题把系统放回数据来源与使用情境。它来自课程的 Embedded Ethics 与 2025 Autumn 专题,不是正则项。
- 相对只报 $\mathrm{TrainLoss}$:同一铰链损失可以在不同群体上间隔分布完全不同。
- 相对“删掉敏感词”:代理特征仍在。
- 相对逻辑:社会伤害不是反模型,不能靠消解证没。
供应链与评价泄漏
一个 AI 系统不只是一组参数,它背后有一整条供应链(supply chain):算力、数据许可、标注劳动、评测集污染、API 依赖。每一环都可能出问题:训练数据是否获得许可;标注由谁完成、在什么劳动条件下完成;下游产品依赖的上游 API 改版或下线时行为会怎样变化。模型卡(model card,记录模型用途、数据与评测结果的说明文档)只写平均准确率,不足以支持部署;它应当写出分组结果、适用范围与已知失败情形。
评价本身也会出错。最常见的两种:
- 在测试集上调超参数。这与课程前面各章的实践直接相关:用测试集调启发式或 $\varepsilon$(ε-贪心的探索率),再把同一测试当最终分数,测试集就变成了训练的一部分。
- 评测集污染:试题进入了预训练语料,模型“背过答案”。
▸指标与伤害
先写清伤害,再选指标。总体准确率、总体回报、总体边缘似然都可以把分组失败藏起来。用测试集调启发式或 $\varepsilon$,再把同一测试当最终分数,评价本身已经泄漏。供应链上的评测集污染(试题进入预训练语料)会让困惑度或准确率虚高,不改变模型在真实分布上的行为。
▸小结
- 前面各章优化的标量(损失、代价、回报、似然)都不自动等于社会代价。
- 顺序是先写伤害,再选指标;至少报各组样本量、各组指标、最差组与总体。
- 公平性定义互不兼容,必须明确选择并说明理由。
- 删除敏感词不能消除代理特征;偏差多来自数据,不是优化器的 bug。
- 供应链(算力、数据许可、标注劳动、API 依赖)与评价泄漏(测试集调参、评测集污染)同样决定系统是否可部署。
▸衔接
社会专题不引入新的最优方程。下一章按作业与考试把算法对回模型族,并把“要报告的量”写进同一张表。
作业(Homework):从定义到可提交的答案
前面各章给出的是定义与递推:反射模型的损失与梯度、搜索问题的状态与代价、MDP 的 Bellman 方程、因子图与贝叶斯网的推断。真正检验是否学会的地方是作业:同一个定义,要落成书面 PDF 里的一段推导,或落成 submission.py 里的一个函数。很多人卡在作业上,往往不是不懂概念,而是没把概念“接”到具体的符号与接口上,比如把 $y=\pm 1$ 当成 $0/1$,或者在搜索状态里漏掉一个必须记住的集合。
本章的做法是:逐题概括每道题在问什么,用 callout 给出可以对拍(用小例子逐步验算)的解答与实现要点,而不照抄题面长文。所有数字与公式以 2025 Spring 的公开作业页为准;若所在学期的题目有微调,先对照作业 zip 包里的 *.tex 题面。本章前半覆盖前三份作业:Foundations(数学与编程预备)、Sentiment(线性分类器)与 Route(最短路搜索),分别对应反射模型与搜索两部分内容。
作业安排与提交规范
2026 Spring 共八份周作业,围绕应用展开,每份都包含书面题与编程题。2026 Spring 作业页上的 zip 包没有在公开站点挂出,题面与同一主讲的 2025 Spring 对齐;2025 Autumn 则用贝叶斯网与 AI & Society 两份作业替换了排课(Scheduling)与车辆跟踪(Car tracking)。下表把每份作业对应到课程的哪一条主线(“轴”),并给出公开题面链接:
| 编号 | 作业 | 轴 | 公开题面 |
|---|---|---|---|
| HW1 | Foundations | 预备 | Spr25 |
| HW2 | Sentiment | 反射 | Spr25 |
| HW3 | Route | 搜索 | Spr25 |
| HW4 | Mountain Car | MDP | Spr25 |
| HW5 | Pac-Man | 博弈 | Spr25 |
| HW6 | Scheduling | 因子 / CSP | Spr25 |
| HW7 | Car tracking | 贝叶斯 / HMM | Spr25 |
| HW8 | Logic | 逻辑 | Spr25 |
| Aut HW6 | Bayesian | 贝叶斯 | Aut25 |
| Aut HW8 | Society | 评价 | Aut25 |
课表入口是 2026 Spring 主页,提交细则见作业规范。几条需要记住的规则如下:
- 编程部分只改
submission.py(在 starter code 标出的BEGIN_YOUR_CODE/END_YOUR_CODE之间写代码),书面部分交排版好的 PDF。 - 本地的
grader.py与 Gradescope 自动评分只显示部分用例,其余为隐藏用例,所以本地全过不等于满分。 - 迟交天数全学期共 7 天,单份作业最多用 2 天。
▸读法(先看这个)
每份作业只做三件事:建模(状态 / 特征 / 公式是什么)、推断(算哪一个数)、对拍(用最小例子验算)。下文每个 callout 的顺序固定为:题在问什么、符号含义、先用具体数字走一遍、再写一般式、最后把要交的答案圈出来。
卡住时从后往前找:数字对不上,多半是符号看错($y$ 是 $\pm 1$ 还是 $0/1$;$V$ 是旧表还是新表;XOR 要不要等于 $1$)。不要从中间公式开始背。
书面题要交的是推导,不是只写最终小数。编程题先在纸上写出 startState / isEnd / 更新式,再写代码。
每份作业先盯死一个最容易看错的符号:
| 作业 | 先盯这个 |
|---|---|
| HW1 | $w_i$ 是权重不是位置;$P(A\mid B)$ 分子是交集 |
| HW2 | $y=\pm 1$,不是 $0/1$;铰链在 $ys\ge 1$ 时梯度为 $0$ |
| HW3 | 代价里的 $x$ 是出发格横坐标;途经点状态必须带集合 $S$ |
| HW4 | 第 $i$ 轮的 $Q$ 只能用 $V_{i-1}$;终点 $V$ 永远是 $0$ |
| HW5 | $d$ 只在最后一个幽灵走完后减一 |
| HW6 | XOR 为 $1$ 才合法;回溯不调用冲突叶子 |
| HW7 | 先 elapseTime 再 observe;pdf 可以大于 $1$ |
| HW8 | “当且仅当”用 $\leftrightarrow$;量词左右顺序不能换 |
▸对拍与荣誉守则
公开页与当年 zip 可能改数字。推荐顺序是:先用小例子手算,再跑 grader.py,最后交 Gradescope。
笔记里的解答用于理解,不能整段贴进作业。课程禁止对照往年官方解答或他人代码。若使用生成式工具,必须提交对话记录(transcript),并且不得用它来“检查”已经写好的答案。
Foundations:数学与编程预备(HW1)
第一份作业不涉及任何 AI 模型,目的是检查后面各章默认读者已经会的工具:求极值(优化)、交换 $\min$ 与求和的次序、用一步递推求期望、极大似然估计、条件概率、向量求梯度、估算算法复杂度,以及一道伦理题。这些工具在后续章节反复出现:加权二次的极值就是平方损失的最小化,骰子期望的“自己等于自己”就是 MDP 中策略评估的雏形,网格最短路的动态规划则是搜索一章的预告。
编程部分是六个短函数:求字典序最小的词、欧氏距离、句子变异、稀疏向量点乘、稀疏向量累加、求非单例词。它们的作用是让读者熟悉 Python 的 dict / set 与 starter code 的提交流程,后面 Sentiment 作业的特征向量就是用 dict 表示的稀疏向量。
优化与概率(问题 1)
▸HW1.1a 加权二次函数的最小点
题在问什么:数轴上有 $n$ 个点 $x_i\in\mathbb{R}$,每个点有正权重 $w_i\gt0$。找一个位置 $\theta\in\mathbb{R}$,让“到各点的加权平方距离和”最小,并证明它是最小点;再说明若某些 $w_i$ 为负会出什么问题。
先算一个数。两点 $x_1=0,x_2=4$,权重 $w_1=1,w_2=3$。展开平方:
$$ f(\theta)=1\cdot(\theta-0)^{2}+3\cdot(\theta-4)^{2}=\theta^{2}+3(\theta^{2}-8\theta+16)=4\theta^{2}-24\theta+48. $$
求导 $f'=8\theta-24=0$,得 $\theta=3$。验算:加权平均 $(1\cdot 0+3\cdot 4)/(1+3)=12/4=3$,与求导结果一致。$f''=8\gt0$,开口向上,是谷底不是山峰。直觉上,权重 $3$ 的点把 $\theta$ 往 $4$ 那边拉得更狠,所以最优点 $3$ 离 $4$ 更近。
一般式。$f(\theta)=\sum_i w_i(\theta-x_i)^{2}$。对 $\theta$ 求导时,每一项按链式法则得到 $2w_i(\theta-x_i)$,再把 $\theta$ 从求和里提出来:
$$ f'(\theta)=2\sum_i w_i(\theta-x_i)=2\Big(\theta\sum_i w_i-\sum_i w_i x_i\Big)=0 \implies \theta^{\ast}=\frac{\sum_i w_i x_i}{\sum_i w_i}. $$
二阶导 $f''=2\sum_i w_i$,与 $\theta$ 无关。全体 $w_i\gt0$ 时 $f''\gt0$,$f$ 是严格凸的二次函数,驻点是唯一的全局最小。这说明:加权平方损失的最优常数预测就是加权平均,这一结论在回归与 $k$-means 的“中心更新”里都会再出现。
$w_i$ 变负会怎样。取 $n=1,w_1=-1$,$f(\theta)=-(\theta-x_1)^{2}$。$\theta$ 拉得越远,$f$ 越小(趋向 $-\infty$),没有最小点。更一般地,只要 $\sum_i w_i\lt0$,$f$ 就是开口向下的抛物线,无下界;若 $\sum_i w_i=0$,$\theta^{\ast}$ 的分母为零,公式失效。权重为负时,问题不再是“靠近这些点”,而是“远离某些点”。
要交的答案:$\theta^{\ast}$ 的式子、二阶导说明是最小、$w_i\lt0$ 时可能无下界。
▸HW1.1b 交换 min 与求和的次序
题在问什么。给定 $x\in\mathbb{R}^{d}$,定义
$$ f(x)=\min_{s\in[-1,1]}\sum_{i=1}^{d}s\,x_i,\qquad g(x)=\sum_{i=1}^{d}\min_{s_i\in[-1,1]}s_i x_i . $$
$f$ 先加再对一个共用的 $s$ 取最小;$g$ 先对每个坐标各自取最小再加。问对一切 $x$ 成立的是 $f\le g$、$f=g$ 还是 $f\ge g$。
符号。$s\in[-1,1]$ 表示在 $-1$ 与 $1$ 之间选一个数(含两端)。$\min$ 表示在允许范围内把式子压到尽可能小。
先算一个数。取 $x=(3,-1)$。
- $f$:只能选一个 $s$。$f= \min_s(s\cdot 3+s\cdot(-1))=\min_s(2s)$。$s=-1$ 时得 $-2$。
- $g$:第一个坐标选 $s_1=-1$ 得 $-3$;第二个选 $s_2=+1$ 得 $-1$。和为 $-4$。
$-2\gt-4$,所以这个例子里 $f\gt g$。
为什么永远 $f\ge g$。两式都在最小化同一个目标 $\sum_i s_i x_i$,区别只在可行集:$g$ 允许每个 $s_i$ 不同(可行集是整个方体 $[-1,1]^{d}$);$f$ 强迫 $s_1=\cdots=s_d$(可行集是方体的一条对角线)。在更小的集合里取最小值,结果只会更大或持平。
也可以写成闭式验证。线性函数 $s\cdot c$ 在 $[-1,1]$ 上的最小值是 $-|c|$(取 $s=-\mathrm{sign}(c)$),所以 $f=-|\sum_i x_i|$,$g=-\sum_i|x_i|$。由三角不等式 $|\sum x_i|\le\sum|x_i|$,取负号后不等式翻转,得 $f\ge g$。$x$ 的各分量全同号(或为零)时取等号。
这意味着什么:“先分别优化再合并”永远不差于“共用一个决策再优化”。更一般地,放松约束(扩大可行集)后得到的最小值,是原问题最小值的一个下界。
要交的答案:$f\ge g$(对一切 $x$),加上上面的可行集论证或三角不等式证明。
▸HW1.1c 骰子游戏的期望得分
题在问什么。反复掷公平的六面骰子,掷出 $1$ 就停。掷出 $3$ 加 $a$ 分,掷出 $6$ 减 $b$ 分,掷出 $2$、$4$、$5$ 不变。问停下来时得分的期望(平均会是多少),要求写出关于 $V$ 的递推、一两句解释与解出的表达式。
为何能写成自己等于自己。记 $V$ 为从游戏开始到结束累计得分的期望。无论第一面是什么(除了 $1$),掷完之后游戏规则与开始时完全一样,骰子没有记忆,所以“再玩下去”的期望还是同一个 $V$。这叫一步递推(first-step analysis),不是循环定义错误;MDP 一章中的策略评估方程 $V_{\pi}=r_{\pi}+\gamma P_{\pi}V_{\pi}$ 正是同一思路。
按第一面分情况列表:
| 第一面 | 概率 | 立刻发生 | 之后的期望 |
|---|---|---|---|
| $1$ | $1/6$ | 得 $0$ 并停止 | $0$ |
| $2$ 或 $4$ 或 $5$ | $3/6=1/2$ | 得 $0$ | 还剩一个完整游戏 $=V$ |
| $3$ | $1/6$ | 得 $a$ | 再加 $V$ |
| $6$ | $1/6$ | 得 $-b$ | 再加 $V$ |
由全期望公式,$V$ 等于各情况“概率 × 该情况下的期望”之和:
$$ V=\frac16\cdot 0+\frac12\cdot V+\frac16(a+V)+\frac16(-b+V). $$
把含 $V$ 的项收在一起:$\frac12+\frac16+\frac16=\frac56$。不含 $V$ 的项:$(a-b)/6$。
$$ V=\frac56 V+\frac{a-b}{6} \implies \frac16 V=\frac{a-b}{6} \implies V=a-b. $$
另一种理解。每一轮“非 1”的投掷中,$3$ 与 $6$ 概率相同;停止前的投掷次数期望是 $6$ 次(几何分布,含最后的 $1$),其中期望各有 $1$ 次 $3$ 和 $1$ 次 $6$,所以期望得分是 $a-b$。
小检查。若 $a=b$,每出现一次 $3$ 的赚会被一次 $6$ 抵消,期望应为 $0$,与 $V=a-a=0$ 一致。
要交的答案:上面的递推、$V=a-b$、一两句说明“未停止则期望仍是 $V$”。
▸HW1.1d 硬币的极大似然估计
题在问什么。一枚硬币正面概率是 $p$($0\lt p\lt1$)。掷了五次,结果是反、正、正、正、正。问哪个 $p$ 让这串结果最像会发生,证明是最大值,并给出一句直观解释。
似然(likelihood)就是“在这个 $p$ 下,正好掷出这串”的概率。五次独立,概率相乘,每次正面贡献 $p$、反面贡献 $1-p$:
$$ L(p)=p^{4}(1-p). $$
先代入三个数感受一下。
- $p=0.5$:$L=0.5^{4}\cdot 0.5=0.03125$。
- $p=0.8$:$L=0.8^{4}\cdot 0.2=0.08192$。
- $p=0.9$:$L=0.9^{4}\cdot 0.1=0.06561$。
$0.8$ 比 $0.5$ 和 $0.9$ 都大。正式求最大:$\log$ 是严格递增函数,只拉伸纵轴、不改峰的位置,而且把乘积变成求和、更好求导,所以改为最大化对数似然 $\ell=\log L=4\log p+\log(1-p)$。
$$ \ell'(p)=\frac4p-\frac{1}{1-p}=0 \implies 4(1-p)=p \implies p=\frac45. $$
$\ell''(p)=-4/p^{2}-1/(1-p)^{2}\lt0$ 对一切 $p\in(0,1)$ 成立,$\ell$ 是凹函数,峰是最大不是最小。边界处也检查一下:$p$ 贴到 $0$ 或 $1$ 时 $L\rightarrow 0$,小于 $L(4/5)$。
一句话解释:$5$ 次里 $4$ 次正面,频率就是 $4/5$。Bernoulli 的最大似然估计(maximum likelihood estimate, MLE)永远等于样本频率:一般地,$h$ 次正面、$t$ 次反面时 $\hat p=h/(h+t)$。
要交的答案:$p=4/5$、求导与二阶导、一句“等于正面频率”。
▸HW1.1e 条件概率不等式
题在问什么。事件 $A$ 与 $B$ 满足三条:$P(A\mid B)=P(B\mid A)$,并集概率 $P(A\cup B)=1/3$,交集概率 $P(A\cap B)\gt0$。要证 $P(A)\gt1/6$(约 5 行)。
符号。$P(A\mid B)$ 读“已经发生 $B$ 的前提下,$A$ 再发生的概率”,定义是 $P(A\cap B)/P(B)$,分子是交集。并集 $A\cup B$ 是“至少一个发生”,交集 $A\cap B$ 是“两个都发生”。
先用数字感受。若 $P(A)=P(B)=0.2$,为使并集恰为 $1/3$,交集须为 $0.2+0.2-1/3=1/15\approx 0.0667$;此时 $P(A\mid B)=P(B\mid A)=(1/15)/0.2=1/3$,且 $0.2\gt1/6\approx 0.167$。需要注意,若随手取 $P(A\cap B)=0.07$,虽有 $P(A\mid B)=0.07/0.2=0.35=P(B\mid A)$,但并集 $0.2+0.2-0.07=0.33$ 只是近似 $1/3$,并不严格满足题设,所以交集应取精确的 $1/15$。
一般证明只走三步。
- 由条件 $P(A\cap B)/P(B)=P(A\cap B)/P(A)$。交集大于 $0$,两边可约去分子,得 $P(A)=P(B)$。
- 容斥公式:$P(A\cup B)=P(A)+P(B)-P(A\cap B)=2P(A)-P(A\cap B)=1/3$。
- 减去一个正数会变小,所以 $1/3=2P(A)-P(A\cap B)\lt2P(A)$,即 $P(A)\gt1/6$。
需要注意交集大于 $0$ 这一条用了两次:一是保证条件概率有定义且可以约分,二是保证第 3 步是严格不等号。
要交的答案:上面三步。不必假设 $A$ 与 $B$ 互斥(实际上交集为正,它们一定不互斥)。
▸HW1.1f 向量函数的梯度
题在问什么。$w\in\mathbb{R}^{d}$ 是列向量,$a_i,b_j\in\mathbb{R}^{d}$ 是常向量,$\lambda\in\mathbb{R}$。函数是“很多平方差”再加一项 $L_2$ 正则:
$$ f(w)=\sum_{i=1}^{n}\sum_{j=1}^{n}\big(a_i^{\top}w-b_j^{\top}w\big)^{2}+\frac{\lambda}{2}\|w\|_{2}^{2}. $$
要写出 $\nabla f(w)$ 这个 $d$ 维向量,且不对维度下标求和。
先当所有东西都是标量($d=1$)。$f(w)=\sum_i\sum_j(a_i w-b_j w)^{2}+(\lambda/2)w^{2}$。令 $u_{ij}=(a_i-b_j)w$,则 $u_{ij}^{2}$ 对 $w$ 的导数是 $2u_{ij}(a_i-b_j)$。正则项导数是 $\lambda w$。
向量时只换记号。$a_i^{\top}w-b_j^{\top}w=(a_i-b_j)^{\top}w$,内积代替了标量乘积。线性函数 $c^{\top}w$ 对 $w$ 的梯度是 $c$,所以 $u_{ij}$ 的梯度是向量 $a_i-b_j$(不再是标量);再用链式法则 $\nabla(u^{2})=2u\nabla u$,以及 $\nabla\frac{\lambda}{2}\|w\|_2^2=\lambda w$:
$$ \nabla f(w)=2\sum_{i=1}^{n}\sum_{j=1}^{n}\big((a_i-b_j)^{\top}w\big)(a_i-b_j)+\lambda w. $$
不要把 $\|\cdot\|_{2}^{2}$ 拆成 $\sum_k w_k^{2}$ 再对每个 $k$ 写一遍。$d=1$ 时上式退回刚才的普通导数,可用来对拍。
要交的答案:上面这一行。保持紧凑,不必展开成坐标。
复杂度(问题 2)
两道复杂度题训练的是“数状态”的习惯:先确定一个对象由几个独立的自由度决定,每个自由度有多少取值,再相乘。这正是后面分析搜索问题状态数(如途经点问题的 $n\cdot 2^{k}$)时的方法。
▸HW1.2 计数与网格最短路的复杂度
第一问:三个矩形有多少种放法。$n\times n$ 格点,矩形的角必须落在格点上。轴对齐矩形由左、右、上、下四个边界决定。
先只放一个矩形:左边界从 $n$ 个 $x$ 里选,右边界也从 $n$ 个 $x$ 里选(允许重合,面积可以为 $0$),共 $n^{2}$。上下同理又是 $n^{2}$。一个矩形 $O(n^{4})$。若要求左 $\lt$ 右,则是 $\binom{n}{2}\approx n^{2}/2$,常数变了但阶不变。三个矩形各自独立选位置,可以完全重叠,所以 $(n^{4})^{3}=O(n^{12})$。只问渐近,重不重叠、谁先谁后(即使把三个矩形视为无序,也最多除以常数 $3!$)都不改指数。
第二问:带负代价的格子最短路。$n$ 行 $2n$ 列,从左上 $(1,1)$ 走到右下 $(n,2n)$,每步只能向下或向右。格子代价 $c(i,j)$ 可在常数时间内求得,可以为负,路径代价包含起点与终点两格。
最小例子 $n=1$,只有一行两列。唯一路径 $(1,1)\rightarrow(1,2)$,代价 $c(1,1)+c(1,2)$。
再大一点 $2\times 4$。走到格子 $(i,j)$ 的最小路径和记为 $F(i,j)$(含该格自身)。只能从上方或左方进来,于是:
$$ F(1,1)=c(1,1),\qquad F(i,j)=c(i,j)+\min\big(F(i-1,j),\ F(i,j-1)\big). $$
没有上方($i=1$)就把 $F(i-1,j)$ 当成 $+\infty$,没有左方($j=1$)同理。按行从上到下、每行从左到右填表,每格需要的两个值都已算好。格子共 $n\cdot 2n=O(n^{2})$ 个,每格只看两个邻居,总时间 $O(n^{2})$,答案是 $F(n,2n)$。
为什么不用其他算法:不能用普通 BFS(步代价不是全 $1$);不能直接用要求非负边权的 Dijkstra / UCS(负代价会破坏“第一次弹出即最优”)。这张图只向下或向右,没有回头边,是有向无环图(directed acyclic graph, DAG),按拓扑序做动态规划即可,负权也不成问题。
要交的答案:$O(n^{12})$;上面的 $F$ 递推与 $O(n^{2})$。
伦理题与编程题(问题 3、4)
▸HW1.3 伦理问题识别与 HW1.4 编程
书面怎么写。对照 NeurIPS 伦理准则(Ethical Guidelines)的清单,每则情景找出一条适用的条目并划线,再写 2 到 5 句:机制是什么、伤害落在谁身上;若认为没有问题,也要论证为什么。要写的是因果机制,不是写感想。
| 情景 | 可对上的一条(示例) | 机制一句话 |
|---|---|---|
| 贷款模型主要看地点,且对黑人与男性假阳性更高 | 偏见 / 分配伤害 | 地点代理了群体,拒贷集中落在农村与特定群体 |
| 文体学给匿名文本找作者,也可用于代码 | 隐私 | 去匿名把“不想被认出”的人重新绑回身份 |
| 未授权刮名人脸训练识别 | 同意与版权 | 人像与照片未经许可进入训练集 |
| iNaturalist 植物识别,用户同意且只在应用内 | 可论证无清单项 | 必须写清同意范围:训练用途、是否外传 |
编程六个短函数。禁用 numpy 等外部库,只交 submission.py。
find_alphabetically_first_word:按字典序取最小词(Python 字符串比较即字典序,min即可)。euclidean_distance:计算 $\sqrt{\sum(a_i-b_i)^{2}}$。mutate_sentences:把原句中相邻的词对当成有向边,沿边重新接出与原句等长的新句子。sparse_vector_dot_product:稀疏向量用dict(键是维度名,值是数值)表示,点乘只遍历较短的那个,在另一个里查不到的键按 $0$ 处理。increment_sparse_vector:原地执行 $v\leftarrow v+\mathrm{scale}\cdot u$。这个函数在 Sentiment 作业里就是 SGD 的更新步。find_nonsingleton_words:返回出现次数至少为 $2$ 的词集合。
Sentiment:线性分类器与情感分析(HW2)
第二份作业把反射模型一章的线性分类器落地:用词袋特征(bag of words)加铰链损失(hinge loss)的 SGD 训练一个影评情感分类器。书面部分先手算 SGD 培养直觉,再看线性分类器的能力边界(词袋特征无法表示否定),然后讨论 sigmoid 加平方损失的梯度消失、虚假相关(spurious correlation)带来的分组公平性问题,最后手算 $k$-means 聚类。编程部分实现词特征、SGD、合成数据生成、字符 $n$-gram 特征与 $k$-means。
动手之前,先把四个符号钉死,几乎所有算错都出在这里:
- $y=+1$ 表示正评,$y=-1$ 表示负评(不是 $0/1$)。
- $\phi(x)$ 是词袋特征:每个词对应一维,值是该词在句中出现的次数;下文短句里每个词最多出现一次,所以出现就是 $1$,没出现就是 $0$。
- 分数 $s=w\cdot\phi(x)$:只把“出现了的词”的权加起来。$ys$ 称为间隔(margin),为正表示分对。
- 铰链损失 $\mathrm{Loss}_{\mathrm{hinge}}=\max(0,1-ys)$:若 $ys\ge 1$(分对且间隔够),损失为 $0$,不改 $w$;否则其梯度为 $-y\phi$,SGD 更新为 $w\leftarrow w-\eta(-y\phi)=w+\eta y\phi$。$\eta=0.1$ 是步长。
词序固定为 $[\mathrm{pretty},\mathrm{good},\mathrm{bad},\mathrm{plot},\mathrm{not},\mathrm{scenery}]$。$w$ 初值 $[0,0,0,0,0,0]$。间隔恰好等于 $1$ 时铰链在该点不可导,题目规定梯度取 $0$。
建立直觉:手算 SGD 与线性不可分(问题 1)
▸HW2.1a 四步 SGD
题在问什么。四条短评按顺序各做一次随机梯度下降(stochastic gradient descent, SGD)。每次只改“这句话里出现过的词”的权。要交的是六维向量,顺序固定为 pretty, good, bad, plot, not, scenery。
每一步只做三件事:算分数 $s=w\cdot\phi$;看 $ys$ 是否小于 $1$;小于 $1$ 才更新 $w\leftarrow w+\eta y\phi$。
步 1 not good,$y=-1$。$w$ 全 $0$,所以 $s=0$,$ys=0\lt1$,要更新。$y=-1$ 表示把出现的词的权各减 $0.1$:
$$ w\leftarrow [0,0,0,0,0,0]+0.1\cdot(-1)\cdot[0,1,0,0,1,0]=[0,\ -0.1,\ 0,\ 0,\ -0.1,\ 0]. $$
步 2 pretty bad,$y=-1$。$s=0$(pretty 与 bad 当前权都是 $0$),$ys=0\lt1$。pretty 与 bad 各减 $0.1$:
$w=[-0.1,\ -0.1,\ -0.1,\ 0,\ -0.1,\ 0]$。
步 3 good plot,$y=+1$。$s=w_{\mathrm{good}}+w_{\mathrm{plot}}=-0.1$,$ys=-0.1\lt1$。这次 $y=+1$,出现的词各加 $0.1$:good 从 $-0.1$ 回到 $0$,plot 变成 $0.1$。
$w=[-0.1,\ 0,\ -0.1,\ 0.1,\ -0.1,\ 0]$。
步 4 pretty scenery,$y=+1$。$s=w_{\mathrm{pretty}}+w_{\mathrm{scenery}}=-0.1$,$ys=-0.1\lt1$。pretty 加 $0.1$ 回到 $0$,scenery 变成 $0.1$。
要交的答案:$[0,\ 0,\ -0.1,\ 0.1,\ -0.1,\ 0.1]$。
检查:bad 与 not 只在负评里出现过,权为负;plot 与 scenery 只在正评里出现过,权为正;pretty 与 good 各在一条正评和一条负评里出现,一减一加回到 $0$。四步都触发了更新,因为 $w$ 很小,没有一句的间隔达到 $1$。
▸HW2.1b 词袋特征下的线性不可分
题在问什么。四句话:bad 负,good 正,not bad 正,not good 负。只用“每个词出现几次”当特征,能否找到一组权让四句全对(零训练误差)?要求用 3 到 5 句证明不能,再补一个特征把它救回来。
线性分类器在干什么。预测符号是 $\mathrm{sign}(w\cdot\phi)$。要全对,分数必须与标签同号(分数为 $0$ 也算错)。偏置项可以忽略,因为题目的特征只有词计数;即使加上偏置,下面的矛盾同样存在(见本 callout 末尾)。
先列加法表。
| 句子 | 要满足 |
|---|---|
good |
$w_{\mathrm{good}}\gt0$ |
not good |
$w_{\mathrm{not}}+w_{\mathrm{good}}\lt0$,即 $w_{\mathrm{not}}\lt-w_{\mathrm{good}}\lt0$ |
bad |
$w_{\mathrm{bad}}\lt0$ |
not bad |
$w_{\mathrm{not}}+w_{\mathrm{bad}}\gt0$,即 $w_{\mathrm{not}}\gt-w_{\mathrm{bad}}\gt0$ |
第二行强迫 $w_{\mathrm{not}}\lt0$,第四行强迫 $w_{\mathrm{not}}\gt0$。同一个数不能既负又正。这与 SGD、铰链、步长都无关,是特征本身的线性不可分,形状与异或(XOR)相同:标签由“是否有 not”与“是 good 还是 bad”两者的组合决定。
若带偏置 $b$,四个不等式为 $b+w_{\mathrm{good}}\gt0$、$b+w_{\mathrm{not}}+w_{\mathrm{good}}\lt0$、$b+w_{\mathrm{bad}}\lt0$、$b+w_{\mathrm{not}}+w_{\mathrm{bad}}\gt0$。第二式减第一式得 $w_{\mathrm{not}}\lt0$,第四式减第三式得 $w_{\mathrm{not}}\gt0$,矛盾依旧。
补一个特征即可。例如 $\phi_{\mathrm{not_good}}(x)=\mathbf{1}\{\text{同时出现 not 与 good}\}$,或把 not good 整块当成单独一维(二元词组,bigram)。加上去之后,not 不再需要同时扮演“翻转 good”和“翻转 bad”两个角色。具体可取 $w_{\mathrm{good}}=1,w_{\mathrm{bad}}=-1,w_{\mathrm{not}}=2,w_{\mathrm{not_good}}=-4$:四句分数依次为 $-1,1,1,-1$(对应 bad、good、not bad、not good),符号全对。
要交的答案:上面的矛盾证明,加一个具体新特征。
预测评分:sigmoid 加平方损失(问题 2)
▸HW2.2 平方损失加 sigmoid 的梯度
题在问什么。现在不预测正负,而是预测 $[0,1]$ 里的评分 $y$。预测值是 $p=\sigma(w\cdot\phi)$,sigmoid 函数 $\sigma(z)=(1+e^{-z})^{-1}$ 把任意实数压进 $(0,1)$。损失是平方 $(p-y)^{2}$。要写出单个样本的损失、对 $w$ 的梯度(用 $p$ 表示),以及 $y=1$ 时梯度在什么条件下能任意小、能不能变成精确的 $0$。
损失。$\mathrm{Loss}(x,y,w)=\big(\sigma(w\cdot\phi(x))-y\big)^{2}$。
梯度。令 $z=w\cdot\phi$,$p=\sigma(z)$。链式法则把梯度拆成三段相乘:平方对 $p$ 是 $2(p-y)$;$\sigma$ 对 $z$ 的导数是 $\sigma(z)(1-\sigma(z))=p(1-p)$(由 $\sigma'(z)=e^{-z}/(1+e^{-z})^{2}$ 整理得到);$z$ 对 $w$ 是 $\phi$。
$$ \nabla_w\mathrm{Loss}=2(p-y)\,p(1-p)\,\phi(x). $$
先代入一个数。设 $\phi=(1)$,$w=2$,则 $z=2$,$p=\sigma(2)\approx 0.88$。若 $y=1$,梯度模长约为 $|2(0.88-1)\cdot 0.88\cdot 0.12|\approx 0.025$。已经很小。
$y=1$ 时的模长等于 $2p(1-p)^{2}\|\phi\|$。$p$ 越靠近 $1$,$(1-p)^{2}$ 越小。要把模长压到任意小,须 $w\cdot\phi\rightarrow+\infty$($\phi$ 固定且非零),即让权重沿 $\phi$ 的方向无限增大。$\sigma$ 永远到不了 $1$(对有限的 $z$,$\sigma(z)\lt1$),所以梯度永远不是精确的 $0$。
需要注意:$p\rightarrow 0$(即 $w\cdot\phi\rightarrow-\infty$)时因子 $p$ 也会让梯度趋于 $0$,但这时预测完全错了($y=1$ 而 $p\approx 0$),模型却几乎不再迈步。这就是 sigmoid 饱和造成的消失梯度(vanishing gradient):处在饱和区的点,无论对错,梯度都很小。这也是分类时常用 logistic 损失(交叉熵)而非平方损失的原因之一,前者的梯度是 $(p-y)\phi$,不含 $p(1-p)$ 这一饱和因子。
要交的答案:损失式、梯度式、$w\cdot\phi\rightarrow+\infty$ 使模长任意小、但不能精确为零。
毒性分类与分组公平(虚假相关)
这道题讨论的是一个现实问题:在毒性评论数据里,“出现身份词”(如提到某一族群)与“有毒”高度相关,模型可能学会“看到身份词就判有毒”这一捷径,从而系统性误伤提及该身份的正常评论。题目用两个固定分类器量化这种现象,并比较“平均损失”与“最差组损失”两种训练目标。
▸HW2.4 毒性分类:平均损失与最大组损失
题在问什么。评论特征 $\phi=(1,d,t)$:$1$ 是偏置,$d=1$ 表示出现身份词(demographic mention),$t=1$ 表示出现“脏词”(toxic word)。标签 $y=+1$ 有毒,$y=-1$ 无毒。训练集共 $200$ 条,按 $(y,d)$ 切成四组。比较两个固定分类器 $f(x)=\mathrm{sign}(w\cdot\phi(x))$:D 只看身份词,T 只看脏词。
先看分类器在干什么(分数 $\gt0$ 预测有毒)。
- D 的权 $w=(-0.1,1,0)$,分数 $-0.1+d$。$d=1$ 得 $0.9\gt0$;$d=0$ 得 $-0.1\lt0$。故 D 判有毒当且仅当出现身份词。
- T 的权 $w=(-0.1,0,1)$,同理判有毒当且仅当 $t=1$。
题目给出的训练集计数如下:
| $y$ | $d$ | $t$ | 条数 |
|---|---|---|---|
| $-1$ | $0$ | $0$ | $63$ |
| $-1$ | $0$ | $1$ | $27$ |
| $-1$ | $1$ | $0$ | $7$ |
| $-1$ | $1$ | $1$ | $3$ |
| $1$ | $0$ | $0$ | $3$ |
| $1$ | $0$ | $1$ | $7$ |
| $1$ | $1$ | $0$ | $27$ |
| $1$ | $1$ | $1$ | $63$ |
四组人数先加起来(同一 $(y,d)$ 下 $t=0$ 与 $t=1$ 两行相加):
| 组 $(y,d)$ | 人数 |
|---|---|
| $(1,1)$ 有毒且有身份词 | $27+63=90$ |
| $(1,0)$ 有毒且无身份词 | $3+7=10$ |
| $(-1,1)$ 无毒且有身份词 | $7+3=10$ |
| $(-1,0)$ 无毒且无身份词 | $63+27=90$ |
可以看到数据中身份词与毒性强相关(有毒的 $100$ 条里 $90$ 条带身份词),这就是 D 能“蒙对”大多数样本的原因。
D 的零一损失。零一损失(zero-one loss)对每条样本错记 $1$、对记 $0$,组损失是组内错误率。D 的预测完全由 $d$ 决定,与 $t$ 无关:有身份词一律判有毒。于是 $(1,1)$ 全对、$(-1,1)$ 全错、$(1,0)$ 全错、$(-1,0)$ 全对。
| $y=1$ | $y=-1$ | |
|---|---|---|
| $d=1$ | $0$ | $1$ |
| $d=0$ | $1$ | $0$ |
平均 $(90\cdot 0+10\cdot 1+10\cdot 1+90\cdot 0)/200=0.1$。最大组损失 $1$(两个小组成绩是满分错)。
T 的零一损失。T 只看 $t$。在 $(y=1,d=1)$ 这 $90$ 条里,$t=0$ 的 $27$ 条会被判无毒,是错的,组损失 $27/90=0.3$。其余三组同理:$(1,0)$ 中 $t=0$ 的 $3$ 条错,$3/10=0.3$;$(-1,1)$ 中 $t=1$ 的 $3$ 条错,$3/10=0.3$;$(-1,0)$ 中 $t=1$ 的 $27$ 条错,$27/90=0.3$。
| $y=1$ | $y=-1$ | |
|---|---|---|
| $d=1$ | $0.3$ | $0.3$ |
| $d=0$ | $0.3$ | $0.3$ |
平均 $(27+3+3+27)/200=0.3$。最大组损失 $0.3$。
谁更好。D 平均更好($0.1\lt0.3$),T 最大组更好($0.3\lt1$)。D 的好成绩来自虚假相关:一旦部署到身份词与毒性不再相关的数据上,它对无毒但提及身份的评论会全部误判。
$\lambda$ 新损失 $\mathrm{TrainLoss}_{\mathrm{new}}=\lambda\cdot\mathrm{TrainLoss}_{\mathrm{avg}}+(1-\lambda)\cdot\mathrm{TrainLoss}_{\max}$,$\lambda\in[0,1]$ 在两个目标间插值。
- $\lambda=1$ 只压平均:对齐功利主义(utilitarianism)的总体福利,偏向 D 这种“多数对、少数组全灭”的分类器(D 得 $0.1$,T 得 $0.3$)。
- $\lambda=0$ 只压最差组:对齐罗尔斯式(Rawlsian)的最弱势优先,偏向 T(T 得 $0.3$,D 得 $1$)。
- 题目还给出第二张训练集表(按上表同样的 $(y,d,t)$ 顺序,计数依次为 $36,33,32,33,32,33,1,0$),其中 $(y=1,d=1)$ 只有 $1$ 条,最大组损失会被一条样本绑架:那一条错了,组损失就是 $1$。可对组损失加样本量下限(太小的组不参与取最大,或与相邻组合并)、或用平滑后再取最大(例如按组大小给组损失加权,或对组错误率做加性平滑)。
- 平台同等关心平均与最差组,取 $\lambda=1/2$,并写清权衡:总体误拦少一点,还是不让某一身份组被系统性误杀。代入可得 D 为 $0.5\cdot 0.1+0.5\cdot 1=0.55$,T 为 $0.5\cdot 0.3+0.5\cdot 0.3=0.3$,此时选 T。
要交的答案:两个“当且仅当”句子、两张表、两个平均、两个最大、$\lambda$ 与伦理框架的对应。
$k$-means 聚类(问题 5)
$k$-means 是无监督学习的代表:没有标签,只想把相近的点归为一簇。它交替做两件事,每一步都让总损失(各点到所属中心的平方距离之和)不增,所以一定收敛;但它收敛到的是局部最优,结果依赖初值。下面的手算题正好演示这一点。
▸HW2.5 $k$-means 手算与缩放
题在问什么。四个二维点其实都在 $x$ 轴上:$\phi=[0,0],\ [4,0],\ [6,0],\ [11,0]$。做 $2$-means,两套初值各跑到收敛,报告每步的划分、中心、最终总损失与最终簇。
一轮 $k$-means 只做两件事。分配:每个点去离它最近的中心(固定中心、优化划分)。更新:中心改成该簇点的均值(固定划分、优化中心;由 HW1.1a,均值正是平方距离和的最小点)。划分不再变就停。
初值 A $\mu_1=[0,0]$,$\mu_2=[11,0]$。所有点都在 $x$ 轴上,距离就是横坐标差的绝对值。
- $0$ 归 $1$;$4$ 距 $0$ 为 $4$、距 $11$ 为 $7$,归 $1$;$6$ 距 $0$ 为 $6$、距 $11$ 为 $5$,归 $2$;$11$ 归 $2$。簇 $\{[0,0],[4,0]\}$、$\{[6,0],[11,0]\}$。
- 更新:$\mu_1=[2,0]$,$\mu_2=[8.5,0]$。
- 再分配:$4$ 距 $2$ 为 $2$、距 $8.5$ 为 $4.5$,仍归 $1$;$6$ 距 $2$ 为 $4$、距 $8.5$ 为 $2.5$,仍归 $2$。划分不变,停止。
损失 $(0-2)^{2}+(4-2)^{2}+(6-8.5)^{2}+(11-8.5)^{2}=4+4+6.25+6.25=20.5$。
初值 B $\mu_1=[0,0]$,$\mu_2=[4,0]$。
- $0$ 归 $1$;$4,6,11$ 都距 $4$ 更近,归 $2$。
- 更新:$\mu_1=[0,0]$,$\mu_2=[7,0]$($(4+6+11)/3=7$)。
- $4$ 距 $0$ 为 $4$、距 $7$ 为 $3$,仍归 $2$。划分不变。
损失 $0+(4-7)^{2}+(6-7)^{2}+(11-7)^{2}=9+1+16=26$。
同一套数据,初值不同,损失 $20.5$ 与 $26$ 都可能停住。$k$-means 不保证全局最小。实践中的对策是多次随机初始化取损失最小的一次,或用 $k$-means++ 等方式挑选分散的初值。
缩放。题目要求一般性的回答,不只针对这组数据。
- 数据与初始中心的全体坐标乘同一个非零常数 $c$(是,簇不变):每段距离同乘 $|c|$,谁离谁更近不会换;中心作为均值也同乘 $c$。归纳下去,每一步的划分都与缩放前相同,最终簇不变(损失整体乘 $c^{2}$)。
- 只缩放某几维(否,簇可能改变):这会改变点之间的相对远近。直观例子:点 $(0,0),(1,0),(0,1)$,只把第 $2$ 维乘 $100$,$(0,1)$ 会从“靠近 $(0,0)$”变成“离谁都远”,划分可变。一个可以完整算出的反例:四点 $(0,0),(0,1),(4,0),(4,1)$,初值 $\mu_1=(0,0),\mu_2=(4,1)$。缩放前 $(0,1)$ 距 $\mu_1$ 的平方距离为 $1$、距 $\mu_2$ 为 $16$,$(4,0)$ 则相反,于是按 $x$ 分成左右两簇,中心 $(0,0.5),(4,0.5)$,损失 $4\cdot 0.5^{2}=1$,收敛。把第 $2$ 维(连同初值)乘 $100$ 后,四点为 $(0,0),(0,100),(4,0),(4,100)$,初值 $(0,0),(4,100)$:$(0,100)$ 距 $\mu_2$ 的平方距离只有 $16$、距 $\mu_1$ 为 $10000$,$(4,0)$ 则相反,于是按 $y$ 分成上下两簇,中心 $(2,0),(2,100)$,损失 $4\cdot 2^{2}=16$,收敛。同样的点落进了不同的簇。这也是聚类前常对各维做标准化的原因。
实现。kmeans 要求把中心初始化为随机挑选的样本,不许用 scikit-learn。预计算每个样本的 $\|x\|^{2}$,用 $\|x-\mu\|^{2}=\|x\|^{2}+\|\mu\|^{2}-2x\cdot\mu$ 计算距离;每轮只需重算 $\|\mu\|^{2}$ 与点乘,而且比较远近不需要开方。
要交的答案:两套初值的逐步划分与中心、损失 $20.5$ 与 $26$、缩放两问。
编程:情感分类器
编程部分把上面手算的东西写成通用代码。特征提取器把一条文本映射成稀疏向量(dict),学习器在特征向量上跑铰链损失的 SGD,再换成字符 $n$-gram 特征比较效果。只允许使用标准库与 starter code 自带的导入。
▸HW2 编程要点
词特征。extractWordFeatures 按空白切开(x.split()),不做大小写归一,所以 great 与 Great 是两个词。返回 dict,键是词,值是出现次数。
学习。learnPredictor 用已有的 dotProduct / increment 做铰链 SGD,不要手改字典:对每个样本先算 $ys$,当 $ys\lt1$ 时调用 increment(w, eta * y, phi)。每一轮(epoch)结束打印训练误差与验证误差。满分线:训练 $\lt4\%$,验证 $\lt30\%$。对不上时先用 generateDataset(其中嵌套的 generateExample 需要自己实现)造线性可分的假数据,以恒等特征提取器 lambda x: x 自测:在可分数据上误差应能降到接近 $0$,否则说明更新式写错(最常见的是把 $y$ 当成 $0/1$,或在 $ys\ge 1$ 时也更新)。
字符 $n$-gram。extractCharacterFeatures(n) 返回一个 extract 函数:先去掉空格与制表符,再用长度为 $n$ 的窗口滑过整个字符串,统计每个 $n$ 字符块出现的次数。例如 $n=3$ 时 not good 先变成 notgood,得到 not、otg、tgo、goo、ood 五个特征,其中跨词的 otg、tgo 部分编码了词序信息。
验证误差通常在 $n=3$ 到 $5$ 最低:太短(如 $n=1,2$)的字符块在正负评里都常见,没有极性;太长则几乎每个块都只出现一两次,特征稀疏、难以泛化。字符特征之所以能接近词特征,是因为 $n$ 在这个范围时字符块大致相当于词或词的主干。词特征会把未见过的长词(或拼写变体、复合词)当成全新维;字符块 good 在 unbelievably good 里仍能触发。书面要写清选中的 $n$,以及这一句反例(一句字符 $n$-gram 很可能胜过词特征的评论,并解释原因)。
Route:网格与地图上的最短路搜索(HW3)
第三份作业对应搜索一章。书面部分先在一个“越往东越贵”的无限网格上分析最小代价路径,再用真 / 假判断题检验对一致代价搜索(UCS)不变量的理解。编程部分在斯坦福校园地图上依次实现三件事:普通最短路(到达任一带指定标签的地点)、带无序途经点的最短路(需要扩充状态)、以及把 A* 归约为改了代价的 UCS 并实现两种启发式。核心训练是状态设计:状态里必须恰好装下“对未来决策有影响”的信息,不多不少。
网格设定如下:在无限的整数网格上,从 $(0,0)$ 走到 $(m,n)$,$m,n\ge 0$。动作为东 / 西 / 南 / 北各走一格,代价 $1+\max(x,0)$,其中 $x$ 是出发格的横坐标。
Grid City:代价分析与 UCS 判断(问题 1)
▸HW3.1a 最小代价与最优路径
题在问什么。从格子 $(0,0)$ 走到 $(m,n)$。每走一步,代价是 $1+\max(x,0)$,这里的 $x$ 是出发前所在格子的横坐标。越靠东($x$ 越大),无论朝哪走都越贵;在 $x\le 0$ 的区域每步都只要 $1$。要给出最小代价的表达式、描述一条最优路径,并说明是否唯一。
先走一遍 $m=2,n=1$,终点是 $(2,1)$。
路线甲:北、东、东。
- 从 $(0,0)$ 北到 $(0,1)$,代价 $1+\max(0,0)=1$。
- 从 $(0,1)$ 东到 $(1,1)$,代价 $1+0=1$。
- 从 $(1,1)$ 东到 $(2,1)$,代价 $1+1=2$。
总和 $1+1+2=4$。
路线乙:东、东、北。
- 从 $(0,0)$ 东到 $(1,0)$,代价 $1$。
- 从 $(1,0)$ 东到 $(2,0)$,代价 $2$。
- 从 $(2,0)$ 北到 $(2,1)$,代价 $1+\max(2,0)=3$。
总和 $1+2+3=6$,更贵。贵在最后一步北走时 $x$ 已经是 $2$。
一般结论。不绕路时必须恰好 $m$ 次东、$n$ 次北。那 $m$ 次东必然分别从 $x=0,1,\ldots,m-1$ 出发,这部分代价固定为 $\sum_{x=0}^{m-1}(1+x)=m(m+1)/2$。北走的代价取决于当时的 $x$,越早北走越便宜,最便宜是全部在 $x=0$ 时北走,每步只付 $1$。故
$$ C^{\ast}=n+\frac{m(m+1)}{2}. $$
代回 $m=2,n=1$:$C^{\ast}=1+3=4$,与路线甲一致。
西或南只增加步数,代价又不会变负,不能更优。更细地说:任何路径中东走的次数至少为 $m$,且横坐标要从 $0$ 升到 $m$,必然各有一次从 $x=0,1,\ldots,m-1$ 出发的东走,这部分至少 $m(m+1)/2$;北走至少 $n$ 次,每次至少付 $1$。任何额外的一步(西、南、多余的东或北)都至少再付 $1$,而在 $x\ge 1$ 处的北走比在 $x=0$ 处贵。所以最优路径只有一条:先 $n$ 次北,再 $m$ 次东($m=0$ 或 $n=0$ 时退化为一直走一个方向,同样唯一)。
要交的答案:上面的式子、一条最优路径、写明唯一。
▸HW3.1b 与 HW3.1c UCS 的真假判断
先记住一条不变量。一致代价搜索(uniform cost search, UCS)按已走路程(past cost)$g$ 从小到大弹出状态。边权非负时,一个点第一次弹出,$g$ 就是从起点到它的最小代价。终点第一次弹出,算法就停,后面还有多少没看过的格子都无所谓。
对本网格(三句)
- “格子无限,所以 UCS 永不停止”:假。终点 $(m,n)$ 第一次弹出就停。无限只表示外面还有格子,不表示算法走不到终点;每步代价至少为 $1$,所以 $g\le C^{\ast}$ 的状态只有有限多个,UCS 在有限步内必然弹出终点。
- “返回最优路径,且只会探索矩形 $0\le x\le m,\ 0\le y\le n$ 里面的格子”:假。弹出只保证 $g\le C^{\ast}$,不保证坐标不越界。取 $m=2,n=0$,最优是东东,代价 $1+2=3$。先往西走到 $(-1,0)$,这一步代价 $1+\max(0,0)=1\lt3$,该点会被弹出。
- “返回最优路径,且只会弹出 $g\le C^{\ast}$ 的点”:真。终点弹出时 $g=C^{\ast}$,此前弹出的点 $g$ 更小或相等。
对任意图(再三句,与上面网格无关)
- 图上加一条边,任意两点最短路代价只会变短或不变。真。原来的所有路径仍然存在,多一条路不会让原路变贵,最小值只能下降或持平。
- 把某个动作代价改到足够小(甚至改成负数),UCS 返回的路径就一定含这个动作。假。两层原因:UCS 的正确性要求边权非负,改成负数后“弹出即最优”不再成立;这条边也可能根本不在任何从 $s$ 到 $t$ 的路上(例如从终点出发、或位于从起点不可达的区域),代价再小也用不上。
- 每条边代价都加 $1$,最小代价那条路径不变。假。路径代价增加的量等于它的步数,步数多的原最优路径会多挨几次 $+1$,步数少的次优路径可能反超。例如原路 $A$ 三步各 $1$(总 $3$),路 $B$ 一步代价 $4$;各加 $1$ 后变成 $6$ 对 $5$,$B$ 反而更短。(对比:每条边代价乘一个正常数,最优路径不变。)
要交的答案:六句真 / 假,各配一句理由。
地图搜索的建模(问题 2 到 4)
编程部分的搜索问题都要实现同一套接口:startState() 返回初始状态,isEnd(state) 判断是否到达目标,actionSuccessorsAndCosts(state) 返回 (动作, 后继状态, 代价) 的列表。UCS 已在 util.py 中给出,作业只需要把问题建模好。三个问题是逐步加码的:先是普通最短路,再在状态里加入“还剩哪些途经点”,最后不改搜索器、只改代价,把 A* 变成 UCS。
▸HW3.2 到 HW3.4 建模要点
普通最短路(HW3.2,ShortestPathProblem)。状态就是当前地点。isEnd(s) 当该地点带有 endTag(任意一家带该标签的地点,例如任意一家餐馆,而不是某一栋楼)时为真。动作是走到相邻命名地点,代价是那段路的米数。先在纸上写出 startState / isEnd / actionSuccessorsAndCosts 三件套,再写代码。书面部分要在斯坦福地图上自选起点与终点标签,可视化结果,各举一个有启发的设置与一个路径不理想的设置,并判断问题是否来自建模假设。
无序途经点(HW3.3,WaypointsShortestPathProblem)。除了人在哪,还要记住哪些标签还没满足,因为同一地点在“还没去健身房”和“已经去过”两种情况下,后续最优走法不同。状态写成 $(\mathrm{loc},S)$,$S$ 是尚未满足的途经标签子集。到达某地时,用该地标签把 $S$ 里对应的位清掉。终点:地点带 endTag 并且 $S=\emptyset$。同一地点可以一次清掉多个标签。实现时 $S$ 要用可哈希的类型(如排好序的 tuple 或 frozenset),才能作为 UCS 内部字典的键;起始状态也要先用起点自身的标签清一次 $S$。
最小例子:地点 $A,B,C$,要从 $A$ 到带 end 的 $C$,途经 {food,gym}。若 $B$ 同时有这两个标签,走到 $B$ 时 $S$ 一次清空。状态数上界是 $n\cdot 2^{k}$($n$ 个地点,$k$ 个途经标签):地点有 $n$ 种取值,$S$ 是 $k$ 个标签的任意子集,有 $2^{k}$ 种。漏掉 $S$、只记地点,搜索会以为“路过一次就算完成”,或者在第二次经过同一地点时把它当成已访问而剪掉,路径会错。反过来,若把“已走过的完整路径”放进状态,状态数会爆炸,这就是“紧凑”的含义:$S$ 只记未满足的标签,不记顺序。
A 归约(HW3.4,aStarReduction)*。不要重写搜索器。起始状态与终止判断保持不变,只把代价改成
$$ \mathrm{Cost}'(s,a)=\mathrm{Cost}(s,a)+h(\mathrm{Succ}(s,a))-h(s), $$
再把这个新问题丢给现成 UCS。为什么正确:沿任一条从起点 $s_0$ 到终点 $s_{\mathrm{end}}$ 的路径把新代价相加,中间的 $h$ 项两两抵消(望远镜求和),得到 $\mathrm{Cost}+h(s_{\mathrm{end}})-h(s_0)$;终点处 $h=0$,$h(s_0)$ 对所有路径都一样,所以新旧问题的最优路径相同。若 $h$ 满足一致性(consistency)$h(s)\le\mathrm{Cost}(s,a)+h(\mathrm{Succ}(s,a))$,则 $\mathrm{Cost}'\ge 0$,满足 UCS 对非负边权的要求。
两种启发式:
- 直线启发(
StraightLineHeuristic):返回到任一终点标签地点的直线(欧氏 / 大圆)距离的最小值。走路的距离不可能短于直线距离,所以它是下界、可采纳;直线距离满足三角不等式,所以也一致。先在构造时预计算所有终点地点的坐标,评估时才快。 - 无途经点启发(
NoWaypointsHeuristic):在原图上假装没有 $S$,求到终点标签的最短路代价(可复用 HW3.2 的建模)。真实问题还要额外经过途经点,代价只会更大,所以它可采纳且通常比直线启发更紧。应在构造时一次性预计算各地点到最近终点的代价,而不是每次评估都跑一遍搜索。
书面。导航把车流赶到小路:用户自己堵在曾经的捷径上;非用户(居民、公交)承担噪声与路口占用。对策是改代价(容量、时间、避开居民区),不是只换启发式,因为启发式只影响搜索速度,不影响返回哪条路。途经点是双用途技术:对网约车司机,可以在送完上一单、接下一单之间插入休息点或卫生间,这需要司机的工时、位置等信息;也可以被平台用来在上一单还没结束时接下单,让司机停不下来,而收集司机作息数据本身也带来隐私风险。
Mountain Car:值迭代、折扣化归与强化学习
这份作业(HW4)把 MDP 一章的理论落到两个层面。书面部分在一条只有五个格子的数轴上手算值迭代,并证明“带折扣的 MDP 可以改写成不带折扣的 MDP”;编程部分换成 OpenAI Gym 里的 Mountain Car:一辆动力不足的小车停在山谷里,必须来回荡几次才能冲上右侧山顶。小车的状态(位置、速度)是连续的,转移概率也不告诉智能体,于是值迭代不能直接用,需要依次尝试“基于模型的蒙特卡洛”“表格 Q 学习”“函数逼近 Q 学习”,最后再讨论“安全探索”这一伦理话题。
核心直觉只有一句:值迭代要求已知模型、状态可枚举;一旦二者缺一,就要么先估模型,要么直接从经验里学 $Q$。手算部分正是为了让读者在最小的例子上看清贝尔曼更新到底在加哪些数。
书面题的数轴 MDP
动手之前,先把这条链画在纸上:
1 | -2 ===== -1 ===== 0 ===== +1 ===== +2 |
这个 MDP 的设定逐条如下:
- 状态是 $\{-2,-1,0,+1,+2\}$,从 $0$ 出发。终止状态只有 $\pm 2$;终止状态之后不再有回报,所以在任何一轮迭代中都有 $V_i(-2)=V_i(+2)=0$。
- 每个非终止状态只有两个动作。动作 $a_1$:以 $80\%$ 的概率向左走一格,以 $20\%$ 的概率向右走一格。动作 $a_2$:以 $70\%$ 的概率向左,以 $30\%$ 的概率向右。可以看出 $a_2$ 是“更常向右”的那个动作。
- 回报只看走进去的那个格子 $s'$:$s'=-2$ 得 $10$,$s'=+2$ 得 $50$,走进其他格子得 $-5$(相当于每多走一步要付一点代价)。
- 折扣 $\gamma=1$,所以 $Q$ 就是“立即回报加后继状态的 $V$”按转移概率的加权平均;$V$ 取两个动作的 $Q$ 中较大的那个。
- 最容易出错的一点:第 $i$ 轮计算 $Q$ 时,后继状态的值必须用上一轮的 $V_{i-1}$。既不能用本轮刚算出来的新值(那是 Gauss–Seidel 式的原地更新,与题目要求的同步更新不同),也不能把上一轮的 $V_i(s)$ 直接抄成 $V_{i+1}(s)$。
▸HW4.1 两轮值迭代
题在问什么。从全零的 $V_0$ 出发做两轮值迭代,交出 $V_1$ 和 $V_2$ 在五个状态上的值,共 $5+5=10$ 个数;再用 $V_2$ 读出三个非终止状态的贪心策略。
需要的公式只有这一行($\gamma=1$)。$T(s'\mid s,a)$ 是在 $s$ 执行 $a$ 后到达 $s'$ 的概率,$R(s,a,s')$ 是这一步的回报:
$$ Q_i(s,a)=\sum_{s'}T(s'\mid s,a)\big[R(s,a,s')+V_{i-1}(s')\big],\qquad V_i(s)=\max_a Q_i(s,a). $$
第 1 轮。$V_0$ 全是 $0$,所以方括号里的后继值全部消失,$Q$ 退化成“立即回报的期望”。
- 在 $s=-1$:$a_1$ 有 $0.8$ 的概率走进 $-2$(得 $10$),$0.2$ 的概率走进 $0$(得 $-5$),所以 $Q(a_1)=0.8\cdot 10+0.2\cdot(-5)=7$。$a_2$ 同理,$Q(a_2)=0.7\cdot 10+0.3\cdot(-5)=5.5$。取较大者,$V_1(-1)=7$。
- 在 $s=0$:无论选 $a_1$ 还是 $a_2$,下一步都只能走进 $\pm 1$,回报都是 $-5$,因此两个 $Q$ 都等于 $-5$,$V_1(0)=-5$。
- 在 $s=+1$:$a_1$ 有 $0.2$ 的概率走进 $+2$(得 $50$),$Q(a_1)=0.8\cdot(-5)+0.2\cdot 50=6$;$a_2$ 有 $0.3$ 的概率走进 $+2$,$Q(a_2)=0.7\cdot(-5)+0.3\cdot 50=11.5$。故 $V_1(+1)=11.5$。
第 2 轮。现在后继值不再全是 $0$。走进终止状态时后继值仍是 $0$;走进非终止状态时,要在回报 $-5$ 之上再加该状态的 $V_1$。
- 在 $s=-1$:$a_1$ 仍有 $0.8$ 的概率进 $-2$,得 $10+0$;有 $0.2$ 的概率进 $0$,得 $-5+V_1(0)=-5+(-5)=-10$。于是 $Q(a_1)=0.8\cdot 10+0.2\cdot(-10)=6$。$a_2$ 同理,$Q(a_2)=0.7\cdot 10+0.3\cdot(-10)=4$。故 $V_2(-1)=6$。
- 在 $s=0$:向左走到 $-1$ 时带上 $V_1(-1)=7$,即 $-5+7=2$;向右走到 $+1$ 时带上 $V_1(+1)=11.5$,即 $-5+11.5=6.5$。
$$ Q(a_1)=0.8(-5+7)+0.2(-5+11.5)=0.8\cdot 2+0.2\cdot 6.5=1.6+1.3=2.9, $$
$$ Q(a_2)=0.7\cdot 2+0.3\cdot 6.5=1.4+1.95=3.35. $$
故 $V_2(0)=3.35$。
- 在 $s=+1$:向左走进 $0$ 时带上的是 $V_1(0)=-5$,不能把 $V_1(+1)=11.5$ 再抄一遍;向右走进 $+2$ 时后继值是 $0$。
$$ Q(a_1)=0.8(-5-5)+0.2\cdot 50=-8+10=2, $$
$$ Q(a_2)=0.7(-5-5)+0.3\cdot 50=-7+15=8. $$
故 $V_2(+1)=8$。(原笔记此处把 $-8+10$ 误算成 $6$,正确值是 $2$;$V_2(+1)$ 与策略不受影响。)
由 $V_2$ 读出策略:在每个状态选 $Q$ 较大的动作。$\pi(-1)=a_1$(因为 $6\gt4$),$\pi(0)=a_2$(因为 $3.35\gt2.9$),$\pi(+1)=a_2$(因为 $8\gt2$)。直觉上,靠近 $-2$ 的格子愿意“更常向左”拿稳 $10$ 分;中间和右侧的格子则愿意选“更常向右”的 $a_2$,去换那 $50$ 分。
要交的答案(十个数加三个动作),按 $(-2,-1,0,+1,+2)$ 的顺序:
$$ V_1=(-2,-1,0,+1,+2)=(0,\ 7,\ -5,\ 11.5,\ 0), $$
$$ V_2=(0,\ 6,\ 3.35,\ 8,\ 0),\qquad \pi(-1)=a_1,\ \pi(0)=a_2,\ \pi(+1)=a_2. $$
注意 $V_2(-1)=6\lt V_1(-1)=7$:值并不单调上升。原因是第 2 轮把“走进 $0$ 之后还要继续付代价”也算了进去,第 1 轮的估计过于乐观。
把折扣 MDP 化成无折扣 MDP
第二道书面题回答一个很实用的问题:手里的求解器只支持 $\gamma'=1$,能不能用它解一个 $\gamma\lt1$ 的问题?答案是可以,关键在于给折扣一个概率解释——“每走一步,游戏都有 $1-\gamma$ 的概率被强制结束”。这样,未来回报被“打折”就等价于“未来回报有一定概率根本拿不到”,期望值的缩放效果完全相同。
▸HW4.2 把 $\gamma\lt1$ 化成 $\gamma'=1$
题在问什么。原 MDP 每走一步,未来值要乘折扣 $\gamma\lt1$。手里的求解器只会解 $\gamma'=1$ 的 MDP。需要构造一个新 MDP(可以增加一个新状态 $o$),使它在原有状态上的最优值与原 MDP 相同。
想法。把 $\gamma\lt1$ 读成:每一步有概率 $1-\gamma$ “游戏被强制结束”。结束时走到一个新的吸收状态 $o$,$V'(o)=0$,此后不再有任何回报。
新转移与新回报($s,s'$ 都是原状态):
$$ T'(s'\mid s,a)=\gamma\,T(s'\mid s,a),\qquad T'(o\mid s,a)=1-\gamma, $$
$$ R'(s,a,s')=\frac{R(s,a,s')}{\gamma},\qquad R'(s,a,o)=0. $$
在 $o$ 上停住:$T'(o\mid o,\cdot)=1$、$R'(o,\cdot,o)=0$。容易检查新转移仍是合法分布:$\sum_{s'}\gamma T(s'\mid s,a)+(1-\gamma)=\gamma+1-\gamma=1$。
为什么 $R$ 要除以 $\gamma$。在新方程里,走到 $s'$ 的概率已经带了一个因子 $\gamma$。如果回报不除回来,这一步的立即回报也会被额外乘上一次 $\gamma$,而原 MDP 中立即回报是不打折的,值会整体错一层。代入新 MDP 的贝尔曼方程($\gamma'=1$;走到 $o$ 的那一项是 $(1-\gamma)(0+V'(o))=0$,可以略去):
$$ V'(s)=\max_a\sum_{s'}\gamma T\Big(\frac{R}{\gamma}+V'(s')\Big) =\max_a\sum_{s'}T\big[R+\gamma V'(s')\big]. $$
右端正是原 MDP 的贝尔曼方程。由于 $\gamma\lt1$ 时该方程的解唯一,所以在原状态上 $V'=V$,最优策略也相同。
要交的答案:$T'$、$R'$,以及上面这三行验证。
编程部分:从基于模型到函数逼近
编程部分的环境是连续的 Mountain Car。下面的实现题按“模型已知程度递减”的顺序排列:先估一个模型再做值迭代,然后完全不估模型、直接做 Q 学习,最后用特征把 Q 学习推广到连续状态。
▸HW4.3 到 HW4.5 实现
为何不能对连续小车直接值迭代。值迭代要为每个状态存一个 $V(s)$,连续状态有无穷个格子,存不下也遍历不完。做法是先把状态离散化,再用环境的 .step 做蒙特卡洛采样:统计“从 $(s,a)$ 出发到达各个 $s'$ 的频率”估出 $\hat T$,对回报求平均估出 $\hat R$,每隔若干步(作业里是 calcValIterEvery)在估出的模型上跑一次值迭代,这就是 ModelBasedMonteCarlo。它的风险在于覆盖不足:如果某些 $(s,a)$ 很少被访问,估出来的 $\hat T$ 描述的是另一盘棋,值迭代会在错误的模型上认真地算出“最优”策略。
表格 Q 学习。不再估 $T$ 和 $R$,而是对每个(离散化后的)$(s,a)$ 直接维护 $Q(s,a)$。每观察到一条经验 $(s,a,r,s')$,更新就这一行:
$$ Q(s,a)\leftarrow Q(s,a)+\alpha\big(r+\gamma\max_{a'}Q(s',a')-Q(s,a)\big). $$
其中 $\alpha$ 是学习率,括号里是时序差分误差:新目标 $r+\gamma\max_{a'}Q(s',a')$ 与旧估计之差。动作用 $\varepsilon$-贪心(作业里对应 explorationProb)选取:以概率 $\varepsilon$ 随机走,否则走当前 $Q$ 最大的动作。$\varepsilon=0$ 时智能体可能永远见不到右侧山顶的回报。
函数逼近。不再给每个格子存一个 $Q$,而是让 $Q(s,a)=w\cdot\phi(s,a)$,其中 $\phi(s,a)$ 是特征向量,$w$ 是权重。本作业的 $\phi$ 是傅里叶特征(Fourier features):对缩放到 $[0,1]$ 的状态坐标 $s_i$,取所有分量都在 $\{0,1,\ldots,m\}$ 内的整数系数组合,再取 $\cos(\pi\sum m_i d_i s_i)$(每个整数系数对应一维坐标;$k$ 维状态共有 $(m+1)^k$ 个这样的特征)。其依据是傅里叶级数:足够多的余弦可以拼出任意光滑函数。更新的是 $w$,不是格子:把上式中的 $Q(s,a)$ 换成 $w\cdot\phi(s,a)$,梯度方向就是 $\phi(s,a)$。二维的 Mountain Car 离散化后格子不多,表格方法通常更稳;状态很高维、或探索时间不够、很多格子从未访问时,函数逼近能把“附近格子”的经验借过来,泛化得更好。
安全探索。max_speed 在物理积分之后把速度截断,所以智能体看到的已经是截断后的动力学。若最优轨迹本来就不超速,把截断去掉(设成很大的值),回报曲线看起来差不多。动作层约束则不同:要求下一步速度不得超过阈值(作业中为 $0.065$),某一步没有任何合法动作时 Actions 返回 None,需要单独处理。这改变了每一步的可行动作集合,学到的策略会真的不同。作业把这类约束类比为医学与安全研究里的伦理准则(如 Belmont Report、Menlo Report):在试错过程中事先划出不可逾越的边界。
书面:对比两套 MDP。设想一辆自动驾驶车在有行人的路口学习。MDP A:状态含行人位置,动作含“不管红灯”,回报只看是否尽快到达,探索完全随机,因此在学习过程中可能撞人。MDP B:把碰撞写成很大的负回报,Actions(s) 直接去掉会撞人的动作,探索只在安全动作里做 $\varepsilon$-贪心。两者的差别正对应上面“改回报”与“改可行集”两种约束方式。
Pac-Man:多智能体 minimax 与 expectimax
HW5 把博弈一章的递推写成代码。与课堂上的两人零和博弈相比,Pac-Man 的特殊之处在于“一个最大化者对多个最小化者”:Pac-Man 想让分数最高,若干幽灵轮流行动、想让分数最低。因此最需要先想清楚的是“一层深度”到底包含什么。
一层深度不是“走一步”,而是“Pac-Man 走一次,然后每个幽灵各走一次”。深度 $d$ 只在最后一个幽灵走完后减一。递归到达终局(输或赢)时用 getScore 作为效用;深度用尽而游戏还没结束时,用 evaluationFunction 给出估值。
▸HW5.1 minimax 递推
题在问什么。写出 $V(s,d)$ 的递推式。$a_0$ 是 Pac-Man(取最大),$a_1,\ldots,a_n$ 是 $n$ 个幽灵(取最小)。判断顺序是:先看棋局是否结束,再看深度是否用尽,最后才按当前行动者分支。
先看 $1$ 个幽灵、$d=1$。树只有两层:Pac-Man 选一步,幽灵再选一步,然后 $d$ 变成 $0$,调用估值函数。Pac-Man 会在“幽灵随后能把它压到的值”里选最大的那个。这就是普通的一层 minimax。
两个幽灵时。顺序是 Pac-Man、幽灵 1、幽灵 2。幽灵 1 走完时 $d$ 还不变;幽灵 2 走完才 $d\leftarrow d-1$。漏掉这一点,要么每个幽灵都减一导致少搜几层,要么提前停止。
$$ V(s,d)= \begin{cases} \mathrm{Utility}(s) & \mathrm{IsEnd}(s)\\ \mathrm{Eval}(s) & d=0\\ \max_{a}V(\mathrm{Succ}(s,a),d) & \mathrm{Player}(s)=a_0\\ \min_{a}V(\mathrm{Succ}(s,a),d) & \mathrm{Player}(s)=a_i,\ 1\le i\lt n\\ \min_{a}V(\mathrm{Succ}(s,a),d-1) & \mathrm{Player}(s)=a_n. \end{cases} $$
实现时用 agentIndex 表示当前行动者:0 取 max,其余取 min;下标超过 $n$ 后回到 $0$,同时 $d\leftarrow d-1$。一层深度对应树高 $n+1$,深度 $d$ 的搜索树高 $d(n+1)$。
对拍根值(递推写错时这组数对不上):
| 图 | $d=1$ | $d=2$ | $d=3$ | $d=4$ |
|---|---|---|---|---|
minimaxClassic |
$9$ | $8$ | $7$ | $-492$ |
mediumClassic |
$9$ | $18$ | $27$ | $36$ |
HW5.2 的 α-β 剪枝必须得到同一组根值,只是少展开一些节点;若剪枝版本的根值与 minimax 不同,说明 $\alpha,\beta$ 窗口没有沿递归正确传递。
要交的答案:上面的分段式;编程部分对上这组根值。
expectimax 解决的是另一种对手模型。minimax 假设幽灵是最坏情况的对手,这在幽灵其实随机乱走时显得过于悲观:Pac-Man 会因为“理论上可能被堵死”而放弃一条大概率成功的路。
▸HW5.3 expectimax
题在问什么。幽灵不再联手使坏,而是在合法动作里均匀随机地选。Pac-Man 仍然取最大;幽灵层改成取期望。
先看一个数。某幽灵有两个合法动作,后继值分别是 $10$ 与 $-4$。minimax 认为幽灵会选最坏的,取 $-4$;expectimax 取平均 $(10-4)/2=3$。如果 Pac-Man 还有另一条稳得 $0$ 的路,minimax 会选 $0$,expectimax 则会赌期望为 $3$ 的那条。
做法是把 HW5.1 里每个 $\min_a$ 换成
$$ \frac{1}{|\mathrm{Actions}(s)|}\sum_{a}V(\mathrm{Succ}(s,a),\ d\text{ 或 }d-1). $$
其中 $|\mathrm{Actions}(s)|$ 是当前幽灵的合法动作数。$d$ 仍只在最后一个幽灵走完后减一。随机幽灵不会专门去堵门,所以 expectimax 智能体会冒险尝试逃生、吃豆。在 trappedClassic 上用深度 $3$ 运行,大约一半的局能赢:失败时最终得分大约 $-502$,成功时大约 $531$ 或 $532$(并列打破规则不同会差 $1$ 分)。还需要注意,估值函数必须与 getScore 同一尺度,否则深度用尽时返回的数与终局效用不可比,比较 max / 期望时会出错。
要交的答案:期望形式的递推式;编程部分对上 trappedClassic 结果的符号与量级。
Scheduling:约束满足问题与排课
HW6 对应因子图 / CSP 一章。书面部分先练“如何把问题建成 CSP”(电灯问题、XOR 链),再精确数一遍回溯的递归调用次数,体会变量顺序与弧一致(AC-3)对搜索量的影响;编程部分实现 $n$ 皇后的对拍,再把真实的 Stanford 排课问题建成带权 CSP。
这里的关键想法是:CSP 的建模就是选变量、定域、写约束(因子);回溯的效率则取决于“约束多早能被检查到”。
▸HW6.1 电灯 CSP 与 XOR 链
电灯在问什么。$n$ 盏灯开始全灭,有 $m$ 个按钮。按钮 $j$ 控制集合 $T_j$ 里的灯,按下就把这些灯全部翻转。同一盏灯被翻转奇数次才会亮。要求构造一个 CSP,用 $m$ 个变量、$n$ 条约束描述“所有灯都亮”。
变量 $B_j\in\{0,1\}$ 表示按钮 $j$ 按不按(按两次等于没按,所以只需 $0/1$)。灯 $i$ 的约束:控制它的那些按钮里,按下的个数是奇数,即异或为 $1$:
$$ \bigoplus_{j:\,i\in T_j}B_j=1. $$
一条约束最多依赖全部 $m$ 个按钮(某盏灯可能被所有按钮控制),所以题目允许使用 $m$ 元约束。
XOR 链:先列真值表。三个变量 $X_1,X_2,X_3\in\{0,1\}$,两个因子 $t_1=X_1\oplus X_2$、$t_2=X_2\oplus X_3$,因子值为 $1$ 才合法。$\oplus$ 为 $1$ 当且仅当两边不同。
| $X_1$ | $X_2$ | $X_3$ | $t_1$ | $t_2$ | 合法? |
|---|---|---|---|---|---|
| $0$ | $0$ | $0$ | $0$ | $0$ | 否 |
| $0$ | $0$ | $1$ | $0$ | $1$ | 否 |
| $0$ | $1$ | $0$ | $1$ | $1$ | 是 |
| $0$ | $1$ | $1$ | $1$ | $0$ | 否 |
| $1$ | $0$ | $0$ | $1$ | $0$ | 否 |
| $1$ | $0$ | $1$ | $1$ | $1$ | 是 |
| $1$ | $1$ | $0$ | $0$ | $1$ | 否 |
| $1$ | $1$ | $1$ | $0$ | $0$ | 否 |
一致赋值只有 $(0,1,0)$ 与 $(1,0,1)$。一句话概括:$X_1=X_3\ne X_2$。
回溯数的是“调用次数”。规则是:只有新赋值与已经完整出现(所有变量都已赋值)的因子不冲突,才继续递归调用;冲突的叶子不算调用。题目要求找出全部解,所以找到一个解后仍要继续。
官方示例用变量序 $X_1,X_2,X_3$:赋完 $X_1,X_2$ 时 $t_1$ 就完整了,失败分支早早被砍,共 $7$ 次。本题要求的是变量序 $X_3,X_1,X_2$。由于 $t_1$ 与 $t_2$ 都含 $X_2$,赋完 $X_3$ 与 $X_1$ 时还没有任何因子完整,要等到赋 $X_2$ 时才检查。逐次列出(花括号里依次是 $X_1,X_2,X_3$ 的当前取值,$[01]$ 表示未赋值):
- $\{[01],[01],[01]\}$:根。
- $X_3=0$:$\{[01],[01],0\}$。
- $X_1=0$:$\{0,[01],0\}$。
- $X_2=1$:$\{0,1,0\}$,合法。$X_2=0$ 使两个因子都为 $0$,不调用。
- $X_1=1$(仍是 $X_3=0$):$\{1,[01],0\}$。随后 $X_2$ 的两个值都不合法,不调用。
- $X_3=1$:$\{[01],[01],1\}$。
- $X_1=0$:$\{0,[01],1\}$。$X_2$ 的两个值都不合法。
- $X_1=1$:$\{1,[01],1\}$。
- $X_2=0$:$\{1,0,1\}$,合法。
共 9 次。次数随变量序而变,因为因子完整出现的早晚不同:先赋一个因子的全部变量,就能更早剪枝。
加上 AC-3 会跳过哪几次。AC-3 在每次赋值后把邻居的域里“已经不可能”的值删掉。赋 $X_3=0$ 后,$t_2$ 迫使 $X_2=1$,再经 $t_1$ 迫使 $X_1=0$,于是第 5 次($X_3=0$ 且 $X_1=1$)根本不会被调用。赋 $X_3=1$ 后对称地跳过第 7 次。共跳过 2 次。
$n$ 皇后(编程对拍)。变量 $i$ 表示第 $i$ 行皇后的位置,域是该行的列号,大小为 $n$。每对行之间有二元约束:列不等、主对角线不等、副对角线不等。$n=8$ 时必须恰好得到 $92$ 个解,朴素回溯恰好 $2057$ 次 backtrack 调用;改用最约束变量(most constrained variable, MCV)启发后为 $1361$ 次(Spring 2020 的作业说明中,MCV 之上再开启 AC-3 会降到 $769$ 次)。如果域更大或约束更少(CSP 不是“最小”的),次数就会对不上。
要交的答案:电灯的变量与 XOR 约束、两个一致赋值、9 次调用列表、跳过 2 次的说明。
排课题是一个真实规模的带权 CSP:输入是 Stanford 课程公报(bulletin)和一份学生的请求档案,输出是满足学分、先修等约束、且偏好权重乘积最大的课表。
▸HW6.3 课表
先在纸上定变量。常见取法:每门已请求的课对应一个变量,域是“某个已注册的季度”或“不选”。or 请求把若干门课绑在一起,要求至多选其中一门。
四类约束怎么落实。
- 每季学分:用
create_sum_variable把该季各门课的学分配成一个“和变量”,再约束它夹在minUnits与maxUnits之间。直接写一个涉及所有课的大因子会让域爆炸,引入和变量是把高元约束拆成低元约束的标准技巧。 - 先修:若请求写了
B after A,或公报里 $A$ 是 $B$ 的官方先修课,则 $B$ 所在的季度必须严格晚于 $A$。 - 同一门课至多出现在一个季度。
in Aut2018,Sum2019这类限定把该课的域直接裁成这些季度。
权重来自 request 的 weight,因子输出正实数,求解器寻找因子乘积最大的赋值。调试顺序:先在 profile*.txt 小档案上对拍(检查学分数、先修是否被违反),再跑完整公报。一个常见坑是漏掉“不选”这个域值,结果出现无解。
要交的答案:submission.py 里通过小档案与隐藏用例;书面部分无需再抄公式。
Car:用贝叶斯网跟踪看不见的车
HW7 对应贝叶斯网与 HMM 一章。故事是:自动驾驶车需要知道周围其他车在哪,但传感器只能给出带噪声的距离读数。作业要求在网格上维护一张“他车在每个格子的概率”的信念图,并随时间不断更新。
先把故事钉死。自己的车位置 $a_t$ 已知。他车位置 $C$ 未知。测距 $D_t$ 是 $\|a_t-C\|$ 再加上高斯噪声(标准差为 $\sigma$)。作业要维护一张格子上的信念
$$ b(c)=P(C=c\mid\text{已见到的测距}), $$
即在给定迄今所有测距的条件下,他车位于格子 $c$ 的后验概率。整个跟踪器只由两种操作组成:“观测到新测距时更新”(发射)和“时间流逝时更新”(转移)。
▸HW7.1 发射
题在问什么。先假设他车静止。每来一个新测距 $d_t$,就把旧信念乘上“若车在这个格子,测到 $d_t$ 有多像”,再除以总和归一化。这叫发射更新(emission update),本质是贝叶斯公式:后验 $\propto$ 先验 $\times$ 似然。
贝叶斯网。$C$ 指向 $D_1,D_2,D_3$。$a_t$ 是已知参数,可以不画,或画成已观察节点。观察到的是 $D$ 与 $a$,不观察 $C$。联合分布按网的结构分解为
$$ P(c,d_{1:3})=p(c)\prod_{t=1}^{3}p(d_t\mid c). $$
先用三个格子走一遍。旧信念 $b=(0.2,\ 0.5,\ 0.3)$。新测距下三个格子的似然(密度值)是 $(0.1,\ 0.8,\ 0.1)$。
- 未归一化:逐格相乘,得 $(0.02,\ 0.40,\ 0.03)$。
- 总和 $Z=0.45$。
- 新信念 $(0.02,\ 0.40,\ 0.03)/0.45\approx(0.044,\ 0.889,\ 0.067)$。
中间那个格子“先验本来就高、又最符合这次测距”,后验被明显抬高。这就是作业可视化里热力图某处变亮的原因。
一般式。记 $b_{t-1}(c)=P(C=c\mid d_{1:t-1})$,似然 $p(d_t\mid c)=\mathrm{pdf}(\|a_t-c\|_{2},\sigma,d_t)$,即均值为真实距离、标准差为 $\sigma$ 的高斯密度在 $d_t$ 处的值。
$$ b_t(c)\leftarrow b_{t-1}(c)\,p(d_t\mid c),\qquad b_t(c)\leftarrow b_t(c)\Big/\sum_{c'}b_t(c'). $$
需要注意,util.pdf 返回的是密度,可以大于 $1$;这不影响乘法,因为最后会归一化。$a_t$ 已知,不要把它放进求和变量里。
要交的答案:网的画法、联合式、上面两行更新。
▸HW7.2 转移
题在问什么。他车现在会动。每个时间步,信念先按转移概率摊开(时间流逝),再乘上新测距的似然(发射)。这正是隐马尔可夫模型(hidden Markov model, HMM)前向算法的一步。
网。隐状态构成链 $C_1\rightarrow C_2\rightarrow C_3$,每一时刻再有 $C_t\rightarrow D_t$。$a_t$ 仍是已知参数。
$$ P(c_{1:3},d_{1:3})=p(c_1)p(d_1\mid c_1)\prod_{t=2}^{3}p(c_t\mid c_{t-1})p(d_t\mid c_t). $$
先用三个格子走一遍。旧信念 $b=(1,0,0)$,即车确定在格子 $1$。转移规则:留在原地的概率 $0.5$,向右一格的概率 $0.5$(格子 $3$ 的“向右”当作留下)。
$$ b'(1)=1\cdot 0.5=0.5,\qquad b'(2)=1\cdot 0.5=0.5,\qquad b'(3)=0. $$
热力图从“一个亮点”变成“向可能开走的方向抹开”:时间流逝只会让不确定性增加,只有观测能把它压回去。若转移矩阵 $T$ 每一行加总为 $1$,则 $\sum b'=1$,这一步不必再归一化。
一般式。对每个新格子 $c'$,把所有可能的来源格子按概率加起来:
$$ b'(c')=\sum_{c}b(c)\,T(c\rightarrow c'). $$
完整一轮的顺序是固定的:先 elapseTime,再 observe。顺序反了,就会用“还没走”的位置去解释新测距。
多车。若各车彼此独立且测距能区分来自哪辆车,就各维护一张 $b$。若测距不能标明来自哪一辆,后验要对 $K$ 辆车与测距之间的所有对应关系(排列)求和,状态数乘上 $K!$ 量级。
要交的答案:网、联合式、elapseTime 的求和式,并注明先推移再发射。
Logic:把英语翻译成逻辑公式并推理
HW8 对应逻辑一章。作业的主要工作不是写推理算法(课程提供的代码已经实现了模型检测与消解),而是“翻译”:把自然语言句子准确地写成命题逻辑或一阶逻辑公式,再交给推理引擎。自动评分器会把读者的公式与标准公式比较是否逻辑等价;不等价时会给出一个“反模型”(counter-model),即一个让两条公式取值不同的赋值。
翻译的难点在于自然语言的连接词和量词往往有歧义:英语的 “or” 可能是相容或也可能是排他或,“每个人有一个父母”中的量词顺序决定了意思。下面按题目顺序逐一说明。
▸HW8.1 与 HW8.2 翻译
题在问什么。把英语一句一句换成公式。命题逻辑用大写开头的原子命题;一阶逻辑还要写全称量词 $\forall$ 与存在量词 $\exists$。代码约定:And / Or 只接收两个参数,三个及以上用 AndList / OrList;变量写成 '$x',谓词首字母大写,常量全小写。
命题逻辑:英语连接词对照
| 英语 | 公式 | 代码 |
|---|---|---|
| 夏天并且在加州,那么不下雨 | $\mathrm{Summer}\wedge\mathrm{CA}\rightarrow\neg\mathrm{Rain}$ | Implies(And(Summer,CA), Not(Rain)) |
| 湿当且仅当下雨或喷灌 | $\mathrm{Wet}\leftrightarrow(\mathrm{Rain}\vee\mathrm{Sprinkler})$ | Equiv(Wet, Or(Rain,Sprinkler)) |
| 白天或夜晚,但不能同时 | $\mathrm{Day}\oplus\mathrm{Night}$ | And(Or(Day,Night), Not(And(Day,Night))) |
第三句最容易误写成普通的 $\vee$。普通 $\vee$ 允许“又是白天又是夜晚”,评分器会把这个赋值当作反模型返回。代码库里没有现成的异或,所以用“至少一个且不同时”拼出来。
一阶逻辑:逐词翻译
- “每一个人有一个父母”:先用全称量词限定“人”,再存在一个父母:$\forall x\,\mathrm{Person}(x)\rightarrow\exists y\,\mathrm{Parent}(y,x)$。题面写明不必强迫父母也是 Person。注意全称量词后面接的是 $\rightarrow$ 而不是 $\wedge$:写成 $\wedge$ 会变成“所有东西都是人”。
- “至少一个人没有子女”:存在一个人,并且不存在他的子女:$\exists x\,\mathrm{Person}(x)\wedge\neg\exists y\,\mathrm{Child}(y,x)$。与上一条相反,存在量词后面接的是 $\wedge$;写成 $\rightarrow$ 会被任何一个“非人”的对象平凡满足。
- 父亲的定义:$\mathrm{Father}(x,y)\leftrightarrow\mathrm{Male}(x)\wedge\mathrm{Parent}(x,y)$。“定义”要用双条件,单向蕴含只给出了必要条件或充分条件之一。
- 孙女的定义:$\mathrm{Granddaughter}(x,y)\leftrightarrow\mathrm{Female}(x)\wedge\exists z\,(\mathrm{Child}(x,z)\wedge\mathrm{Child}(z,y))$。中间那一代 $z$ 必须显式写出来并用存在量词约束。题面允许一个人是自己的孩子,所以不必加 $x\ne z$ 之类的条件。
量词位置会改变意思。$\forall x\exists y$ 表示“每个人可以有各自的父母”;写成 $\exists y\forall x$ 就变成“全世界共用同一个父母”。评分器给出的反模型就是让这两种写法取值不相等的一个赋值。
要交的答案:submission.py 里的七个公式;书面部分不必再抄,但上面的对照可用来对拍反模型。
说谎者谜题展示了逻辑推理的典型用法:把每条线索写成公式放进知识库,再让推理引擎回答“谁说了真话”“谁撞了服务器”。手工推理时,最有效的办法是按“唯一的真话者是谁”分情况穷举。
▸HW8.3 说谎者
题在问什么。四个人:Mark、John、Nicole、Susan。每人说了一句话。已知恰好一人说真话,恰好一人撞了服务器。要把六条事实写成公式,并推理出谁说真话、谁撞了服务器。
记号。$T(X)$:$X$ 说真话。$C(X)$:$X$ 撞了服务器。
先把口语换成双条件。说真话则这句话成立;说谎则这句话的否定成立。两者合在一起就是 $T(X)\leftrightarrow(\text{这句话})$。
| 谁 | 原话 | 公式 |
|---|---|---|
| Mark | 不是我撞的 | $T(M)\leftrightarrow\neg C(M)$ |
| John | 是 Nicole 撞的 | $T(J)\leftrightarrow C(N)$ |
| Nicole | 不是我,是 Susan | $T(N)\leftrightarrow\big(\neg C(N)\wedge C(S)\big)$ |
| Susan | Nicole 在说谎 | $T(S)\leftrightarrow\neg T(N)$ |
另外两条:恰好一个 $T$ 成立,恰好一个 $C$ 成立(“恰好一个”可以写成“存在一个 $x$ 满足条件,且对所有 $y\ne x$ 都不满足”)。六条事实按 3a-0 到 3a-5 分开对拍。
纸上穷举:假设唯一的真话者是谁。
- 只有 Mark 说真话。他的话成立 $\Rightarrow\neg C(M)$。John 说谎 $\Rightarrow$ 不是 Nicole 撞的 $\Rightarrow\neg C(N)$。Susan 说谎 $\Rightarrow$ “Nicole 说谎”为假 $\Rightarrow T(N)$。这样已经有 Mark 与 Nicole 两个真话者,与“恰好一人说真话”矛盾。
- 只有 John 说真话。他的话成立 $\Rightarrow C(N)$。Mark 说谎 $\Rightarrow$ “不是我”为假 $\Rightarrow C(M)$。Mark 与 Nicole 都撞了,与“恰好一人撞”矛盾。
- 只有 Nicole 说真话。她的话成立 $\Rightarrow\neg C(N)$ 且 $C(S)$。Mark 说谎 $\Rightarrow C(M)$。又是两人撞了服务器,矛盾。
- 只有 Susan 说真话。她的话只要求 $\neg T(N)$,已经满足。Mark 说谎 $\Rightarrow C(M)$。John 说谎 $\Rightarrow\neg C(N)$。Nicole 说谎:在 $\neg C(N)$ 已经成立时,要让整句 $\neg C(N)\wedge C(S)$ 为假,只能让 $C(S)$ 也为假。于是撞服务器的只有 Mark。四个条件全部相容。
要交的答案:六条公式;运行 3a-run 应得到 Susan 说真话、Mark 撞了服务器。纸上的穷举用来理解,不必整段贴进 PDF。
最后一题考察一阶逻辑的“公理化”:只给出几条关于后继、奇偶与“更大”的约束,看推理引擎能否推出“每个数都有一个比它大的偶数”。它的教学要点是:推理引擎只知道知识库里写下的东西,日常常识不会自动成立。
▸HW8.4 奇偶与可解释性
题在问什么。用六条约束描述“后继、奇偶、更大”,查询已经写好:每个数都存在一个比它大的偶数。Larger 只是一个谓词名字,不要偷用“大于”的日常性质(例如“自己不能比自己大”),题面没写的性质就不能用。表中 $\mathrm{succ}(x)$ 指第一条约束保证唯一存在的那个后继 $y$。
| 约束 | 公式 | 一句话 |
|---|---|---|
| 恰好一个后继,且不是自己 | $\forall x\,\exists! y\,\mathrm{Succ}(x,y)\wedge y\ne x$ | 每个 $x$ 指向别人 |
| 奇偶恰好一个 | $\forall x\,(\mathrm{Odd}(x)\oplus\mathrm{Even}(x))$ | 不能两空也不能两真 |
| 偶的后继是奇 | $\forall x\,\mathrm{Even}(x)\rightarrow\mathrm{Odd}(\mathrm{succ}(x))$ | 偶数的下一个是奇数 |
| 奇的后继是偶 | $\forall x\,\mathrm{Odd}(x)\rightarrow\mathrm{Even}(\mathrm{succ}(x))$ | 奇数的下一个是偶数 |
| 后继比自己大 | $\forall x\,\mathrm{Larger}(\mathrm{succ}(x),x)$ | 往后走一步就变大 |
| 更大可传递 | $\forall x,y,z\,\mathrm{Larger}(x,y)\wedge\mathrm{Larger}(y,z)\rightarrow\mathrm{Larger}(x,z)$ | 链式比较成立 |
查询(已给出,不要改):$\forall x\,\exists y\,\mathrm{Even}(y)\wedge\mathrm{Larger}(y,x)$。推理思路:若 $x$ 是奇数,则 $\mathrm{succ}(x)$ 是偶数且比 $x$ 大;若 $x$ 是偶数,则 $\mathrm{succ}(\mathrm{succ}(x))$ 是偶数,由“后继比自己大”两次再用传递性,它比 $x$ 大。
可解释性(书面)。推理系统除了回答 Yes / No,还会输出一棵消解树(resolution tree)。那是形式推导的步骤,而不是“因为 Nicole 人品不好”这种因果故事。如果知识库写错了,解释也会认真地为错误的模型辩护——解释的可信度不会高于知识库本身。
可靠与完备。可靠性(soundness):推出来的都是真的。完备性(completeness):真的都能推出来。
- 大语言模型会产生幻觉,推出假句子,所以不可靠;它也推不出大量本该成立的句子,所以也不完备。
- 安全关键场景(假结论会伤人)应选可靠但不一定完备的系统:宁可答“不知道”,也不给错误答案。
- 如果旁边另有验证器,可以选完备但不可靠的系统:先尽量多地捕获候选结论,再由验证器过滤掉错的。
要交的答案:六条公式按 4a-0 到 4a-5 对拍;书面写清消解树是什么,以及在可靠 / 完备之间如何选择。
2025 Autumn 的替换作业
不同学期的作业组合并不完全相同。2025 Autumn 用两份作业替换了排课与车辆跟踪:一份是系统发生树上的概率推断,一份是关于 AI 与社会的书面作业。2026 Spring 不交这两份,其中的社会类题目被拆进各周书面作业的伦理小节。
▸Aut HW6 系统发生树
系统发生树(phylogenetic tree)是一个树形贝叶斯网:节点是物种或基因,每条边上有一张条件概率表(conditional probability table, CPT),描述“子节点的状态在给定父节点状态下的分布”(例如进化过程中的突变概率)。
forward_sampling必须按拓扑序进行:先采样没有父节点的变量(根),再依次采样子节点,保证每次采样时父节点的值已经确定。- 一个完整样本的联合概率是一路上各 CPT 取值的乘积,这正是贝叶斯网的链式分解。
- 拒绝采样(rejection sampling):先按先验采样整棵树,观测变量(通常是叶子上的现存物种)对不上就扔掉,留下的样本的频率用来估计后验。观测越罕见,被扔掉的样本越多,效率越低。
- 当祖先节点的状态是隐藏的,学习 CPT 参数就需要极大似然估计(MLE)配合 EM 算法:E 步推断隐藏祖先的后验,M 步按期望计数重估 CPT。
- 调试建议:卡住时先在只有 $3$ 个节点的树上手算一个样本及其概率,再与代码对比。
▸Aut HW8 Society
这份书面作业要求围绕一个具体的 AI 产品展开分析,而不是泛泛而谈:数据从哪里来、对用户是否透明、训练数据有没有版权与劳动问题(例如标注工人的待遇)、按人群分组的指标在哪一组掉了下去。格式上,PDF 里禁止嵌入超链接,URL 必须粘贴成可见文本。2026 Spring 把同类问题拆进贷款、导航、毒性、自动驾驶等书面小节,不再单独开这一份作业。
▸衔接
作业是按应用组织的(情感分类、小车、Pac-Man……),考试则按模型族组织。下一章把同一张对照表对回考试:考题问的是“损失的梯度”还是“优先队列弹出时的不变量”,而不是“情感”或“小车”本身。
实践:把作业经验迁移到考试与项目
作业按应用组织、考试按模型族组织,两者必须能互相翻译:看到一道题面,要能立刻判断它属于哪个模型族(反射、搜索、MDP、博弈、CSP、贝叶斯网、逻辑),应该用什么推断算法,最终需要报告哪些量。经验上,失分更多来自状态表示与因子设计这类“建模”层面的错误,而不是步长之类的超参数。
这一章给出作业与模型族的对照表、对拍(与参考答案或手算结果逐项比对)的顺序、最常见的实现失败,以及做课程项目时的选题建议。
作业与模型族的对照
下表把每份作业归入一个模型族,列出所用的推断方法和必须报告的量。输入是题面,输出是“这是哪个模型族”以及“要报告什么”。
| 作业 | 模型族 | 推断 | 必须报告 |
|---|---|---|---|
| Foundations | 预备 | 手算 + 短函数 | 导数、期望、大 O |
| Sentiment | 反射 | SGD | 训练 / 验证零一、特征维 |
| Route | 搜索 | UCS / A* | 代价、展开数、$h$ 是否一致 |
| Mountain Car | MDP / RL | Q 学习 | 回报曲线、$\varepsilon$、$\alpha$ |
| Pac-Man | 博弈 | minimax / expectimax | 根值、展开叶子、节点类型 |
| Scheduling | CSP / 因子图 | 回溯、束搜索 | 是否可行、$K$、违反约束 |
| Car tracking | 贝叶斯网 | 粒子滤波 | 后验 / ESS、粒子数 |
| Logic | 逻辑 | 翻译与检验 | 公式、小论域反模型 |
| Society | 评价 | 分组指标 | 伤害、各组样本量与分数 |
表中 $K$ 是束搜索的束宽,ESS 是粒子滤波的有效样本量(effective sample size),用来判断粒子权重是否退化。
对拍顺序
通用的调试顺序是由小到大:先手写 $2$ 到 $4$ 个状态的小例子并手算答案,再跑实现与之比对,最后才上作业给的大图。各模型族的具体做法如下:
- 搜索:先对拍最优代价,再对拍展开节点数(后者对实现细节更敏感)。
- 博弈:先对拍不剪枝时的根值,再打开 α-β,对拍被展开的叶子集合。
- 粒子滤波:在隐状态空间 $|Z|$ 很小的例子上,与精确的前向算法对拍。
- 逻辑:先自己构造反模型,确认公式在哪个赋值上与预期不同。
常见实现失败
最常见的五类实现失败是:状态漏了字段(导致不同情形被当成同一状态)、启发式不可采纳、Q 学习不探索、CPT 未归一化、量词范围写错。此外,优先队列中代价相同的元素、粒子权重相同的粒子,其并列处理方式必须与自动评分器一致,否则展开数等计数会对不上。
▸项目失败模式
课程项目常见的失败方式包括:选题时把四条轴叠在一次实验里,到中期仍没有可对拍的基线,用测试集调 $h$ 或 $\varepsilon$,不固定随机种子(seed)。合理的范围是先固定模型族与评价指标,再更换启发式或网络。另一种典型错误是把语言模型当作万能的推断工具,再在报告里写“已经做了 minimax”——这属于模型族选错,而不是调参失败。
课程项目的选题
如果本学期有可选项目或 AI Product Deep Dive,动手之前先写三句话:任务是什么、属于哪个模型族、要报告哪些量。只有当它能落在单一轴上时再开始做。例如,路径规划不要同时上 Q 学习和 α-β;需要自然语言接口时,把“语言翻译成形式表示”与“在形式表示上做符号推断”分开评估,这样出错时能定位是哪一环的问题。
▸衔接
实验协议写清之后,复习时只需保留各条轴的定义、递推式与正确性条件,即下一章的内容。
复习:公式、算法选择与正确性条件
这一章是期末前的速查表。闭卷考试通常覆盖:损失与 SGD、FutureCost 以及 UCS / A* 的最优性条件、贝尔曼方程与 Q 学习、minimax 与 α-β、因子与变量消去(VE)、d-分离、消解。编程题则覆盖:可哈希的状态表示、优先队列的键、α-β 的窗口传递、粒子权重的归一化。下面按“公式 → 选算法 → 正确性条件”三部分整理,具体推导见前面各模型族的章节。
核心公式速查
- 反射模型:分数 $s=w\cdot\phi(x)$,其中 $\phi(x)$ 是特征向量。铰链损失 $\max(0,1-ys)$ 在 $ys\lt1$ 时梯度 $\nabla_w=-y\phi(x)$,在 $ys\gt1$ 时梯度为 $0$。逻辑损失 $\log(1+e^{-ys})$ 的梯度为 $-\frac{y}{1+e^{ys}}\phi$。SGD 加 $L_2$ 正则时梯度多一项 $\lambda w$。
- 搜索:$\mathrm{FutureCost}(s)=\min_a[\mathrm{Cost}(s,a)+\mathrm{FutureCost}(\mathrm{Succ})]$,终点处为 $0$。UCS 的优先队列键是起点到当前状态的代价 $g$,边权非负时状态弹出即最优。A* 的键是 $g+h$。一致性:$h(s)\le\mathrm{Cost}(s,a)+h(\mathrm{Succ})$(且终点处 $h=0$),一致蕴含可采纳。
- MDP:$V_{\mathrm{opt}}(s)=\max_a\sum_{s'}T(s,a,s')[R+\gamma V_{\mathrm{opt}}(s')]$。Q 学习:$Q\leftarrow(1-\alpha)Q+\alpha(r+\gamma\max_{a'}Q(s',a'))$,与前面作业中的“加上 $\alpha$ 倍误差”写法等价。
- 博弈:max / min / chance 三类节点分别取最大、最小、期望。α-β:$\alpha$ 是 max 方已保证的下界,$\beta$ 是 min 方已保证的上界,当 $\alpha\ge\beta$ 时剪枝,剪枝不改变根值。
- 因子图:赋值的权重 $w(x)=\prod_j f_j$。变量消去的一步:消去 $x_i$ 时,把所有含 $x_i$ 的因子 $F_i$ 乘起来再对 $x_i$ 求和,得到新因子 $f_{\mathrm{new}}(x_S)=\sum_{x_i}\prod_{f\in F_i}f$,其中 $x_S$ 是这些因子涉及的其他变量。总代价约为 $|\mathcal{D}|^{w+1}$,$|\mathcal{D}|$ 是域大小,$w$ 是消去顺序对应的树宽(treewidth)。
- 贝叶斯网:$P(x)=\prod_i P(x_i\mid x_{\mathrm{Pa}(i)})$。对撞结构($A\to C\leftarrow B$)在观察到 $C$(或其后代)后由阻断变为开通。HMM 前向:$\alpha_t(z)=P(x_t\mid z)\sum_{z'}\alpha_{t-1}(z')T(z'\rightarrow z)$,即“先转移再发射”。
- 逻辑:$\mathrm{KB}\models\phi$ 当且仅当 $\mathrm{KB}\wedge\neg\phi$ 不可满足。消解规则 $(C\vee P),(D\vee\neg P)\vdash C\vee D$。$\forall x\exists y$ 中 $y$ 的 Skolem 化结果是 $f(x)$(因为 $y$ 依赖于 $x$)。
按题面选算法
下表左列是题面特征,中列是适用方法,右列是容易误选的方法。
| 题面 | 适用 | 易错项 |
|---|---|---|
| 一步输出标签 | 反射 + SGD | UCS |
| 确定后继、最小代价路径 | UCS / A* | 值迭代(除非要整张表) |
| 随机后继、要策略 | 值迭代或 Q 学习 | 一条预先路径 |
| 对手对抗 | minimax + α-β | expectimax |
| 对手随机已知 | expectimax | α-β |
| 固定变量、零一约束 | CSP 回溯 + AC-3 | FutureCost |
| 要 $Z$ 或边缘 | VE | 只跑束搜索当精确 |
| 时间链、大 $\lvert Z\rvert$ | 粒子滤波 | 整段轨迹上 VE |
| 是否必然 | 消解 / 模型检测 | 语言模型似然 |
几点说明:随机后继时最优行为依赖于实际到达的状态,所以需要策略而不是一条预先规划好的路径;对手随机时 α-β 的剪枝前提(对手取最小)不成立;束搜索是近似方法,不能用来求精确的归一化常数 $Z$ 或边缘分布。
各算法的正确性条件
- UCS / 一致启发式下的 A*:边权必须 $\ge 0$;状态在弹出时标记为已完成,而不是入队时。
- 只有可采纳性不足以保证图搜索版 A* 可以禁止重新打开已弹出的状态;要做到“弹出即最优”,启发式必须一致。
- 值迭代收敛:需要 $\gamma\lt1$,或者在任何策略下几乎必然到达吸收(终止)状态。
- Q 学习收敛:每个 $(s,a)$ 被访问无穷多次,使用表格表示,学习率 $\alpha$ 适当衰减。
- α-β:只适用于纯 minimax(没有 chance 节点);$\alpha,\beta$ 窗口必须沿递归传递下去。
- AC-3 之后所有定义域都非空 $\not\Rightarrow$ 问题可满足:弧一致只是局部一致性。
- VE 的消去顺序差时,中间因子会爆炸,但只要算得完,结果仍然正确:顺序只影响代价,不影响值。
- 粒子权重必须归一化;若所有粒子的似然都为零,需要重新初始化粒子。
- 用消解证明 $\mathrm{KB}\models\phi$ 时,必须先把 $\neg\phi$ 加入知识库再推出空子句。
▸三条失败轴
- 拟合或求解失败:模型族选错、状态漏字段、启发式不一致、探索率为 $0$、CPT 未归一化。
- 评价失败:把展开节点数当作唯一分数、用训练回报来选 $\varepsilon$、把边缘分布的最大值当成 MAP 赋值(逐变量取最大不一定是联合最大)。
- 部署失败:没有报告分组指标、用语言的流畅程度代替逻辑蕴含、跟踪系统的误报代价没有写明。
参考文献
课程主页
- Stanford CS221. Artificial Intelligence: Principles and Techniques,2026 Spring。https://stanford-cs221.github.io/spring2026/
- Stanford CS221. Autumn 2025。https://stanford-cs221.github.io/autumn2025/
- Stanford CS221. Spring 2025 模块。https://stanford-cs221.github.io/spring2025/modules/
- Stanford Explore Courses. CS 221。
往年作业说明可用来核对作业部分的对拍数值,例如 Pac-Man 的根值表与 expectimax 得分:https://stanford-cs221.github.io/autumn2023/assignments/pacman/index.html;Mountain Car 的数轴 MDP、傅里叶特征与安全探索阈值:https://stanford-cs221.github.io/autumn2023/assignments/mountaincar/index.html;排课作业的 $n$ 皇后调用次数与 XOR 链示例:https://stanford-cs221.github.io/spring2021/assignments/scheduling/index.html;AC-3 下的 $769$ 次:https://stanford-cs221.github.io/spring2020/assignments/scheduling/index.html。
讲义与教材
- Percy Liang 等. CS221 模块讲义(反射、搜索、MDP、博弈、因子图、贝叶斯网、逻辑)。
- Russell S, Norvig P. Artificial Intelligence: A Modern Approach。综合参考;记号与课程不完全相同。
- Sutton R, Barto A. Reinforcement Learning: An Introduction。MDP 与强化学习。
- Koller D, Friedman N. Probabilistic Graphical Models。因子图与贝叶斯网;CS228 教材。
- Tsang E. Foundations of Constraint Satisfaction。CSP。
工具与使用建议
- 作业的自动评分对节点计数与浮点数都很严格,建议先在小图上对拍 UCS / A* / α-β。
- NumPy 用于 SGD 与粒子滤波;搜索与博弈用可哈希的状态加上堆(优先队列)。
- 逻辑作业先手写小模型再交给求解器:量词范围的错误无法靠求解器发现,因为求解器只会忠实地推理写错的公式。


