news 2026/10/19 14:42:08

深度学习基础重修:从手算前向传播、梯度下降到反向传播

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深度学习基础重修:从手算前向传播、梯度下降到反向传播

1. 为什么我要“重修”深度学习基础

先交代一下背景。我算是半路出家做AI应用的,之前用现成框架跑过不少模型,分类、生成、微调都摸过,但说实话,对模型内部发生的事情一直停留在“调参侠”阶段。前向传播、梯度下降、反向传播这几个词,每个都认识,合在一起就懵。上次硬着头皮啃教材,公式抄了一整页,合上书就忘,典型的“背会了没学会”。

这次决定换个思路重修,不抄公式、不背定义,先把每个概念用大白话还原本质,再用手算和代码把过程走一遍。标题里的“日记”不是噱头,我是真的在用一个新手的心态从头推理,遇到卡壳的地方就记下来,琢磨明白再往下走。

如果你也是那种会用框架但说不清梯度从哪来、反向传播为什么要“反向”的人,这篇笔记就是写给你看的。我不保证把数学讲得多优雅,但保证每句话你都能读得下去,读完之后能自己动手复现一遍最基础的过程。

2. 前向传播:让数据走一遍流水线

2.1 先从一张最简单的网络图说起

我这次没有再去看那种画了几十层的大网络拓扑图,而是自己动手画了一个极简的网络:输入层两个神经元,隐藏层两个神经元,输出层一个神经元。就这,五条连线。

为什么先从这么小的开始?因为前向传播这件事本质特别朴素:把输入数据从第一层往最后一层“推”一遍,每一层都做两件事——加权求和、过激活函数。仅此而已。之前学不懂的原因就是一上来就面对几千个参数的大网络,注意力全被规模绕晕了,反而看不穿这个基本操作。

拿真实世界做个类比:一家工厂生产一个产品,原材料进第一道工序,加工完传给第二道,再加工,最终出成品。每一道工序有自己的“配方”(权重)和“验收标准”(激活函数)。前向传播就是完整地跑一遍流水线,看看成品到底是啥。模型在推理时(比如预测一个结果)做的本质就是这个:输入进去,流一遍,得到输出。

2.2 手算一个前向传播,把数字摁明白

我这次特意找了最简单的例子手算,确保每一步数字都对得上。假设:

  • 输入:x1 = 0.5,x2 = 0.1(可以理解成某个样本的两个特征)
  • 权重全部取小随机数:w11 = 0.3, w12 = 0.6, b1 = 0.1;w21 = 0.2, w22 = 0.4, b2 = 0.2
  • 输出层权重:v1 = 0.5, v2 = 0.8, b3 = 0.3
  • 隐藏层激活函数用Sigmoid,输出层不用激活(这是回归任务的做法,输出直接是数值)

隐藏层第一个神经元的输入:z1 = 0.5 x 0.3 + 0.1 x 0.6 + 0.1 = 0.31

过Sigmoid:a1 = sigmoid(0.31) = 0.5768

隐藏层第二个神经元:z2 = 0.5 x 0.2 + 0.1 x 0.4 + 0.2 = 0.34

过Sigmoid:a2 = sigmoid(0.34) = 0.5842

输出层神经元(过加权求和,不过激活):pred = 0.5768 x 0.5 + 0.5842 x 0.8 + 0.3 = 0.2884 + 0.4674 + 0.3 = 1.0558

如果真实标签是1.0,那这个模型前向传播下来的误差就是0.0558,看起来不大,但这只是恰好选了运气好的随机数。再多算几个样本就知道,权重初始化和数据分布碰在一起,预测值经常离目标十万八千里,这时候就需要后面两个东西登场:梯度下降和反向传播。

手算的意义不在于算得快,而在于建立“数据流过网络”的体感。我在草稿纸上画了好几次这张图,每次算完都拿Python代码对一遍,确认没有算错,这种双重确认给了我很大的底气。

2.3 为什么要有激活函数

