☰
梯度下降与反向传播:彻底搞懂神经网络是如何学习的
2026/10/17 4:59:16 网站建设 项目流程

很多刚开始学神经网络的朋友,桌上往往堆着一大串名词:前向传播、反向传播、激活函数、过拟合、批量归一化……可真要问一句“神经网络到底是怎么学习的”,很多人会卡壳。我自己带过不少新人,也走过弯路,最终的体会是:只要把“梯度下降”这条线彻底理清楚,整个神经网络训练过程就能串起来一大半。

这篇文章不打算堆满公式和术语,而是沿着“从梯度下降到神经网络学习”这条主线,从学习的最底层逻辑讲起,一步步拆解梯度下降为什么是神经网络训练的引擎、反向传播是怎么把梯度送到每一层参数的、训练中常见的梯度问题怎么排查,最后给出一份我实际跑小型网络的完整记录和踩坑手册。无论你是刚入门的新手,还是有点基础但被训练细节困扰的人,都能拿来参考。

1. 神经网络里的“学习”,本质上是一次参数搜索

1.1 学习的对象:一组数量巨大的参数

把神经网络说得朴素一点,它其实就是一个带参数的数学函数。这个函数接收输入,比如一张图片、一段文本、一组数值,通过层层计算,最终输出一个结果。这个结果可能是“属于第几类”的预测分数,也可能是一个回归值。

这个函数内部的“可调节旋钮”就是参数,也就是我们常说的权重和偏置。一个稍微大一点的网络,参数动辄几十万甚至上亿。所谓“训练模型”,本质就是在这亿万个旋钮组成的高维空间里,找到一组组合,让它对已有数据输出得尽量准,同时对没见过的新数据也别太离谱。

如果用公式写出来,就是找一个函数 f(x; W),其中W是所有参数的集合。给定一批输入X和对应的真实答案Y,我们希望网络输出 f(X; W) 尽可能接近 Y。学得“好”还是“不好”,直接取决于W选得怎么样。

1.2 损失函数:给模型的答案打分

既然目标是要让输出接近真实答案,那总得有个量化标准来衡量“接近程度”。这就是损失函数干的事。

  • 回归任务里常用均方误差:把每个样本的预测值和真实值的差值求平方再求平均,差值越大损失越大。
  • 分类任务里常用交叉熵:把模型输出的概率分布和真实标签的分布做比较,真实标签对应位置的概率越低,损失越大。

损失函数是一个标量,数值越小,说明模型在训练数据上表现越好。于是,训练神经网络的问题就转化成了一个数学优化问题:找到一组参数W,让损失函数L(W)的取值尽可能小。

1.3 为什么不能“一步到位”把参数解出来

很多刚接触这块的人会问一个特别自然的问题:既然损失函数是个数学表达式,为什么不能直接求导等于零,把最优参数解出来?

在极少数简单情况下确实可以。比如线性回归,损失函数是参数的二次函数,可以直接用正规方程一次算出解析解。但神经网络不行,原因有几个:

一是网络结构非线性,激活函数引入了一个又一个非线性变换,损失函数在参数空间里不再是光滑的“碗”,而是沟壑纵横的险峻地形;二是参数数量太大,哪怕有解析解,面对几百万参数的矩阵求逆,计算量和存储量都完全不可行。

所以“一步到位”这条路被堵死了,只能靠数值迭代一步步逼近。这正是梯度下降登上舞台的地方。

2. 梯度下降:沿着最陡的方向一路向下

2.1 梯度到底是什么

想象你站在一片群山之中,雾气很浓,看不到远处,唯一知道的规则是:每一步都要往下走,目标是山谷最低点。你该怎么走?最简单的策略就是——先感受一下脚下哪个方向是向下的,而且坡度最陡,然后朝那个方向迈一步,不断重复。

这就是梯度下降的直观版本。”

那“感受哪个方向最陡”这件事,数学上靠的是梯度。梯度是一个向量,它的每个分量对应一个参数在该点的偏导数。偏导数的含义是:这个参数稍微改变一点点,损失函数会跟着变化多少。把所有偏导数拼成一个向量,这个向量指向的就是损失函数增最快的方向。

所以,想让损失函数下降,就要朝梯度的反方向移动。在多少损失曲面上走一小步,这一小步走完,再重新算梯度,再走下一步,反复迭代,就能逐步下降到一个比较低的点。

