Skip to content
Aidenz
Go back

从零训练大模型(二):数学与机器学习最小基础

26 分钟阅读 · 7616 字
Edit page

本章前置:读完第 01 章,环境已搭好、跑通过第一段代码。本章不依赖仓库代码,是纯粹的”打地基”,只要你会基本的加减乘除、知道”变量”是什么就行。

你将学到:用最朴素的语言搞懂后面会反复用到的几样东西——标量/向量/矩阵/张量与它们的形状(尤其 (B,T,C))、矩阵乘法的直觉;函数与导数(斜率)、偏导、梯度(指向上升最快的方向)、链式法则(反向传播的核心);概率分布、期望、为什么要用对数和负对数;最后用一个最简单的一维线性回归,完整地走一遍”训练”——亲手推导梯度、用学习率更新参数,第一次直观感受”模型在学习”。

👈 上一章:环境搭建与第一次运行返回总览


很多人一听”大模型”,第一反应是”我数学不好,学不来”。别怕。这一章我们只讲真正用得上的那一点点,而且每个公式后面都用大白话拆开讲。你不需要会解微分方程,只需要建立几个直觉:数据长什么样、模型怎么”算错了多少”、又怎么”照着错的方向调一点点”。把这几样想通,后面所有花哨的算法都只是它们的放大版。

读这一章时,请允许自己慢一点。看不懂公式先别慌——先读公式后面的解释,弄明白”它想干嘛”,再回头看符号

1. 标量、向量、矩阵、张量:数据的形状

机器学习里的数据,本质上都是”一堆数字”。我们按”维度”给它们起名字。

  • 标量(scalar):就是一个数。比如温度 36.5、损失值 2.31。没有方向、没有排列,孤零零一个。
  • 向量(vector):一串排好队的数。比如 [1.0, -0.5, 3.2]。可以把它想成一个箭头,或者”某个东西的一组特征”。我们说它的**长度(维度)**是 3。
  • 矩阵(matrix):一个数字表格,有行有列。比如一个 2 行 3 列的矩阵:
[123456]\begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}

这是一个形状为 (2, 3) 的矩阵:2 行、3 列。

  • 张量(tensor):把上面的概念推广到任意多维。标量是 0 维张量,向量是 1 维,矩阵是 2 维,再往上就是 3 维、4 维……张量就是”多维数字数组”的统称。PyTorch 里所有数据都是张量,这也是 torch.Tensor 名字的由来。

“形状(shape)“是张量最重要的属性,它告诉你”每一维有多大”。形状写成一个括号里的数字元组。比如:

  • 形状 ():标量。
  • 形状 (3,):长度 3 的向量。
  • 形状 (2, 3):2×3 的矩阵。
  • 形状 (2, 16, 50304):一个 3 维张量——还记得第 01 章那次前向计算的输出吗?就是这个形状。

牢牢记住 (B, T, C)

在这门课里,你会一次又一次看到三维形状 (B,T,C)(B, T, C)。它几乎是处理文本时的”标准包装”:

  • BB = batch,一批里有几个序列(我们总是一次处理一批,效率高);
  • TT = time / tokens,每个序列有多长(多少个 token);
  • CC = channels / features,每个 token 用多长的向量来表示(也就是第 01 章的 n_embed,或在最后一层是词表大小)。

举个具体例子:(2, 16, 128) 表示”2 个序列,每个序列 16 个 token,每个 token 用一个 128 维向量表示”。现在记住这个记法,等到第 05、06 章讲 Transformer 和注意力时,你会发现所有操作都在跟这三个维度打交道。

2. 矩阵乘法:不只是”乘一乘”

神经网络里最频繁的操作,就是矩阵乘法(matrix multiplication,常记作 @matmul)。它看起来抽象,直觉其实很简单。

先看规则。两个矩阵 AA(形状 m×nm \times n)和 BB(形状 n×pn \times p)相乘,结果 C=ABC = A B 的形状是 m×pm \times p关键约束:AA 的列数必须等于 BB 的行数(都是 nn),否则乘不了。结果里第 ii 行第 jj 列的元素是:

Cij=k=1nAikBkjC_{ij} = \sum_{k=1}^{n} A_{ik}\, B_{kj}

把它读成大白话:CC 里位置 (i,j)(i, j) 的数,等于”AA 的第 ii 行”和”BB 的第 jj 列”对应位置相乘再求和k=1n\sum_{k=1}^{n} 就是”把 kk 从 1 到 nn 的每一项加起来”。

维度对齐的口诀:把形状写在一起,(m×n)(m \times n)(n×p)(n \times p),中间的 nn 必须相同、会”消掉”,剩下外侧的 mmpp 就是结果形状:

