机器学习数学 · 怎么学
docs/math/有 22 篇正文和 251 张闪卡。这一页不讲任何新知识,只回答三件事:怎么读才留得住、时间不够砍哪些、怎么判断自己真的学会了。
这一页面向的读者和 math 专栏不是同一批人
- 本页面向会写代码的读者,正文含 Python / numpy 代码。
- 本页不属于 看得见的机器学习数学 的零基础科普定位——那个专栏面向小学高年级到初中生,全程不写一行代码。不会写代码也不影响读那 22 篇,本页可以跳过。
- 本页不替代 学习路径 · 路径 F。路径 F 服务零基础读者,给的是"读什么、什么顺序";本页服务工程读者,给的是"怎么学、怎么验收"。两者并存。
你该从哪部分开始
这一页有三个可以独立使用的部分:
| 部分 | 给谁 | 花多久 |
|---|---|---|
| 一、闪卡优先四拍协议 | 所有人。这是前置,另外两部分都建立在它上面 | 读 5 分钟 |
| 二、五级学习阶梯 | 想完整走完 22 篇的人 | 分 5 级,每级若干天 |
| 三、2 小时十课计划 | 时间只有一个下午的人 | 120 分钟 |
第三部分是第二部分的压缩版——同一批承重墙,砍掉 12 篇。两者互为入口:先跑十课建立全局手感、再回头走阶梯补全,或者直接走阶梯,都行。
一、闪卡优先四拍协议
站点里每篇 xxx.md 都配了一份 xxx.cards.md。多数人把闪卡当成读完正文后的附赠品——顺序反了。
提取练习带来的长期留存,显著高于重读。 所以下面这个循环里,刷卡在前,读正文在后。
一篇课文的四拍
| 拍 | 动作 | 时长 | 为什么是这个顺序 |
|---|---|---|---|
| ① 盲刷 | 只打开 xxx.cards.md,遮住 A,逐张出声作答。答不出的记下卡号 | 5 min | 先验测试效应:哪怕全答错,也会显著提升随后阅读的编码质量。你要带着问题去读,不是带着眼睛 |
| ② 快读前三节 | 正文的 场景问题 / 实现方案 / 为什么这么做,允许扫读 | 10 min | 这三节是"识别"层,读一遍够了 |
| ③ 慢读第四节 | 为什么别的选择不行,对照①里答错的卡逐条比对 | 10 min | 全篇唯一在戳破直觉、而不是装配直觉的小节。你答错的卡,答案八成在这里 |
| ④ 复刷 + 口述 | 再刷全卡,必须出声、必须完整句子。卡外再加一句自问:"这个比喻在哪儿会骗我?" | 8 min | 提取练习 + 主动寻找比喻的失效边界 |
四拍合计约 33 分钟。顺序不可调换——把①挪到最后,整个协议就退化成"读一遍再自测一下",先验测试效应完全消失。
间隔回刷
每天开工前,先盲刷:
- 前一篇的全卡
- 本级已过篇目里随机抽 5 张
答不出的卡,回到第③拍重读对应的"为什么别的选择不行"。
判定线
一篇算过的唯一标准
第④拍能连续两次全卡口述无卡壳。
"我觉得看懂了"不构成通过依据。看懂别人的解释和自己能生成解释是两种能力,而主观上几乎无法区分——这叫解释深度错觉,它最擅长伪造的就是"我懂了"这个感觉。所以下面每一级的里程碑,判据全是跑得出 / 跑不出、数值对得上 / 对不上。
二、五级学习阶梯
22 篇按依赖关系分成 5 级。每级的卡数是该级所含篇目 .cards.md 的实际张数之和。
| 级别 | 名称 | 篇目 | 篇数 | 卡数 |
|---|---|---|---|---|
| L1 | 完全初学者 | 1–3 | 3 | 37 |
| L2 | 基本理解 | 4–9 | 6 | 83 |
| L3 | 实际使用者 | 10–14 | 5 | 50 |
| L4 | 问题解决者 | 15–19 | 5 | 50 |
| L5 | 自信的实践者 | 20–22 | 3 | 31 |
| 合计 | 22 | 251 |
三个缺口先说在前面
专栏出于零基础定位,主动划走了三处工程上最容易踩坑的内容。它们不在 22 篇正文里,是本页刻意补的洞:
| 缺口 | 被划走的依据 | 挂在 |
|---|---|---|
| ① softmax 会溢出 | ml-math-visual-doc「指数与 softmax 篇必含项」明确:不展开数值稳定性实现 | L4 |
② ln(k) 随机基线 | ml-math-visual-doc「对数与信息量篇必含项」明确:不写 perplexity 的完整定义式 | L4 |
| ③ 张量 shape 的账 | 全专栏 22 篇都在二维方格纸上,从不出现 shape | L3 入口 |
补洞练习会故意打破专栏"不写一行代码"的规矩——这正是缺口的定义。但不建议把这三项回填进 docs/math/ 正文:那个专栏的零代码约束是它最有价值的自律条款之一。
L1 · 完全初学者
篇目:向量是什么 → 向量的加减与数乘 → 线性组合与张成 (3 篇 / 37 张卡)
① 这个阶段应该理解什么
一根箭头和一对数字是同一件事的两种说法;加法是接着走第二段路,数乘是原地拉长缩短;几根箭头按不同倍数组合,能扫出一片区域。
②「掌握」长什么样
你看到 (3,2) 时,脑子里先出现箭头、再出现数字,而不是反过来。听到"线性组合"不再需要在心里翻译一道。
③ 最需要重点关注的
只有一句:坐标 (3,2) 的意思是"3 个向右 + 2 个向上"。这句话里那两个被省略的"向右""向上",就是 L2 整个矩阵概念的钥匙。
④ 里程碑(可判定)
给你任意一对二维向量和一组系数,你能在方格纸上手画出线性组合的结果;并对给定的两根箭头说出"能张成整个平面"还是"只能张成一条线",且说得出判据。画错或判据说不出,即未通过。
⑤ 动手练习:换基藏宝图
在方格纸上用 (1,0) (0,1) 画一条 4 步寻宝路线,记下坐标序列;然后换成 (2,1) (-1,1) 两根基向量,把同一条物理路线重新记一遍坐标。两串数字完全不同,纸上的路线一模一样。全程纸笔,不写代码。
⑥ 常犯的错误
- 跳过盲刷直接读正文——四拍协议第一次就废了,后面每一级都会继承这个坏习惯
- 以为向量必须"从原点出发"才叫向量
- 把"张成"当成一个术语去背,而不是当成"这几根积木能拼到哪儿"
⑦ 自测问题
"坐标
(3,2)就是 3 个向右 + 2 个向上"——这句话里的"向右"和"向上"具体是什么?如果换掉它们会怎样?
L2 · 基本理解
篇目:矩阵是一次变换(枢纽)→ 矩阵乘法 → 行列式与面积 → 解方程组 → 特征向量的直觉 → 点积 (6 篇 / 83 张卡)
① 这个阶段应该理解什么
矩阵是把整张方格纸搬一次家;矩阵乘法是连搬两次(所以换顺序结果就变);行列式是一格面积被放大几倍;行列式为 0 = 空间被压扁 = 倒不回去;特征向量是搬家过程中没被拐弯的那几个方向;点积是两根箭头有多顺。
②「掌握」长什么样
看到任意 2×2 矩阵,你能不做乘法就说出它大概把方格纸变成了什么样:转了?拉了?翻了?压扁了?看到 det = 0,你的第一反应是"信息没了",而不是"算出来是零"。
③ 最需要重点关注的
第 6 篇的"面积归零"和第 7 篇的"倒不回去"是同一件事。 这条因果链是 L2 的承重墙。另外记住矩阵的列就是基向量搬家后的落点——这一条能让你省掉一半计算。
④ 里程碑(可判定)
拿一个你没见过的 2×2 矩阵:先画出单位正方形被搬家后的形状,再数格子量出面积,最后手算行列式——三个结果必须对得上。再换一个 det = 0 的矩阵,说出对应方程组无解/多解的几何原因。三者对不上,即未通过。
⑤ 动手练习:搬家实验册
选 4 个矩阵:纯旋转、纯拉伸、剪切、det = 0。每个画"搬家前 / 搬家后"两张方格纸,标注 (1,0) 和 (0,1) 各自落到了哪。第 4 张要能一眼看出整个平面被压成了一条线。纸笔,不写代码。
⑥ 常犯的错误
- 卡片刷到 83 张时开始只在心里默答——口述这一条一旦松掉,整个协议立刻退化成重读
- 把矩阵乘法当成"对应位置相乘"
- 以为特征向量是"最重要的方向"(它只是没被拐弯的方向)
⑦ 自测问题
行列式为 0 的矩阵,为什么对应的方程组"倒不回去"?请只用面积来说,不许出现"不可逆"这三个字。
L3 · 实际使用者
入口仪式:先补缺口 ③(shape 的账)
22 篇正文全程在二维方格纸上,从不出现 shape。而真实代码里全是多维张量——这是直觉与代码之间唯一的桥,必须自己搭。
核心就是 L2「连搬两次」在代码里的样子:(m×n)(n×p) → (m×p),中间那个 n 必须对齐。
import numpy as np
x = np.random.randn(4, 16, 64) # (B=batch, T=seq, d=特征维)
W = np.random.randn(64, 128)
print((x @ W).shape) # 先猜,再跑 → (4, 16, 128)
print((x @ W.T).shape) # 故意写错一维,看真实报错长什么样
第二行一定要跑,把报错原文看一遍。以后 90% 的 shape 问题都长这样。
篇目:高维空间的直觉 → 正交基与投影 → 秩与低秩 → 长度与归一化 → 线性与非线性 (5 篇 / 50 张卡)
① 这个阶段应该理解什么
高维里随机两根箭头几乎必然接近垂直(所以余弦 0.8 在 1536 维是强信号、在 2 维几乎没信息);换一组正交量尺只换读数、不换箭头;秩是真正独立的方向有几根;归一化之后点积直接就是余弦;纯线性叠一百层还是一层。
②「掌握」长什么样
听到"embedding 是 1536 维",你能立刻说出这个数字为什么不是越大越好。看到任何相似度分数,你的第一个问题是"几维?"。
③ 最需要重点关注的
"折一下才能造出弯的"这句话是全专栏最危险的比喻。 它让你懂得太早——真实答案要的是"很多很多折"的量变到质变,而"折一下"这个短语在脑子里刻的是"折了一下"。读这篇时主动提醒自己:折一下什么都造不出来。
④ 里程碑(可判定)
跑完下面的练习,你的直方图必须复现"维度升高、余弦分布向 0 收窄"这一现象。跑不出来即判定未通过——这是整个阶梯里第一个当场可证伪的断言。
⑤ 动手练习:亲眼看高维坍缩
import numpy as np
for d in [2, 10, 100, 1000]:
a = np.random.randn(2000, d)
b = np.random.randn(2000, d)
cos = (a * b).sum(1) / (np.linalg.norm(a, axis=1) * np.linalg.norm(b, axis=1))
print(d, round(cos.std(), 4))
标准差会一路收窄。每写一行先写出 shape 注释,跑通后故意改错一维——这是缺口③的肌肉记忆。
⑥ 常犯的错误
- 把补缺口的代码当成"额外作业"跳过,于是本级唯一的实证环节没了
- 用 3 维直觉硬推高维
- 以为归一化是"丢信息的坏事"
- 以为投影是一种近似——在正交基下它是精确分解
⑦ 自测问题
余弦相似度都是 0.8,一个来自 2 维、一个来自 1536 维。哪个更值得相信?为什么?
L4 · 问题解决者
篇目:概率分布与采样 → 期望与方差 → 指数与 softmax → 对数与信息量 → 交叉熵 (5 篇 / 50 张卡)
① 这个阶段应该理解什么
分布是把总量 1 切成宽度不等的段、采样是闭眼落一点;n 个独立抖动叠加只长 √n 倍(这就是 Attention 要除以 √d_k 的原因);softmax = 先取指数拉开差距、再摊平成一整块;对数回答"翻了多少次"、并把连乘变连加;交叉熵只看你给正确答案打的那个概率有多意外。
②「掌握」长什么样
你看到一个 loss 数字,能立刻判断模型是不是压根没在学;听到 temperature=0.7,你知道它在拧哪个旋钮,而不是以为它是随机数种子。
③ 最需要重点关注的
第 18 篇那句「对数把连乘变连加」不是数学趣闻,是长序列概率不下溢的唯一原因。别当装饰读过去。
补缺口 ①:softmax 会炸
import numpy as np
def softmax_naive(z):
e = np.exp(z)
return e / e.sum()
def softmax_stable(z):
e = np.exp(z - z.max()) # 减 max:分子分母同乘一个常数,结果不变
return e / e.sum()
z = np.array([1000., 1001.])
print(softmax_naive(z)) # [nan nan] ← 这一行必须亲眼看见
print(softmax_stable(z)) # [0.26894142 0.73105858]
exp(1000) 直接溢出成 inf,inf / inf 就是 nan。减掉 max 后最大项变成 exp(0)=1,不可能溢出;而分子分母同时乘以 exp(-max),结果一模一样。
正文明确写了「不展开数值稳定性实现」,所以这个 nan 你只能自己撞一次。
补缺口 ②:loss 的数字手感
k 分类任务,随机瞎猜的 loss 就是 ln(k)。
import numpy as np
for k in [2, 10, 1000, 50000]:
p = np.ones(k) / k # 均匀预测 = 完全没学到东西
print(k, round(-np.log(p[0]), 4), round(np.log(k), 4)) # 两列必须相同
背下这四个数:
| 任务 | 随机基线 loss |
|---|---|
| 二分类 | 0.69 |
| 10 分类 | 2.30 |
| 1000 分类 | 6.91 |
| 词表 5 万的语言模型 | 约 10.8 |
这是你以后每次训练的第一个体检指标:第 0 步的 loss 应该约等于 ln(k);如果差很多,是初始化或 loss 实现有问题。训练到一半卡在 ln(k) 不动,说明模型没在学。
④ 里程碑(可判定)
你手写的交叉熵,喂进均匀分布的预测,输出必须等于 ln(k),误差在 1e-6 以内。同时你能不查表说出 10 分类与 1000 分类的随机基线。对不上即未通过。
⑤ 动手练习:一个脚本补两个洞
手写 softmax(溢出版 / 稳定版对照)+ 手写 cross_entropy;用它验证随机初始化下 loss ≈ ln(k);再画一组同一批 logits 在 T = 0.1 / 1 / 10 下的分布形状对比。
⑥ 常犯的错误
- 把两个补洞练习推迟到"以后再说"——它们是本级仅有的实证环节,跳过等于本级没做
- 以为 softmax 就是"除以总和"
- 以为 softmax 输出的 0.9 代表模型有 90% 的把握
- 以为交叉熵是预测与答案的减法差值
- 以为 loss 必须降到 0 才算完美
⑦ 自测问题
10 分类任务,训练 loss 稳稳停在 2.3。模型学到东西了吗?
L5 · 自信的实践者
篇目:导数就是坡度 → 梯度下降 → 链式法则与反向传播 (3 篇 / 31 张卡)
① 这个阶段应该理解什么
导数就是坡度(高度变化 ÷ 前进距离);梯度是各方向坡度合成的一根箭头(回到 L1 的"箭头"),指向上升最快处,所以要走它的反方向;学习率是步长;链式法则是传动比连乘,反向传播是从罚分出发倒着乘回去;连乘一串小于 1 的数会指数级衰减。
②「掌握」长什么样
你能从 L1 的"箭头"一路讲到 L5 的"梯度",中间不断链。看到深层网络训不动,你的第一反应是去看每层梯度的量级,而不是先调大学习率。
③ 最需要重点关注的
三条最反直觉的:梯度不指向谷底(它只指当前最陡);学习率越大不等于收敛越快;停在平地不等于停在谷底。
④ 里程碑(可判定)
你手写的两层网络能拟合 XOR;把激活函数换成恒等(纯线性)后,模型必须显著变差。
这一条是"成功也算失败"
如果纯线性版本照样学会了 XOR,那不是你厉害,是你的实现有 bug,回去查。
这条判据同时回验 L3 的 线性与非线性——纯线性叠多少层还是一层,一层线性模型不可能拟合 XOR。
⑤ 动手练习:纯 numpy 两层网络拟合 XOR
手写前向 + 手写反传,不许用任何框架的自动微分。
- 打印每层梯度的 L2 范数,观察它沿链衰减
- 学习率取
0.001 / 0.1 / 10各跑一次,画三条 loss 曲线,对上正文那张"太小 / 合适 / 太大"的图 - 激活换成恒等,确认它永远学不会 XOR
- 训练第 0 步的 loss 应该 ≈
ln(2)≈ 0.69(L4 缺口②的直接应用)
⑥ 常犯的错误
- 最后 31 张卡不刷了,因为"感觉快学完了"——而这一级的卡恰恰是把前四级串起来的那些
- 以为反向传播是把误差平均分给每层
- 以为反向传播是独立于前向的另一套算法
- 以为层数越多梯度自然越大
⑦ 自测问题
20 层网络,每层回传时梯度被乘上 0.9。第 1 层拿到的梯度大约是最后一层的多少倍?这个数说明了什么工程问题?
一页总表
| 级别 | 篇目 | 卡数 | 补的缺口 | 通过判据(可证伪) |
|---|---|---|---|---|
| L1 | 1–3 | 37 | — | 手画线性组合 + 说出张成判据 |
| L2 | 4–9 | 83 | — | 画搬家 + 数格子 + 算 det,三者对上 |
| L3 | 10–14 | 50 | ③ shape 的账 | 直方图复现高维余弦收窄 |
| L4 | 15–19 | 50 | ① softmax 溢出 ② ln(k) 基线 | 均匀预测的交叉熵 == ln(k),误差 < 1e-6 |
| L5 | 20–22 | 31 | — | XOR 学得会,换纯线性必须学不会 |
| 22 篇 | 251 | 3 个 |
三、2 小时十课计划
120 分钟 ÷ 10 课 = 每课 12 分钟。
先说清楚预期
12 分钟内你不可能从零写出代码。 下面所有动手练习都设计成「跑一段 8 行以内的现成代码,然后改一个数,看它坏掉」。真正的产出是手感,不是代码量。写代码留给最后的收尾项目。
哪 20% 带来 80% 的效果
筛选判据:只保留"读懂一个大模型前向 + 反向全过程"这条路径上的承重墙。把这条链摆出来:
箭头进来 → 矩阵搬家 → 折一下 → 归一化 → 点积打分 → softmax → 算罚分 → 沿链倒着乘回去
这是数据流经模型的顺序,不是传统教材的数学结构顺序。传统线代课按"向量 → 矩阵 → 行列式 → 特征值"排,那是按数学结构排的;本计划按数据怎么流排。换了排序判据,留下来的东西就不一样。
留下的十块承重墙:
| # | 概念 | 不可绕过的理由 |
|---|---|---|
| 1 | 坐标 = 基的线性组合 | 没有它,"矩阵是搬家"讲不通。整条链的第 0 块砖 |
| 2 | 矩阵是把方格纸搬一次家 | 枢纽。神经网络每层的主体动作就是它 |
| 3 | shape 的账 | 直觉与代码之间唯一的桥 |
| 4 | 点积与归一化 | 相似度、检索、Attention 打分是同一个运算 |
| 5 | 高维近乎垂直 | 决定"余弦 0.8"这个数字该不该信 |
| 6 | 纯线性叠加仍是线性 | 激活函数存在的唯一理由 |
| 7 | softmax 与 temperature | 打分转概率的唯一手段,前向链的终点 |
| 8 | 交叉熵与 ln(k) | 训练在最小化的东西就是它 |
| 9 | 梯度下降与学习率 | 参数怎么变的,答案全在这里 |
| 10 | 链式法则与消失/爆炸 | 反向传播的全部内容 |
砍掉了什么,以及为什么敢砍
被砍的篇目不是垃圾,只是不在这条最短路径上
| 砍掉 | 理由 | 什么时候回来补 |
|---|---|---|
| 向量的加减与数乘 矩阵乘法 | 不是不重要,是折进第 1、2 课当推论讲,不单独占时间 | 已含在课内 |
| 行列式与面积 解方程组 特征向量的直觉 | 传统线代课的核心,却不在这条数据流链上。你读一整年 LLM 代码可以一次都不算行列式、不解方程组、不求特征向量 | 做 PCA / 谱方法时 |
| 正交基与投影 | 它的算法本体就是点积(第 4 课已覆盖)。Q/K/V 三次投影用第 2 课"搬家" + 第 4 课"点积"就够解释 | 深入 Attention 变体时 |
| 秩与低秩 | 只在读 LoRA 时才是承重墙 | 读 LoRA / 做微调时,把它当第 11 课 |
| 概率分布与采样 期望与方差 | 采样、top-k/top-p 属于推理调参,不属于"读懂模型在算什么" | 调采样参数时 |
| 对数与信息量 | 最重要的两个产出(ln 把连乘变连加、ln(k) 基线)直接并进第 8 课 | 已含在课内 |
每课固定节拍
盲刷卡 3 min → 读 + 练 6 min → 口述 + 复习题 3 min
第 1 课 · 坐标就是"几个向右 + 几个向上"
- 学习目标:把"一对数字"和"一根箭头"在脑子里焊死,并意识到坐标里藏着一组被省略的基
- 关键概念:向量是箭头 · 坐标 = 基的线性组合 · 张成
- 动手练习(纸笔):方格纸上用
(1,0)(0,1)画一条 4 步路线记下坐标;换成(2,1)(-1,1)把同一条路线重记一遍。数字全变,路线没动 - 推荐资源:本站 what-is-vector 闪卡 + linear-combination-span 闪卡;站外可看 3Blue1Brown《线性代数的本质》前两集(YouTube / B 站官方频道,免费)
- 预期结果:你能说出"坐标依赖于你选了哪组尺子"——这是第 2 课的钥匙
复习题
(3,2)里那个"向右""向上"是什么?谁规定的?- 两根箭头张成一条线而不是一个平面,什么情况下会发生?
- "线性组合"用大白话怎么说?
- 同一根箭头换一组基,箭头变了吗?什么变了?
- 为什么说坐标系是"人挑的"而不是"世界自带的"?
第 2 课 · 矩阵是把整张方格纸搬一次家
- 学习目标:看到矩阵先想动作,再想数字
- 关键概念:矩阵 = 线性变换 · 矩阵的列 = 基向量搬家后落在哪 · 乘法 = 连搬两次(所以不可交换)
- 动手练习(纸笔):写下
[[2,1],[0,1]],直接读出它的两列(2,0)和(1,1),在方格纸上画出单位正方形被搬家后的样子。全程不做一次乘法 - 推荐资源:本站 矩阵是一次变换 只读「实现方案」+「为什么别的选择不行」两节;站外 3Blue1Brown 第 3–4 集
- 预期结果:给你任意 2×2 矩阵,你能不算就大致说出它把网格变成了什么样
复习题
- 矩阵的第一列在几何上是什么意思?
- 为什么
AB ≠ BA?用"搬家"解释,不许说"不满足交换律" - 单位矩阵在搬家语言里是什么动作?
- 神经网络一层里的
Wx在做什么? - 如果一个矩阵把整个平面压成一条线,它的列有什么特点?
第 3 课 · shape 的账【补缺口 ③】
学习目标:把第 2 课的二维直觉接到真实代码的多维张量上
关键概念:
(m×n)(n×p) → (m×p),中间那个 n 必须对齐 ·(B, T, d)每一维是什么 · 矩阵乘"收缩"的是哪一维动手练习:
import numpy as np x = np.random.randn(4, 16, 64) # (batch, seq, d_model) W = np.random.randn(64, 128) print((x @ W).shape) # 先猜,再跑 print((x @ W.T).shape) # 故意写错,把报错原文看一遍推荐资源:NumPy 官方文档的 Broadcasting 一节(免费)。本课在 22 篇正文中没有对应篇目——这是你自己补的洞
预期结果:以后看任何模型代码,你能对着 shape 说出"这一步在把哪一维搬家"
复习题
(4,16,64) @ (64,128)结果是什么形状?哪一维消失了?- 报错提示两个操作数维度不匹配时,通常是哪一维没对齐?
(B,T,d)三个字母各代表什么?- 为什么
@只关心最后两维? - 你怎么在写代码之前就知道输出 shape?
第 4 课 · 点积:相似度、检索、Attention 打分是同一件事
学习目标:一个运算,吃掉三个概念
关键概念:点积 = 两根箭头有多顺 · 归一化后点积直接就是余弦 · Attention 的
QKᵀ就是一堆点积动手练习:
import numpy as np a = np.array([3., 4.]) b = np.array([4., 3.]) cos = a @ b / (np.linalg.norm(a) * np.linalg.norm(b)) an = a / np.linalg.norm(a) bn = b / np.linalg.norm(b) print(cos, an @ bn) # 两个数必须一样推荐资源:本站 dot-product-similarity 闪卡(11 张全刷)+ norm-normalization 闪卡
预期结果:看到
QKᵀ你想到的是"一堆箭头两两比顺不顺"附赠一句(替掉被砍的期望与方差):Attention 要除以
√d_k,是因为 n 个独立抖动叠加只长 √n 倍,分母在抵消维度带来的抖动
复习题
- 点积为负说明什么?
- 归一化之后,为什么点积就不用再除以长度了?
- 长度和方向,点积各占多少?
QKᵀ结果矩阵的(i,j)位置是什么含义?- 为什么 Attention 要除以
√d_k?
第 5 课 · 高维里,随便两根箭头都近乎垂直
学习目标:学会先问"几维?"再谈相似度大小
关键概念:维度升高 → 随机向量夹角余弦向 0 收紧 · 余弦 0.8 在 1536 维是强信号、在 2 维几乎没信息
动手练习(本课最重要):
import numpy as np for d in [2, 10, 100, 1000]: a = np.random.randn(2000, d) b = np.random.randn(2000, d) cos = (a * b).sum(1) / (np.linalg.norm(a, axis=1) * np.linalg.norm(b, axis=1)) print(d, round(cos.std(), 4)) # 看它一路收窄推荐资源:本站 高维空间的直觉「为什么别的选择不行」一节
预期结果:你亲眼看到标准差随维度收窄——这是十课里唯一一个当场可证伪的断言
复习题
- 上面那个 std 为什么会一路变小?
- 为什么"embedding 维度越高越好"是错的?
- 余弦 0.8 在 2 维和 1536 维,哪个更值得信?
- 高维里"离得近"为什么比二维直觉难得多?
- 这个现象为什么是向量检索能工作的前提?
第 6 课 · 纯线性叠一百层,还是一层
学习目标:搞懂激活函数为什么必须存在
关键概念:连做多次线性变换等价于一次(第 2 课"连搬两次"的直接推论)· 插入折弯才造得出弯的
动手练习:
import numpy as np A, B, C = [np.random.randn(3, 3) for _ in range(3)] x = np.random.randn(3) print(np.allclose(C @ (B @ (A @ x)), (C @ B @ A) @ x)) # True:三层 == 一层推荐资源:本站 线性与非线性「实现方案」
预期结果:
True这个输出就是"深度学习需要非线性"的证明⚠️ 正文说"折一下才能造出弯的"——这个比喻会让你懂得太早。真实答案要的是"很多很多折"。别停在"折一下"
复习题
- 上面为什么打印
True? - 没有激活函数,100 层网络等价于几层?
- 激活函数的本职是防数值爆炸吗?
- ReLU 这么简单,凭什么够用?
- "折一下"这个说法在哪儿会骗你?
第 7 课 · softmax:先拉开差距,再摊成一块蛋糕【补缺口 ①】
学习目标:会写稳定版 softmax,并知道 temperature 拧的是哪个旋钮
关键概念:
exp拉开差距 → 除以总和摊平 · temperature 是"进exp前先除以 T" · 不减 max 会溢出动手练习(必须亲眼看见
nan):import numpy as np def softmax_naive(z): e = np.exp(z) return e / e.sum() def softmax_stable(z): e = np.exp(z - z.max()) return e / e.sum() z = np.array([1000., 1001.]) print(softmax_naive(z)) # [nan nan] ← 必须看见这一行 print(softmax_stable(z)) # [0.26894142 0.73105858] s = np.array([1., 2., 3.]) for T in [0.1, 1, 10]: print(T, softmax_stable(s / T).round(4))推荐资源:本站 指数与 softmax 与其「记忆口诀」。溢出这一段正文明确不展开,是你补的洞
预期结果:
nan那一行是本课全部价值所在
复习题
softmax_naive(z)为什么返回nan?- 减
max为什么不改变结果? - T 变大,分布形状怎么变?T 趋近 0 呢?
- softmax 就是"除以总和"吗?
- softmax 输出的 0.9 代表模型有 90% 把握吗?
第 8 课 · 交叉熵 = 罚分,以及 ln(k) 这个体检指标【补缺口 ②】
学习目标:看到 loss 数字就能判断模型有没有在学
关键概念:罚分只看你给正确答案打的那个概率有多意外(
-ln p)· k 分类随机基线 =ln(k)·ln把连乘变连加动手练习:
import numpy as np for k in [2, 10, 1000, 50000]: p = np.ones(k) / k print(k, round(-np.log(p[0]), 4), round(np.log(k), 4)) # 两列必须相同背下:二分类 0.69 · 10 分类 2.30 · 1000 分类 6.91 · 词表 5 万约 10.8
推荐资源:本站 cross-entropy 闪卡(10 张全刷)+ 对数与信息量 只看「对数把连乘变连加」那段
预期结果:以后每次训练,第 0 步的 loss 就是你的第一个体检项
复习题
- 10 分类 loss 卡在 2.3,模型学到东西了吗?
- 交叉熵为什么只看正确答案那一项?
- 给正确答案打 0 分,罚多少?
ln把连乘变连加,这对长序列有什么用?- loss 降到 0 才算完美吗?
第 9 课 · 蒙眼下山:梯度是箭头,学习率是步子
学习目标:搞懂参数到底怎么变的,以及学习率为什么难调
关键概念:梯度 = 各方向坡度合成的一根箭头(回到第 1 课)· 指向上升最快 → 走反方向 · 步子太大会跨过谷底来回弹
动手练习(最小化
f(x) = x²):for lr in [0.01, 0.5, 1.01]: x = 5.0 for _ in range(30): x -= lr * 2 * x # 2x 就是 x² 的坡度 print(lr, round(x, 4)) # 起点是 5.0,越接近 0 越好三行分别是 2.73(走得太慢,30 步还没到)、0.0(正好)、9.06(比起点还远,正在发散)。把第三行的步数从 30 改成 100,你会看到它彻底跑飞。
推荐资源:本站 梯度下降 的三条路径对比图 + derivative-slope 闪卡
预期结果:
lr = 1.01那一行不是收敛得慢,是朝反方向跑,你对"学习率越大越快"这个直觉永久免疫
复习题
- 梯度指向谷底吗?
- 为什么走梯度的反方向?
lr = 1.01为什么发散?- 梯度为 0 一定是最低点吗?
- "探一次、迈一步、站定再探"对应代码里哪三行?
第 10 课 · 传动比连乘:反向传播与梯度消失
学习目标:把整条链倒着走一遍,理解深层网络为什么难训
关键概念:串联传动比相乘 · 反传 = 从罚分出发沿链倒乘回去 · 连乘一串小于 1 的数 → 指数衰减
动手练习:
for c in [0.9, 1.0, 1.1]: print(c, [round(c ** n, 6) for n in [10, 50, 100]])看
0.9 ** 100 ≈ 0.000027和1.1 ** 100 ≈ 13780推荐资源:本站 chain-rule-backprop 闪卡(11 张全刷);站外可看 Karpathy 的
micrograd(GitHub,约 100 行)与其 Neural Networks: Zero to Hero 系列第 1 集(YouTube,免费)预期结果:你能说出残差连接和梯度裁剪分别为了解决什么
复习题
- 反向传播是把误差平均分给每层吗?
- 20 层每层乘 0.9,第 1 层拿到的梯度是最后一层的多少倍?
- 梯度爆炸和梯度消失,根源是同一个东西吗?
- 反传是独立于前向的另一套算法吗?
- 残差连接为什么能缓解梯度消失?
四、收尾项目:tiny_attention.py
一个约 80 行的纯 numpy 脚本,把十课全串在里面。跑得出下面 5 条断言,这 2 小时才算没白花。
任务:手写一个「极小注意力分类器」的前向 + 反向,不许用任何框架的自动微分。
结构与对应课次
| 步骤 | 对应课次 |
|---|---|
输入 (B=8, T=4, d=16) 随机 embedding | 第 1、3 课 |
Wq / Wk / Wv 三次线性搬家 | 第 2、3 课 |
归一化后做 QKᵀ 点积打分,除以 √d | 第 4、5 课 |
| 稳定版 softmax(减 max)得到注意力权重 | 第 7 课 |
| 加权求和 → ReLU 折一下 | 第 6 课 |
| 输出层 → softmax → 交叉熵(k = 10) | 第 8 课 |
| 手写反传,沿链倒着乘回去 | 第 10 课 |
| 梯度下降更新参数 | 第 9 课 |
五条验收断言
| # | 断言 | 覆盖 | 不通过说明 |
|---|---|---|---|
| 1 | 每一步 shape 都和你写代码前写下的注释一致 | 第 3 课 | 你还没建立 shape 的账 |
| 2 | 第 0 步 loss ≈ ln(10) ≈ 2.303(误差 < 0.05) | 第 8 课 | 初始化或 loss 实现有 bug |
| 3 | 把 logits 换成 [1000., 1001.],稳定版不出 nan,朴素版出 | 第 7 课 | 溢出防护没写对 |
| 4 | 把 ReLU 换成恒等函数,模型必须显著变差;若照样学得会,说明实现有 bug,回去查 | 第 6 课 | ← 成功也算失败的证伪型断言 |
| 5 | lr = 0.001 / 0.05 / 5.0 三条 loss 曲线分别呈现太慢 / 收敛 / 发散 | 第 9 课 | 学习率直觉没建立 |
再加一个自查:打印每层梯度的 L2 范数,观察它沿链逐层衰减——这就是第 10 课那个 0.9 ** 100 在真实网络里的样子。
本站不提供参考实现
故意的。提供了,这个项目就退化成抄写练习,而它的全部价值在于自己写。
卡住的时候回到对应课次的闪卡,而不是找答案。