2.2 更新公式与学习率

梯度下降的更新公式非常简单,只有一个动作:

W ← W - η · ∇L(W)

W是当前的参数,∇L(W) 是损失函数对当前参数的各偏导组成的梯度,η就是学习率。

学习率决定了每一步迈多大。这个值直接决定训练成败,我见过太多新手一上来就栽在这上面:

  • 学习率太大,参数一步跨过头,在损失曲面的谷底两边来回震荡,甚至直接震荡到损失爆炸,训练过程变成一条冲向无穷大的曲线。
  • 学习率太小,相当于每天都在小心翼翼的踩碎步,虽然方向正确,但在一个平坦区域里要磨蹭很久,训练效率极度低下。

关于学习率的取值,我的经验是:先从一个稳妥的默认值开始,比如0.001,然后观察损失曲线的走势。要是损失在前几个迭代里纹丝不动或者下降极慢,可以试着调大;要是损失曲线狂野震荡,就调小。具体怎么判断,后面实操部分细说。

2.3 三种取梯度的方式:全量、随机、小批量

梯度下降本身是一个框架,不同实现方式取决于“每次更新参数时用多少样本计算梯度”。

方式每次更新要算多少数据特点常见使用场景
全量梯度下降全部训练样本梯度估计准确,但计算量大,大数据集跑不动小数据集、理论演示
随机梯度下降1个样本计算快,梯度噪声大,损失曲线波动明显,但有时能跳出局部困扰大数据集、在线学习
小批量梯度下降一个小批次折中方案,兼顾速度和稳定性,梯度带一定噪声反而有利于泛化几乎所有深度学习的默认选择

实际训练深度学习模型时,几乎没人再用全量梯度。目前最常用的是小批量方式,batch size 一般取32、64、128。batch size 也是要调的超参数之一:显存够用的情况下,调大一点,梯度更稳定;调小一点,梯度噪声更大,有时候反而能躲开某些陷阱。

3. 神经网络的梯度从哪来:反向传播

3.1 链式法则:梯度在层与层之间接力

现在我们知道了,训练神经网络就是在损失函数这个地形图上做梯度下降。但问题紧接着来了:损失函数是最外层的一堆复合计算,它和网络内部的每一个权重都有关系,这亿万个权重的梯度到底怎么算?

答案就是反向传播。

如果还记得一点微积分,你会发现神经网络的前向传播就是一层套一层的复合函数。比如有三层网络,那么最终输出可以写成:

out = layer3(layer2(layer1(x, W1), W2), W3)

损失函数就是out的某个函数。根据链式法则,如果我们想知道某个内部权重对损失的影响,可以从输出端反着一层一层往回算。比如要算损失对W1的偏导,就先算出损失对第3层输出的影响,再影响第2层,再影响第1层,一路乘积下来。

这个计算过程,和前向传播的方向正好相反,所以叫“反向传播”。

3.2 用一个小网络看透反向传播的本质

光说概念容易飘,我拿一个极简网络手动走一遍。假设网络结构是:输入层2个节点,隐藏层2个节点,输出层1个节点,激活函数用sigmoid,损失函数用均方误差。

前向传播是怎么算的,大家应该都清楚:输入从第一层传进去,每一层先做线性加权,再过激活函数,最后算损失。

反向传播的核心在于,把输出层的“误差”提炼成一个数值δ,然后沿着连接路径往回传。我用纯Python写一个微型实现,不依赖任何框架,把最核心的更新逻辑展示出来:

import math def sigmoid(z): return 1.0 / (1.0 + math.exp(-z)) # 网络结构:2 -> 2 -> 1 W1 = [[0.1, 0.2], [0.3, 0.4]] # 输入层 -> 隐藏层 b1 = [0.05, 0.06] W2 = [0.5, 0.6] # 隐藏层 -> 输出层 b2 = 0.7 x = [0.8, 0.9] y = 0.3 # 前向传播 h1 = sigmoid(W1[0][0]*x[0] + W1[0][1]*x[1] + b1[0]) h2 = sigmoid(W1[1][0]*x[0] + W1[1][1]*x[1] + b1[1]) out = sigmoid(W2[0]*h1 + W2[1]*h2 + b2) loss = 0.5 * (out - y) ** 2 # 反向传播 # 输出层误差项 delta_out = (out - y) * out * (1 - out) # 隐藏层到输出层的梯度 dW2_0 = delta_out * h1 dW2_1 = delta_out * h2 db2 = delta_out # 隐藏层误差项(核心:误差从输出端传回隐藏层) delta_h1 = delta_out * W2[0] * h1 * (1 - h1) delta_h2 = delta_out * W2[1] * h2 * (1 - h2) # 输入层到隐藏层的梯度 dW1_00 = delta_h1 * x[0] dW1_01 = delta_h1 * x[1] dW1_10 = delta_h2 * x[0] dW1_11 = delta_h2 * x[1]

