惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Cisco Blogs
Cyberwarzone
Cyberwarzone
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
SecWiki News
SecWiki News
Martin Fowler
Martin Fowler
T
Tor Project blog
N
Netflix TechBlog - Medium
C
Cybersecurity and Infrastructure Security Agency CISA
V
Vulnerabilities – Threatpost
V
Visual Studio Blog
GbyAI
GbyAI
PCI Perspectives
PCI Perspectives
D
DataBreaches.Net
Jina AI
Jina AI
H
Heimdal Security Blog
云风的 BLOG
云风的 BLOG
P
Privacy International News Feed
A
About on SuperTechFans
J
Java Code Geeks
美团技术团队
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
News | PayPal Newsroom
有赞技术团队
有赞技术团队
MyScale Blog
MyScale Blog
博客园 - 司徒正美
C
Check Point Blog
T
Threat Research - Cisco Blogs
Attack and Defense Labs
Attack and Defense Labs
宝玉的分享
宝玉的分享
AI
AI
Simon Willison's Weblog
Simon Willison's Weblog
C
Cyber Attacks, Cyber Crime and Cyber Security
I
Intezer
P
Proofpoint News Feed
Blog — PlanetScale
Blog — PlanetScale
Apple Machine Learning Research
Apple Machine Learning Research
Hugging Face - Blog
Hugging Face - Blog
The Last Watchdog
The Last Watchdog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Vercel News
Vercel News
I
InfoQ
阮一峰的网络日志
阮一峰的网络日志
Cisco Talos Blog
Cisco Talos Blog
W
WeLiveSecurity
Hacker News: Ask HN
Hacker News: Ask HN
Recent Commits to openclaw:main
Recent Commits to openclaw:main
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
D
Docker
博客园 - Franky
Security Archives - TechRepublic
Security Archives - TechRepublic

土法炼钢兴趣小组的算法知识备份