(m×n)(n必须相等×p)    (m×p)(m \times \underbrace{n) \cdot (n}_{\text{必须相等}} \times p) \;\to\; (m \times p)

这个”中间对齐、两头留下”的口诀,以后看任何网络层的形状变化都用得上。

为什么神经网络爱用它?因为矩阵乘法正好表达了”一组输入特征,经过一层权重,变成一组新特征”。比如一个把 128 维向量变成 512 维向量的”线性层”,本质就是拿输入去乘一个 128×512128 \times 512 的权重矩阵。第 03、05 章里的 nn.Linear 干的就是这件事;第 06 章注意力机制里的 QQKKVV 计算也全是矩阵乘法。

3. 函数与导数:斜率说明了”往哪调”

函数

函数就是”输入进去,输出出来”的规则。写成 y=f(x)y = f(x):给一个 xx,按规则 ff 算出一个 yy。比如 f(x)=x2f(x) = x^2,输入 3 就输出 9。

在机器学习里,我们最关心一类特殊的函数:损失函数。它的输入是”模型的参数”,输出是”模型现在有多差”。我们的全部目标,就是调整参数,让这个输出(损失)尽量小

导数 = 斜率 = 变化的快慢

导数(derivative) 衡量”当我把输入挪动一点点,输出会变化多少”。它就是函数曲线在某一点的斜率。记作 dydx\frac{dy}{dx}f(x)f'(x)

直觉:站在一座山的某个点上,导数告诉你”朝某个方向迈一小步,海拔会升还是降、升降多快”。

  • 导数为正:输入增大,输出也增大(上坡)。
  • 导数为负:输入增大,输出反而减小(下坡)。
  • 导数为零:此处是”平地”,可能是山顶或谷底。

举个能手算的例子,f(x)=x2f(x) = x^2 的导数是 f(x)=2xf'(x) = 2x。在 x=3x = 3 处斜率是 6(陡峭地上坡);在 x=0x = 0 处斜率是 0(谷底)。

为什么这对训练至关重要? 因为如果损失函数对某个参数的导数是正的,就说明”把这个参数调大,损失会变大”——那我们就该反着来,把它调小;反之亦然。导数,就是模型告诉我们”该往哪个方向调参数”的指南针。这个思想下一节、以及第 4 节的线性回归里就会用到。

偏导:多个变量时,“只动一个”

真实模型有成千上万、甚至上亿个参数,不止一个 xx。当函数有多个输入,比如 L(w,b)L(w, b),我们就用偏导(partial derivative):只动其中一个变量、把其它都当常数不动,看输出怎么变。记作 Lw\frac{\partial L}{\partial w}(读作”L 对 w 的偏导”)。

\partial 这个符号(读”偏”)就是在提醒你:这是”只看 ww 这一个方向”的斜率,别的变量先按住不动。

梯度:所有偏导打包,指向”上升最快”的方向

把一个函数对它所有参数的偏导,排成一个向量,就叫梯度(gradient),记作 L\nabla L(那个倒三角读作”nabla”):

L=(Lw1,Lw2,,Lwn)\nabla L = \left( \frac{\partial L}{\partial w_1}, \frac{\partial L}{\partial w_2}, \dots, \frac{\partial L}{\partial w_n} \right)

大白话:梯度是一个”方向箭头”,它指向让损失上升最快的方向

这有个绝妙的推论:既然梯度指向”上升最快”,那么它的反方向 L-\nabla L 就是”下降最快”的方向。我们想让损失变小,所以就沿着梯度的反方向走一小步。这就是大名鼎鼎的梯度下降(gradient descent),是几乎所有深度学习训练的核心。第 4 节我们就亲手走一遍。

链式法则:反向传播的引擎

最后一块拼图。神经网络是”一层套一层”的——输出是 f(g(x))f(g(x)) 这样复合起来的。要算最终损失对最里层某个参数的导数,得用链式法则(chain rule):

dydx=dydududx,其中 u=g(x),  y=f(u)\frac{dy}{dx} = \frac{dy}{du} \cdot \frac{du}{dx}, \quad \text{其中 } u = g(x),\; y = f(u)

大白话:一连串变化的总斜率,等于每一段斜率相乘。好比齿轮组,大齿轮带小齿轮——要算最末端转多快,就把每一级的传动比乘起来。