注意观察代里最核心的两行:

delta_h1 = delta_out * W2[0] * h1 * (1 - h1)

这个式子里的 W2[0] 和 h1*(1-h1),前者是“输出层到隐藏层1这条路径的通道系数”,后者是“隐藏层1节点自身激活函数的导数”。也就是说,输出层的误差信号通过W2这个权重传递回隐藏层,再被sigmoid的导数“调节”一下,就变成了隐藏层1误差的“源头”。有了这一层误差,计算W1的梯度就水到渠成了。

3.3 自动微分与深度学习框架

手工手算这么一个小网络已经够繁琐,真实网络动辄几十上百层,根本不可能手推。深度学习框架真正帮你做的事情,就是自动微分:你只管把网络结构定义出来,把前向传播写好,框架会在后向阶段利用链式法则自动算出每个参数的梯度。

但我不建议新人因此就不理解原理。为什么?因为训练中遇到的所有神秘问题——某个层没有学到东西、梯度变成NaN、损失不下降——最终都要回到“梯度的计算与传递”这个逻辑上去找原因。如果你连梯度从哪来都不知道,排查问题就只能靠瞎改超参数,效率极低。

4. 训练真网络时的四个梯度“绊脚石”

4.1 梯度消失与梯度爆炸

深层神经网络一多起来,第一个大坑就是梯度消失和梯度爆炸。

回顾我们刚才的微型网络,你会发现误差信号要想从输出层传到靠近输入层的参数,必须经过乘法链。假如网络有100层,每一层的梯度规模都略小于1,那么100个小于1的数相乘,最终梯度会小到让前面几层几乎什么都学不到。反过来,如果每层梯度都略大于1,那100个大于1的数相乘,梯度会爆炸成一个天文数字。

早期的sigmoid激活函数特别容易引出梯度消失问题,因为sigmoid的导数最大只有0.25,多层一乘就趋近于0。后来大家普遍换成ReLU、Leaky ReLU等激活函数,梯度路径短了很多,这个问题才大幅缓解。

此外,权重初始化非常关键。如果初始权重都很大,信号在每一层乘法里被放大,前向和反向都会失控;初始化太随机也不行。现在的通用做法是用He初始化或Xavier初始化,让网络一开始的每一层输出方差保持稳定。这个细节不要略过,很多时候模型训不起来,不是代码写错,而是初始化方式不对。

4.2 鞍点和局部极小值

既然是下山,很多人担心爬进一个“局部最低点”,出不来。但深度学习中真正难缠的其实不是局部极小值,而是鞍点。鞍点的意思是:这个点在某些方向上是山谷(往下),在另一些方向上是山脊(往上),整体看它不是最低点。

在高维参数空间里,鞍点远比局部极小值常见。梯度下降到鞍点附近时,各个方向上的梯度都接近零,参数更新变得非常缓慢,看起来就像“训练停滞”了。

对付这个问题的常用手段是给梯度下降加“惯性”,也就是动量。动量的思路很简单:每次更新的方向,不仅看当前梯度,还看历史上累积的移动方向。假设你的参数滚进了一个小坑或鞍点的平坦区域,当前梯度很小,但历史惯性会让它继续往前冲一段,有机会直接越过障碍。

4.3 学习率的衰减与热身

固定学习率从头训到尾,通常不是最优解。早期参数离最佳位置很远,大步快跑没问题;后期靠近谷底时,如果还迈着大步,会在最低点附近来回震荡,损失曲线底部微颤,模型精度很难再上去。