国密算法与国密 TLS 系列索引 【系统架构设计】架构质量属性:不只是"高可用高性能" 【系统架构设计百科】告警策略:如何避免"狼来了" 【系统架构设计】CQRS:读写分离的架构哲学 【系统架构设计】空间架构:极端扩展场景的解法 【系统架构设计】微服务架构深度审视:优势、代价与适用边界 【系统架构设计】扩展性原理:水平、垂直与对角扩展 【系统架构设计】无状态设计:扩展的第一步也是最难的一步 【系统架构设计】缓存架构:从本地到分布式的多级缓存体系 【系统架构设计】管道与过滤器:Unix 哲学的架构表达 【系统架构设计】复杂性管理:架构的核心战场 【系统架构设计】消息队列架构:异步解耦的设计与陷阱 【系统架构设计】CDN 架构:全球加速的设计原理 【系统架构设计】连接池设计:被忽视的性能杀手 【系统架构设计】弹性设计模式:熔断器、舱壁与超时 【系统架构设计】高可用设计模式:冗余、故障转移与仲裁 【系统架构设计】容量规划:从拍脑袋到数据驱动 【系统架构设计】数据库扩展:分库分表的工程实践与替代方案 【系统架构设计】SLO 工程:可靠性的量化管理 【系统架构设计】性能建模:用数学思维分析系统瓶颈 【系统架构设计】混沌工程:主动验证系统的韧性 【系统架构设计】零拷贝与内存映射:数据搬运的极致优化 【系统架构设计】线程模型:从 thread-per-request 到协程 【系统架构设计】容灾架构:多活与灾备设计 【系统架构设计】数据库性能模式:索引、查询与连接管理 【系统架构设计】数据建模:从关系范式到文档模型的真实权衡 【系统架构设计】吞吐量优化:批处理、流水线与并发模型 【系统架构设计】流处理架构:从批处理到实时的范式迁移 【系统架构设计】搜索引擎架构:倒排索引之上的系统设计 【系统架构设计】时序数据架构:监控与 IoT 的存储设计 【系统架构设计】数据迁移与版本化:在线不停机的数据演进 【系统架构设计】数据湖与数据仓库:分析架构的演进路线 【系统架构设计】API 网关设计:入口层的职责边界 【系统架构设计】应用层数据一致性模式:在正确性与性能之间走钢丝 【系统架构设计】多模数据库选型:Polyglot Persistence 的工程实践 【系统架构设计】服务发现与注册:动态拓扑的基础设施 【系统架构设计】配置管理架构:从配置文件到配置中心 【系统架构设计】全链路压测:大规模系统的性能验证 【系统架构设计】幂等性设计:分布式环境下的安全重试 【系统架构设计】契约测试与 Schema 演进:服务间的信任协议 【系统架构设计】长连接与推送架构:WebSocket、SSE 与 MQTT 【系统架构设计】延迟分析:从 P50 到 P999 的全链路追踪 【系统架构设计百科】DDD 战术模式:聚合、实体与值对象 【系统架构设计百科】防腐层与开放主机服务:系统集成的 DDD 方案 【系统架构设计百科】领域事件与事件风暴:从业务到架构的桥梁 【系统架构设计百科】CQRS + Event Sourcing 完整实战:从领域建模到部署 【系统架构设计百科】DDD 与微服务:用领域模型划分服务边界 【系统架构设计】DDD 战略设计:限界上下文与上下文映射 【系统架构设计百科】认证架构:从 Session 到 JWT 到 OIDC 【系统架构设计】API 设计哲学:REST vs GraphQL vs gRPC 的真实权衡 排序算法专题:从 TimSort 到并行排序 【密码学百科】国密算法体系:SM2/SM3/SM4/SM9 全景解读 【密码学百科】承诺方案:Pedersen 承诺、向量承诺与多项式承诺 【密码学百科】不经意传输与隐私信息检索:OT、OT 扩展与 PIR 【密码学百科】门限密码学:门限签名、门限解密与分布式密钥生成 完美哈希:从理论到 gperf 实践 【密码学百科】安全多方计算:从 Yao 的混淆电路到实用 MPC 【密码学百科】同态加密:从 Paillier 到全同态加密(FHE) 【密码学百科】零知识证明系统:zk-SNARKs、zk-STARKs 与 Bulletproofs 【密码学百科】概率论与密码分析:生日攻击、差分分析与线性分析 【密码学百科】计算复杂性与归约:密码安全性证明的基石 【密码学百科】秘密共享:Shamir 方案、VSS 与安全多方计算入口 【密码学百科】椭圆曲线代数:Weierstrass 方程、点群运算与曲线选择 【密码学百科】离散对数与配对密码学:从 DLP 到 BLS 签名 【密码学百科】格密码数学基础:SVP、LWE 与格基约化 【密码学百科】抽象代数:群、环、域的密码学视角 【密码学百科】有限域算术:GF(2^n) 运算与在 AES/ECC 中的应用 【密码学百科】数论进阶:二次剩余、椭圆曲线上的 Weil 配对 【密码学百科】密码学简史:从凯撒密码到量子时代 【密码学百科】威胁模型与安全目标:CIA 三要素之外 【密码学百科】Kerckhoffs 原则与现代密码设计哲学 【密码学百科】随机性:密码学的基石 【密码学百科】信息论入门:熵、完美保密与 Shannon 定理 【密码学百科】分组密码原理:Feistel 网络与 SPN 结构 【密码学百科】AES 逐步拆解:SubBytes 到 MixColumns 的数学 【密码学百科】分组密码工作模式全览:ECB/CBC/CTR/OFB/CFB 【密码学百科】流密码:RC4 的兴衰与 ChaCha20 的崛起 【密码学百科】密码学哈希函数:MD5→SHA-2→SHA-3 的进化之路 【密码学百科】MAC 与 HMAC:消息认证的正确姿势 【密码学百科】认证加密(AEAD):GCM、ChaCha20-Poly1305 与 OCB 【密码学百科】密钥派生函数:HKDF、PBKDF2、Argon2 与密码存储 【密码学百科】公钥密码的数论基础:模运算、群、原根 【密码学百科】RSA 从原理到攻击:教科书 RSA 为什么不安全 【密码学百科】Diffie-Hellman 密钥交换与离散对数问题 【密码学百科】椭圆曲线密码学(ECC):从几何直觉到点群运算 【密码学百科】数字签名:ECDSA、EdDSA 与 Schnorr 签名 【密码学百科】现代密钥交换:X25519、ECDHE 与前向保密 【密码学百科】混合加密与 KEM/DEM 范式:ECIES 与 HPKE 【密码学百科】填充方案:PKCS#1 v1.5、OAEP 与 PSS 【密码学百科】TLS 协议全解析:从握手到 0-RTT 【密码学百科】PKI 与数字证书:信任链的构建与崩塌 【密码学百科】密码认证协议:从 SRP 到 OPAQUE 【密码学百科】零知识证明入门:如何证明你知道而不泄露 【密码学百科】安全信道构造:Noise 协议框架与 Signal 协议 【密码学百科】密钥管理工程:HSM、KMS 与密钥生命周期 【密码学百科】侧信道攻击:从时序攻击到功耗分析 【密码学百科】密码学实现陷阱:三层漏洞分类、审计工具链与系统性预防 密码敏捷性:如何设计可升级的密码系统 【密码学百科】OpenSSL/BoringSSL 架构剖析:ENGINE、Provider 与 FIPS 模块 排序基准测试:用数据说话
【Transformer 与注意力机制】08.5 神经网络基础:从 MLP 到 RNN 的最后一块地基
2026-05-10 · via 土法炼钢兴趣小组的算法知识备份

