笃行
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
  • 个人经历 & 后台通用心法

    • 个人简介 & 后台通用心法
    • 面试开场自我介绍叙事脚本

机器学习数学 · 怎么学

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–3337
L2基本理解4–9683
L3实际使用者10–14550
L4问题解决者15–19550
L5自信的实践者20–22331
合计22251

三个缺口先说在前面

专栏出于零基础定位,主动划走了三处工程上最容易踩坑的内容。它们不在 22 篇正文里,是本页刻意补的洞:

缺口被划走的依据挂在
① softmax 会溢出ml-math-visual-doc「指数与 softmax 篇必含项」明确:不展开数值稳定性实现L4
② ln(k) 随机基线ml-math-visual-doc「对数与信息量篇必含项」明确:不写 perplexity 的完整定义式L4
③ 张量 shape 的账全专栏 22 篇都在二维方格纸上,从不出现 shapeL3 入口

补洞练习会故意打破专栏"不写一行代码"的规矩——这正是缺口的定义。但不建议把这三项回填进 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
手写前向 + 手写反传,不许用任何框架的自动微分。

  1. 打印每层梯度的 L2 范数,观察它沿链衰减
  2. 学习率取 0.001 / 0.1 / 10 各跑一次,画三条 loss 曲线,对上正文那张"太小 / 合适 / 太大"的图
  3. 激活换成恒等,确认它永远学不会 XOR
  4. 训练第 0 步的 loss 应该 ≈ ln(2) ≈ 0.69(L4 缺口②的直接应用)

⑥ 常犯的错误

  • 最后 31 张卡不刷了,因为"感觉快学完了"——而这一级的卡恰恰是把前四级串起来的那些
  • 以为反向传播是把误差平均分给每层
  • 以为反向传播是独立于前向的另一套算法
  • 以为层数越多梯度自然越大

⑦ 自测问题

20 层网络,每层回传时梯度被乘上 0.9。第 1 层拿到的梯度大约是最后一层的多少倍?这个数说明了什么工程问题?


一页总表

级别篇目卡数补的缺口通过判据(可证伪)
L11–337—手画线性组合 + 说出张成判据
L24–983—画搬家 + 数格子 + 算 det,三者对上
L310–1450③ shape 的账直方图复现高维余弦收窄
L415–1950① softmax 溢出
② ln(k) 基线
均匀预测的交叉熵 == ln(k),误差 < 1e-6
L520–2231—XOR 学得会,换纯线性必须学不会
22 篇2513 个

三、2 小时十课计划

120 分钟 ÷ 10 课 = 每课 12 分钟。

先说清楚预期

12 分钟内你不可能从零写出代码。 下面所有动手练习都设计成「跑一段 8 行以内的现成代码,然后改一个数,看它坏掉」。真正的产出是手感,不是代码量。写代码留给最后的收尾项目。

哪 20% 带来 80% 的效果

筛选判据:只保留"读懂一个大模型前向 + 反向全过程"这条路径上的承重墙。把这条链摆出来:

箭头进来 → 矩阵搬家 → 折一下 → 归一化 → 点积打分 → softmax → 算罚分 → 沿链倒着乘回去

这是数据流经模型的顺序,不是传统教材的数学结构顺序。传统线代课按"向量 → 矩阵 → 行列式 → 特征值"排,那是按数学结构排的;本计划按数据怎么流排。换了排序判据,留下来的东西就不一样。

留下的十块承重墙:

#概念不可绕过的理由
1坐标 = 基的线性组合没有它,"矩阵是搬家"讲不通。整条链的第 0 块砖
2矩阵是把方格纸搬一次家枢纽。神经网络每层的主体动作就是它
3shape 的账直觉与代码之间唯一的桥
4点积与归一化相似度、检索、Attention 打分是同一个运算
5高维近乎垂直决定"余弦 0.8"这个数字该不该信
6纯线性叠加仍是线性激活函数存在的唯一理由
7softmax 与 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 课的钥匙

复习题

  1. (3,2) 里那个"向右""向上"是什么?谁规定的?
  2. 两根箭头张成一条线而不是一个平面,什么情况下会发生?
  3. "线性组合"用大白话怎么说?
  4. 同一根箭头换一组基,箭头变了吗?什么变了?
  5. 为什么说坐标系是"人挑的"而不是"世界自带的"?

第 2 课 · 矩阵是把整张方格纸搬一次家

  • 学习目标:看到矩阵先想动作,再想数字
  • 关键概念:矩阵 = 线性变换 · 矩阵的列 = 基向量搬家后落在哪 · 乘法 = 连搬两次(所以不可交换)
  • 动手练习(纸笔):写下 [[2,1],[0,1]],直接读出它的两列 (2,0) 和 (1,1),在方格纸上画出单位正方形被搬家后的样子。全程不做一次乘法
  • 推荐资源:本站 矩阵是一次变换 只读「实现方案」+「为什么别的选择不行」两节;站外 3Blue1Brown 第 3–4 集
  • 预期结果:给你任意 2×2 矩阵,你能不算就大致说出它把网格变成了什么样