如果网络里只做加权求和,多层叠加后本质还是一个线性变换,换句话说,堆多少层都等价于一层,模型的表达能力约等于零。激活函数的作用是给网络引入非线性,让它能拟合曲线、分类边界这些不是直线的模式。

Sigmoid把一切实数压缩到0和1之间,好处是输出范围稳定,坏处是饱和区梯度接近0,反向传播时容易造成梯度消失,这也是后来ReLU这类函数流行的原因。ReLU简单粗暴,负数直接压成0,正数保持不变,计算快,梯度在正半轴恒为1,大大缓解了梯度消失问题。

选型建议就一条:隐藏层优先ReLU及其变体,输出层看你任务——回归任务用线性,二分类用Sigmoid,多分类用Softmax。这不是玄学,每个选择背后都是数学性质和任务目标的匹配。

3. 梯度下降:下山的路怎么走

3.1 损失函数就像一张地形图

前向传播跑完,模型给出了预测值,接着要拿预测值和真实值对比,算出一个“错得有多离谱”的分数,这个分数就是损失。常用的均方误差公式是:

L = (1/2)(pred - y)²

为什么要加那个1/2?主要是为了求导的时候把平方项的2抵消掉,算是约定俗成的习惯,省得每次导数都要带个系数。虽然不影响最优解的位置,但是能让公式干净不少。

把网络的全部参数(权重和偏置)当成自变量,损失作为因变量,就会在高维空间形成一个曲面。最优解就是曲面上最低的那个点。梯度下降做的就是:站在曲面的某个位置,看看哪个方向是向下最陡的方向,沿着那个方向迈一小步,然后重复这个过程,直到走到一个低处。

生活化理解就是摸黑下山:你看不见整个山的全貌,只能感受脚底下的坡度,每次挑最陡的坡往下走。运气好能走到谷底,运气不好可能被困在半山腰的小洼地(局部最优),也可能在山谷两边来回震荡(振荡)。

3.2 梯度到底是个啥,怎么求

梯度这个概念以前总觉得玄,后来我用“做菜放盐”来类比就通了:菜咸了,说明盐放多了,要么减少盐,要么增加水,你总要知道往哪个方向调整才能让味道往“正好”那边走。梯度的每一维就是“这个参数对损失的影响方向和大小”。

数学上,梯度就是损失函数对每个参数的偏导数组成的向量。比如有两个参数w1、w2,就要算 ∂L/∂w1 和 ∂L/∂w2。参数更新的标准写法是:

w_new = w_old - 学习率 x 梯度

注意这里的减号:如果梯度是正的,说明增大这个参数会让损失变大,所以要往相反方向减;如果梯度是负的,就反过来加。这一减一加,参数就在朝损失下降的方向移动。

3.3 学习率:步子迈多大才合适

学习率是梯度下降里最需要手调的参数,没有之一。我复现时试过三组学习率,效果截然不同:

  • 学习率=0.01:慢慢悠悠地下降,几十轮过去才看到明显效果,训练时间长
  • 学习率=1.0:第一步直接跳飞,损失爆炸到几百,完全废了
  • 学习率=0.1:前期冲得快,后期在最优值附近来回震荡,但整体能收敛

这让我彻底明白:学习率太小,训练慢到没法用;学习率太大,参数直接跨过最优解,损失不减反增。实际工程里一般会用学习率调度(先用大的快速下降,再逐渐调小精细收敛),或者用带动量的优化器(Adam就是带自适应学习率的经典选择)。

4. 反向传播:误差倒着传回去

4.1 链式法则就是把“快递”一层一层拆开

前向传播是数据从输入流向输出,反向传播则是误差从输出流向输入,沿途计算每个参数对损失的梯度。为什么非得“反着来”?因为网络是一层套一层的复合函数,想求第1层的权重对最终损失的贡献,必须借助链式法则一层层往回拆。