一、先把整条链放在桌上

很多教材一上来就给你一个大词:神经网络能逼近复杂函数。这个说法没有错,但太抽象。更实用的理解方式是:神经网络是一台可微的函数机器,它不断重复做四件事

第一,拿着当前参数做一次前向传播,算出预测值。

第二,用损失函数衡量这次预测离真实答案差多远。

第三,用链式法则把这个误差沿计算图反向传回去,算出每个参数的梯度。

第四,用梯度下降或它的变种更新参数,让下一次前向传播更接近目标。

如果把一轮训练压成最短的伪流程,其实就是:

  1. prediction = model(input)
  2. loss = criterion(prediction, target)
  3. loss.backward()
  4. optimizer.step()

RNN、CNN、Transformer 全都活在这个闭环里。它们的差异不是“会不会训练”,而是前向那张图怎么组织,信息在图里怎么流动。普通 MLP 让信息从左往右流;RNN 让隐藏表示沿时间继续流;Transformer 则让不同位置之间通过 attention 直接互相读写。

所以这一篇你真正要抓住的不是某个孤立公式,而是这条总链:

  • 神经元决定单个计算单元算什么;
  • 层和矩阵乘法决定很多神经元怎样一起算;
  • 前向传播决定模型一次预测的计算顺序;
  • 损失函数决定“什么算错”;
  • 反向传播决定“错了以后每个参数怎么改”;
  • 梯度下降决定“沿哪个方向动”;
  • 序列模型则是在这套闭环之上重新设计信息流。

二、一个神经元到底在算什么

“神经网络”这个名字很容易让人走神,仿佛它是什么生物学模拟器。对工程理解更有帮助的说法是:一个神经元就是一个可训练的打分器,再加一个非线性门

最小形式可以写成:

\[ z = w \cdot x + b \]

\[ h = \sigma(z) \]

第一步 w \cdot x + b 是线性打分。输入向量 x 投到权重向量 w 指定的方向上,再加偏置 b。第二步 \sigma(z) 是激活函数,它决定这个打分如何被压缩、折弯或截断。

这张图把“线性打分”这件事画得最直观:

单个神经元如何用超平面把输入空间切开

图里每个散点是一条二维输入,绿色直线是 w·x + b = 0 这条分界线。它把平面切成两半:一半 z >= 0,一半 z < 0。红色箭头 w 表示这个神经元最敏感的方向。

这张图想说明三个事实。

第一,单个神经元本质上是在学一个方向。它问的是:“输入在这个方向上有多强?”

第二,偏置 b 决定阈值在哪里。没有 b,分界线只能穿过原点;有了 b,分界线可以平移。

第三,如果后面不接非线性,神经元只能做线性切分。它可以判断“在线的一边还是另一边”,但做不出真正复杂的弯曲边界。

这也是为什么说神经元不是一个“会思考的小脑细胞”。它更像一个方向检测器:w 决定看哪里,b 决定门槛多高,激活函数决定通过之后怎么表达。

三、为什么非线性是分水岭

只看 w \cdot x + b,你可能会以为:那我叠很多层线性变换,不就越来越强了吗?遗憾的是,不行。线性变换的复合还是线性变换。两层线性层连起来,最后仍然等价于一层更大的线性层。

