多路径求和:单输出网络藏起来的那个 Σ
结构只改了一处
隐藏层完全不动,只把输出层从 1 个神经元变成 2 个:一个判猫,一个判狗。参数从 9 个变成 12 个。
目标也从一个数变成一对:y = (猫 1.0, 狗 0.0)。代价相应地是两项之和:
C = (a2_1 − y₁)² + (a2_2 − y₂)² = (0.6129 − 1.0)² + (0.4740 − 0.0)² = 0.1498 + 0.2247 = 0.3745注意第一项 0.1498 —— 这正是上一篇里那个单输出网络第一轮的全部代价。因为猫那条支路的参数和上一篇完全一样。
后果:隐1 有了两个下游
这是唯一的变化,但它改变了反向传播。
上一篇里,隐1 只连着一个输出,误差信号从那一个地方传回来。现在隐1 同时喂给猫和狗两个神经元,两边都会对它提要求 —— 而且要求可能不一样。
那么隐1 到底该听谁的?
加号是法则自带的,不是凑出来的
这个加号不是「有多条路径就加起来」的约定,而是多元链式法则直接给出的:
若 C 通过若干中间量
u₁, u₂, …依赖x,则∂C/∂x = Σⱼ (∂C/∂uⱼ)·(∂uⱼ/∂x)
本例里,直接用到 a1_1 的中间量有两个:z2_1(猫的加权和)和 z2_2(狗的加权和)。代进去:
∂C/∂a1_1 = (∂C/∂z2_1)·(∂z2_1/∂a1_1) + (∂C/∂z2_2)·(∂z2_2/∂a1_1) = δ_o1 × w2_11 + δ_o2 × w2_21有几个直接下游,就有几项。 输出层要是有 10 个神经元,这里就是 10 项相加。
上一篇的数字,是这个和的第一项
这是两个动画对齐带来的好处 —— 可以直接看出多出来的是什么。
第一轮,隐1 的两条路径:
猫这条:δ_o1 × w2_11 = −0.1837 × 0.7 = −0.1286 ← 上一篇的接力棒,一模一样狗这条:δ_o2 × w2_21 = +0.2364 × (−0.5) = −0.1182 ← 新增的 合计 = −0.2467上一篇隐1 的接力棒是 −0.1286,这里是 −0.2467,差不多翻倍。多出来的部分完完全全来自狗那条新路径。
所以单输出网络不是「没有求和」,而是求和只有一项 —— n=1 的特例,看不出是个求和。
符号:两条路径这次是同向的
值得看一眼的是符号怎么来的。
δ_o1 = −0.1837(负):猫的输出 0.613 低于目标 1.0,要往上抬δ_o2 = +0.2364(正):狗的输出 0.474 高于目标 0.0,要往下压
两个 δ 符号相反,但传到隐1 之后两项都是负的,叠加成了更大的负数。原因是权重:
猫:负 δ × 正权重 (+0.7) = 负狗:正 δ × 负权重 (−0.5) = 负 ← 权重把符号翻了过来上一篇讲的「误差信号穿过负权重会翻号」,在这里成了两条路径能同向叠加的原因。它们说的是同一件事:符号由权重决定,不由 δ 决定。
隐2 那边是反过来的,两项都是正的(+0.0735 和 +0.1418),合计 +0.2153。
求和之后,一切照旧
合并完接力棒,剩下的步骤和单输出时一字不差:
δ_h1 = −0.2467 × σ′(z1_1)=0.2098 = −0.0518然后各乘 x1 / x2 / 1 得到本层三个梯度,到输入层停止三轮之后:
| 第 1 轮 | 第 2 轮 | 第 3 轮 | 第 4 轮起点 | |
|---|---|---|---|---|
| 代价 | 0.3745 | 0.2238 | 0.1481 | 0.1071 |
| 猫(目标 1.0) | 0.613 | 0.695 | 0.747 | 0.782 |
| 狗(目标 0.0) | 0.474 | 0.362 | 0.290 | 0.244 |
两个输出各自朝自己的目标移动,互不打架。
小结
和单输出网络的唯一区别,就是把接力棒从「一项」换成「几项相加」。其余每一步 —— 算 δ、乘斜率、乘激活值、更新参数 —— 全都一样。
真实网络里每层几百个神经元,求和项从 2 项变成几百项,性质不变。矩阵乘法做的就是这件事:Σⱼ δⱼ·wⱼ 写成矩阵形式,就是权重矩阵的转置乘以 δ 向量。
到这里,3B1B 前四章的内容就都落到了能跑的数值上。下一步是 Karpathy 的 micrograd:用代码实现 Value 类和自动求导,这些手算的梯度将由 .backward() 直接算出。