链式法则本质不复杂:复合函数 f(g(x)) 对x求导,等于 f'(g(x)) 乘以 g'(x)。拆快递一样,先拆外层(输出层的导数),再拆里层(隐藏层的导数),每个包裹里面还套着包裹。反向传播做的就是:把误差(最外层的结果)一层层往内传导,每经过一层、每个参数就顺便拿一份“梯度快递”。

4.2 接着前面的数字,完整手算一遍反向传播

继续用刚才那个网络的参数和预测结果。真实标签y=1.0,预测pred=1.0558。

第一步:算输出层的误差贡献。损失 L = 0.5 x (pred - y)² = 0.5 x 0.0558² ≈ 0.00156。

∂L/∂pred = pred - y = 0.0558。

这就是输出神经元收到的“误差信号”。接下来它要分别对v1、v2、b3求导:

∂L/∂v1 = (pred - y) x a1 = 0.0558 x 0.5768 ≈ 0.0322

∂L/∂v2 = 0.0558 x 0.5842 ≈ 0.0326

∂L/∂b3 = 0.0558

第二步:误差传给隐藏层。隐藏层第一个神经元a1对输出有贡献,所以它要承接一部分输出层误差:

∂L/∂a1 = (pred - y) x v1 = 0.0558 x 0.5 = 0.0279

再往下走一层,要考虑激活函数Sigmoid的导数。Sigmoid导数有个好记的公式:sigmoid(z) x (1 - sigmoid(z))。

a1 = 0.5768,所以 sigmoid'(z1) = 0.5768 x (1 - 0.5768) = 0.2441。

∂L/∂z1 = ∂L/∂a1 x sigmoid'(z1) = 0.0279 x 0.2441 ≈ 0.00681

然后才能求对w11的梯度:

∂L/∂w11 = ∂L/∂z1 x x1 = 0.00681 x 0.5 ≈ 0.00341

到这里,w11这个参数就有了更新方向:w11_new = 0.3 - 学习率 x 0.00341。同理可以算w12、w21、w22,以及两个偏置b1、b2。

整个过程走完一遍,我最大的感悟是:反向传播没有高深莫测,就是“链式法则的工程化应用”。你不需要在脑子里同时处理所有公式,只需要从输出往输入一步步推,每一步都是简单的一元复合函数求导。

4.3 计算图与自动微分

手算能帮忙理解原理,但真实框架里你肯定不会手动算梯度——框架用的是“反向模式的自动微分”,它和你刚才手算的过程在逻辑上完全一致:先走一遍前向传播,把每层的中间值都存下来,再反向按链式法则把梯度算出来。

这就像你记了每道工序的半成品,后面发现问题能直接溯源到是哪道工序出的问题。PyTorch里的backward()做的就是这件事,TensorFlow里的GradientTape同理。理解了这个底层逻辑,你就不会把一个框架当黑魔法,它只是替你背了一遍链式法则。

5. 从头到脚复现一遍:我的实操记录

5.1 用NumPy写一个小MLP

我不建议一辈子只用框架,新手最值得做的一件事:用Numpy写一个两层的小网络,把上面那个手算过程自动化,然后拿它训练一个能用的模型。代码不长,但是每一步都看得见。

import numpy as np def sigmoid(x): return 1 / (1 + np.exp(-x)) def sigmoid_deriv(a): return a * (1 - a) # 造一点线性可分的数据 np.random.seed(42) X = np.array([[0,0],[0,1],[1,0],[1,1]], dtype=float) y = np.array([0, 1, 1, 0], dtype=float) # 异或问题 # 初始化 n_in, n_hidden, n_out = 2, 4, 1 W1 = np.random.randn(n_in, n_hidden) * 0.5 b1 = np.zeros((1, n_hidden)) W2 = np.random.randn(n_hidden, n_out) * 0.5 b2 = np.zeros((1, n_out)) lr = 0.5 for epoch in range(10000): # 前向传播 z1 = X @ W1 + b1 a1 = sigmoid(z1) z2 = a1 @ W2 + b2 pred = sigmoid(z2) # 二分类用sigmoid输出 # 损失 loss = np.mean((pred - y.reshape(-1, 1)) ** 2) # 反向传播 d_pred = (pred - y.reshape(-1, 1)) / len(X) d_z2 = d_pred * sigmoid_deriv(pred) d_W2 = a1.T @ d_z2 d_b2 = np.sum(d_z2, axis=0, keepdims=True) d_a1 = d_z2 @ W2.T d_z1 = d_a1 * sigmoid_deriv(a1) d_W1 = X.T @ d_z1 d_b1 = np.sum(d_z1, axis=0, keepdims=True) # 更新 W2 -= lr * d_W2 b2 -= lr * d_b2 W1 -= lr * d_W1 b1 -= lr * d_b1 if epoch % 1000 == 0: print(f"epoch {epoch}, loss {loss:.6f}")