也就是说,让深度网络真正变“深”的,不是层数本身,而是层与层之间插进去的非线性。没有非线性,深网络会塌缩成浅网络。

下面这张图把这个问题画得很清楚:

线性打分经过激活函数之后才真正弯下来

左边是线性打分 z = wx + b,它就是一条直线。右边是同一个 z 经过 ReLU 和 tanh 之后的输出。你可以看到,激活函数把直线“折”成了新的形状。ReLU 让负半轴直接归零,tanh 则把大值压饱和。

这一步非常关键,因为真实世界里大多数有意思的映射都不是线性的。房价不是面积的线性函数,图像标签不是像素值的线性函数,语言语义更不是 token 向量的线性函数。深度学习之所以能拟合复杂关系,靠的不是把矩阵乘法堆得更厚,而是在线性层之间不断插入非线性,让表示空间反复被折弯。

从这里开始,神经网络就不再是“一次线性打分”,而是“线性变换 + 非线性 + 线性变换 + 非线性”的函数复合机器。

四、一层、一个 batch、一个 MLP:形状怎么走

单个神经元只输出一个数,但真实任务需要一组特征。所以我们会把很多神经元并排放在一起,用一整个矩阵一次性算完一层。

如果输入维度是 d_in,这一层有 d_out 个神经元,那么:

\[ H = \sigma(XW + b) \]

这里的形状最重要。很多深度学习 bug 不是数学 bug,而是形状 bug。最常见的一组张量形状如下:

符号 含义 形状
X 一个 batch 的输入 B \times d_in
W 这一层的权重矩阵 d_in \times d_out
b 这一层的偏置 1 \times d_out
Z = XW + b 激活前的线性输出 B \times d_out
H = sigma(Z) 这一层的隐藏表示 B \times d_out

这里 B 是 batch size。也就是说,前向传播不是“每次只算一个样本”,而是把一批样本堆成矩阵,一次矩阵乘法全算完。GPU 正是擅长这种运算,这也是为什么神经网络天然喜欢用矩阵表达。

把一层接一层串起来,就得到多层感知机(Multilayer Perceptron, MLP):

\[ Z_1 = XW_1 + b_1 \]

\[ H_1 = \tanh(Z_1) \]

\[ \hat{Y} = H_1W_2 + b_2 \]

这里 H_1 就是隐藏表示。它不是最终答案,而是网络自己造出来的中间语言。这个概念要记住,因为到了下一篇,RNN 的 h_t 本质上就是“隐藏表示沿时间传递之后的版本”。

五、前向传播:一次预测到底算了什么

现在可以把“前向传播”说具体了。以前面这个两层 MLP 为例,一次前向传播就是把输入从头算到尾。

如果是回归任务,比如本文的 toy experiment,那么输出层可以直接给一个实数:

\[ \hat{Y} = H_1W_2 + b_2 \]

如果是分类任务,最后通常不是直接拿这个输出当结果,而是把它看成 logits,再交给 softmax 变成概率分布。

为了把概念彻底钉住,我们先看一个最小的标量前向例子。取:

  • x = 2.0
  • w1 = 0.60
  • b1 = -0.20
  • w2 = 1.40
  • b2 = -0.10
  • target = 0.20

那么前向传播按顺序是:

\[ z = w_1x + b_1 = 0.60 \times 2.0 - 0.20 = 1.0 \]

\[ h = \tanh(z) = \tanh(1.0) = 0.761594 \]

\[ \hat{y} = w_2h + b_2 = 1.40 \times 0.761594 - 0.10 = 0.966232 \]

你可以看到,哪怕只是最小网络,前向传播也已经不是一句“算输出”那么简单了。它是一串明确的中间结果:先有 z,再有 h,最后有 \hat{y}。而这些中间结果之所以重要,是因为反向传播必须沿着同一条链倒回来。

六、损失函数:模型到底在最小化什么

前向传播只会告诉你“这次预测是多少”,但不会告诉你“这次错得多严重”。训练要发生,必须先定义一个可微的误差尺度。这个尺度就是损失函数(loss function)。

对回归任务,一个常见选择是均方误差。为了让导数更干净,通常会在前面乘一个 1/2

\[ L = \frac{1}{2B} \|\hat{Y} - Y\|^2 \]

这里 B 是 batch size。前面的 1/2 没有改变最优点,只是让求导时 2 被抵掉。对刚才那个标量例子,它变成:

