VOL.V · 走进 AICH 20( 20 , 24 )

神经网络 = 矩阵乘 + 非线性

第 01 章开出的支票——「神经网络每一层是矩阵乘加一个弯」——这一章全额兑付。我们要把前向传播拆到一个数字都不剩:一层网络为什么恰好是 Wx + b,那个「弯」为什么一步都不能省,「深度」到底深了什么。配的 Demo 是一个真的在你面前计算的迷你网络:两个输入滑杆,五个神经元,每根连线的粗细就是权重,拨一下输入,看激活像水一样流过去。看懂它,7B 的大家伙只是它的复印放大版,复印了十亿倍而已。

前向传播ReLU通用近似表示学习

从一个神经元到一层

第 04 章拆过单个神经元:持一组权重 w,对输入 x 打分 w·x + b,过一个非线性 f——一台模板匹配机。现在把 m 个神经元并排站成一层,每人一套模板:m 次点积。第 07 章说过,一批点积就是一次矩阵乘——把 m 套模板按摞成 W(m×n),整层的计算浓缩成一行:

h = f(Wx + b)一层 = 线性变换 + 平移 + 逐元素掰弯

用全书的语言给这三个动作验明正身:W 是第 06 章的变换说明书(把 n 维输入搬进 m 维空间,行视角 = m 台打分器,列视角 = 输入基向量的去向);+b 是平移(仿射项,把「过原点」的枷锁解开——第 03、14 章两次伏笔);f 是逐元素非线性,唯一一个不属于线性代数的动作。整个深度网络就是这一行的叠罗汉:y = f₃(W₃ f₂(W₂ f₁(W₁x)))——洋葱从右往左剥(第 07 章),数据一层层被搬运、平移、掰弯。

批量版本只是给这行公式加上第 19 章的形状语言:把 B 条样本按行摞成 X(B×n),整批前向是 H = f(XWᵀ + b)——数据行优先、矩阵右乘转置(第 19 章翻译表第一行),偏置靠广播摊给每一行。写成能跑的代码,一个两层网络的前向不到十行:

forward.py
import numpy as np

def relu(z): return np.maximum(0, z)

def forward(X, W1, b1, W2, b2):
    Z1 = X @ W1.T + b1          # (B, n) → (B, h)   搬运+平移
    H1 = relu(Z1)               # (B, h)            折纸
    Z2 = H1 @ W2.T + b2         # (B, h) → (B, c)   logits
    return Z2

X  = np.random.randn(32, 784)                # 32 张展平的手写数字
W1 = np.random.randn(256, 784) * np.sqrt(2/784)  # He 初始化(第 10 章)
b1 = np.zeros(256)
W2 = np.random.randn(10, 256) * np.sqrt(2/256)
b2 = np.zeros(10)
logits = forward(X, W1, b1, W2, b2)          # (32, 10)

这十行就是「手写数字识别」网络的完整前向——加上训练循环(下一章)就能到 98% 准确率。请特别注意它没有的东西:没有循环遍历神经元(矩阵乘一次包办),没有逐样本处理(batch 轴广播),没有任何超出本书的运算。PyTorch 的 nn.Linear + nn.ReLU,内核就是这几行加了自动求导的外衣。

那个弯,为什么一步不能省

第 07 章已经给过判决书:没有 f,矩阵连乘可以用结合律合并,一百层塌缩成一层——深度成了摆设。但「为什么加个弯就救活了」值得一幅更具体的画。看 ReLU,当今的默认掰弯器,规则朴素到羞涩:f(z) = max(0, z),负数拍成零,正数原样放行。

它的几何本领是折纸。一个神经元 ReLU(w·x + b) 在输入平面上划出一条直线(w·x + b = 0——注意这正是第 04 章「点积为零」的垂直分界线,w 是这条折痕的法线方向),线的一侧原样保留,另一侧被「压平」——空间沿这条线被折了一下。一层 m 个神经元,就是沿 m 条不同的线各折一次;层层叠加,折痕相互作用,平面被折成一张复杂的折纸。线性层负责转动和拉伸纸,ReLU 负责折,深度就是折的轮数。第 06 章那对「直线切不开的螺旋」,就是这样被折到某个中间空间里变成线性可分的——每折一次,解不开的缠绕就松动一分。