跑出来的损失会一路往下掉,说明前向传播和反向传播配合着奏效了。如果你看到loss不减反增或者卡在某个值不动,基本就是学习率或者初始化的问题,后面一节我详细说。

5.2 我踩过的坑:损失不降的三个排查方向

学这个的时候我踩过不少坑,列几个最有代表性的:

第一个坑是激活函数用错。比如输出层用了Sigmoid,但任务其实是回归,导致损失永远降不到理想范围。这属于“选型错误”,不是优化问题。我后来每条经验都是:先确认输出层的激活函数和损失函数跟任务匹配,再谈调参。

第二个坑是学习率设置不当。我有一次把学习率设成1.0直接爆炸,loss一秒变nan。排查时用了一个笨办法:每几轮打印一次权重和梯度的均值,看到梯度的数量级是几百,立刻意识到是学习率太大。后来我给自己定了个习惯:新数据集先拿0.01跑一轮,看看loss初始值大概什么水平,再根据下降速度调节。

第三个坑是忘记做数据归一化。输入特征的量级差距太大的时候,大数值特征会直接主导梯度,小数值特征学不动。归一化之后,训练速度快了很多,稳定性也好了,就像做菜时所有食材都切成差不多大小的丁,好入味。

5.3 梯度消失:为什么深层网络难训练

前面提过Sigmoid的饱和区梯度会趋于0。如果网络层数多了,反向传播时梯度每一层都乘一个小数,从输出传到输入,梯度会指数级缩小,前面的层几乎收不到有效的更新信号。这就是梯度消失。

我在复现三层网络时试过,用Sigmoid当隐藏层激活,第三层(最靠近输入的层)的权重基本不动,一眼看去训练好像成功了,其实只是后面两层在硬扛。换成ReLU后这种情况明显改善。ReLU在正数部分导数恒为1,反向传播时梯度穿过它不会被缩小,前面的层也能正常学习了。

另外还有一个常见问题是局部最优解和鞍点。高维损失空间里鞍点比局部最优更常见,在鞍点附近梯度接近0,训练看起来很“平”,但其实不是到了好地方。带动量的优化器(Momentum)能帮助冲过这种平坦区域,这就是为什么工程上大家普遍用Adam,它的自适应学习率和动量机制就是为了应对这些地形问题。

6. 重修之后,我的几个新认识

6.1 三个概念其实是一件事的三个视角

这一轮学完,我最大的认知提升是:前向传播、梯度下降、反向传播不是三个独立的知识点。前向传播定义了模型在某个参数下的输出;梯度下降定义了如何通过“参数-损失”的地形往低处走;反向传播则是一个高效算出“该往哪个方向走”的工具。它们环环相扣,少了任何一个都没法完成训练。

之前总觉得训练模型是在“调参数”,现在我更愿意说:训练是在“用误差反推参数的世界观”。你允许误差从输出往输入传,让每个参数知道自己应该朝哪个方向调整,这个机制就是深度学习的发动机。

6.2 知其所以然后,调参才叫调参

以前跟着网上教程折腾模型时,遇到loss曲线不收敛,我只能凭感觉随机改动学习率、隐藏层宽度等配置,经常越调越乱。现在起码有了排查的路数:先确认前向传播的数值输出是否合理,再看损失下降的速率是否正常,最后根据梯度的数量级判断学习率的方向。