\[ L = \frac{1}{2}(\hat{y} - y)^2 \]

代入前向结果:

\[ L = \frac{1}{2}(0.966232 - 0.20)^2 = 0.293556 \]

这个数就是“模型当前有多错”。如果是分类任务,常见的损失不是 MSE,而是交叉熵,因为输出要被解释为概率分布。不同损失函数会影响梯度形式,但训练逻辑不变:先把误差写成一个可微的标量,再让这个标量对参数求导。

七、先手算一个最小反向传播例子

“反向传播”这个词最容易把人吓住。但如果你把它压回最小例子,它其实只是链式法则按计算图从右往左走一遍。

先看图:

一个标量网络的前向数值和反向梯度

这张图里,前向路径是:x -> z -> h -> \hat{y} -> loss。反向路径则从 loss 往回,一层一层把梯度乘回来。

对刚才那组数值,反向传播一步一步是这样的。

第一步,先对输出求导:

\[ \frac{dL}{d\hat{y}} = \hat{y} - y = 0.966232 - 0.20 = 0.766232 \]

第二步,输出层参数 w2 的梯度:

\[ \frac{dL}{dw_2} = (\frac{dL}{d\hat{y}}) \times h = 0.766232 \times 0.761594 = 0.583558 \]

第三步,把梯度继续传回隐藏表示:

\[ \frac{dL}{dh} = (\frac{dL}{d\hat{y}}) \times w_2 = 0.766232 \times 1.40 = 1.072725 \]

第四步,经过 tanh 这层时要乘上局部导数。因为 h = \tanh(z),所以:

\[ \frac{dh}{dz} = 1 - h^2 = 1 - 0.761594^2 = 0.419974 \]

于是:

\[ \frac{dL}{dz} = (\frac{dL}{dh}) \times (1 - h^2) = 1.072725 \times 0.419974 = 0.450517 \]

第五步,再传回第一层参数:

\[ \frac{dL}{dw_1} = (\frac{dL}{dz}) \times x = 0.450517 \times 2.0 = 0.901034 \]

偏置的梯度更简单,因为 z = w_1x + b_1b_1 的导数就是 1,所以:

\[ \frac{dL}{db_1} = 0.450517, \frac{dL}{db_2} = 0.766232 \]

这就是反向传播的全部:把最终误差乘上每一层的局部导数,一层层传回去。如果学习率 η = 0.1,那么这一步更新以后:

  • w1 <- w1 - 0.1 \times 0.901034
  • w2 <- w2 - 0.1 \times 0.583558

参数会往损失下降的方向挪一点。下一次前向传播,\hat{y} 就会离目标更近一点。

八、把标量推广到矩阵:两层 MLP 的 batch 求导

手算标量例子是为了建立直觉,真实训练时不可能一条样本一条样本、一个参数一个参数地推。工程上我们总是按 batch、按矩阵来写。

仍然以前面的两层回归 MLP 为例:

\[ Z_1 = XW_1 + b_1 \]

\[ H_1 = \tanh(Z_1) \]

\[ \hat{Y} = H_1W_2 + b_2 \]

\[ L = \frac{1}{2B} \|\hat{Y} - Y\|^2 \]

那么反向传播可以整批写成:

\[ dY = (\hat{Y} - Y) / B \]

\[ dW_2 = H_1^T dY \]

\[ db_2 = \operatorname{row\_sum}(dY) \]

\[ dH_1 = dY W_2^T \]

\[ dZ_1 = dH_1 ⊙ (1 - H_1^2) \]

\[ dW_1 = X^T dZ_1 \]

\[ db_1 = \operatorname{row\_sum}(dZ_1) \]

这里 表示逐元素相乘,\operatorname{row\_sum} 表示把一个 batch 在第 0 维上求和,得到偏置梯度。你会发现,矩阵版和标量版没有本质区别:

  • 标量版里“乘上隐藏值”,矩阵版里变成 H_1^T dY
  • 标量版里“乘上激活函数导数”,矩阵版里变成 dH_1 ⊙ (1 - H_1^2)
  • 标量版里“对偏置求导就是 1”,矩阵版里变成把 batch 维度上的梯度直接相加。

这也是为什么说反向传播本质上不是“深度学习黑魔法”,而是普通链式法则的批量矩阵化实现。