这幅折纸图直接给出通用近似定理的直觉版:分段线性函数(折纸的数学名字)可以逼近任何连续函数,只要折痕够多——就像折线可以逼近任何曲线。定理的严格版说,一层足够宽的隐藏层理论上就够;但实践选择了而不是,因为折纸是复利游戏:后层的折痕作用在前层已折过的纸上,n 层每层 m 道折痕,产生的线性区域数是指数级的——同样的参数预算,深网络「花样」多得多。「深度」学习的深,归根到底深在这里。

通用近似定理还有一面必须诚实交代,不然它会被高估。定理只说「存在一组权重能逼近」,不保证梯度下降找得到它,更不保证找到的那组在新数据上灵——「能表达」「能学到」「能泛化」是三座不同的山,定理只翻了第一座。深度学习的实践智慧(架构归纳偏置、正则化、数据规模)大都在翻后两座。所以别拿「反正 MLP 万能」当架构懒惰的借口:卷积之于图像、注意力之于序列,赢就赢在把「什么样的函数更可能是答案」的先验焊进了结构里——用第 19 章的话说,预先选好让信息在哪根轴上流动

亲手拨一个真网络

迷你 MLP 前向传播:2 → 3 → 2DEMO 13

这个网络在解一个玩具分类题:根据两个特征把输入判给 A 队或 B 队。读图约定:连线粗细 = 权重绝对值,青蓝为正、橙红为负;节点深浅 = 激活值大小——全书的双色系统最后一次上岗。实验清单:① 缓缓拉 x₁ 从 −2 到 2,盯住 readout 第二行:哪些隐藏神经元被 ReLU「拍死」(激活 0)、在哪个输入值上「苏醒」——苏醒的那一瞬,就是一条折痕的位置;② 找一组让 P(A) ≈ P(B) ≈ 50% 的输入——你正站在决策边界(某条折缝)上;③ 观察 h3 这个神经元:它的两条输入权重都为正,像一台「x₁ 和 x₂ 都大才兴奋」的与门检测器——特征检测器不是比喻,你正看着一台;④ 全程留意:整个网络没有任何魔法元件,只有你学了十九章的点积、矩阵乘和一个 max(0, ·)。

掰弯器图鉴

ReLU 不是唯一的弯,认一遍常见家族,读代码不慌:

  • sigmoid σ(z) = 1/(1+e⁻ᶻ):把任何数压进 (0,1),早期网络的标配、如今的「概率出口」与「门」专业户(LSTM 的门、SwiGLU 的门都是它或近亲)。缺点:两端平坦,梯度趋零——深层里用它当主力,信号走几层就饿死(第 21 章的消失病灶之一)。
  • tanh:sigmoid 的以零为中心版,输出 (−1,1),RNN 时代的主力,今天偶见于输出层。
  • ReLU:折纸机,便宜(一次比较)、梯度非 0 即 1(不衰减),深度学习起飞的功臣之一;代价是「死 ReLU」(见坑②)。
  • GELU / SiLU(Swish):ReLU 的圆角版——负半轴不是硬砍而是平滑压低,处处可导。BERT、GPT 系用 GELU,Llama 系用 SiLU 的门控组合 SwiGLU。性能略优,代价是算得贵一点。

选型的心法一句话:隐藏层默认 GELU/SiLU 家族(现代)或 ReLU(简单场景),输出层按任务选(分类 softmax、回归不加弯、多标签 sigmoid)。掰弯器的花样再多,记住它们的共同使命:逐元素、非线性、别把梯度掐死——除此之外都是微调口味。

Demo 网络的户口本

