1. 从"看得懂公式"到"讲得清逻辑":这门课到底难在哪
学完一门AI入门课,很多人会有一种奇怪的错觉:课件上的公式都能看懂,作业也能跑通,但合上讲义让自己复述一遍,脑子就空了。这不是记性问题,而是知识组织方式出了偏差。我前后带过几批零基础的同学复习这门课,最常见的困惑集中在同一处——课上的内容排布其实是一条隐线:符号推理负责讲"智能如何被形式化表达",搜索与优化负责讲"如何在解空间里找到答案",概率与统计负责讲"如何在不确定中做判断",神经网络负责讲"如何从数据里自动学出规律"。这四个部分不是四门互不相干的课,而是一次逐层递进的抽象迁移。
标题里"符号推理到神经网络"这几个字,本身就是整门课的骨架。前面那套符号化的做法,把世界压缩成命题、规则、逻辑式,靠人工把知识写进去;后面那套连接主义的做法,把知识藏在成千上万个可调参数里,靠数据把权重调出来。中间那个从"人写规则"到"机器调参数"的转折点,才是复习时最该死死咬住的地方。多数人复习低效,是因为把它当成四个并列的模块分别背,背完再做几道题,结果考试一变问法就崩。
这篇笔记面向的是正在复习或准备二次梳理入门课程的人,也适合刚学完想系统归类一遍的读者。我会按这条隐线把内容重新排布一次,讲清每个阶段的核心假设、关键公式背后的意图、容易踩的坑,以及那些讲义上不写、但真正在做题和写代码时会卡住你的细节。所有涉及代码的部分我都会给出可直接运行的片段,涉及计算的部分我会把推导过程写开,方便你对照自己的笔记逐项核验。
提醒一句:复习阶段最忌讳"以看懂为终点"。判断自己是否掌握的标准只有一个——能否在空白纸上从动机出发,把某个方法的输入、输出、假设、代价、失效条件完整讲一遍。
先说清楚复习节奏这件事。入门课的复习不是从第一页翻到最后一页,那样只会把本来就模糊的印象搅得更乱。我一般建议按"三次覆盖"来走:第一次只做主线串联,用一页纸画出四大模块之间的承接关系,不碰任何细节;第二次逐个模块攻坚,把每个方法补齐五要素;第三次专攻边界与对比,把所有"看起来像但本质不同"的概念成对拉出来比较。下面各节的展开,基本对应第二次和第三次的覆盖内容。
2. 符号推理:把"智能"翻译成可计算的形式
2.1 命题逻辑、谓词逻辑与知识表示的层级差别
符号推理这一块,初学者最容易混的是命题逻辑和谓词逻辑的适用边界。命题逻辑把整个世界切成一个个不能再拆的原子命题,比如"今天下雨",它只有真假两值,没有内部结构。这样做的代价是表达力受限:你想说"所有人都要呼吸"这种带对象和量词的句子,只能穷举成"甲要呼吸且乙要呼吸且丙要呼吸",对象一多就崩了。谓词逻辑引入个体、谓词和量词,把"人"这个类、"呼吸"这个性质、"所有"这个量词分开表达,才让"全体"这类陈述有了紧凑的写法。
这里有一个复习时特别值得强调的点:表达力的提升从来不是免费的。命题逻辑的可满足性判定是 NP 完全的,而到了一阶谓词逻辑,可满足性直接变成不可判定的。这不是课程在刁难人,而是在告诉你一个真实约束——你给系统越强的表达能力,推理就越可能算不出来。很多入门教材把这句结论一笔带过,考试却偏爱考"为什么不能全用一阶逻辑"。答案就在于可判定性和实际推理效率的权衡。
理解这个层级差别,可以拿数据库做类比。原子命题像一张只有单列的表,谓词逻辑像有主键和外键的关系表,量词像 SQL 里的ALL和EXISTS。你写 SQL 时从来不会觉得量词有多难,因为数据库引擎帮你做了规划;但符号推理这门课要求你自己把规划过程写清楚,难度自然上来了。复习时试着把你熟悉的几句 SQL 改写成谓词逻辑式,这个练习能把量词、变元约束这些抽象概念落回地面。
2.2 归结原理的手工推演流程与常见错位
归结原理是这块内容的高频考点,也是作业里最容易出错的环节。它的基本想法很朴素:如果两个子句一个含 P、一个含 ¬P,其余部分合起来就构成了它们逻辑上的推论,于是可以把 P 和 ¬P 消掉,得到新子句。反证法的用法是:要证明 KB ⊨ α,就把 ¬α 加进知识库,然后不断归结,直到推出空子句。
手工推演时最常见的三个错位,我建议你在复习时专门列成一张检查表:
| 常见错误 | 具体表现 | 正确做法 |
|---|---|---|
| 忘记先化子句形 | 直接对原公式归结 | 先消蕴含、内移否定、标准化变元、Skolem 化、去全称量词、拆合取 |
| 变元冲突未标准化 | 两个子句用了同名变元,归结后含义错乱 | 每次归结前把参与的子句变元改名,保证互不相同 |
| 归结方向乱走 | 乱配对,子句数量爆炸 | 优先合并含互补文字的对,朝空子句方向缩短 |
标准化变元这一条,讲义往往只有一句注释,但它是实际卡住最多人的地方。原因很好理解:两个子句里的 x 各自独立,你要让它们做归结,就必须先确认它们指同一个对象,或者把其中一个改名成 y 以免误绑。这一步在纸上看着像形式主义,一旦写进程序里便立刻显出必要性,因为程序不会替你做"人脑的默契绑定"。
Skolem 化的处理也值得单独说一句。存在量词消去时引入的 Skolem 常量或函数,本质上是在给"存在某个东西"这句话发一张身份证。如果存在量词不在任何全称量词的作用域内,就引入一个常量;如果它在全称量词里面,就必须引入以该全称变元为参数的函数。很多人只记住了"存在量词要换掉",却忘了区分这两种情况,结果写出来的子句在语义上已经不成立了。判定方法很简单:从公式最外层往里读,看当前的存在量词左边有没有还没被消去的全称量词。
2.3 前向链接、反向链接与"该选哪个"的实际判据
规则系统里,前向链接从已知事实出发,不断触发规则产生新事实,直到没有新东西可推;反向链接从目标出发,寻找能让目标成立的规则,再递归去验证规则的前提。这两个方向不是随便选的,选择依据是"事实多还是问题多"。
一句话总结判据:数据驱动用前向,目标驱动用反向。监控告警这类场景事实源源不断、结论不预先知道,用前向链接更自然;诊断类场景结论候选有限、事实需要现查,用反向链接更省。复习时如果你只会背"前向从事实到结论",遇到应用题还是会犹豫,所以务必带上这个判据一起记。
还有一个细节容易被忽略:前向链接在 Horn 子句上的复杂度是多项式的,因为每条规则的前提是正文字的合取,匹配过程不会产生组合爆炸。而一般子句上的归结是指数级的。这就是为什么实际的知识库系统偏爱把规则限制成 Horn 形式——不是理论上更优雅,而是工程上算得动。复习到这一层,你对"形式化表达为什么要设限"这件事就有了实感,而不是当成一条需要背的结论。
3. 搜索与优化:解空间里的行走策略
3.1 无信息搜索的四种策略与它们的真实代价
搜索这块内容看起来最好背,但恰恰是被背坏得最彻底的部分。BFS、DFS、一致代价、迭代加深,这四种策略的完备性、最优性、时间空间复杂度,考试必考,但很多人背完表格之后,遇到"该用哪个"还是没主意。
复习时我建议换一个角度:别记结论,记代价的来源。BFS 的空间复杂度是 b^d,因为要把整层节点全存着,这是它保证最短路径的代价;DFS 空间只要 b·m,因为它只存一条路径,代价是可能一头扎进深坑不回头;一致代价搜索按路径代价从小到大扩展,能处理边权不等的情况,代价是要维护一个优先队列;迭代加深把 DFS 的空间优势和 BFS 的层序优势拼在一起,代价是浅层节点被反复生成。
真正需要理解的是"重复生成"这件事为什么可以接受。迭代加深每一轮都把浅层重算一遍,看着很浪费,但由于分支因子 b 的存在,最后一层的节点数远超前面所有层之和,重复部分占比很小。做一个粗略估算:b=10、d=5 时,最后一层有 10 万节点,前面所有层加起来约 1.1 万,重复生成的开销大约占一成。这个数量级关系是理解迭代加深为什么实用的关键,也是考试里"为什么迭代加深不比 BFS 差太多"的标准回答思路。
启发式搜索则要牢牢抓住 A* 的两个条件:可采纳性要求启发函数永不高估真实代价,一致性(单调性)要求 h(n) ≤ c(n,n') + h(n')。可采纳保证最优解,一致性保证不重复扩展节点。这两个条件的关系常被考到——一致性蕴含可采纳,反之不成立。想找一个"可采纳但不一致"的例子,画个图就能构造出来:从起点到某节点有两条路,启发值在中间节点上跳了一下,就破坏了单调性但没破坏可采纳性。
3.2 用 Python 把 A* 跑通并观察启发函数的实际影响
光看公式很难建立直觉,下面这段代码是一个可直接运行的最小 A* 实现,用的是网格地图,你可以改启发函数亲自看效果差异。
import heapq def astar(grid, start, goal, h): rows, cols = len(grid), len(grid[0]) open_heap = [(h(start, goal), 0, start)] g_cost = {start: 0} parent = {start: None} while open_heap: _, g, cur = heapq.heappop(open_heap) if cur == goal: path = [] while cur is not None: path.append(cur) cur = parent[cur] return path[::-1], g if g > g_cost.get(cur, float('inf')): continue r, c = cur for dr, dc in ((1,0), (-1,0), (0,1), (0,-1)): nr, nc = r + dr, c + dc if not (0 <= nr < rows and 0 <= nc < cols): continue if grid[nr][nc] == 1: continue ng = g + 1 nxt = (nr, nc) if ng < g_cost.get(nxt, float('inf')): g_cost[nxt] = ng parent[nxt] = cur heapq.heappush(open_heap, (ng + h(nxt, goal), ng, nxt)) return None, float('inf') # 曼哈顿距离:网格四连通下可采纳且一致 manhattan = lambda a, b: abs(a[0]-b[0]) + abs(a[1]-b[1]) # 欧氏距离:也可采纳,但在四连通网格上会略微低估更多,扩展节点数偏多 euclid = lambda a, b: ((a[0]-b[0])**2 + (a[1]-b[1])**2) ** 0.5跑起来之后建议做三组对照:h 恒为 0(退化成一致代价搜索)、用曼哈顿距离、用一个故意高估的 h(比如把曼哈顿距离乘 2)。你会看到扩展节点数从多到少再到"快但可能不是最优"的变化过程。这个实验的价值在于把"可采纳性"从一个定义变成一个你能亲眼看见的现象:h 高估之后路径变短了但代价变大了,这就是失去最优性保证的具体样子。
实测下来还有一个容易忽略的点:上面代码里if g > g_cost.get(cur, ...)这一句是防止同一个节点被以更差的代价重复扩展。少了这一句,程序照样能出结果,但在某些地图上会明显变慢。这类"不影响正确性但影响效率"的细节,恰恰是讲义不会写、自己写才会踩的地方。
提示:如果你想让 A* 在超大网格上也不爆内存,可以考虑加权 A*,把启发值乘一个略大于 1 的系数。代价是放弃最优性保证,换来扩展节点数的大幅下降。工程上这是个常用折中,但考试答题时必须写清楚这是在牺牲什么。
3.3 局部搜索与遗传算法:什么时候"不保证最优"反而是对的
爬山法、模拟退火、遗传算法这几个放在一起讲,核心就一句话:当解空间大到没法系统枚举、或者目标函数没有好的梯度信息时,放弃"保证最优"换"快速得到足够好"是划算的。
爬山法只往更优的邻居走,会卡在局部最优;模拟退火以一定概率接受更差的解,这个概率随温度下降而衰减,从而有机会跳出局部最优;遗传算法维护一个种群,通过选择、交叉、变异在解空间里并行试探。三者共同的假设是:解空间里"好解"是成片出现的,靠近好解的地方大概率还有更好的解。这个假设如果不成立,局部搜索的意义就大打折扣。复习时能把这条隐含假设讲出来,说明你真的理解了,而不是在背算法流程。
退火温度的设置有个实用经验:初始温度要大到让差解的接受概率接近 1,终止温度要小到几乎不接受差解,中间的降温系数取 0.9 到 0.99 之间比较常见。降温太快等于退回爬山法,太慢则算力白白消耗。遗传算法这边,种群规模太小会早熟收敛,太大会拖慢每代计算,交叉率和变异率一般分别落在 0.7~0.9 和 0.01~0.1 的区间。这些数值不是定论,但作为起点够用了。
4. 概率与学习:在不确定中把话说清楚
4.1 贝叶斯公式不是算概率,是更新信念
很多人学完贝叶斯公式,只会做"已知患病率和检测准确率,求阳性者真患病概率"这类题,做完就忘。这很可惜,因为贝叶斯公式真正的价值在于它是一个信念更新机制:先验概率代表你原来的看法,似然代表新证据对不同假设的支持程度,后验是你看到证据之后应该调整到的看法。
用一个具体数字走一遍。假设某类问题的先验发生率是 1%,检测的灵敏度是 95%,特异度是 90%。一个人检测阳性,他真正有问题的概率是多少?代入公式:P = 0.95×0.01 / (0.95×0.01 + 0.10×0.99) ≈ 0.00875。也就是说,即使检测阳性,真实概率也只有约 8.8%。这个结果反直觉,但它清楚地展示了先验的力量——当基础发生率极低时,假阳性会淹没真阳性。
这个计算在复习时的意义不是背数字,而是建立一个判断习惯:看到"准确率 95%"这种说法,先问基础发生率是多少。这个习惯在做分类任务的评估时同样管用:类别极不均衡时,准确率这个指标会严重失真,必须换成精确率、召回率或 F1。课程里讲混淆矩阵、讲精确率召回率的权衡,本质上就是在讲这件事。
条件独立假设是另一个复习重点。朴素贝叶斯假设给定类别后各特征相互独立,这个假设在现实中几乎从不成立,但模型往往还work,原因在于分类只需要后验的相对大小正确,不需要绝对值准确。这个"错得刚好够用"的道理,是理解很多工程实践的关键,也是考试里"朴素贝叶斯的独立性假设不成立为何还能用"的标准答法。
4.2 从最大似然到梯度下降的完整链路
这块内容是符号世界走向学习世界的桥。复习时务必把这条链走通一遍,不要跳步。
第一步,明确模型:给定输入 x 和参数 θ,模型输出一个预测。第二步,定义损失:用负对数似然或者均方误差衡量预测与真实之间的差距。第三步,把学习问题转化为优化问题:找一组 θ 让损失最小。第四步,因为解不出闭式解,用梯度下降迭代逼近。
这四步里,第二步到第三步的转化是最容易被跳过也最该讲清楚的地方。为什么要用负对数似然?因为似然是"在这组参数下观测到这批数据的概率",我们想让这个概率最大;取负号就变成最小化,取对数是因为连乘容易underflow且求导后连乘变连加。每一步变形都有具体理由,不是数学家的审美偏好。
梯度下降的更新式 θ ← θ - α∇L 里,α 是学习率。学习率太大,损失震荡甚至发散;太小,收敛慢得让人失去耐心。实践中常用的做法是先用一个较大的值试几步,看损失曲线:如果震荡就调小,如果几乎不降就调大,找到能稳定下降的量级之后再细化。这个"看曲线调参"的笨办法,比任何理论公式都实用。
还有一个常被忽略的坑:梯度的方向是损失上升最快的方向,所以要走负梯度。这句话人人都懂,但手推的时候符号错一次,整个更新方向就反了。我在批改作业时见过不止一次,损失曲线一路向上,学生还在怀疑学习率。检查方法很简单:更新一步之后损失必须下降,如果上升了,先检查符号再看学习率。
5. 神经网络:把"学习"变成可微分的函数组合
5.1 感知机到多层网络:为什么必须引入非线性
单层感知机只能划一条直线,连异或都解决不了。这个结论几乎每本教材都写,但复习时要追问一步:加了隐藏层就够了吗?答案是还不够,必须加非线性激活函数。因为两个线性变换叠加仍然是线性变换,无论叠多少层,整体还是一个线性映射,表达能力并没有提升。真正让深度网络具备强表达能力的,是"线性变换 + 非线性激活"的交替堆叠。
理解这一点之后,激活函数的选择就有了依据。Sigmoid 把输出压到 (0,1),适合做概率输出,但两端饱和导致梯度接近于零,层数一多就传不回去;ReLU 在正区间梯度恒为 1,缓解了这个问题,代价是负区间梯度为零,可能出现神经元"死亡";Tanh 零中心,比 Sigmoid 好一些,但饱和问题依旧。复习时把这张对照表和"梯度消失"的成因连起来记,比单独背特性有效得多。
反向传播这块,最容易在纸上写对、在心里想不通。它本质上是链式法则在计算图上的系统化应用:前向传播算出每个节点的值,反向传播从损失出发,把梯度一层层往回乘。有一个理解角度很有帮助——反向传播算的不是"某个参数有多重要",而是"损失对这个参数有多敏感"。敏感性高的参数,改一点点损失就变很多,所以要改得谨慎;敏感性低的,改一大步影响也不大。
5.2 用 NumPy 手写一次反向传播
只看公式永远学不会,我强烈建议至少手写一次。下面是一个两层网络的完整前向与反向,不依赖任何框架,跑通之后你对维度对齐、梯度累加这些细节会彻底踏实。
import numpy as np np.random.seed(0) N, D, H, C = 64, 10, 32, 3 X = np.random.randn(N, D) y = np.random.randint(0, C, size=N) W1 = np.random.randn(D, H) * np.sqrt(2.0 / D) b1 = np.zeros(H) W2 = np.random.randn(H, C) * np.sqrt(2.0 / H) b2 = np.zeros(C) def forward(X): z1 = X @ W1 + b1 a1 = np.maximum(0, z1) # ReLU z2 = a1 @ W2 + b2 exp = np.exp(z2 - z2.max(axis=1, keepdims=True)) probs = exp / exp.sum(axis=1, keepdims=True) return z1, a1, probs lr = 1e-2 for step in range(500): z1, a1, probs = forward(X) loss = -np.log(probs[np.arange(N), y] + 1e-12).mean() dz2 = probs.copy() dz2[np.arange(N), y] -= 1 dz2 /= N dW2 = a1.T @ dz2 db2 = dz2.sum(axis=0) da1 = dz2 @ W2.T dz1 = da1 * (z1 > 0) dW1 = X.T @ dz1 db1 = dz1.sum(axis=0) W1 -= lr * dW1; b1 -= lr * db1 W2 -= lr * dW2; b2 -= lr * db2 if step % 100 == 0: print(f"step {step:4d} loss {loss:.4f}")这段代码里有三处值得你单独盯住。第一处是 softmax 里减去了每行的最大值,这是防止指数溢出,不是为了数学正确性——数学上减不减都一样,工程上不减就可能得到 inf。第二处是dz2的构造:先复制概率,再把正确类别的概率减 1,再除以样本数。这个"减 1"来自交叉熵对 logits 求导的结果,是整段代码里最浓缩的一行,值得自己推一遍。第三处是dz1 = da1 * (z1 > 0),ReLU 的导数就是这么简单,但要注意用的是前向时的 z1,不是 a1。
注意:初始化用
sqrt(2/fan_in)而不是全零或全一,是因为全零初始化会让同一层的所有神经元梯度完全一样,永远学不出差异;全一会让激活值随层数指数增长。这个细节讲义通常一句话带过,但它是你能训出结果和训不出结果的分水岭。
5.3 训练不收敛时的排查顺序
我在带人复习时被问得最多的不是"反向传播怎么推",而是"我的网络不收敛,怎么办"。这个问题有比较固定的排查顺序,按顺序走能省很多时间:
| 排查顺序 | 检查项 | 典型症状 | 处理方向 |
|---|---|---|---|
| 1 | 数据与标签是否对齐 | 损失不降,准确率恒定 | 打印几条样本人工核对 |
| 2 | 损失函数与输出层是否匹配 | 损失为负或异常大 | 多分类配 softmax+交叉熵 |
| 3 | 学习率是否合适 | 损失震荡或几乎不动 | 调小或调大一个数量级再试 |
| 4 | 初始化是否合理 | 早期就饱和,梯度近零 | 换用 fan_in 缩放初始化 |
| 5 | 梯度是否传回浅层 | 浅层参数几乎不变 | 打印各层梯度范数 |
| 6 | 是否过拟合 | 训练降验证升 | 加正则、早停、减容量 |
这个顺序的原则是"从便宜且致命的检查开始"。数据对齐和损失匹配这两项,检查成本几乎为零,但一旦有问题,后面所有调参都是白费。很多人一上来就调学习率,试了半天才发现标签和特征错位,这种时间浪费完全可以通过固定排查顺序避免。
打印梯度范数这一招特别值。在各层梯度上算一下 L2 范数,如果浅层范数比深层小好几个数量级,基本可以判定是梯度消失,此时该动的是激活函数或初始化,而不是继续调学习率。这个诊断动作在框架里只需几行代码,但能直接把"瞎调"变成"定向修"。
6. 四个模块的横向对照:把知识压成一张网
6.1 符号派与连接派的分歧到底在哪
复习到收尾阶段,最有价值的动作是把前后两大范式摆在一起对照。它们的根本分歧不在技术细节,而在知识从哪来:符号派认为知识应该由人显式写进去,规则、逻辑、本体都是人为构造的;连接派认为知识可以从数据中隐式学出来,参数就是知识的载体。
这个分歧带来一连串连锁差异。可解释性上,符号系统的推理链条可以逐步追溯,你能指着某条规则说"就是它推出的结论";神经网络的判断藏在权重里,只能靠事后分析去猜。数据需求上,符号系统要的是专家写的规则,冷启动成本高但样本需求低;神经网络要大量标注数据,冷启动靠数据堆。泛化方式上,符号系统靠规则的组合覆盖新情况,神经网络靠相似性插值。维护方式上,符号系统改规则即可,神经网络要重训。
理解这张对照关系,很多原本孤立的知识点就自动归位了。比如"为什么专家系统在开放场景里难以维护"——因为规则之间会冲突,规则数量增长时一致性维护的成本爆炸;"为什么神经网络在小样本场景表现差"——因为它靠的是从数据里估计参数,样本不足就估不准。这些结论不需要单独背,从范式差异里能直接推出来。
6.2 一张表梳理全部核心方法的关键属性
下面这张表建议你自己在纸上默写一遍,写完对照,比反复看书有效。
| 方法 | 核心假设 | 输入 | 输出 | 主要代价 | 失效条件 |
|---|---|---|---|---|---|
| 命题逻辑推理 | 世界可离散为原子命题 | 命题与规则 | 真假判定 | 表达力弱 | 需要刻画对象内部结构 |
| 一阶逻辑归结 | 知识可写成子句形 | 子句集 | 反证结论 | 不可判定、易爆炸 | 规则规模大、含函数符号 |
| A* 搜索 | 启发函数可采纳 | 图、起点、终点、h | 最优路径 | 内存占用高 | 状态空间巨大、h 难设计 |
| 局部搜索 | 好解成片分布 | 状态、邻域、目标函数 | 近似最优解 | 无最优性保证 | 解空间崎岖、多峰 |
| 朴素贝叶斯 | 特征条件独立 | 特征向量 | 类别后验 | 独立性假设强 | 特征强相关 |
| 梯度下降 | 损失可微 | 参数、梯度 | 局部最优参数 | 依赖学习率 | 非凸、鞍点、梯度消失 |
| 多层神经网络 | 目标函数可微可组合 | 数据、标签 | 预测 | 需大量数据、黑箱 | 小样本、强解释需求 |
默写这张表时,我建议重点盯"失效条件"这一列。多数人复习时把注意力放在前几列,因为那是"是什么";但真正区分掌握程度的,是能否说清"什么时候不该用"。考试的应用题往往就是在考这一列——给你一个场景,问该选哪个方法,答题的关键就是把场景特征和失效条件对上。
6.3 复习节奏与自测方法的具体安排
最后说说怎么把上面这些内容真正变成自己的。我给的一个可执行方案是七天一轮:
第一天只做主线串联,用一张 A4 纸画出四个模块的承接图,标出每个模块解决的前一个模块解决不了的问题。第二天攻符号推理,重点是手工做三道归结题,每题都要写清子句化的每一步。第三天攻搜索,把 A* 的代码跑通并做前述三组对照实验。第四天攻概率,手推一遍贝叶斯更新和梯度下降的链路。第五天攻神经网络,手写一遍反向传播并故意制造一次不收敛,然后用排查表定位。第六天做横向对照,默写那张属性表。第七天只做一件事:找三道综合应用题,不看笔记独立作答,答完再对。
自测的判定标准要严格一点:能默写公式不算掌握,能解释公式里每个符号为什么在那儿才算;能跑通代码不算掌握,能说清每行代码删掉会怎样才算。用这个标准卡自己,一轮下来哪里虚一目了然。
我个人在复盘这类课程时还有个习惯:把所有"当时觉得懂了、后来发现没懂"的点单独记在一个本子上,每隔一段时间翻一次。这个本子往往比任何笔记都值钱,因为里面记的全是自己的真实盲区,而不是别人整理的重点。入门课的知识密度不算高,但概念之间的连接很多,靠一次性理解很难吃透,反复回到自己的盲区上打补丁,效率比从头再读一遍高得多。