九、怎么实现:先自己写一遍,再看框架版

到这里,最好的办法不是继续背公式,而是自己写一遍。下面这段代码经删减,只保留同目录脚本 plot_neural_network_figures.py 里的核心训练路径,去掉了作图和快照保存。它实现的就是上面那套两层 MLP:前向、损失、反向、更新,一个不少。

import numpy as np

rng = np.random.default_rng(11)
x = np.linspace(-2.0, 2.0, 96).reshape(-1, 1)
y = np.sin(3.0 * x) + 0.35 * x + 0.08 * rng.normal(size=(96, 1))

hidden = 32
W1 = rng.normal(scale=0.8, size=(1, hidden))
b1 = np.zeros((1, hidden))
W2 = rng.normal(scale=0.35, size=(hidden, 1))
b2 = np.zeros((1, 1))

lr = 0.035
for step in range(2600):
  Z1 = x @ W1 + b1
  H1 = np.tanh(Z1)
  \hat{Y} = H1 @ W2 + b2

  dY = (\hat{Y} - y) / x.shape[0]
  dW2 = H1.T @ dY
  db2 = np.sum(dY, axis=0, keepdims=True)
  dH1 = dY @ W2.T
  dZ1 = dH1 * (1.0 - H1 ** 2)
  dW1 = x.T @ dZ1
  db1 = np.sum(dZ1, axis=0, keepdims=True)

  W1 -= lr * dW1
  b1 -= lr * db1
  W2 -= lr * dW2
  b2 -= lr * db2

这段代码故意没有框架,也没有“自动求导”。它把神经网络训练写成了最裸露的样子:你必须自己保存中间结果,自己写导数,自己更新参数。把这一版看懂,再去看 PyTorch,就会发现框架只是替你做了两件事:记录计算图、自动算梯度。

对应的 PyTorch 版会短得多:

import torch
import torch.nn as nn

class TinyMLP(nn.Module):
  def __init__(self):
    super().__init__()
    self.net = nn.Sequential(
      nn.Linear(1, 32),
      nn.Tanh(),
      nn.Linear(32, 1),
    )

  def forward(self, x):
    return self.net(x)


model = TinyMLP()
optimizer = torch.optim.AdamW(model.parameters(), lr=3e-2)
criterion = nn.MSELoss()

for step in range(2600):
  pred = model(x_tensor)
  loss = criterion(pred, y_tensor)

  optimizer.zero_grad()
  loss.backward()
  optimizer.step()

从“自己写导数”切到“框架自动微分”,并不是原理变了,而是 bookkeeping 被封装掉了。前向还是那次前向,损失还是那个损失,梯度还是那组梯度,更新还是那步更新。

十、训练到底发生了什么

现在来看训练过程本身。梯度下降不是一句抽象口号,它在参数空间里真的走出一条路。

先看最简单的二维参数面:

梯度下降沿着损失曲面一步步往低处走

这张图里,横轴是线性模型的权重 w,纵轴是偏置 b。灰色等高线表示不同损失值,橙色折线是梯度下降从起点一路往低处挪的轨迹。起点很差,离真实参数很远;更新若干步之后,参数逐渐贴近低损失区域。

这张图说明了一个经常被说得很玄的事实:训练不是“模型突然学会了”,而是参数在损失曲面上连续移动。学习率控制每一步走多远,梯度决定往哪里走。

再看本文真实跑过的两层 MLP 训练实验:

toy MLP 的拟合曲线和 loss 下降过程

左图是同一个 MLP 在不同训练步数下的拟合结果。你能看到,刚开始几乎是一条乱线;80 步时开始有点形状;400 步时已经抓住了大致弯曲;2600 步时基本贴上目标曲线。右图则是 loss 的对数坐标曲线,它从高位一路往下掉,最后停在 0.033948

这里还有两个值得顺手说明的点。

第一,这个 toy experiment 用的是全 batch 更新,不是 mini-batch。原因很简单:教学上这样更干净,图也更稳定。真实深度学习训练通常用 mini-batch,因为数据更大、噪声更有利于优化、GPU 也更适合按 batch 吞数据。

第二,这个实验不是在“证明神经网络很强”,而是在证明训练闭环是可工作的。只要你把前向、损失、反向和更新写对,一台很小的 MLP 就能把一条非线性曲线从不会拟合,慢慢训到会拟合。