所以实践中几乎所有训练任务都会配一套学习率调度:

  • 阶段衰减:每跑若干轮,把学习率乘以一个衰减因子,比如0.1。这种简单粗暴的方式在很多任务上已经够用。
  • 余弦衰减:学习率随训练进度按余弦曲线从初始值平滑下降到接近零。近年在大规模任务里很常见。
  • 热身:最开始的几百步让学习率从很小的值线性升到设定值,避免模型一开始因为参数还没稳定就被大步子带飞。

我的建议是新手先把“阶段衰减”跑明白,再上余弦衰减,因为阶段衰减最容易观察和理解,遇到问题也好往回找原因。

4.4 优化器选型:SGD+Momentum 还是 Adam

最小深度学习中,还有一个绕不开的话题是优化器。以梯度下降为起点,发展出了一系列优化算法:SGD、SGD+Momentum、RMSprop、Adam等。

Adam从某种意义上像是“自适应的梯度下降”,它会为每个参数单独估算合适的更新步长。它用起来省心,默认参数在很多任务上都能有不错的表现,这也是为什么新手都爱先试Adam。

但我的实际经验是:Adam未必在什么任务上都赢。在不少中小数据集和某些需要精细控制的场景里,带上动量的SGD反而收敛更稳、泛化更好。Adam的一个缺点是它可能让模型拟合到过于“锐利”的极小值上,泛化能力不一定优于SGD。遇到任务不要死磕某一款优化器,可以都试一下。

下表是我常用的优化器配置起点:

优化器关键超参初始学习率建议
SGDmomentum=0.90.1 ~ 0.01(需配合学习率衰减)
SGD+Momentummomentum=0.90.01 ~ 0.001
Adamlr、beta1=0.9、beta2=0.9990.001(默认偏低较稳)
AdamW权重衰减分开设置0.001

Adam里那一个小的epsilon参数偶尔也值得调,出现梯度极小时,它对数值稳定性有影响,但大多数时候保持框架默认值即可。

5. 实操记录:从零训练一个小型分类网络

5.1 实验设定:用两层网络识别手写数字

为了让“从梯度下降到神经网络学习”这条线真正落进脑子里,我建议每个人都亲手跑一个最小的项目。下面是我常用做教学示范的一个任务:识别28×28像素尺寸的手写数字图片,总共10个类别。

网络不用太复杂,一个三层的多层感知机就够了:输入层784个神经元(因为28×28=784),隐藏层设128个神经元,激活函数用ReLU,输出层10个神经元,最后接softmax得到10个类别的概率。损失函数用交叉熵。

数据预处理这一步很关键:图片像素值原来是0-255的整数,我直接除以255归一化到0-1区间。没做这一步的话,输入特征方差过大,梯度更新容易失控;做了之后,训练稳定性和收敛速度都会明显改善。

代码层面,即使用框架也很简单,但为了不依赖具体工具,我用“伪代码+关键逻辑”来描述:

# 网络结构示意 hidden = relu(输入 @ W1 + b1) output = softmax(hidden @ W2 + b2) loss = cross_entropy(output, 真实标签) # 每轮训练循环 for each batch: 前向传播拿到 loss 反向传播拿到所有参数的梯度 优化器更新参数:W -= lr * 梯度

5.2 训练过程与损失曲线的观察

训练开始前,我先验证了一件看似不起眼但特别重要的事:初始损失是否合理。对于10类分类任务,如果网络刚初始化随机权重,各个类别的预测概率接近均匀分布,那么交叉熵损失的合理初始值应该在 log(10) ≈ 2.3 附近。

如果初始化后损失值不是这个量级,比如是4、5甚至更大,说明我的权重初始化或数据预处理有问题,得先改,再往下训。这个检查习惯帮我节省了大量定位问题的时间。

训练时我设置初始学习率为0.001,batch size取64。最开始几百步,损失从2.3左右快速下降,这是最容易让人兴奋的阶段。随着训练进行,损失下降幅度逐渐减小,这时候把它判别一下:到底是进入正常的收敛阶段,还是提前撞上了学习率过低或梯度退化?判断依据之一是,把损失曲线做平滑后,持续下降但斜率变小,基本就是正常收敛。

在跑了15轮左右,我把学习率降到0.0001,损失又往下走了一段。最终模型在测试集上的准确率从比例低到78%提升到了97%左右。看起来不算炫目,但作为理解梯度下降如何驱动网络训练的最小项目,这个结果足够说明问题。

5.3 三个典型问题排查实录