为什么重要?因为神经网络有很多层,损失要”沿着层一路传回去”才能算出每层参数的梯度。这个”从输出端把梯度一层层乘回输入端”的过程,就叫反向传播(backpropagation),而它的数学本质就是链式法则。好消息是:你几乎永远不用亲手做这件事——第 03 章你会看到,PyTorch 的 autograd 会自动、精确地替你完成所有链式求导。你只需理解”它在做什么”,并相信它。

4. 概率、期望、对数:语言模型的语言

大模型本质上是个”猜下一个词”的概率机器,所以我们得熟悉几个概率概念。

概率分布

概率(probability) 是 0 到 1 之间的数,表示某件事发生的可能性,0 是不可能、1 是必然。概率分布(probability distribution) 则是”把所有可能结果各自的概率列出来”,并且它们加起来等于 1

比如掷一个公平骰子,分布是”1 点到 6 点各 1/6”;加起来 6×16=16 \times \frac{1}{6} = 1

在语言模型里,模型对”下一个 token 是什么”给出一个分布:词表里 50304 个 token 各有一个概率,全部加起来是 1。第 01 章那个 (2, 16, 50304) 的 logits,经过一个叫 softmax 的操作(第 05、07 章细讲)就变成这样的概率分布。

期望:概率加权的平均

期望(expectation) 是”考虑了概率之后的平均值”,记作 E\mathbb{E}。对一个能取值 x1,x2,x_1, x_2, \dots、对应概率 p1,p2,p_1, p_2, \dots 的随机量:

E[x]=ipixi\mathbb{E}[x] = \sum_i p_i \, x_i

大白话:把每个可能取值,按它出现的概率加权,再求和。掷公平骰子点数的期望是 16(1+2++6)=3.5\frac{1}{6}(1+2+\dots+6) = 3.5。强化学习章节(第 15、16 章)里反复出现的”期望奖励”,就是这个意思:把每种可能结果的奖励按其概率加权平均。

为什么处处用对数?

接下来你会在损失函数里频繁见到对数(logarithm,记作 log\log)。对数有两个我们极其看重的好处。

好处一:把乘法变成加法。 对数有个性质:

log(ab)=loga+logb\log(a \cdot b) = \log a + \log b

为什么需要?因为一句话的概率,是它每个 token 概率的连乘。比如一句 100 个 token 的话,概率是 100 个小数相乘——每个都小于 1,乘到后面会变成一个小得可怕的数(比如 1020010^{-200}),计算机根本存不下(这叫”数值下溢”)。取对数后,连乘变成连加,小数变成”一串负数相加”,计算机算起来又稳又准。

好处二:数值稳定 + 单调。 对数是单调递增的:aa 越大,loga\log a 也越大。所以”让概率最大”和”让对数概率最大”是等价的目标,但后者算起来稳定得多。

负对数:损失登场

还有个细节:概率在 0 到 1 之间,它的对数是负数或 0(比如 log0.12.3\log 0.1 \approx -2.3,log1=0\log 1 = 0)。而我们习惯让”损失”是个越小越好的正数。于是只要在前面加个负号,得到负对数(negative log):

损失=logp\text{损失} = -\log p

大白话:模型对正确答案给的概率 pp 越高(越接近 1),logp-\log p 就越接近 0(损失越小);pp 越低(模型越离谱),logp-\log p 就越大(惩罚越重)。 这正是我们想要的”打分尺”。第 07 章你会看到,语言模型的核心损失——交叉熵(cross-entropy)——本质就是”对正确 token 的负对数概率取平均”。现在埋下这个伏笔即可。

5. 机器学习的核心循环:用一个一维线性回归全程走一遍

概念铺垫够了,现在把它们串起来,看看”训练”到底是怎么一回事。我们用最简单的模型:一维线性回归。

模型、参数、损失:三个核心名词

  • 模型(model):一个带”可调旋钮”的函数。这里就是一条直线 y^=wx+b\hat{y} = w x + b。给一个输入 xx,它预测一个输出 y^\hat{y}(读作”y hat”,表示”预测值”,区别于真实值 yy)。
  • 参数 / 权重(parameter / weight):模型里那些可以调整的数,这里是斜率 ww 和截距 bb训练,就是不断调整参数的过程。
  • 损失函数(loss function):衡量”预测有多差”的数值,越小越好。

任务设定

假设我们有一堆数据点 (xi,yi)(x_i, y_i),想找一条直线尽量穿过它们。我们用均方误差(Mean Squared Error, MSE) 当损失:把每个点的”预测值减真实值”平方,再求平均。设共有 NN 个数据点:

L(w,b)=1Ni=1N(y^iyi)2=1Ni=1N(wxi+byi)2L(w, b) = \frac{1}{N} \sum_{i=1}^{N} \left( \hat{y}_i - y_i \right)^2 = \frac{1}{N} \sum_{i=1}^{N} \left( w x_i + b - y_i \right)^2