到这里,我们已经把普通神经网络从头走通了:它能表达什么,怎么前向,怎么定义损失,怎么求导,怎么训练。现在终于可以问下一个关键问题:既然 MLP 已经能拟合复杂函数,为什么还需要 RNN?

原因不是“MLP 不够强”,而是“MLP 的信息流不适合序列”。下面这张图把问题画得很清楚:

固定窗口 MLP 和带状态的 RNN 在序列上的信息流差异

上半部分是把一段固定长度序列直接拍平成一个大向量,再交给 MLP。这样做有三个硬伤。

第一,长度固定。输入是一个大向量,就意味着你必须预先决定最多接多少个 token。短了要 padding,长了要截断。

第二,位置绑定参数。第 1 个位置和第 57 个位置虽然都可能出现形容词,但在这种设计里,它们会落到不同参数上,模型必须在每个位置重新学一遍类似规律。

第三,没有天然状态。MLP 不会在读到第 50 个 token 时自动携带前 49 个 token 的摘要。你只能把历史显式拼进输入,或者把窗口拉得很大,而这又会迅速推高参数量。

一个很典型的例子是 not good。如果模型只看当前词,good 大概率会被判成正面。要让模型理解 not 会反转 good,它就需要把前面的信息带到后面。对长度固定、位置绑定的 MLP 来说,这意味着你得为每种窗口长度和位置关系都学一套组合规则。这很笨,也很浪费。

十二、RNN 究竟是在 MLP 上加了什么

RNN 相对 MLP 的核心改动,其实只有一句话:让当前隐藏表示依赖上一时刻的隐藏表示。

把普通 MLP 写成最抽象的形式,是:

\[ h = F_θ(x) \]

而最基本的 RNN 则变成:

\[ h_t = \phi(W_x x_t + W_h h_{t-1} + b) \]

\[ y_t = W_y h_t + c \]

和普通前馈网络相比,RNN 只多出了一条来自 h_{t-1} 的边。可就是这条边,把“隐藏表示”变成了“状态”。状态让网络在读第 t 个 token 时,不只看当前输入 x_t,还看过去的总结 h_{t-1}

从这个角度看,RNN 不是另一种神秘机器,而是把普通神经网络沿时间展开:同一套参数 W_x, W_h, W_y 在每一个时间步重复使用。也正因为它可以展开成一条很长的前馈链,所以训练它时需要把梯度沿时间往回传,这就是下一篇要讲的 BPTT(Backpropagation Through Time)。

换句话说,RNN 仍然活在本文讲的那条大闭环里:前向、损失、反向、更新,一个步骤都没少。它变的不是训练原则,而是前向图里的信息流。

十三、关键概念回顾

神经网络最小的计算单元是“线性打分 + 非线性激活”。很多这样的单元并排起来是一层,很多层串起来就是 MLP。矩阵乘法的作用,不是把公式写得好看,而是让一整个 batch 的一整层神经元一次算完。

前向传播的任务是算出预测值,损失函数的任务是把“错了多少”变成一个可微标量,反向传播的任务是沿计算图把这个误差一层层乘回来,梯度下降的任务则是让参数沿着下降方向挪一步。整套训练闭环就是这四步的重复。

手算标量例子能帮你理解链式法则,矩阵版公式则说明真实工程里为什么能高效 batch 化。框架自动微分没有改变原理,只是替你把链式法则的记账工作做掉了。

普通 MLP 的问题不在于表达力不够,而在于它天然面向固定长度向量,没有状态,也不会自动在不同位置共享序列规则。RNN 正是在这个点上做了最小但关键的改动:让隐藏表示沿时间继续流动。

十四、常见误解

14.1 “神经网络就是模拟大脑”

历史上确实有生物神经元的启发,但现代深度学习更实用的理解方式是“可微函数近似器”。把它理解成矩阵乘法、非线性、损失函数和优化器组成的计算系统,比把它理解成大脑模拟更不容易跑偏。

14.2 “只要层数够深,线性层自己就会变复杂”

不会。线性复合仍然是线性。真正让深度网络拥有复杂表达力的,是层与层之间的非线性。

14.3 “反向传播是一种特殊的 AI 黑魔法”

不是。它就是链式法则沿计算图的系统化应用。标量例子里你手算的每一步,框架都在大规模张量上重复同样的事。

