用纯数字理解大模型训练:前向传播、损失计算与反向传播 1. 为什么“纯数字”才是理解大模型训练的最佳入口很多人第一次接触大语言模型训练脑子里冒出来的全是“Transformer”“注意力机制”“梯度下降”这些术语结果看了三天文档连模型到底在干什么都说不清楚。我自己刚开始也是这样后来发现一个特别有效的办法把整个训练过程翻译成纯数字的运算用最朴素的加减乘除去理解反而一下子就通了。这篇文章是“从零训练一个大语言模型”连载的第二篇第一篇我们聊了整体流程和准备工作这一篇专门解决一个问题大语言模型训练到底在算什么我会用纯数字的方式把前向传播、损失计算、反向传播、参数更新这四个核心环节拆开讲不涉及任何复杂公式推导只要你会加减乘除和一点点指数运算就能看懂。适合谁来读如果你是对大模型感兴趣但数学基础一般的人或者你已经看过一些教程但总觉得隔着一层纱那这篇就是写给你的。我会用具体的数字例子一步一步算给你看让你真正理解模型内部发生了什么。读完这一篇你再去看那些专业论文会发现很多概念其实没那么吓人。先打个预防针真实的大模型训练当然比这里讲的复杂得多参数量动辄几十亿上百亿但核心原理和用几个数字演示的版本是完全一致的。就像你学开车教练车和F1赛车的操作逻辑本质上是一样的只是规模和精度不同。我们先把教练车开明白再去想F1的事。2. 用一组具体数字走完一次完整的前向传播2.1 把一句话变成数字词嵌入的直观理解假设我们有一个极简的模型词表里只有四个词“我”“爱”“学”“习”。我们要让模型学会“我爱学习”这句话。第一步是把词变成数字这个过程叫词嵌入。假设每个词用两个数字表示真实模型可能是几千维这里为了好算只用两维词向量表示我[1, 0]爱[0, 1]学[1, 1]习[0.5, 0.5]这些数字一开始是随机生成的模型训练的目的就是把这些数字调整到“合适”的值。什么叫合适就是让“我”和“爱”组合起来能预测出“学”让“爱”和“学”组合起来能预测出“习”。现在输入“我爱”模型要预测下一个词是“学”。我们把“我”的向量[1, 0]和“爱”的向量[0, 1]拿出来准备送进网络。2.2 注意力机制的纯数字版谁跟谁更相关注意力机制听起来很玄其实核心就一句话当前词应该关注前面哪些词。我们用数字来算一遍。假设现在要处理“学”这个词它需要回头看“我”和“爱”。怎么决定看谁更多用点积算相似度“学”和“我”的相似度1×1 1×0 1“学”和“爱”的相似度1×0 1×1 1两个都是1说明“学”觉得“我”和“爱”一样重要。但实际语言中“爱”对“学”的影响通常更大。这个“应该更大”的判断就是模型要通过训练学出来的。训练之前模型不知道谁更重要训练之后它会调整参数让“爱”的权重变大。假设经过softmax归一化一种把数字变成概率的方法我们得到“我”的权重是0.4“爱”的权重是0.6。那么“学”这个位置的输出就是0.4 × [1, 0] 0.6 × [0, 1] [0.4, 0.6]这个[0.4, 0.6]就是注意力层的输出它融合了前面两个词的信息而且“爱”的贡献更大。2.3 前馈网络把融合后的信息再做一次变换注意力层的输出[0.4, 0.6]会送进一个前馈网络。假设这个网络只有一层权重矩阵是W [[0.5, 0.2], [0.3, 0.8]]偏置是[0.1, 0.1]。计算过程第一维0.4×0.5 0.6×0.3 0.1 0.2 0.18 0.1 0.48第二维0.4×0.2 0.6×0.8 0.1 0.08 0.48 0.1 0.66输出是[0.48, 0.66]。然后经过一个激活函数比如ReLU就是把负数变成0正数不变这里两个都是正数所以不变。2.4 输出层从数字变回词的概率最后一步是把[0.48, 0.66]变成四个词的概率。假设输出层的权重矩阵是W_out [[0.2, 0.1], [0.3, 0.4], [0.5, 0.2], [0.1, 0.3]]计算每个词的得分“我”0.48×0.2 0.66×0.1 0.096 0.066 0.162“爱”0.48×0.3 0.66×0.4 0.144 0.264 0.408“学”0.48×0.5 0.66×0.2 0.24 0.132 0.372“习”0.48×0.1 0.66×0.3 0.048 0.198 0.246然后用softmax把这些得分变成概率。softmax的算法是先对每个数求e的指数再除以所有指数之和。e^0.162 ≈ 1.176e^0.408 ≈ 1.504e^0.372 ≈ 1.451e^0.246 ≈ 1.279总和 1.176 1.504 1.451 1.279 5.410概率“我”1.176 / 5.410 ≈ 0.217“爱”1.504 / 5.410 ≈ 0.278“学”1.451 / 5.410 ≈ 0.268“习”1.279 / 5.410 ≈ 0.236模型预测下一个词是“爱”的概率最高0.278但正确答案是“学”。这说明模型现在预测错了需要调整参数。怎么调这就是下一节要讲的损失计算和反向传播。注意这里的所有数字都是随机设定的真实训练中这些数字会通过不断迭代自动调整。你不需要记住具体数值而是要理解这个“输入→变换→输出→比较→调整”的循环。3. 损失函数用一个数字衡量模型有多“错”3.1 交叉熵损失把错误程度量化模型预测“学”的概率是0.268但正确答案就是“学”。我们希望这个概率越接近1越好。交叉熵损失就是用来衡量这个差距的公式很简单损失 -log(正确答案的概率)这里正确答案是“学”概率是0.268所以损失 -log(0.268) ≈ 1.317这个1.317就是当前样本的损失值。损失越大说明模型错得越离谱。如果模型预测“学”的概率是0.9损失就是-log(0.9)≈0.105小很多。如果概率是0.99损失≈0.01几乎为零。所以训练的目标就是让这个损失值越来越小。怎么让它变小调整模型里的所有参数就是前面那些权重矩阵和词向量。调整的方法叫梯度下降。3.2 为什么用-log而不是直接用1减去概率你可能会想直接用1减去正确答案的概率不就行了吗比如1-0.2680.732也能衡量错误程度啊。为什么非要用log原因有两个。第一log函数在概率接近0时会给一个非常大的惩罚。比如概率是0.01-log(0.01)≈4.6而1-0.010.99差别不大。但概率是0.001时-log(0.001)≈6.9而1-0.0010.999几乎没变。log能放大“严重错误”的惩罚让模型更快地纠正大错。第二log函数和softmax搭配使用时求导特别简洁计算效率高。这在参数量巨大的大模型里非常重要。所以交叉熵损失是行业标准做法不是随便选的。3.3 一个批次的总损失把多个样本平均起来实际训练不会一次只处理一个样本而是一次处理一批batch。假设一个批次有32个样本每个样本都有一个损失值我们把它们平均一下就得到这个批次的平均损失。比如32个样本的损失分别是1.317、0.892、1.045……加起来除以32得到平均损失1.102。这个1.102就是当前批次的整体表现。训练过程中我们会看到这个数字逐渐下降从1.1降到0.8再降到0.5最后可能降到0.1以下。损失曲线是判断训练是否正常的最重要指标。实操心得如果你看到损失突然变成NaN不是数字通常是学习率太大导致数值爆炸。解决办法是把学习率调小比如从0.001降到0.0001。如果损失一直不下降可能是数据有问题或者模型结构设计不合理。4. 反向传播从错误反推每个参数该怎么调4.1 梯度是什么用生活例子理解反向传播的核心是计算梯度。梯度这个词听起来很数学其实就是一个意思如果我把某个参数稍微调大一点点损失会变大还是变小变大多少举个例子。假设你在山上想走到山谷最低点。你环顾四周发现往东走一步高度下降0.5米往西走一步高度上升0.3米。那么“往东”就是你应该走的方向0.5就是那个方向的梯度下降的梯度。反向传播就是在算每个参数的“方向”和“大小”。具体到我们的例子损失是1.317。我们想知道如果把输出层权重W_out里第一个数字0.2调大一点损失会变成多少这个“变化率”就是梯度。如果梯度是正的说明调大0.2会让损失增加那我们就应该调小它。如果梯度是负的说明调大0.2会让损失减小那我们就应该调大它。4.2 链式法则一层一层往回算反向传播的名字就来自于这个过程从损失出发一层一层往回传算出每个参数的梯度。先算输出层的梯度。损失对“学”的得分0.372的梯度是预测概率0.268减去真实概率1等于-0.732。这个-0.732的意思是如果“学”的得分增加1损失会减少0.732。所以我们应该增加“学”的得分。然后算“学”的得分对输出层权重的梯度。得分 0.48×0.5 0.66×0.2所以对0.5的梯度是0.48对0.2的梯度是0.66。结合前面的-0.732最终对0.5的梯度是-0.732×0.48≈-0.351对0.2的梯度是-0.732×0.66≈-0.483。这两个梯度都是负数说明增加这两个权重会减少损失。所以下一步更新参数时我们会把它们稍微调大一点。同样的过程会一直往回传传到前馈网络、注意力层、词嵌入层算出每一个参数的梯度。这就是反向传播的完整链路。4.3 梯度消失和梯度爆炸为什么深层网络难训练当网络层数很多时梯度在往回传的过程中会不断乘以权重矩阵里的数字。如果这些数字都小于1比如0.5乘上十次就变成0.5^10≈0.001梯度几乎消失了。参数几乎不更新模型学不动。这就是梯度消失。反过来如果数字都大于1比如1.5乘上十次就变成1.5^10≈57.7梯度变得巨大。参数更新时一下子跳太远损失反而爆炸。这就是梯度爆炸。大模型通常有几十层甚至上百层所以这个问题特别突出。解决办法包括使用残差连接让梯度可以走“捷径”、层归一化把每层的输出标准化、使用ReLU等不容易饱和的激活函数。这些技术细节我们会在后续连载里展开这里你只需要知道反向传播不是简单地把梯度乘回去而是需要很多工程技巧来保持数值稳定。注意如果你自己动手训练一个小模型发现损失降到一定程度就不动了很可能是梯度消失。可以试试减小网络深度或者加入残差连接。5. 参数更新让模型一点点变聪明5.1 学习率每次调多少才合适算出梯度之后下一步就是更新参数。更新公式很简单新参数 旧参数 - 学习率 × 梯度学习率是一个很小的数字比如0.001。它决定了每次调整的幅度。如果学习率太大参数会跳来跳去损失震荡不下降如果太小训练速度会慢得让人抓狂。拿前面的例子来说输出层权重0.5的梯度是-0.351学习率是0.001那么新值就是0.5 - 0.001 × (-0.351) 0.5 0.000351 0.500351你看只增加了0.000351非常微小。但经过成千上万次迭代这些微小的调整累积起来就能让模型从随机猜测变成准确预测。5.2 批量大小和学习率的关系批量大小batch size是指一次更新参数时用了多少个样本。批量越大梯度越稳定因为平均了更多样本的噪声但计算越慢。批量越小梯度噪声越大但更新越频繁。经验法则是批量大小翻倍学习率也可以适当调大。比如批量从32增加到64学习率可以从0.001调到0.002左右。但这不是绝对的需要根据实际训练情况微调。批量大小学习率建议特点160.0005噪声大适合小数据集320.001常用起点640.002训练稳定需要更多显存1280.003适合大规模训练5.3 优化器比朴素梯度下降更聪明的办法上面讲的“新参数 旧参数 - 学习率 × 梯度”叫随机梯度下降SGD。实际训练大模型时几乎没人直接用SGD而是用Adam或AdamW这类优化器。Adam的核心思想是为每个参数单独调整学习率。有些参数梯度一直很大就给它小一点的学习率有些参数梯度一直很小就给它大一点的学习率。这样每个参数都能以合适的步伐更新训练更快更稳。Adam还引入了“动量”的概念。就像下山时不仅看当前坡度还看之前的速度。如果一直往同一个方向走就加速如果方向变了就减速。这样能避免在局部最小值附近震荡。实操心得如果你刚开始训练一个小模型直接用Adam学习率设0.001通常都能跑起来。如果损失不下降试试0.0001。如果下降太慢试试0.003。Adam对学习率不那么敏感容错率高适合新手。6. 把整个循环串起来一次迭代的完整时间线6.1 从输入到更新的十二个步骤现在我们把前面所有内容串起来看看一次完整的训练迭代到底发生了什么从数据集中取一个批次的数据比如32个句子。把每个句子里的词转换成词向量查表。把词向量送进注意力层计算每个词应该关注哪些词。注意力层的输出送进前馈网络做非线性变换。前馈网络的输出送进输出层得到每个词的概率分布。用交叉熵损失计算预测和真实答案的差距。把32个样本的损失平均得到批次损失。从批次损失出发反向传播计算每个参数的梯度。用优化器如Adam根据梯度更新所有参数。清空梯度准备下一次迭代。重复步骤1到10直到损失降到满意水平。保存模型参数训练结束。这个过程看起来简单但实际训练中每一步都有很多细节。比如数据怎么分批、梯度怎么累积、学习率怎么衰减、什么时候保存检查点这些工程细节决定了训练能不能成功。6.2 一个数字的旅程跟踪“学”的概率变化让我们跟踪一个具体数字的变化。初始时模型预测“学”的概率是0.268。经过一次参数更新后假设概率变成了0.271。变化很小对吧但经过100次迭代可能变成0.35经过1000次可能变成0.62经过10000次可能变成0.89。这个从0.268到0.89的过程就是模型“学会”预测“学”的过程。训练的本质就是让正确的概率一点点上升让错误的概率一点点下降。没有魔法就是无数次微小的数字调整。你可以自己写一个简单的脚本记录每次迭代后“学”的概率然后画成曲线。你会看到一条波动上升的曲线中间可能有平台期也可能有短暂下降但总体趋势是向上的。这条曲线就是模型学习的轨迹。6.3 训练多久才算够早停和验证集训练不是越久越好。训练太久模型会“死记硬背”训练数据在新数据上表现反而变差这叫过拟合。判断过拟合的方法是把数据分成训练集和验证集训练集用来更新参数验证集用来评估效果。如果训练集损失还在下降但验证集损失开始上升说明模型开始过拟合了应该停止训练。这个策略叫早停。通常我们会每隔一段时间比如每1000次迭代在验证集上评估一次记录最好的模型参数。训练集损失验证集损失判断下降下降正常训练继续下降持平接近最优可以准备停止下降上升过拟合立即停止不下降不下降学习率太小或模型有问题注意验证集不能用来更新参数只能用来评估。如果用验证集调参数它就变成了训练集的一部分评估结果就不可信了。正确做法是再分出一个测试集只在最终评估时用一次。7. 常见困惑与实操中的坑7.1 为什么损失降不下去排查清单很多人第一次训练模型时会遇到损失不下降的情况。根据我的经验按以下顺序排查学习率太大损失震荡或变成NaN。调小10倍试试。学习率太小损失下降极慢。调大10倍试试。数据有问题标签错了或者输入输出没对齐。随机抽查几条数据看看。模型太小参数量不够学不会复杂模式。增加层数或隐藏维度。初始化太差所有参数初始化为0导致对称性无法打破。用随机初始化。激活函数不合适用了sigmoid导致梯度消失。换成ReLU或GELU。这个清单我打印出来贴在显示器旁边每次训练不顺利就对照着查能解决80%的问题。7.2 为什么训练损失很低但生成效果很差这是另一个常见困惑。训练损失很低说明模型在训练数据上预测得很准。但生成文本时你发现它说的全是训练集里的原话稍微换个开头就胡言乱语。这是典型的过拟合。解决办法增加数据量、增加Dropout随机丢弃一些神经元、减小模型规模、加入权重衰减让参数不要太大。其中增加数据量是最有效的但也是最贵的。如果数据有限Dropout和权重衰减是性价比最高的选择。7.3 数值精度float32和float16的取舍大模型训练通常用float16半精度来节省显存和加速计算。但float16的数值范围小容易溢出。所以实践中常用混合精度训练前向传播和反向传播用float16参数更新用float32。这样既能享受float16的速度又能保持float32的稳定性。PyTorch和TensorFlow都提供了自动混合精度的工具几行代码就能开启。如果你自己写训练循环记得在损失缩放loss scaling上花点心思防止梯度下溢。实操心得混合精度训练能让显存占用减少约40%训练速度提升20%到30%。但如果你发现损失不稳定先关掉混合精度用纯float32跑一遍确认模型本身没问题再开启混合精度调优。8. 从数字到直觉训练大模型的思维模型8.1 把模型想象成一个巨大的调音台经过上面的数字拆解你现在应该对训练过程有了具体的感知。我想再给你一个思维模型把模型想象成一个有几十亿个旋钮的调音台。每个旋钮就是一个参数。训练就是不断微调这些旋钮让输出的音乐预测结果越来越接近目标。反向传播告诉你每个旋钮应该往哪个方向拧、拧多少。学习率决定你每次拧的幅度。优化器决定你是匀速拧还是根据情况加速减速。损失函数就是你的耳朵告诉你当前音乐和目标差多远。这个比喻的好处是你不需要理解每个旋钮的具体作用只需要知道整个系统在协同工作。大模型之所以强大不是因为某个旋钮特别关键而是因为几十亿个旋钮一起微调能表达出极其复杂的模式。8.2 规模带来的质变为什么大模型“涌现”出能力你可能会问既然原理这么简单为什么小模型做不到大模型的事答案是规模带来质变。当参数量从百万级增加到百亿级模型能记住的模式从几千个增加到几十亿个。一些在小模型上完全学不会的任务比如多步推理、代码生成在大模型上突然就“会”了。这种现象叫涌现。它不是魔法而是量变引起质变。就像水分子在0度以下突然结冰不是单个水分子变了而是大量水分子集体行为的结果。大模型的涌现能力也是大量参数协同作用的结果。理解这一点很重要你不需要为每个任务设计特殊结构只需要把模型做大、数据做多、训练做久很多能力会自动出现。这也是为什么现在的大模型都走“大力出奇迹”的路线。8.3 下一步从理解原理到动手实践这一篇我们用纯数字走完了大模型训练的核心循环。你现在应该能回答这些问题前向传播在算什么损失函数为什么用交叉熵反向传播怎么算梯度参数怎么更新学习率和批量大小怎么配合下一篇连载我们会从数字回到代码用一个极简的Python实现把今天讲的每一步变成可运行的代码。你会看到几十行代码就能训练一个迷你语言模型虽然它只会说“我爱学习”这一句话但所有核心机制都在里面。如果你等不及可以自己先试试用NumPy实现一个两层的神经网络输入是[1,0]和[0,1]输出是四个词的概率用交叉熵损失和梯度下降训练它预测“学”。你会发现即使这么简单的模型也需要仔细调整学习率和初始化才能收敛。这个过程会让你对今天讲的数字有更深的体会。我在实际动手过程中最大的感受是看懂原理和跑通代码之间有一条鸿沟。原理告诉你“应该这样做”但代码会告诉你“实际上会遇到什么”。比如数值精度问题、梯度消失问题、内存不够问题这些只有亲手跑过才会真正理解。所以下一篇的代码实践建议你一定要跟着敲一遍不要只是复制粘贴。