大白话:对每个点,算”我猜的 y^i=wxi+b\hat y_i = w x_i + b“和”真值 yiy_i“差多少,平方一下(让正负差都变成正、且差得越多惩罚越狠),所有点求平均。 平方还有个好处:让这个损失对 w,bw, b 而言是个平滑的”碗形”,有唯一的谷底,正好用梯度下降去找。

手推梯度

要”下山”,得先知道坡度——也就是损失对 ww 和对 bb 的偏导。我们一步步推,用到的就是第 3 节的求导和链式法则。

为了少写求和号,先看单个点的损失 =(wx+by)2\ell = (w x + b - y)^2。令中间量 e=wx+bye = w x + b - y(这就是这个点的”误差”),于是 =e2\ell = e^2

ww 求偏导,用链式法则(ddwe2=2ededw\frac{d}{dw}e^2 = 2e \cdot \frac{de}{dw},而 dedw=x\frac{de}{dw} = x):

w=2ex=2(wx+by)x\frac{\partial \ell}{\partial w} = 2 e \cdot x = 2 (w x + b - y)\, x

bb 求偏导(dedb=1\frac{de}{db} = 1):

b=2e1=2(wx+by)\frac{\partial \ell}{\partial b} = 2 e \cdot 1 = 2 (w x + b - y)

把所有点平均起来,就得到整体损失的梯度:

Lw=2Ni=1N(wxi+byi)xi\frac{\partial L}{\partial w} = \frac{2}{N} \sum_{i=1}^{N} (w x_i + b - y_i)\, x_i Lb=2Ni=1N(wxi+byi)\frac{\partial L}{\partial b} = \frac{2}{N} \sum_{i=1}^{N} (w x_i + b - y_i)

大白话:每个点”误差 ×\times 输入”的平均,告诉我们 ww 该怎么调;每个点”误差”本身的平均,告诉我们 bb 该怎么调。误差越大、方向越一致,梯度就越大,该调的幅度也越大。

用学习率更新参数

有了梯度,就能沿”下降最快”的方向(梯度反方向)迈一步。每一步这样更新:

wwηLw,bbηLbw \leftarrow w - \eta \, \frac{\partial L}{\partial w}, \qquad b \leftarrow b - \eta \, \frac{\partial L}{\partial b}

这里 η\eta(希腊字母,读 “eta”)是学习率(learning rate),一个我们自己设的小正数(比如 0.01),控制每步迈多大

  • η\eta 太大:步子迈太猛,可能”一步跨过谷底”甚至越走越高,训练不稳定;
  • η\eta 太小:走得太慢,要很多步才到谷底。

箭头 \leftarrow 表示”用右边算出的新值,覆盖左边的旧值”。注意那个减号:它正是”沿梯度反方向走”的体现——梯度指向上坡,我们减去它,就往下坡走。

走一个具体的数字例子

来感受一下”训练真的在学习”。假设真实规律是 y=2xy = 2x(我们假装不知道,要让模型自己学出来)。只用一个数据点 (x,y)=(1,2)(x, y) = (1, 2),初始参数 w=0,b=0w = 0, b = 0,学习率 η=0.1\eta = 0.1

第 1 步:当前预测 y^=wx+b=01+0=0\hat y = w x + b = 0 \cdot 1 + 0 = 0。误差 e=y^y=02=2e = \hat y - y = 0 - 2 = -2

梯度:

Lw=2ex=2(2)1=4,Lb=2e=2(2)=4\frac{\partial L}{\partial w} = 2 e x = 2 \cdot (-2) \cdot 1 = -4, \qquad \frac{\partial L}{\partial b} = 2 e = 2 \cdot (-2) = -4

更新:

w00.1(4)=0.4,b00.1(4)=0.4w \leftarrow 0 - 0.1 \cdot (-4) = 0.4, \qquad b \leftarrow 0 - 0.1 \cdot (-4) = 0.4

第 2 步:新预测 y^=0.41+0.4=0.8\hat y = 0.4 \cdot 1 + 0.4 = 0.8。比起第一步的 0,离目标 2 更近了! 误差从 2-2 缩小到 0.82=1.20.8 - 2 = -1.2

继续算下去:误差 1.2-1.2,梯度 2(1.2)1=2.42 \cdot (-1.2) \cdot 1 = -2.4,更新 w0.4+0.24=0.64w \leftarrow 0.4 + 0.24 = 0.64,b0.64b \leftarrow 0.64,预测变成 1.281.28……