实操中踩的坑肯定不止一次,这里分享三个最常见的问题,每个我都亲测过。

问题一:训练中loss变成NaN

出现NaN通常有两种原因:一是学习率太大,参数更新幅度剧烈,前向传播算出的概率分布出现极端值,交叉熵里的log直接爆掉;二是输入数据里有缺失值、NaN值,梯度计算也连带着变成NaN。

我的排查顺序是:先打印一小批输入,确认数据没问题;再把学习率直接除以10,重新跑几步。如果是学习率问题,降低后loss会恢复正常;如果数据有问题,那就要检查数据加载和预处理的每个环节。

问题二:训练损失降得很好,但验证集表现差

这是非常典型的过拟合。网络把训练数据背了下来,却在没见过的测试数据上泛化能力差。典型表现是训练损失一直下,验证损失先降后涨。

应对方法:一是增加训练数据量或做数据增强,让模型看不到太多“重复答案”;二是加入正则化手段,比如dropout,随机丢弃一部分神经元,强制网络学冗余特征;三是L2权重衰减,也就是让参数不要膨胀得太夸张。我把dropout加在隐藏层之后,验证集准确率从92%提升到96%。

问题三:训练卡在某个准确率很久上不去

这种情况排除数据和超参数问题后,最容易被忽视的一个原因是初始化方式不对。我之前有次所有代码都是对的,但网络怎么训准确率都只能到45%,后来换成He初始化,准确率很快就突破了。激活函数和初始化得搭配着来:ReLU搭配He初始化,tanh或sigmoid搭配Xavier初始化。

下面是我总结的排查速查表,贴在这里给读者直接抄作业:

现象优先怀疑方向快速排查动作
loss变成NaN学习率过大 / 数据含NaN降低学习率,检查数据
训练损失下降但验证损失上升过拟合加入dropout、正则,检查训练数据量
损失不下降学习率过小 / 初始化不当 / 梯度消失调大学习率,检查初始化,观察中间层梯度
损失剧烈震荡学习率过大 / batch size过小降低学习率,增大batch size
准确率停滞低值初始化不合理 / 特征未归一化换He/Xavier初始化,检查输入数据分布

6. 从这条线延伸出去的学习路径建议

6.1 先手推一个小网络,再碰框架

我见过不少新人一上来就直接用深度学习框架搭模型,看着几行代码就跑通了MNIST,欢喜得不行。可一旦要换个新任务,换个新结构,瞬间就懵了。原因很简单:框架把梯度下降和反向传播全封装好了,表面上省事,但也屏蔽了最关键的学习信号。

我的建议是,入门的门槛不要跳过:先用纯Python或简单的数学库,手写一个两层网络的反向传播;再试着用框架复现同一个网络,对比一下梯度方向是否一致。这个过程最快只需要一两天,但带来的理解深度是“掉进封装里”的几周学习量都比不上的。

6.2 数学基础只求“够用”

很多人听说神经网络要数学,先被微积分吓跑了。其实按照“从梯度下降到神经网络学习”这条主线,你需要的数学核心并不多:导数、偏导、复合函数求导(链式法则)、一点点线代里的向量和矩阵乘法。就算不会向量微积分的各种复杂公式,只要清楚“梯度是个向量,每个分量是某个参数对损失的敏感性”,后面看大部分资料都不吃力。

6.3 每次只改一个变量

最后说点训练实验时的老生常谈:做实验要控制变量。我见过很多人同时改了学习率、网络层数、激活函数、优化器,结果模型表现变化了,却完全说不清楚是哪个改动起的作用。

正确做法是:跑一个基线实验,记录下loss曲线和准确率;之后每次只动一个因素,观察并记录下来。这个习惯会让你的经验积累速度快得多,因为每次实验都能给你一条清晰的因果反馈。

我个人在实际操作中的体会是:理解了梯度下降,神经网络的神秘感会消失一大半——它就是在一个超高的参数地形图上寸步寸金的挪动,而反向传播则是那盏照亮脚下步子的探照灯。如果这篇文章能让你把“梯度下降”和“神经网络学习”这两件事在脑子里连成一条完整的链路,那它最大的目的就达到了。接下来不必贪多,亲手把那个小网络跑起来,看看每条曲线是怎么变化的,你会真正拥有属于自己的训练直觉。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询