把 Demo 里那台机器的参数户口念一遍,体会「读权重」的感觉:W₁ 是 3×2(两维输入进三个神经元),连偏置 b₁ 共 9 个参数;W₂ 是 2×3 加 b₂,共 8 个;全网 17 个参数,构成一个从 ℝ² 到概率二元组的函数。它的三条折痕把输入平面切成最多七个线性区域,每个区域里输出是输入的仿射函数——你拖动滑杆时的每一次「手感突变」,都是滑过了一条折痕。7B 模型与它的差别只有两个字:规模。结构上,它是完整的。

最后一层:从分数到概率

输出层通常不掰弯,直接给出每个类别的裸分数,行话叫 logits。要变成概率,交给第 04 章的老朋友 softmax(指数化 + 归一化,预算分配)。训练时的损失函数「交叉熵」,本质是「正确类别分到的概率的负对数」——概率越接近 1 损失越小。这一段先点到为止(损失与梯度是下一章的戏份),但有个工程区分现在就要立:模型文件里存的、API 返回的 logprobs 算的,都是 logits 一系;概率只是它们的 softmax 影子。数值上永远用「log-softmax + NLL」的融合算子而不是先 softmax 再取 log——第 05 章 epsilon 防爆哲学的亲兄弟。

现代前向的两件标配:残差与归一化

真实的深网络里,「一层」的样子比 Wx+b 再丰满两圈,两件标配都见过伏笔,这里装配到位。残差连接:x + F(x)——每层只学「修正量」,主干信号原样直通(第 03 章残差流、第 07 章 I+F、第 16 章谱救援,三线合一);工程后果是「深度不再可怕」,几百层照样训。归一化(LayerNorm/RMSNorm):每个 token 的向量先减均值除标准差(或只除 RMS),再乘可学的逐维缩放 γ(第 10 章对角兽)——把激活的范数摁在稳定区间,防止数值随深度漂移(第 05 章)。于是一个 Transformer 块的前向全景是:x ← x + Attn(Norm(x)),再 x ← x + FFN(Norm(x))——归一化、注意力/MLP、加回主干,三拍子重复几十层。你已经能读懂大模型结构图的每一个框了,下一章再给每个框配上「怎么学」。

中间层在学什么:表示的接力

这套三拍子里归一化放在子层之前(Pre-Norm)还是之后(Post-Norm),曾是架构圈的一场路线之争:原版 Transformer 用 Post-Norm,深了难训;现代大模型清一色 Pre-Norm——主干上从输入到输出没有任何归一化拦路,恒等通道彻底畅通,几百层的梯度也能一路走回去。一个「块内顺序」的细节,决定了模型能不能堆到今天的深度,这就是工程与谱分析(第 16 章)咬合的样子。

把网络竖着看是公式,横着看是表示的接力(第 06 章的旅程叙事,现在有了完整词汇):原始输入住在「像素基」「词表基」里——对任务极不友好的坐标系(第 11 章:one-hot 里猫狗不相干);每过一层,数据被换到一个稍微友好一点的表示空间;到最后一层,任务所需的信息已经「线性可用」——一个朴素的线性分类头就能收割。深度网络 = 一台学习换基的机器,前面所有层都在为最后那个线性头打扫坐标系。第 14 章的线性探针,就是拿着探照灯逐层检查「打扫到什么程度了」;迁移学习的「冻结骨干、换头微调」,赌的就是「打扫工作可以复用」。

前向的价签:2 × 参数量

形状账(第 19 章)在前向传播上还能开出一张著名的价签。每个参数(矩阵元素)在一次前向里恰好参与一次「乘 + 加」(它和输入的某个分量相乘,再累进求和)——所以一次前向 ≈ 2 × 参数量次浮点运算。7B 模型每生成一个 token 约 140 亿次运算;一张标称 100 TFLOPS 的卡,理论上每秒能吐七千多个 token,实际远少——因为逐 token 生成时矩阵乘退化成矩阵乘向量,算力喂不饱、瓶颈掉到内存带宽上(第 07 章 compute-bound vs memory-bound 的现场)。这也是为什么推理服务拼命攒 batch(把矩阵乘向量攒回矩阵乘矩阵)、为什么「每秒 token 数」随并发不降反升的反直觉现象存在。一张价签,三个工程谜题一并开锁。

◆ AI 连接

