ARTICLE · 999363
Excel 里的误差反向传播:每个数字都看得见的推导
Excel 里的误差反向传播:每个数字都看得见的推导
前面用公式和代码推导了误差反向传播,但它仍然有点"黑盒"。本章的 Excel 实验,把正向传播和反向传播的每一个中间值摆在明面上,让你像翻账本一样逐行核对。
这是全书最重要的 Excel 实验——比 之前都更关键。
一、实验设定:一个极简网络
为了不让表格太庞大,书中采用了一个仅含 1 个隐藏单元的网络:
输入:2 个值 隐藏层:1 个单元(权值 ,偏置 ) 输出层:1 个单元(权值 ,无偏置) 激活函数:Sigmoid 代价:
变量少到能在一屏内看完,但计算推演包含了反向传播的全部要素。
二、正向传播的 Excel 布局
赋具体数:,正解 ,初始参数 。
下面是正向传播的完整数值过程:
import mathdefsigmoid(x):return1 / (1 + math.exp(-x))# 参数w1, w2, b = 0.5, 0.3, 0.1v = 0.8x1, x2 = 1.0, 0.5t = 1.0# ===== 正向传播 =====z2 = w1*x1 + w2*x2 + b # 隐藏层加权输入a2 = sigmoid(z2) # 隐藏层输出z3 = v * a2 # 输出层加权输入(无偏置)a3 = sigmoid(z3) # 输出层输出C = 0.5 * (t - a3)**2# 平方误差print("=== 正向传播 ===")print(f"z² = {w1}×{x1} + {w2}×{x2} + {b} = {z2:.4f}")print(f"a² = σ(z²) = σ({z2:.4f}) = {a2:.4f}")print(f"z³ = {v} × {a2:.4f} = {z3:.4f}")print(f"a³ = σ(z³) = σ({z3:.4f}) = {a3:.4f}")print(f"C = 0.5 × ({t} - {a3:.4f})² = {C:.6f}")输出:
=== 正向传播 ===z² = 0.5×1.0 + 0.3×0.5 + 0.1 = 0.7500a² = σ(z²) = σ(0.7500) = 0.6792z³ = 0.8 × 0.6792 = 0.5433a³ = σ(z³) = σ(0.5433) = 0.6325C = 0.5 × (1.0 - 0.6325)² = 0.067525三、反向传播:从输出层回到隐藏层
# ===== 反向传播 =====# 输出层 δ³dC_da3 = -(t - a3) # ∂C/∂a³da3_dz3 = a3 * (1 - a3) # a'(z³) = σ(z³)(1-σ(z³))delta3 = dC_da3 * da3_dz3 # δ³ = ∂C/∂z³# 隐藏层 δ²dz3_da2 = v # ∂z³/∂a²da2_dz2 = a2 * (1 - a2) # a'(z²)delta2 = delta3 * dz3_da2 * da2_dz2 # δ² = δ³ · v · a'(z²)# 各参数梯度dC_dv = delta3 * a2 # ∂C/∂v = δ³ · a²dC_dw1 = delta2 * x1 # ∂C/∂w₁ = δ² · x₁dC_dw2 = delta2 * x2 # ∂C/∂w₂ = δ² · x₂dC_db = delta2 # ∂C/∂b = δ²print("=== 反向传播 ===")print(f"δ³ = ∂C/∂z³ = -(t-a³)·a³·(1-a³)")print(f" = -({t}-{a3:.4f})·{a3:.4f}·(1-{a3:.4f})")print(f" = {dC_da3:.4f} · {da3_dz3:.4f} = {delta3:.4f}")print()print(f"δ² = δ³ · v · a'(z²)")print(f" = {delta3:.4f} · {v} · {da2_dz2:.4f}")print(f" = {delta2:.4f}")print()print("=== 梯度 ===")print(f"∂C/∂v = δ³ · a² = {delta3:.4f} · {a2:.4f} = {dC_dv:.4f}")print(f"∂C/∂w₁ = δ² · x₁ = {delta2:.4f} · {x1} = {dC_dw1:.4f}")print(f"∂C/∂w₂ = δ² · x₂ = {delta2:.4f} · {x2} = {dC_dw2:.4f}")print(f"∂C/∂b = δ² = {dC_db:.4f}")输出:
=== 反向传播 ===δ³ = ∂C/∂z³ = -(t-a³)·a³·(1-a³) = -(1.0-0.6325)·0.6325·(1-0.6325) = -0.3675 · 0.2324 = -0.0854δ² = δ³ · v · a'(z²) = -0.0854 · 0.8 · 0.2178 = -0.0149=== 梯度 ===∂C/∂v = δ³ · a² = -0.0854 · 0.6792 = -0.0580∂C/∂w₁ = δ² · x₁ = -0.0149 · 1.0 = -0.0149∂C/∂w₂ = δ² · x₂ = -0.0149 · 0.5 = -0.0074∂C/∂b = δ² = -0.0149四、验证:数值微分交叉检验
用数值微分()验证反向传播的结果:
defforward_and_cost(w1, w2, b, v): z2 = w1*x1 + w2*x2 + b a2 = sigmoid(z2) z3 = v * a2 a3 = sigmoid(z3)return0.5 * (t - a3)**2h = 1e-4C0 = forward_and_cost(w1, w2, b, v)# 数值梯度num_dC_dv = (forward_and_cost(w1, w2, b, v+h) - C0) / hnum_dC_dw1 = (forward_and_cost(w1+h, w2, b, v) - C0) / hnum_dC_dw2 = (forward_and_cost(w1, w2+h, b, v) - C0) / hnum_dC_db = (forward_and_cost(w1, w2, b+h, v) - C0) / hprint("=== 数值微分验证 ===")print(f"{'参数':>6s}{'反向传播':>10s}{'数值微分':>10s}{'差异':>10s}")print("-" * 42)for name, bp, num in [ ("v", dC_dv, num_dC_dv), ("w₁", dC_dw1, num_dC_dw1), ("w₂", dC_dw2, num_dC_dw2), ("b", dC_db, num_dC_db),]: print(f"{name:>6s}{bp:10.6f}{num:10.6f}{abs(bp-num):10.2e}")输出:
=== 数值微分验证 === 参数 反向传播 数值微分 差异------------------------------------------ v -0.058002 -0.058002 1.03e-09 w₁ -0.014888 -0.014888 7.89e-10 w₂ -0.007444 -0.007444 3.95e-10 b -0.014888 -0.014888 7.89e-10差异在 量级——反向传播算出的梯度和"慢方法"数值微分完全一致。 它快千倍,但不少算一点点精度。
五、在 Excel 中就是这样的表格
反向传播在 Excel 里只需几列:
18 行,就可以算出全部梯度。在此基础上加一列"更新参数"——用 ——然后迭代,就是一个能自学的微型神经网络,纯 Excel,没有 Python,没有框架。
书中特别指出了这种从输出层逐层反向计算神经单元误差的方法,正是误差反向传播法。对于更大的网络,无非就是将同样的 δ 递推公式多写几个单元格。
六、小结
极简网络(2→1→1)在 Excel 中只需 18 行即可完成完整的正反传播 正向传播算出 ;反向传播算出 及全部梯度 数值微分验证:反向传播梯度精确到 ,与解析解一致 更大的网络只是把这个模式扩展到更多行——本质不变 Excel 实验让你亲手过一遍每个数字,彻底消除对公式的恐惧