你看到了吗?每一步预测都在向 2 靠近,损失在减小。这就是”训练”——没有魔法,就是”算损失 → 算梯度 → 沿反方向挪一小步”,重复很多很多次。一个有上亿参数的大模型,训练的内核和这个例子一模一样,只是参数多得多、损失函数复杂得多、梯度由 PyTorch 自动算而已。

埋个伏笔:你刚才”手算梯度”很辛苦吧?在真实模型里,谁来算这成千上万个偏导?答案是 PyTorch 的 autograd——下一章(第 03 章)你会亲眼看到,它自动算出的梯度,和你这里手推的完全一致。而”算损失→反向求梯度→更新参数”这套循环,正是第 03 章要你亲手写出来的”训练循环五件套”,也是后面预训练、SFT、PPO 等所有阶段共用的骨架。

小结

  • 数据都是张量,核心属性是形状;文本处理里最常见的是 (B,T,C)(B, T, C):批大小、序列长度、特征维度。
  • 矩阵乘法是网络的基本动作,口诀”中间维度对齐、两头维度留下”;它表达”一组特征经过一层权重变成新特征”。
  • 导数 = 斜率,告诉我们”该往哪调”;偏导是”只动一个变量”的斜率;梯度把所有偏导打包,指向上升最快方向,所以我们沿反方向走;链式法则是反向传播的数学引擎。
  • 概率分布各项加起来为 1;期望是概率加权平均;用对数是为了把连乘变连加、保证数值稳定;负对数 logp-\log p 天然适合当损失(预测越准、损失越小)。
  • 训练的核心循环:定义模型(带参数)→ 用损失函数打分 → 求梯度 → 用学习率沿梯度反方向更新参数 → 重复。我们用一维线性回归亲手走通了全程。

自测题

  1. 一个张量形状是 (8, 64, 256),放在 NLP 语境里,这三个数最可能各代表什么?

    提示 / 答案`8` = batch 大小 `B`(一批 8 个序列);`64` = 序列长度 `T`(每序列 64 个 token);`256` = 特征维度 `C`(每个 token 用 256 维向量表示)。即 `(B, T, C)`。
  2. 两个矩阵形状分别是 (4, 32)(32, 10),能相乘吗?结果形状是多少?如果第二个是 (10, 32) 呢?

    提示 / 答案`(4, 32) @ (32, 10)` 可以乘(中间的 32 对齐),结果 `(4, 10)`。换成 `(10, 32)` 就不能乘了,因为第一个的列数 32 ≠ 第二个的行数 10。
  3. 梯度指向”上升最快”的方向,那我们想让损失变小,该往哪个方向更新参数?为什么更新公式里有个减号?

    提示 / 答案往**梯度的反方向**走(下降最快)。更新公式 $w \leftarrow w - \eta \nabla L$ 里的减号,正是"减去梯度=朝反方向走"的体现。
  4. 语言模型的损失里为什么常用”负对数概率”而不直接用概率?说出两个理由。

    提示 / 答案(a) 对数把"许多概率连乘"变成"连加",避免极小数下溢、数值更稳定;(b) 加负号后,模型给正确答案的概率越高,损失 $-\log p$ 越小(趋近 0),概率越低损失越大——正好是个"越小越好"的打分尺。
  5. 沿用正文例子:真实规律 y=2xy=2x,单点 (1,2)(1,2),当前 w=0.4,b=0.4w=0.4, b=0.4,学习率 0.10.1。手算这一步之后的 wwbb

    提示 / 答案预测 $\hat y = 0.4\cdot1+0.4 = 0.8$,误差 $e = 0.8-2 = -1.2$。$\frac{\partial L}{\partial w} = 2ex = -2.4$,$\frac{\partial L}{\partial b} = 2e = -2.4$。更新:$w \leftarrow 0.4 - 0.1\cdot(-2.4) = 0.64$,$b \leftarrow 0.64$。预测变为 $1.28$,更接近 2。

深入参考

  • 训练目标(最大似然 → 负对数似然 → 交叉熵 → 困惑度)的完整推导,见本教程 第 07 章
  • 优化方法(梯度下降 → Adam → AdamW、学习率调度)的展开,见 第 08 章
  • 工程速查参考:../foundations/ 下的基础文档与 ../07_objectives_zh.md../08_optimization_zh.md(随课程推进逐步对照)。

地基打好了。下一章我们把这些数学直觉落到真实代码上——用 PyTorch 创建张量、让 autograd 自动算出你刚才手推的那些梯度,并亲手写一个能跑的最小训练循环。

下一章 👉 第 03 章:PyTorch 极简入门


Edit page