复习题

  1. 矩阵的第一列在几何上是什么意思?
  2. 为什么 AB ≠ BA?用"搬家"解释,不许说"不满足交换律"
  3. 单位矩阵在搬家语言里是什么动作?
  4. 神经网络一层里的 Wx 在做什么?
  5. 如果一个矩阵把整个平面压成一条线,它的列有什么特点?

第 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 说出"这一步在把哪一维搬家"

复习题

  1. (4,16,64) @ (64,128) 结果是什么形状?哪一维消失了?
  2. 报错提示两个操作数维度不匹配时,通常是哪一维没对齐?
  3. (B,T,d) 三个字母各代表什么?
  4. 为什么 @ 只关心最后两维?
  5. 你怎么在写代码之前就知道输出 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 倍,分母在抵消维度带来的抖动

复习题

  1. 点积为负说明什么?
  2. 归一化之后,为什么点积就不用再除以长度了?
  3. 长度和方向,点积各占多少?
  4. QKᵀ 结果矩阵的 (i,j) 位置是什么含义?
  5. 为什么 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))    # 看它一路收窄
    
  • 推荐资源:本站 高维空间的直觉「为什么别的选择不行」一节

  • 预期结果:你亲眼看到标准差随维度收窄——这是十课里唯一一个当场可证伪的断言

复习题

  1. 上面那个 std 为什么会一路变小?
  2. 为什么"embedding 维度越高越好"是错的?
  3. 余弦 0.8 在 2 维和 1536 维,哪个更值得信?
  4. 高维里"离得近"为什么比二维直觉难得多?
  5. 这个现象为什么是向量检索能工作的前提?

第 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 这个输出就是"深度学习需要非线性"的证明

  • ⚠️ 正文说"折一下才能造出弯的"——这个比喻会让你懂得太早。真实答案要的是"很多很多折"。别停在"折一下"

复习题

  1. 上面为什么打印 True?
  2. 没有激活函数,100 层网络等价于几层?
  3. 激活函数的本职是防数值爆炸吗?
  4. ReLU 这么简单,凭什么够用?
  5. "折一下"这个说法在哪儿会骗你?

第 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 那一行是本课全部价值所在

复习题

  1. softmax_naive(z) 为什么返回 nan?
  2. 减 max 为什么不改变结果?
  3. T 变大,分布形状怎么变?T 趋近 0 呢?
  4. softmax 就是"除以总和"吗?
  5. 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 就是你的第一个体检项

复习题

  1. 10 分类 loss 卡在 2.3,模型学到东西了吗?
  2. 交叉熵为什么只看正确答案那一项?
  3. 给正确答案打 0 分,罚多少?
  4. ln 把连乘变连加,这对长序列有什么用?
  5. 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 那一行不是收敛得慢,是朝反方向跑,你对"学习率越大越快"这个直觉永久免疫

复习题

  1. 梯度指向谷底吗?
  2. 为什么走梯度的反方向?
  3. lr = 1.01 为什么发散?
  4. 梯度为 0 一定是最低点吗?
  5. "探一次、迈一步、站定再探"对应代码里哪三行?

第 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,免费)

  • 预期结果:你能说出残差连接和梯度裁剪分别为了解决什么

复习题

  1. 反向传播是把误差平均分给每层吗?
  2. 20 层每层乘 0.9,第 1 层拿到的梯度是最后一层的多少倍?
  3. 梯度爆炸和梯度消失,根源是同一个东西吗?
  4. 反传是独立于前向的另一套算法吗?
  5. 残差连接为什么能缓解梯度消失?

四、收尾项目: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 课← 成功也算失败的证伪型断言
5lr = 0.001 / 0.05 / 5.0 三条 loss 曲线分别呈现太慢 / 收敛 / 发散第 9 课学习率直觉没建立

再加一个自查:打印每层梯度的 L2 范数,观察它沿链逐层衰减——这就是第 10 课那个 0.9 ** 100 在真实网络里的样子。

本站不提供参考实现

故意的。提供了,这个项目就退化成抄写练习,而它的全部价值在于自己写。

卡住的时候回到对应课次的闪卡,而不是找答案。


关于这 2 小时的诚实预期

它兑现什么,不兑现什么

这份计划不会让你"学会机器学习数学"。

它兑现的是另一件事:打开一篇论文时,你的默认反应从"跳过公式"变成"这个公式在搬什么家"。

被砍掉的 12 篇不是垃圾,只是不在这条最短路径上。等你真要读 LoRA,回去补 秩与低秩;真要调采样参数,回去补 概率分布与采样。完整的 22 篇顺序在 路径 F。

最近更新: 2026/9/10 11:38