01
先画地图:AI 用的数学,核心就三块
把深度学习拆开看,每一行模型代码背后只有三类数学在干活。线性代数管「数据怎么摆」——模型里所有东西(一段文本、一张图片、一批样本)都被摆成向量或矩阵,运算基本就是矩阵乘法。概率统计管「模型怎么表达不确定」——模型最后吐出的几乎总是一个分布,训练目标也是某种概率代价。微积分管「参数怎么改」——梯度、链式法则、梯度下降,全在这一块。
好消息是:不需要把大学教材从头啃一遍。下表是每块里真正会被模型代码用到的最小集合,按这个集合学,一周就有手感;集合之外的部分(证明、测度论、凸优化),等真的需要了再回来补也完全来得及。
这一页的用法建议(照着做效率最高):
- 按顺序读:三块是层层依赖的——不懂点积就看不懂注意力,不懂链式法则就看不懂反向传播。
- 边读边跑:每个代码块都完整可复制,REPL 里亲手敲一遍比看十遍记得牢。
- 速查表当词典:读别的教程卡在符号上时,回这里搜一下就走。
- 第 06 节是毕业考:手写反向传播跑通了,这一页的任务才算完成。
- 线性代数要留的
- 向量 / 矩阵 / 矩阵乘法 / 点积 / 特征向量
- 概率统计要留的
- 条件概率 / 贝叶斯 / 分布 / 交叉熵 / softmax
- 微积分要留的
- 导数 / 链式法则 / 梯度下降 / 学习率
- 起点要求
- 高中数学 + 会一点 Python
- 载体语言
- numpy 数组(本页)与 torch 张量(API 对应)
- 学完能看懂
- nn.Linear、temperature、cross entropy、lr、backward
- 学完能做
- 读懂模型代码的张量运算,手推两层网络的反向传播
- 本页不覆盖
- 测度论概率、凸优化证明、信息论推导
02
环境准备:装 numpy 就够,torch 可选
本页所有例子只依赖 numpy;PyTorch 用来对照同一段数学在框架里长什么样,装不装都不影响阅读。建议用虚拟环境,别往系统 Python 里硬塞。
numpy 对版本没有特别要求,装新版即可;本页输出基于 Python 3.12 + NumPy 2.5 实测,不同小版本的打印格式可能有细微差别(比如 numpy 2.x 的标量会显示成 np.float64(…)),数值本身一致。
# 1) 建一个独立环境,不污染系统 Python python3 -m venv .venv source .venv/bin/activate # Windows 是 .venv\Scripts\activate # 2) 本页主力,只有它也够跑完全部例子 pip install numpy python -c "import numpy; print(numpy.__version__)" # 3) 可选:想对照 PyTorch 再装(CPU 版即可入门) pip install torch
# uv 更快:一行建环境,装包几乎无感 uv venv source .venv/bin/activate uv pip install numpy uv pip install torch # 可选
# 本地 Jupyter:每个单元格就是一段 python3,改一个数立刻看到结果 pip install notebook jupyter lab # 或者什么都不装:浏览器打开 Google Colab, # numpy / torch 都已预装,格子里的 >>> 演示可以直接照抄
03
十分钟热身:向量、矩阵与第一个 softmax
不求全,只求把后面反复出现的三件东西先跑通:向量、矩阵乘法、softmax。复制进 REPL 或 Notebook,逐行看输出。
import numpy as np # 向量:一摞有序的数,模型里几乎所有东西的形态 v = np.array([1.0, 2.0, 3.0]) print(v.shape) # (3,) # 矩阵乘法:把同一个「变换」批量作用到一摞列向量上 A = np.array([[1.0, 0.0], [0.0, 2.0]]) # 对角阵:x 不动,y 翻倍 V = np.array([[1.0, 2.0], [3.0, 4.0]]) # 两列 = 两个被变换的向量 print(A @ V) # [[1. 2.] # [6. 8.]] # 第一个 softmax:任意一组数 → 和为 1 的概率分布 def softmax(x, t=1.0): e = np.exp((x - x.max()) / t) # 减 max 防溢出;除以 t 是温度 return e / e.sum() print(softmax(np.array([2.0, 1.0, 0.1])).round(3)) # [0.659 0.242 0.099]
-
01
先看 shape,再谈运算
90% 的 numpy 报错是形状对不上。任何一行写完先
print(x.shape),脑子里的形状和代码里的形状一致了,运算自然对。 -
02
@ 是「批量变换」
A @ V一次变换了 V 的两列——这就是「矩阵乘法为什么存在」的答案:它天生就是为了批量处理而发明的记号。 -
03
softmax 三行写完
exp 归一化之前先减
x.max(),结果数学上完全等价但数值稳定——这是全行业通用的行规,第 07 节解释为什么。 -
04
用 torch 对齐验证
torch.softmax(torch.tensor([2., 1., 0.1]), dim=-1)应给出同样三个数。两边能对上,说明你理解的数学是对的。
$ python warmup.py (3,) [[1. 2.] [6. 8.]] [0.659 0.242 0.099]
顺手对齐一遍 PyTorch
import torch logits = torch.tensor([2.0, 1.0, 0.1]) print(torch.softmax(logits, dim=-1)) # tensor([0.6590, 0.2418, 0.0992]):与 numpy 版完全一致 print(torch.softmax(logits / 0.7, dim=-1)) # tensor([0.7658, 0.1835, 0.0507]):温度 0.7,第一类更敢押注
04
三个知识块:每块只留模型代码里真正出现的部分
线性代数:向量、矩阵与「批量变换」
向量就是一排有序的数:一个词的嵌入是向量,一句话(token 序列)摆成矩阵,一批样本再摞成更高维的张量——模型世界里没有「一段文字」「一张图」,只有数组。矩阵乘法 A @ V 的直观读法是:A 是一台变换机器,V 的每一列是一个输入,乘一次等于把所有输入一起变完。深度学习的「前向传播」,本质就是一长串矩阵乘法连着做,中间夹着逐元素的非线性函数。
点积是线代里出场率最高的运算:两个同长向量对应相乘再求和,结果越大方向越一致。把向量先除以自身长度(归一化)再做点积,得到的就是夹角余弦:1 表示同向、0 表示垂直。注意力机制的底座就是这一下——query 和每个 key 算相关度,分数高的词多分一点注意力。特征向量则是「矩阵作用后方向不变」的特殊方向(A v = λv),直觉上是被变换机器偏爱的方向:PCA 降维、谱方法都在找它们。
A = np.diag([2.0, 3.0]) # 变换机器:x 放大 2 倍,y 放大 3 倍 v = np.array([1.0, 1.0]) print(A @ v) # [2. 3.]:一次乘法,批量变换 u = np.array([1.0, 0.0, 1.0]); w = np.array([1.0, 1.0, 0.0]) cos = u @ w / (np.linalg.norm(u) * np.linalg.norm(w)) print(cos.round(3)) # 0.5:夹角 60°,方向只算半像 vals, vecs = np.linalg.eig(A) # 特征向量:变换后方向不变的方向 print(vals) # [2. 3.]:坐标轴本身就是它的特征向量
把「矩阵的一行 = 一个神经元的权重」这个视角接回模型代码:神经网络的全连接层(nn.Linear(4, 2))本质就是一次矩阵乘法加一个偏置,x @ W + b,没有任何多余的东西:
x = np.array([[1.0, 2.0, 3.0, 4.0]]) # 一个样本,4 维 W = np.arange(8, dtype=float).reshape(4, 2) / 10 # 4 个输入 × 2 个神经元 b = np.array([0.1, -0.1]) print(x @ W + b) # [[4.1 4.9]]:这就是 nn.Linear 的全部数学
更高维同理:卷积是「局部小矩阵乘」,注意力是「带权重的矩阵乘」——记住 x @ W + b,模型里的每一层都是它的变奏。
特征向量暂时用不上别慌:它在本页的角色是「看懂 PCA 降维和谱方法的直觉」。做 AI 前两年你可能一次都不手算它,但读论文遇到「主成分」「谱聚类」时,想起「找矩阵偏爱的方向」就够了。
概率统计:模型输出的语言
条件概率 P(A|B) 回答「已知 B 时,A 多大概率」;把它倒过来推就是贝叶斯公式 P(B|A) = P(A|B)·P(B) / P(A)——知道「有病的人里多少比例验出阳性」,可以反推「验出阳性的人里多少真有病」。垃圾邮件过滤、医学检测、贝叶斯优化,骨架都是这一行。常见分布记两个就够:伯努利管「是 / 否」(分类标签),高斯管「连续测量」(权重初始化 rng.normal 的默认来源)。
p_dis = 0.01 # 患病率 P(D):基础率 p_pos_dis = 0.99 # 敏感度 P(+|D):有病的人 99% 验出阳性 p_pos_ok = 0.05 # 假阳性 P(+|健康):健康人也有 5% 阳性 p_pos = p_dis * p_pos_dis + (1 - p_dis) * p_pos_ok print(p_dis * p_pos_dis / p_pos) # 贝叶斯公式逐项代入 # 0.1666…:阳性结果里只有约 1/6 真有病
「检测很准」和「你有病」是两回事——基础率越低,假阳性占的比重越大。垃圾邮件过滤、异常检测都要过这道坎。
交叉熵就是「猜错有多大代价」:−Σ yᵢ·log pᵢ,真实类别上模型给的概率越低,代价越大。押对 70% 代价 0.357,只剩 10% 代价 2.303——对数把「错得离谱」惩罚得极重,这就是它当损失函数的原因。它的搭档是 softmax + 温度:softmax 把一组打分变成概率分布,除以温度 t 控制分布的尖平;大模型生成文本时的 temperature 参数就是它——t 调小输出保守确定,t 调大天马行空。
p = np.array([0.7, 0.2, 0.1]) # 模型给出的预测分布 y1 = np.array([1, 0, 0]); y3 = np.array([0, 0, 1]) print((-np.sum(y1 * np.log(p))).round(3)) # 0.357:押对了,代价小 print((-np.sum(y3 * np.log(p))).round(3)) # 2.303:押错到只剩 10%,代价大 x = np.array([3.0, 2.0, 0.5]) for t in (0.5, 1.0, 2.0): e = np.exp((x - x.max()) / t) print(t, (e / e.sum()).round(2)) # 0.5 [0.88 0.12 0.01] 温度越低分布越尖:0.88 → 0.69 → 0.53 # 1.0 [0.69 0.25 0.06] # 2.0 [0.53 0.32 0.15]
顺带认识熵 H(p)——分布自身的不确定性,也是交叉熵的前半截:
p = np.array([0.5, 0.5]) print(-(p * np.log(p)).sum()) # 0.6931:五五开,最拿不准 q = np.array([0.99, 0.01]) print(-(q * np.log(q)).sum()) # 0.0560:快确定了,熵很小
微积分:导数、链式法则与梯度下降
导数回答「x 动一点,y 动多少」,几何上就是斜率。多个参数时对每个参数各求一次偏导,排成向量就是梯度 ∇f,指向上升最快的方向;训练要下降,所以每步沿负梯度走:w ← w − lr·∇f,这就是梯度下降。学习率 lr 是唯一的驾驶参数:太大会在碗壁之间来回震荡甚至飞成 NaN,太小则半天挪不动。
为什么需要链式法则?因为网络是嵌套函数 y = f(g(h(x))):损失在最外层,参数在最内层,梯度只能一层层乘回来(f′·g′·h′)。「逐层乘回来」这套算法就是反向传播——第 06 节的手写实现里,你会看到它每一层长什么样。tanh 的导数是 1 − tanh²,sigmoid 的是 σ(1−σ),这两个式子手推网络时天天用。
torch 的 autograd 把这套「逐层乘回来」自动化:你只需声明哪些张量需要梯度,backward() 会沿着计算图把整条链算完:
import torch x = torch.tensor(2.0, requires_grad=True) y = (x - 3) ** 2 # 同一只碗 y.backward() # 自动求导:链式法则全自动 print(x.grad) # tensor(-2.):和手推的 2*(2-3) 一致
以后写完任何手推梯度,都可以用第 03 节的中心差分对拍一遍——数值梯度是解析梯度的廉价验算器,这一招叫梯度检查(gradient check),是写自定义层的标准保险。
f = lambda x: (x - 3) ** 2 # 一只碗 df = lambda x: 2 * (x - 3) # 导数 = 斜率 h = 1e-6 # 数值导数验算:中心差分 print((f(2.0 + h) - f(2.0 - h)) / (2 * h)) # -2.0000000001…,真实导数正是 -2 x = 0.0 # 梯度下降:沿负梯度滚到碗底 for _ in range(60): x -= 0.1 * df(x) # lr = 0.1 print(x) # 2.9999954…:基本到底了
三块在一次训练步里如何分工
把三块拼回一次真实的训练迭代,分工一目了然——第 06 节会把这三行扩成 30 行完整代码:
- 前向传播
- 线性代数:一摞矩阵乘法把输入算成预测(X @ W1 + b1 …)
- 计算损失
- 概率统计:softmax 把输出变成分布,交叉熵量化「错了多少」
- 反向传播
- 微积分:链式法则把误差逐层乘回每个参数
- 参数更新
- 还是微积分:梯度下降沿负梯度走一步,学习率决定步幅
同一个循环在训练里有一堆名字:SGD、optimizer.step()、backward() + step()——名字在变,骨架永远是「前向 → 损失 → 反向 → 更新」这四拍。第 06 节会把这四拍一次写全。
05
速查表:符号、API、损失、求导
这一节当词典用。忘了符号含义、numpy 函数名、温度采样怎么写,直接在下面搜——中英文关键词都能搜到。
| 符号 | 含义 |
|---|---|
| x ∈ ℝⁿ | 一个 n 维向量,比如 3 个数排成一排 |
| xᵢ | 向量的第 i 个分量(公式里从 1 数,代码里从 0 数) |
| ‖x‖ | 向量长度(L2 范数):各分量平方和开根号 |
| x·y 或 ⟨x,y⟩ | 点积:对应位置相乘再求和,值大 = 方向像 |
| A ∈ ℝ^(m×n) | m 行 n 列的矩阵 |
| Aᵢⱼ | 矩阵第 i 行第 j 列的元素 |
| Aᵀ | 转置:行列互换,形状 (m,n) 变 (n,m) |
| Iₙ | n 阶单位矩阵:乘谁谁不变 |
| ∇f | 梯度:f 对每个参数的偏导排成的向量,指向最陡上升方向 |
| ∂L/∂w | 损失 L 对参数 w 的偏导数(梯度里的一个分量) |
| P(A|B) | 条件概率:B 已发生时 A 的概率 |
| E[X] | 期望:随机变量取值的长期加权平均 |
| H(p) | 熵:分布的不确定性,越平越大 |
| argmax | 让表达式取最大值的那个自变量 |
| ∑ | 求和:∑ᵢ xᵢ 就是把所有分量加起来 |
| 𝒩(μ, σ²) | 正态(高斯)分布:均值 μ、方差 σ²,钟形曲线 |
| numpy API | 作用 |
|---|---|
| np.array([1., 2.]) | 从列表造向量,造矩阵传嵌套列表 |
| a.shape | 形状。一切报错先打印它 |
| a.reshape(2, 3) | 改形状,元素总数不变 |
| a @ b / np.dot(a, b) | 矩阵乘法;一维输入时就是点积 |
| a.T | 转置 |
| np.linalg.norm(v) | 向量长度(默认 L2) |
| v / np.linalg.norm(v) | 归一化:只留方向,长度变 1 |
| np.eye(n) | n 阶单位矩阵 |
| np.diag([2., 3.]) | 由对角元素造对角阵:各轴独立缩放 |
| np.linalg.eig(A) | 特征值与特征向量:找「方向不变」的方向 |
| np.linalg.solve(A, b) | 解线性方程组 Ax = b |
| np.tanh / np.exp | 逐元素非线性 / 指数(softmax 的原料) |
| np.zeros / np.ones | 造全 0 / 全 1 数组:偏置初始化常用 |
| np.argmax(v) | 最大值的下标:分布里挑最可能的类 |
| np.linalg.inv(A) | 逆矩阵 A⁻¹(深度学习里少用,解方程请用 solve) |
| API / 写法 | 作用 |
|---|---|
| np.exp / np.log | 逐元素指数 / 自然对数(交叉熵的原料) |
| softmax(x) 手写版 | 见第 03 节:exp 前先减 x.max() |
| torch.softmax(logits, dim=-1) | torch 版;dim 指定沿哪一维归一化 |
| torch.softmax(logits / t, dim=-1) | 温度采样:t 小更确定,t 大更随机 |
| -np.sum(y * np.log(p)) | 单样本交叉熵(y 是 one-hot 真值) |
| F.cross_entropy(logits, y) | torch 常用损失:直接吃原始 logits,内部含 softmax |
| np.mean((y - yhat) ** 2) | MSE:回归任务常用损失 |
| rng = np.random.default_rng(0) | 建可复现随机数生成器,0 是种子 |
| rng.normal(0, 1, (3, 4)) | 从高斯分布抽样:均值 0、标准差 1 |
| rng.uniform(-1, 1, size=(64, 1)) | 从均匀分布抽样 |
| rng.choice(n, p=probs) | 按概率分布 probs 采一个下标(采样生成) |
| 1 / (1 + np.exp(-x)) | sigmoid:把打分压到 0~1,当概率用 |
| torch.multinomial(probs, 1) | 按概率分布采样一个下标(大模型生成用它) |
| np.clip(p, 1e-9, 1.0) | 把概率夹在安全区间,防 log(0) |
| p.round(3) | 打印时控制小数位,输出干净 |
| API / 写法 | 作用 |
|---|---|
| (f(x+h) - f(x-h)) / (2*h) | 中心差分:数值导数,用来验证手推公式 |
| np.gradient(y, x) | 对数组直接求数值梯度 |
| w = torch.tensor(…, requires_grad=True) | 声明这个张量需要梯度 |
| loss.backward() | 反向传播:自动算出全部梯度写入 .grad |
| w.grad | 查看 w 的梯度(即 ∂L/∂w) |
| with torch.no_grad(): | 推理时关闭梯度记录,省内存提速 |
| lr(学习率) | 每步沿负梯度走多远:太大震荡发散,太小龟速 |
| torch.optim.SGD(params, lr=0.05) | 随机梯度下降优化器 |
| optimizer.zero_grad() | 每步开始前清梯度,否则会累加上一步的 |
| optimizer.step() | 用算好的梯度真正更新参数 |
| torch.optim.Adam(params, lr=1e-3) | 自适应学习率优化器:比 SGD 更省调参心力的默认选择 |
| torch.nn.utils.clip_grad_norm_(…) | 梯度裁剪:梯度范数超限时按比例缩,防爆炸 |
| lr_scheduler(学习率调度) | 训练后期把 lr 调小:先快后慢,收尾更稳 |
| 梯度消失 | 链式法则里连乘大量小数,深层梯度趋近 0——激活函数选型的历史动因 |
没有匹配的条目。换个词试试,比如「点积」「softmax」「学习率」。
06
实战:纯 numpy 手写一个能学习的两层网络
目标任务:给 64 个标量样本,让网络自己学出 y = 3x + 1。结构 1 → 16 → 1,写成一行就是 y = tanh(x·W1 + b1)·W2 + b2——只有两个参数矩阵;隐藏层用 tanh,损失是 MSE,优化器就是第 04 节的梯度下降。没有任何黑盒,每一行都对应前面讲过的知识块。
第 1 步:造数据、初始化参数
import numpy as np rng = np.random.default_rng(0) # 固定种子,结果可复现 X = rng.uniform(-1, 1, (64, 1)) # 64 个标量样本,形状 (64, 1) Y = 3 * X + 1 # 要学的目标:一条直线 W1 = rng.normal(0, 1, (1, 16)); b1 = np.zeros(16) # 隐藏层参数 W2 = rng.normal(0, 1, (16, 1)); b2 = np.zeros(1) # 输出层参数 lr = 0.05 # 学习率:每步走多远
种子固定为 0,意味着你在自己机器上跑出的 loss 数字会和本页输出一模一样——调参实验第一课:可复现优先。
第 2 步:前向传播=两连矩阵乘法(线性代数)
h = np.tanh(X @ W1 + b1) # (64,1)@(1,16) → (64,16),非线性压到 -1~1 y = h @ W2 + b2 # (64,16)@(16,1) → (64,1),网络输出 loss = np.mean((y - Y) ** 2) # MSE:平均平方误差,越小学得越像
第 3 步:反向传播=链式法则逐层乘回来(微积分)
dy = 2 * (y - Y) / y.size # dL/dy:MSE 对输出的导数 dW2, db2 = h.T @ dy, dy.sum(0) # 输出层梯度:上游差 × 本层输入 dh = dy @ W2.T * (1 - h ** 2) # 穿过 tanh:它的导数是 1 − tanh² dW1, db1 = X.T @ dh, dh.sum(0) # 隐藏层梯度:同一套乘法再来一遍
对照看:每一层的梯度都是「上游传来的误差 × 本层输入的转置 × 本层激活函数的导数」——这就是链式法则在矩阵世界里的长相。torch 的 loss.backward() 做的就是这两步的自动化。
第 4 步:完整程序(直接复制运行)
import numpy as np rng = np.random.default_rng(0) X = rng.uniform(-1, 1, (64, 1)) # 64 个标量样本 Y = 3 * X + 1 # 目标:学一条直线 W1 = rng.normal(0, 1, (1, 16)); b1 = np.zeros(16) W2 = rng.normal(0, 1, (16, 1)); b2 = np.zeros(1) lr = 0.05 for step in range(2001): # ---- 前向:线性代数 ---- h = np.tanh(X @ W1 + b1) y = h @ W2 + b2 loss = np.mean((y - Y) ** 2) # ---- 反向:链式法则 ---- dy = 2 * (y - Y) / y.size dW2, db2 = h.T @ dy, dy.sum(0) dh = dy @ W2.T * (1 - h ** 2) dW1, db1 = X.T @ dh, dh.sum(0) # ---- 更新:梯度下降 ---- W1 -= lr * dW1; b1 -= lr * db1 W2 -= lr * dW2; b2 -= lr * db2 if step % 500 == 0: print(f"step {step:4d} loss {loss:.4f}") x0 = np.array([[0.5]]) pred = np.tanh(x0 @ W1 + b1) @ W2 + b2 print("predict x=0.5 ->", pred.round(2), "(target 2.50)")
step 0 loss 20.8016 step 500 loss 0.0008 step 1000 loss 0.0004 step 1500 loss 0.0004 step 2000 loss 0.0003 predict x=0.5 -> [[2.52]] (target 2.50)
loss 从 20.8 掉到 0.0003,预测 2.52 贴着目标 2.50——网络真的从数据里「学」出了那条直线,而不是被我们写死。
收敛形态也有信息量:绝大多数改善发生在前几百步,之后进入漫长的平台期。真实训练里通常配一个验证集,loss 不再下降就早停,省下的时间比调参多。
第 5 步:跑起来之后,改这三个数
lr = 0.9 # 改大:loss 可能不降反升,甚至 NaN # lr = 0.005 # 改小:收敛明显变慢 # Y = np.sin(3 * X) # 换个更难的目标:直线 → 曲线
想再进一步:把回归改成分类,只需三处替换——损失从 MSE 换成交叉熵,输出层后面接 softmax,目标 Y 换成 one-hot 标签;网络结构、前向、反向的骨架一字不用动。这正是第 04 节「三块分工」图的意义:换任务换的是概率那一块,其余两块照旧。
07
避坑:新手最先撞上的四个坑
坑一:softmax 里 exp 溢出成 NaN
logits 一大(比如 1000),np.exp 直接上溢成 inf,inf/inf 得 nan。解法是行业通用行规:减去最大值再 exp,数学上完全等价(分子分母同乘同一个常数 e^(−max),分布不变),数值上永远安全。大模型生成里 logits 同样可能上千,所有框架的 softmax 内部都做了这一步。
x = np.array([1000.0, 999.0]) # np.exp(x) # overflow → inf/inf = nan e = np.exp(x - x.max()) # 减 max:等价且稳定 print((e / e.sum()).round(3)) # [0.731 0.269]
坑二:* 是逐元素乘,@ 才是矩阵乘
写损失、写梯度时混用两者,不报错但结果悄悄错——这是最阴险的一类 bug。手推梯度时想「这步是点积还是逐元素」,落到代码里就对应 @ 还是 *,一一对应就不会错。
a = np.array([1.0, 2.0]); b = np.array([3.0, 4.0]) print(a * b) # [3. 8.]:逐元素相乘,shape 不变 print(a @ b) # 11.0:点积,出来的是标量
坑三:(n,) 和 (n,1) 不是一回事
一维数组和列向量在广播时行为完全不同。转置一维数组什么都不会发生(.T 对 (n,) 无效),误以为转了方向,后面的乘法就全错。
a = np.array([1.0, 2.0, 3.0]) # shape (3,) b = np.array([[1.0], [2.0], [3.0]]) # shape (3,1) print((a * b).shape) # (3, 3):广播悄悄扩张了 col = a[:, None] # 想要列向量,显式造:(3,1) print(col.shape) # (3, 1)
经验法则:凡是「一行是一个样本」的场合,坚持用二维形状 (batch, features);(n,) 只留给纯一维运算。形状约定一旦固定,广播 bug 就少一大半。
坑四:loss 变 NaN,按顺序查三处
# 1) 学习率太大:步子迈飞了 → lr 除以 10 再试 # 2) log(0):概率下溢为 0 → 写成 np.log(p + 1e-9) # 3) 除零:分母用 y.size 这类真实计数,别写死常数 # 4) 梯度爆炸:连乘后巨大 → clip_grad_norm_ 裁剪一下
08
进阶:把三块数学接回模型
下面这些资源按「先直觉、后严谨」排序,前两个用一周,后两个用一两个月:
别按顺序全啃:先用 3Blue1Brown 建直觉,再拿 Karpathy 的视频配本页第 06 节动手,卡住了再回花书查严谨定义。数学书的正确读法是按需查阅,不是从头到尾。
- 3Blue1Brown《线性代数的本质》:动画把「矩阵 = 空间变换」讲透,B 站有官方账号,中文配音。
- Mathematics for Machine Learning(Deisenroth 等):免费电子书,线代 → 概率 → 优化正好覆盖本页三块,深度适中。
- Karpathy: Neural Networks — Zero to Hero:从手写反向传播一路写到 GPT,和本页第 06 节无缝衔接,全程视频 + 代码。
- 《Deep Learning》(花书):Goodfellow 等;第 2–4 章就是本页内容的严谨版,查阅式阅读。
- NumPy Quickstart:官方快速教程,把数组操作补齐。
- PyTorch Tutorials:把本页每个 numpy 例子翻译成 torch 张量版本,是最好的练习题。
09
收尾:合上页面前,勾一遍
五条全勾上,你手里就有一张能读懂大部分模型代码的数学地图。哪条没把握,就回对应小节再跑一遍代码——数学是跑会的,不是看会的。
0 / 5