14.4 “MLP 能万能逼近,所以序列任务也不需要 RNN”

万能逼近说的是固定输入维度下的表达能力,不是结构上对序列是否合适。序列长度可变、规则需要跨位置共享、历史需要形成状态,这些都是结构问题。

14.5 “有了 Transformer,就不用再学 MLP 和反向传播了”

正好相反。Transformer 每层里都有 FFN,本质就是 MLP;它的训练也完全依赖前向、损失、反向、更新这条闭环。你不理解 MLP 和反向传播,就只能把 Transformer 当成一堆会动的公式。

十五、下一步

到这里,普通神经网络这台机器已经立住了。下一篇的问题不再是“神经网络怎么训练”,而是“这台机器怎样改造之后才能处理变长序列”。

09 RNN 与序列建模 会从最基本的循环结构讲起,把 h_t = f(x_t, h_{t-1}) 这件事彻底展开,然后进入 BPTT、梯度消失爆炸、LSTM、GRU 和 Seq2Seq。你可以把它看成本文的自然下一步:把隐藏表示从一张静态计算图里的中间量,变成沿时间持续流动的状态。

再往后,10 RNN 的根本局限 会解释为什么“状态沿时间传递”这条路最终走到了天花板,以及 Transformer 为什么能从 RNN 的补丁演化成新的主角。

十六、参考文献

  1. McCulloch, W. S., Pitts, W. (1943). A Logical Calculus of the Ideas Immanent in Nervous Activity. Bulletin of Mathematical Biophysics. 形式神经元模型的早期源头。
  2. Rosenblatt, F. (1958). The Perceptron: A Probabilistic Model for Information Storage and Organization in the Brain. Psychological Review. 感知机模型。
  3. Rumelhart, D. E., Hinton, G. E., Williams, R. J. (1986). Learning representations by back-propagating errors. Nature. 多层网络反向传播的经典论文。
  4. Cybenko, G. (1989). Approximation by superpositions of a sigmoidal function. Mathematics of Control, Signals and Systems. 万能逼近定理的经典结果之一。
  5. Hornik, K., Stinchcombe, M., White, H. (1989). Multilayer feedforward networks are universal approximators. Neural Networks. 多层前馈网络表达能力。
  6. LeCun, Y., Bottou, L., Bengio, Y., Haffner, P. (1998). Gradient-Based Learning Applied to Document Recognition. Proceedings of the IEEE. 梯度学习工程化的代表工作。
  7. Bishop, C. M. (2006). Pattern Recognition and Machine Learning. Springer. 概率视角下的机器学习教材。
  8. Goodfellow, I., Bengio, Y., Courville, A. (2016). Deep Learning. MIT Press. 深度学习教材。
  9. Elman, J. L. (1990). Finding Structure in Time. Cognitive Science. 现代 RNN 的早期代表工作。

上一篇:08 嵌入:从 one-hot 到分布式表示

下一篇:09 RNN 与序列建模

回到:Transformer 与注意力机制 总览

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-15 · transformer

【Transformer 与注意力机制】系列总览

从《Attention Is All You Need》出发,把注意力机制、Transformer 架构、训练范式、模型变体、推理工程、可解释性与未来架构串成一条 58 篇主线加一篇桥接文的深度博客线。

2026-04-15 · transformer

【Transformer 与注意力机制】06|梯度下降与反向传播

神经网络真正会「学习」靠的是两件事:把误差变成可微分的损失函数,再沿着这个损失对参数的梯度方向一点点往下挪。本文从一维抛物线讲到多变量梯度,从两层网络的手算反向传播讲到为什么 backprop 是 O(参数量),再到 Transformer 为什么几乎一律选 Adam/AdamW,希望把「网络是怎么学的」这件事彻底讲透。

2026-04-15 · transformer

【Transformer 与注意力机制】01|为什么要从这里开始

这是【Transformer 与注意力机制】系列的第一篇,承担两件事:一是把这套五十多篇文章为谁写、解决什么问题、彼此之间是什么关系交代清楚;二是为完全没基础的读者画出一条从向量、点积、矩阵乘法走到自注意力、再走到大语言模型的爬升路径,让你在投入时间之前先知道终点在哪、路上要经过哪些坎、读完之后你会、还不会做什么事。