别以为 MLP 是 Transformer 时代的老古董——恰恰相反,大模型参数的约三分之二躺在每层的 MLP 块(FFN)里:那个「4096 → 11008 → 4096」的一涨一缩(第 06 章算过它的账),Llama 家用的变体叫 SwiGLU(掰弯器换成带门控的 Swish,思想不变:线性、掰弯、线性)。可解释性研究还发现 FFN 的行为像一座「键值记忆库」:第一个矩阵的行是「什么模式该触发」(键,点积匹配——第 04 章),第二个矩阵的列是「触发后往残差流里写什么」(值,配方叠加——第 03 章)。「巴黎是法国的首都」这类事实,就分布式地存在这些矩阵行列的对子里。你已经有了阅读这类论文的全部零件。

▸ Android 类比

前向传播的工程形态,你在移动端见过:TFLite/ONNX 模型文件本质是「一串算子 + 一堆矩阵常量」的清单,Interpreter 按拓扑序逐层执行——和 RenderEffect 链、音频效果链是同构的 pipeline 思维。你现在多了一层透视:清单里 FULLY_CONNECTED 节点的 weights 字段,就是一份 W;量化版模型里它是 int8 的——第 23 章会讲这次「省钱换基」的代价怎么算。用 Netron 打开任何一个 .tflite 文件看看,你会认出本章的全部角色。

⚠ 坑

三个新手仪式性翻车点。① 全零初始化 = 死局:同层神经元若初始权重全同,前向输出全同、反向梯度也全同,永远学成一模一样——「对称性」锁死了分化。必须随机初始化来破缺对称,而方差怎么定是第 10 章 Xavier/He 的戏。② 死 ReLU:某神经元若对所有训练样本都输出负,ReLU 后恒为 0,梯度也恒为 0——它再也醒不来,整行权重成了摆设(第 12 章「激活矩阵秩骤降」的病灶之一);Leaky ReLU/GELU 给负半轴留一丝坡度,就是给昏迷者留一条静脉。③ logits 当概率用:logits 可以是 −7.3 或 42,拿去当置信度、做阈值判断前忘了 softmax,是线上事故的常客——数值像概率(0~1)不等于是概率。

✋ 动手自测

① 一层 784 → 256 的全连接层有多少参数(别忘 b)?

② 若把 Demo 网络的 ReLU 全部删掉,它的决策边界会变成什么形状?为什么?

③ 用折纸语言解释:为什么两层各 10 个神经元的网络,表达力通常强于一层 20 个?

④ 在 Demo 里,把输入调到让三个隐藏神经元全部激活为 0——输出还会随输入变化吗?这对应哪个坑?

⑤ FFN 的「键值记忆」解读里,键匹配用的是本书哪一章的运算、值写入用的是哪一章的概念?

⑥ 用「2×参数量」价签估算:13B 模型生成 500 token 的回答,总共多少次浮点运算?(答案的量级:10¹⁶——一次日常问答,一亿亿次乘加。)

小结

一层 = 矩阵乘(搬运)+ 偏置(平移)+ 逐元素非线性(折纸),批量版是 X@Wᵀ+b 加广播,十行 NumPy 就是一个能认手写数字的网络;没有弯,百层塌成一层;有了弯,分段线性折纸可以逼近一切,而「深」让折痕以复利增长——但「能表达」不等于「能学到」更不等于「能泛化」,架构的先验负责翻后两座山;残差与归一化是现代前向的两件标配,一次前向的价签约等于两倍参数量;中间层的使命是换基接力,把任务信息打扫到「线性可用」;中间层的使命是换基接力,把任务信息打扫到「线性可用」,深度网络是一台学习换基的机器;logits 是裸分,softmax 才是概率;初始化要破缺对称,ReLU 会死,FFN 是大模型的参数主力兼「键值记忆库」。前向的账清了,前向的账本至此全部透明,自然的下一问是:这几亿个矩阵元素,是怎么被「学」出来的?下一章进入反向传播——你会发现所谓梯度,不过是本书的老朋友们(转置、链式矩阵乘、Jacobian)排了个队。