1. 先把课程骨架拆开:两条技术路线到底怎么衔接
“AI入门课程复习全攻略”这件事我做过好几轮,带新人做过,自己回炉也做过。这门课最坑的地方不在于难,而在于知识结构本身是断的:前半学期讲状态空间搜索、一阶逻辑、归结原理、专家系统,读起来像离散数学的续集;后半学期突然跳到最小二乘、梯度下降、反向传播、卷积核,画风突变像换了门课。多数人两段各自硬啃,考完一周就忘干净,下次再看到“启发式函数”和“链式法则”还是发懵。
我这份复习笔记的目的很直接:把符号推理和神经网络这两条线接成一条能讲通的故事。你要知道每个知识点在整门课里站在什么位置,手推题该从哪里下笔,概念题该怎么区分。适合的人群也挺明确——正在准备期末的本科生、跨专业补基础的人、已经工作但想把AI底层重新捋一遍的开发者,都能按这个顺序走。下面全是我自己复习时用的顺序、手算清单和踩过的坑,不是对课程大纲的复述。
1.1 符号推理那条线到底在解决什么问题
符号推理的核心命题只有两个:知识怎么表示,答案怎么搜出来。整条线是从这两个问题长出来的。
状态空间搜索解决“搜索”这一半:把问题抽象成状态集合、动作集合、转移函数、目标测试、路径代价这五元组,然后在状态图上找一条路。BFS、DFS、一致代价、A*、IDA*都是在这套抽象上换不同的遍历策略。博弈搜索(minimax、alpha-beta)是把对手也当成状态转移的一部分。约束满足问题(CSP)则是换一种结构:状态不是“走到哪”,而是“每个变量取什么值”,回溯加前向检查、弧相容这些技巧本质上是剪枝。
逻辑推理解决“表示”这一半:命题逻辑、一阶逻辑、量词、合一、归结反驳,这套东西给你一种形式化的语言,能让机器做演绎。专家系统则是把这两半拼起来落地:规则库加推理机,前向链从事实推结论,后向链从目标倒推需要的条件。
理解这一层以后再回头看,你会发现符号推理整条线其实是一个统一的思想:把智能拆成人能写下来的显式规则,然后靠搜索和演绎把这些规则跑起来。它的优点是每一步都能解释、能追溯、不需要数据;缺点是规则写不完,遇到噪声和模糊输入就崩。这个“规则写不完”的痛点,恰恰是后半学期神经网络要补的坑。
1.2 神经网络这条线补的到底是什么缺口
机器学习那条线换了个思路:不写规则,从数据里估一个函数。线性回归是最小化平方损失,逻辑回归是把线性输出过一层sigmoid再用交叉熵,感知机是错分就调权重,多层感知机加非线性激活之后具备了万能逼近能力,反向传播负责把损失对每个参数的偏导算出来。
这里的逻辑递进很清楚:单层线性模型表达能力不够 → 加非线性激活 → 参数变多没法手算梯度 → 用链式法则做反向传播 → 网络变深之后出现梯度消失和过拟合 → 加初始化技巧、正则化、更好的优化器。每一环都是在补前一环的洞。
所以我复习时的顺序从来不是按教材章节走,而是按“问题—补丁”这条链走:先问“为什么线性模型不行”,再问“加了隐藏层为什么就能行”,再问“梯度怎么算”,最后问“算出来为什么训不动”。把这条链讲顺了,卷积、池化、感受野这些后面加的东西都只是“针对图像结构做的参数共享优化”,理解成本会低很多。
1.3 复习顺序为什么不能从神经网络开始
很多人复习时想先啃难的部分,直接跳到反向传播,结果卡在链式法则的矩阵维度上出不来,因为缺少前面“损失函数—假设空间—归纳偏置”这些铺垫,你不知道自己在优化什么,推公式就变成了纯符号游戏。
我推荐的顺序是:先花两天把符号推理的概念地图画出来(不需要记住所有算法细节)→ 再用一天把机器学习的目标函数和梯度关系打通 → 然后集中两天手推反向传播 → 最后回到搜索部分做限时手算。这个顺序的好处是,符号推理部分的“状态、动作、代价、最优性”这些词汇在后面复习强化学习、搜索增强、规划模块时会反复出现,先把词汇表建好,后面阅读任何材料都省力。
提示:复习时不要按“章节”做笔记,按“问题”做笔记。比如“什么情况下A*会退化成BFS”“什么情况下交叉熵比平方损失更合适”,一个问题一张卡,比抄一页定义有用得多。
2. 符号推理部分:把搜索和知识表示啃下来
2.1 状态空间搜索:手算一遍比看十遍定义管用
搜索这块的复习重点不是背算法名字,而是搞清楚完备性、最优性、时间空间复杂度这三个维度分别由什么决定。我一般拿一张表把这些维度对齐,边填边讲理由。
| 算法 | 完备性 | 最优性 | 时间 | 空间 | 关键点 |
|---|---|---|---|---|---|
| 广度优先 | 是(分支有限时) | 是(步长相同) | 指数 | 指数,最要命 | 空间是瓶颈 |
| 深度优先 | 否(可能无限深) | 否 | 指数 | 线性 | 省内存,可能迷路 |
| 一致代价 | 是 | 是 | 指数 | 指数 | 按路径代价扩展 |
| 贪心最佳优先 | 否 | 否 | 指数 | 指数 | 只看启发式,会走偏 |
| A* | 是 | 是(可采纳时) | 指数 | 指数 | f = g + h |
手算演练我建议从一个小网格地图开始:5×5的格子,起点在左上,终点在右下,中间放两三个障碍。自己写一张表,记录每一步的open表和closed表,把g值、h值、f值都写出来。这个动作看着笨,但它能让你真正理解“A*第一次弹出目标节点时就是最优解”这个结论依赖什么——依赖启发式函数的可采纳性(永不高估真实代价)。
很多人分不清可采纳和一致这两个概念。可采纳说的是h(n) ≤ 真实最优代价h*(n),保证最优性;一致说的是h(n) ≤ c(n,n') + h(n'),也就是三角不等式,保证不会重复扩展节点。一致一定可采纳,可采纳不一定一致。这个区别在选择题里出现频率极高,我当初就是靠手算一个反例记住的:构造一个h值在某条边上不满足三角不等式但整条路径仍然不高估的图,扩展顺序立刻就会乱。
import heapq def a_star(start, goal, neighbors, h): # neighbors(node) 返回 [(next_node, cost), ...] open_heap = [(h(start), 0, start, [start])] best_g = {start: 0} closed = set() while open_heap: f, g, node, path = heapq.heappop(open_heap) if node in closed: continue if node == goal: return path, g closed.add(node) for nxt, cost in neighbors(node): ng = g + cost if nxt in closed: continue if ng < best_g.get(nxt, float('inf')): best_g[nxt] = ng heapq.heappush(open_heap, (ng + h(nxt), ng, nxt, path + [nxt])) return None, float('inf')这段代码不长,但两个细节值得抠:一是if node in closed: continue这句,它在h一致时可以省掉,在不一致时必须靠“弹出时再判断”来兜底;二是best_g的更新条件,早期我写成if nxt not in best_g,结果同一节点用更贵的路径先入堆、更便宜的路径反而进不去,直接导致结果偏大。这种错误在手写代码里特别常见,考试写伪代码时也容易漏。
注意:A*的复杂度结论只对“启发式足够好”的情况有改善。h恒为0时它就是一致代价搜索;h高估时它可能很快但不再保证最优,这就是所谓的加权A*。
2.2 知识表示与逻辑推理:归结原理的复习抓手
一阶逻辑这块的复习重点我认为只有三个:量词的作用域、合一(unification)、归结反驳(resolution refutation)。其余的定义性内容扫一遍就行。
合一的本质是“找一个变量替换,让两个原子公式在语法上完全相同”。比如Knows(x, John)和Knows(Mary, y),替换{x/Mary, y/John}之后两个式子一样,这个替换就是合一子。求最一般合一子(MGU)的过程就是从左往右逐项比对,遇到变量就绑定,遇到常量不一致就失败,遇到函数嵌套就递归下去。手算题里最常见的陷阱是出现检验(occurs check),也就是不能把x绑定到包含x的项上,否则会构造出无限递归的结构。这条在教材里往往只有一句话,但考试真会考。
归结反驳的流程我总结成四步:把前提和目标否定都化成子句集(去掉蕴含、把否定推进去、化前束范式、Skolem化、丢掉全称量词、分配律拆成子句)→ 两两归结 → 产生空子句则证明成立 → 归结不出则说明反证失败。这套流程的关键在于化成子句集这一步不能错,99%的归结题失败都是前面变形变错了。我当时专门练了十个化子句集的题,把“否定目标”这一步反复确认,因为把结论否定漏掉会让整个证明白做。
这里补一个小经验:写子句的时候给每个子句编号,归结出来新子句继续编号并在旁边标出“由几号和几号归结、合一子是什么”。这个习惯在考试里能救命,因为阅卷看的是过程,中间步骤只要显示清楚,即使最后没归结出空子句也能拿大部分分。我自己复习时就是这么练的,把每条归结的依据写在旁边,比单纯写答案有效率高得多。
2.3 专家系统与推理机:老技术里能捞出的新东西
专家系统这部分很多课只给一节课,学生容易直接跳过。我不建议跳,因为它讲的是规则的组织方式和推理方向,这套思维在今天的规则引擎、风控策略、业务流程自动化里还在用。
核心结构是:知识库(规则集合)+ 工作内存(事实集合)+ 推理机(前向链或后向链)。前向链是数据驱动:从已有事实出发,找能触发的规则,推出新事实,循环直到没有新事实产生;后向链是目标驱动:把目标当待证子目标,找结论能匹配的规则,再把规则前提变成新的子目标,递归下去。前向链适合事实多、结论少的场景,后向链适合目标明确、需要解释路径的场景。
这里有个概念容易被混:冲突消解(conflict resolution)。多条规则同时可触发时按什么顺序执行?常见策略有优先级、特异性(更具体的规则优先)、新近性(用最新事实的规则优先)。这个点小,但它是“为什么同样的规则库跑两次结果不同”的根源,实操里非常关键。我在复习时把这个和后面的“随机种子”对照着记,一个是规则触发的确定性排序问题,一个是训练过程的随机性问题,本质都是“执行顺序影响结果”。
3. 机器学习到神经网络:从梯度下降到反向传播
3.1 线性模型与损失函数:先把手算梯度练熟
进入机器学习部分,我建议先把最小二乘和逻辑回归的梯度手推一遍,不要急着上框架。原因很简单:后面所有深度学习的推导都是这两者的重复。
线性回归用平方损失L = (1/2m)Σ(wx+b-y)²,对w求导得到(1/m)Σ(wx+b-y)x。逻辑回归用sigmoid加交叉熵,L = -(1/m)Σ[y log ŷ + (1-y) log(1-ŷ)],对z求导的结果漂亮得不像话:ŷ - y。这个结果不是巧合,是交叉熵配sigmoid的天然搭配,也是为什么分类问题几乎不用平方损失。
复习时我会专门做一个对比表,把这两个模型的假设、损失、梯度、输出解释对齐:
| 项目 | 线性回归 | 逻辑回归 |
|---|---|---|
| 输出 | 实数 | 0到1的概率 |
| 损失 | 平方损失 | 交叉熵 |
| 对线性输出z的梯度 | ŷ - y(乘上系数后) | ŷ - y |
| 求解 | 正规方程可闭式解 | 一般只能迭代 |
| 评估 | MSE、R² | 准确率、AUC |
有意思的是两者对z的梯度形式一样,区别在前向那层有没有sigmoid。很多人复习到这一层才恍然:所谓“深度学习的梯度”,起点其实就是这么个简单形式,只是被链式法则串起来了。
心得:交叉熵和平方损失的区别,我一般用一句话解释给新人——平方损失对“错得很离谱”的样本惩罚过重,sigmoid在饱和区梯度又接近0,两者叠加就会训得很慢;交叉熵的梯度不带sigmoid导数那一项,饱和区照样有梯度。
3.2 反向传播:链式法则的矩阵化写法
反向传播是整门课的分水岭。我复习时把它拆成单变量手推 → 标量网络手推 → 矩阵化实现三步,一步都不能跳。
单变量就是y = σ(wx+b),损失用交叉熵,先求∂L/∂z得到ŷ-y,再求∂L/∂w = (ŷ-y)·x,∂L/∂b = ŷ-y。这一步五分钟能推完。
标量网络是加一层隐藏层,h = σ(w1·x + b1),y = σ(w2·h + b2),你需要对w1的梯度经过两层链式相乘。这一步的作用是让你体会“梯度是从后往前一层层乘出来的”,以及为什么激活函数的导数小于1会导致梯度消失。
矩阵化就是把标量换成向量,前向Z = XW + b,A = σ(Z),反向dZ = dA ⊙ σ'(Z),dW = A_prevᵀ · dZ,db = sum(dZ, axis=0)。维度对齐是这一步唯一的难点,我的处理方式是每次都在纸上标出每个量的形状,比如X: (m, n_in)、W: (n_in, n_out)、dZ: (m, n_out),那么dW必须是(n_in, n_out),唯一能凑出这个形状的组合就是A_prevᵀ · dZ。靠形状反推公式,比硬记可靠得多。
import numpy as np def train_two_layer(X, y, hidden=8, lr=0.1, epochs=500): m, n_in = X.shape rng = np.random.default_rng(42) # He 初始化:针对 ReLU 的方差控制 W1 = rng.normal(0, np.sqrt(2.0 / n_in), size=(n_in, hidden)) b1 = np.zeros(hidden) W2 = rng.normal(0, np.sqrt(2.0 / hidden), size=(hidden, 1)) b2 = np.zeros(1) for _ in range(epochs): # 前向 Z1 = X @ W1 + b1 A1 = np.maximum(0, Z1) Z2 = A1 @ W2 + b2 A2 = 1 / (1 + np.exp(-Z2)) # 反向 dZ2 = (A2 - y.reshape(-1, 1)) / m dW2 = A1.T @ dZ2 db2 = dZ2.sum(axis=0) dA1 = dZ2 @ W2.T dZ1 = dA1 * (Z1 > 0) # ReLU 导数 dW1 = X.T @ dZ1 db1 = dZ1.sum(axis=0) # 更新 W1 -= lr * dW1; b1 -= lr * db1 W2 -= lr * dW2; b2 -= lr * db2 return W1, b1, W2, b2这段代码我特意用真实数据集的形状来写,因为考试和面试里最常问的就是“dW的维度是多少”。另外注意dZ2里除了m,y要reshape成列向量,否则广播会把结果算成一个矩阵,维度悄悄变了但程序不报错,这种错误最难查。我在第一次手写的时候就踩过,训练loss一直不降,查了两个小时才发现是广播问题。
3.3 训练稳定性:学习率、初始化、正则化的复习重点
理论推导会了不代表能训出结果,工程上的三个旋钮必须理解透彻。
学习率:太大表现为loss震荡甚至变成nan,太小表现为loss几乎不动。判断方法很土但有效——把loss曲线打出来看形状。震荡是学习率问题,缓慢下降是学习率太小,先降后升是过拟合或者学习率在后期偏大。我一般从0.01起手,配合学习率衰减。
初始化:全零初始化会让同一层的神经元永远学出相同的参数,因为梯度完全一样。小随机数在深层网络里会导致前向激活值逐层缩小、反向梯度逐层消失。所以有Xavier(适配tanh,方差按输入输出维度平均)和He(适配ReLU,方差按输入维度)两套方案。这个推导值得手推一遍,核心就是让每层输出的方差保持一致。
正则化:L2是给损失加参数范数,等价于权重衰减;dropout是训练时随机置零一部分神经元;早停是根据验证集表现决定何时停止。三者针对的都是过拟合,但机制不同:L2限制参数幅度,dropout破坏神经元之间的共适应,早停限制训练时长。
| 现象 | 最可能原因 | 优先尝试 |
|---|---|---|
| 训练loss不降 | 学习率过小、初始化不当、标签错位 | 调大学习率、换He初始化、查数据 |
| 训练loss震荡 | 学习率过大、batch太小 | 降学习率、增大batch |
| 训练loss降但验证loss升 | 过拟合 | 加正则、加数据、早停 |
| loss变nan | 学习率过大、除零、log(0) | 降学习率、加eps、查数值稳定性 |
注意:不要一上来就调网络结构。我见过太多人一训不动就加层加宽,实际上八成问题出在数据管道和学习率上。先把一个极小数据集(比如20个样本)训到过拟合,证明模型和代码是通的,再去调泛化。
4. 复习节奏与笔记方法:两周把整门课串起来
4.1 三轮复习法的具体时间分配
我不太相信“一遍看透”,更相信重复加变换角度。我的三轮安排是这样:
第一轮(3天)画地图。目标是能合上书,用一张A3纸把整门课的知识结构画出来:搜索、逻辑、概率、机器学习、神经网络各自解决什么问题,互相之间用什么概念连接。这一轮不追求细节,允许自己写“这块不熟”,标记出来即可。地图画完你会第一次感觉到整门课是个整体。
第二轮(5天)手推加手算。这一轮只做三件事:手推梯度(线性回归、逻辑回归、两层网络)、手算搜索(A*的open/closed表、alpha-beta剪枝的剪枝点)、手写子句集化归。每个动作都在纸上完成,不查资料。卡住的地方标记,查完再重推一遍。
第三轮(3天)限时模拟加口述。找几套题限时做,做完不只对答案,还要能对着空气把每道题的思路讲一遍。讲不顺的地方就是没真懂的地方。这个“口述检验”我觉得比刷题有效,因为做题可以靠模式匹配,讲出来必须逻辑连贯。
剩下一天做错题回顾和公式卡片整理。整轮下来大概12天,留两天缓冲。
4.2 概念卡片怎么写才有用
我用的卡片模板固定五栏:定义、直觉、公式、易错点、关联。举两个例子。
第一个是“可采纳性”:定义是h(n)≤h*(n);直觉是“估计不能比实际更乐观之外还更乐观”,换句话说永远不高估;公式就是那个不等式;易错点是“一致⇒可采纳,反之不成立”;关联是“A*最优性证明、加权A*”。
第二个是“反向传播”:定义是“用链式法则从损失反向逐层计算参数梯度的方法”;直觉是“把误差按贡献分摊回每一层”;公式是dZ = dA ⊙ σ'(Z)这一串;易错点是“它只是求梯度的方式,不是学习算法本身,学习算法是梯度下降”;关联是“计算图、自动微分、梯度消失”。
这个模板的好处是强制你把“定义”和“直觉”分开写。只会背定义说明没懂,能用自己的话讲直觉才算过关。我复习时会把卡片打乱抽,抽到哪张就口述哪张,说不出来就翻回教材对应章节。
4.3 必做手算清单
下面这张清单是我认为复习时必须亲手做一遍的,跳过任何一项都会在考试或面试里露怯。
| 序号 | 手算内容 | 检验标准 |
|---|---|---|
| 1 | 画A*的open/closed表 | 能解释为什么第一次弹出目标即最优 |
| 2 | 构造一个不一致但可采纳的启发式 | 能说清扩展顺序为什么乱 |
| 3 | alpha-beta剪枝手算 | 能标出每个被剪掉的分支及原因 |
| 4 | 化子句集并做一次归结 | 能写出每步合一子 |
| 5 | 线性回归梯度推导 | 能写出矩阵形式和标量形式 |
| 6 | 逻辑回归梯度推导 | 能解释为什么是ŷ-y |
| 7 | 两层网络反向传播 | 能标出每个量的维度 |
| 8 | 卷积输出尺寸和参数量 | 能写出公式并解释每项含义 |
| 9 | 计算一次感受野 | 能从小网络倒推 |
| 10 | 混淆矩阵与准确率/召回率 | 能用一个不平衡数据例子说明准确率骗人 |
卷积输出尺寸那个公式(W - F + 2P)/S + 1,很多人背下来了但说不清P为什么乘2、S为什么在分母。我的理解方式是:padding在两边各补一次,所以是2P;stride是每隔S个位置取一个窗口,所以可放置的起点数量要除以S再取整。理解到这层,遇到“same padding”该补多少就能自己推:要输出等于输入,解方程就出来了。
5. 常见卡点与排查:复习时最容易栽的几个坑
5.1 概念混淆速查
复习到后期,真正拉分的不是难题,而是这些似是而非的辨析。
| 容易混的一对 | 关键区别 |
|---|---|
| 可采纳 vs 一致 | 前者保证最优,后者保证不重复扩展;后者更强 |
| BFS vs 一致代价 | 前者按层数,后者按累计代价;步长不等时结果不同 |
| 前向链 vs 后向链 | 数据驱动 vs 目标驱动;事实多选前者,目标明确选后者 |
| 判别模型 vs 生成模型 | 学P(y|x) vs 学P(x,y);逻辑回归是判别,朴素贝叶斯是生成 |
| 参数 vs 超参数 | 训练学出来的 vs 人手设定的 |
| 过拟合 vs 欠拟合 | 训练好验证差 vs 两边都差 |
| 梯度消失 vs 梯度爆炸 | 连乘因子小于1 vs 大于1 |
| 卷积 vs 全连接 | 局部连接加参数共享 vs 全连接 |
这张表我建议自己重新写一遍,不要抄。抄的过程没有辨析,写的过程会逼你想“这两者到底差在哪一步”。
5.2 手算环节的典型错误
维度对不上还硬算。反向传播的矩阵推导,只要有一个维度错了,后面全错。我的做法是每个中间量先写形状再写数值,宁可多写一行。
激活函数导数算错。sigmoid导数是σ(1-σ),tanh导数是1-tanh²,ReLU导数在0处按惯例取0或1都行但要说明,Softmax配交叉熵的梯度是ŷ-y而不是对每个元素单独求导。这几个我在复习时抄在一张纸贴在桌前,因为混一个就全盘皆错。
混淆“训练误差”和“泛化误差”。看到训练集准确率99%就以为模型很好,这是新手最常见的误判。必须看验证集,必须看类别分布是否均衡。
搜索题不写访问标记。图搜索里如果没有closed集,节点会被重复扩展,复杂度直接爆炸。手写伪代码时这一行经常被漏,而它恰恰是评分点。
5.3 复习末期的自检问题
我在复习最后一天会问自己下面这些问题,能顺畅答出来才算过关:
- 为什么A*在启发式可采纳时第一次弹出目标就是最优解?证明的关键一步在哪里?
- 为什么全零初始化会让隐藏层神经元学出相同的参数?
- 为什么交叉熵比平方损失更适合分类?从梯度形式解释。
- 为什么卷积的参数比全连接少那么多?省下来的参数量怎么算?
- 符号推理和神经网络各自的失效场景是什么?为什么现在很多系统要把两者结合?
- 如果训练loss正常但验证集表现很差,你的排查顺序是什么?
最后这个问题最能检验理解程度。我的排查顺序是:先看数据有没有泄漏或者划分错误,再看模型是否过拟合(对比训练验证曲线),再看正则强度,最后才考虑加数据或者改结构。这个顺序是从“最可能出错且最容易验证”的方向排的,实操中能省很多时间。
我个人在带人复习这门课的时候有一个体会:真正让人卡住的从来不是某个公式,而是不知道这个公式在整个体系里的位置。符号推理那半段的价值,不在于你现在还会不会手写归结,而在于它给了你一套“把问题形式化再求解”的习惯;神经网络那半段的价值,不在于你能背出反向传播的四行公式,而在于你理解“用数据替代规则”这件事的代价和边界。把这两点想通,复习就不再是应付考试,而是给自己搭了一套能持续往上加东西的地基。