这套排查顺序说白了就是“由上到下、由前到后”的工程化思维,跟手算反传时“从输出往输入推”的思路如出一辙。理解了原理之后,你获得的不是公式,而是一套处理模型问题的直觉。

6.3 一些实用的学习建议

如果你也在学这块,我给你几个实操性建议:

第一,一定自己手算一次全流程,数字不必复杂,但每一步的过程要完整,用纸笔或用表格都行。我当初在表格里一行行写下每个中间变量的数值,前后核对了好几遍,才真正把链路打通。

第二,手算完一定要拿Numpy复现一遍,重点不是训练多好的模型,而是对照手算验证梯度公式正确。如果手算和代码跑出来的梯度相差几个量级,多半是链式法则哪一步漏了一个乘项。

第三,遇到loss不降的问题时,不要上来就调参。先打印一下中间层输出的数值范围和梯度范围,建立“看到什么数值、说明哪里有问题”的经验库,比瞎猜高效得多。

这批重修日记写到这,最基础的一章算告一段落了。下一块我想接着把损失函数的可选择范围、不同优化器的设计动机,以及一个小型图像分类任务从零到一的完整实现串一遍,到时候有新的踩坑体验再拿出来接着跟大家分享。前向传播、梯度下降和反向传播这三板斧虽然入门,但最终所有花哨的模型和技巧,底子都在这里。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 14:42:00

用OpenClaw搭建多智能体AI团队:从需求到代码的全自动流水线

先说结论:这套东西跑通之后,我最大的感受是“需求到代码的中间环节终于不用人肉盯着转发了”。过去一个需求从口头描述到最终实现,产品、架构、开发三个角色之间至少来回拉扯三五轮,信息在传递过程中不断衰减——产品经理脑子里的…

作者头像 李华
网站建设 2026/10/19 14:40:05

MySQL字符集:为什么必须用utf8mb4?乱码与emoji报错解析

1. 乱码与报错背后:两个“utf8”的真相先讲一个我实际踩过的坑。某次上线一个带用户昵称功能的模块,开发环境一切正常,上了生产环境没几天,运营那边就反馈说有人注册时直接报错,错误信息大致是Incorrect string value:…

作者头像 李华
网站建设 2026/10/19 14:39:32

邮件安全合规性评估系统:可解释AI驱动的企业级邮件治理

1. 项目概述:这不是一个“发邮件查病毒”的简单工具,而是一套面向企业级邮件治理的决策支持系统 “27大数据毕设源码 基于机器学习与多维特征融合的邮件安全合规性评估与可视化 基于关联规则挖掘的邮件安全协议共现关系研究”——这个标题初看冗长&#…

作者头像 李华
网站建设 2026/10/19 14:37:17

Java SQL注入审计实战:从MyBatis动态SQL到二次注入防御

别急着找漏洞,先搞清楚Java里SQL注入是怎么发生的做了几年的代码审计,接触过不少Java项目,发现很多人一上来就拿着工具扫,扫出一堆"疑似注入点",结果复核的时候一半都是误报。真正的问题反而被漏掉。Java代码…

作者头像 李华
网站建设 2026/10/19 14:36:38

移动端自动化测试的角色级模型拆分方法

1. 为什么“角色级模型拆分”是移动端自动化测试的破局点最近在帮某高校实验室做一套跨平台教育类App的回归测试体系升级,遇到一个典型困境:过去用传统UI自动化框架跑完一轮全量用例要47分钟,其中72%的时间花在等待页面加载、处理弹窗、适配不…

作者头像 李华
网站建设 2026/10/19 14:35:36

ESMFold显存实测:RTX 5090上被漏算的8GB到底藏在哪里?

2. 前言:从一次"显存焦虑"说起 做蛋白质结构预测的朋友,最近应该都听说了ESMFold。这个模型不需要多序列比对(MSA),直接靠单个序列就能出结构,速度比AlphaFold2快了几个量级,简直是高…

作者头像 李华