用 Excel 逐步展示 MLP 的前向传播、反向传播和参数更新

配套 Excel 文件:MLP计算过程.xlsx

这篇文章配合 Excel 阅读,不写成纯理论推导。

为避免公式在不同博客平台、Windows 编辑器或 Markdown 预览器中出现乱码,本文的公式全部使用 ASCII 写法。例如:

eta        表示学习率
alpha      表示 Leaky ReLU 负半轴斜率
delta_out  表示输出层误差信号
delta_h    表示隐藏层误差信号
sum(...)   表示求和
*          表示乘法

1. 这个 Excel 展示了什么

目标是用 Excel 把一个很小的 MLP,也就是多层感知机,完整拆开。

它展示的不是训练很多轮之后的模型,而是展示“一次样本输入之后,模型如何完成一次前向传播、如何计算损失、如何反向传播得到梯度、如何根据梯度更新参数”。

简单说,这份 Excel 展示了下面这条链路:

输入样本
-> 隐藏层线性组合
-> 隐藏层激活输出
-> 输出层线性组合
-> 最终预测
-> 损失
-> 输出层梯度
-> 隐藏层梯度
-> 参数更新前后对比

2. 先理解网络结构

本例中的 MLP 非常小,结构如下:

1 个输入特征
6 个隐藏层神经元
1 个输出层神经元

也可以写成:

x
-> h1, h2, h3, h4, h5, h6
-> y_hat

其中:

  • x 是输入样本。
  • h1 到 h6 是隐藏层的 6 个神经元。
  • y_hat 是模型最终预测值。
  • y 是真实目标值。

本例使用的激活函数是 Leaky ReLU。为了避免公式乱码,本文用 alpha 表示 Leaky ReLU 在负半轴的斜率。

Leaky ReLU 的定义如下:

leaky_relu(z) = z,                 if z > 0
leaky_relu(z) = alpha * z,         if z <= 0

本例中:

alpha = 0.05

所以,如果某个神经元的线性组合结果是正数,激活输出就是它本身;如果是负数,就乘以 0.05,保留一个较小的负值。

3. 参数与样本

3.1 样本和超参数

本例的输入、目标和学习率如下:

项目数值
输入样本 x5
真实目标 y208
学习率 eta0.01
Leaky ReLU 负半轴斜率 alpha0.05

这里的学习率 eta 会在参数更新时使用:

new_parameter = old_parameter - eta * gradient

3.2 隐藏层参数

每个隐藏层神经元都有两个参数:

W_xh_j  输入 x 到第 j 个隐藏神经元的权重
b_h_j   第 j 个隐藏神经元的偏置

Excel 中整理出的初始参数如下:

隐藏神经元W_xhb_h
h11010
h22010
h33010
h42010
h51010
h62010

3.3 输出层参数

每个隐藏层神经元到输出层都有一个权重:

W_ho_j  第 j 个隐藏神经元到输出层的权重

对应数值如下:

隐藏神经元W_ho
h110
h220
h310
h410
h530
h620

输出层还有一个偏置:

b_out = 20

4. 前向传播

前向传播的作用是:把输入 x 一步一步算成预测值 y_hat,再用预测值和真实值计算损失。

4.1 第一步:隐藏层线性组合

对每个隐藏层神经元,都先计算一个线性组合:

z_h_j = x * W_xh_j + b_h_j

这里:

  • z_h_j 表示第 j 个隐藏神经元的线性组合结果。
  • x 是输入样本。
  • W_xh_j 是输入到该隐藏神经元的权重。
  • b_h_j 是该隐藏神经元的偏置。

以 h1 为例:

z_h_1 = x * W_xh_1 + b_h_1
z_h_1 = 5 * 10 + 10
z_h_1 = 60

6 个隐藏层神经元的线性组合结果如下:

隐藏神经元计算过程z_h
h15 * 10 + 1060
h25 * 20 + 10110
h35 * 30 + 10160
h45 * 20 + 10110
h55 * 10 + 1060
h65 * 20 + 10110

4.2 第二步:隐藏层激活

隐藏层线性组合之后,要经过 Leaky ReLU 激活函数:

a_h_j = leaky_relu(z_h_j)

也就是:

a_h_j = z_h_j,             if z_h_j > 0
a_h_j = alpha * z_h_j,     if z_h_j <= 0

本例中所有 z_h_j 都是正数,所以激活函数不会改变数值:

隐藏神经元z_ha_h
h16060
h2110110
h3160160
h4110110
h56060
h6110110

这里的 a_h 是隐藏层输出,它会作为输出层的输入。

4.3 第三步:输出层线性组合

输出层把 6 个隐藏层输出加权求和,然后加上输出层偏置。

公式如下:

z_out = sum(a_h_j * W_ho_j) + b_out

把本例的数值代入:

z_out =
  60  * 10
+ 110 * 20
+ 160 * 10
+ 110 * 10
+ 60  * 30
+ 110 * 20
+ 20

逐项计算:

60  * 10 = 600
110 * 20 = 2200
160 * 10 = 1600
110 * 10 = 1100
60  * 30 = 1800
110 * 20 = 2200
b_out    = 20

所以:

z_out = 600 + 2200 + 1600 + 1100 + 1800 + 2200 + 20
z_out = 9520
```excel
=SUMPRODUCT(hidden_activation_range, output_weight_range) + output_bias

4.4 第四步:最终预测

输出层线性组合之后,仍然经过 Leaky ReLU:

y_hat = leaky_relu(z_out)

因为本例中:

z_out = 9520

它是正数,所以:

y_hat = 9520

这就是当前模型对样本 x = 5 的预测值。

4.5 第五步:计算损失

真实目标值是:

y = 208

预测值是:

y_hat = 9520

本 Excel 使用平方损失:

Loss = (y_hat - y)^2

代入数值:

Loss = (9520 - 208)^2
Loss = 9312^2
Loss = 86713344

这个损失非常大,说明当前初始参数让模型预测得过大。

4.6 前向传播小结

前向传播最终得到:

项目数值
z_out9520
y_hat9520
y208
Loss86713344

到这里,模型已经知道“自己预测错了多少”。下一步反向传播要解决的是:每个参数应该为这个错误承担多少责任。

5. 反向传播

反向传播的目标是计算梯度。

梯度可以理解为:

如果某个参数变大一点,损失会怎样变化。

如果梯度是正数,说明这个参数继续变大会让损失变大,所以更新时要把它减小。

如果梯度是负数,说明这个参数继续变大会让损失变小,所以更新时会把它增大。

本例中大多数梯度是正数,因为预测值 9520 远大于真实值 208,模型需要把很多参数往减小输出的方向调整。

6. 先计算输出层误差信号 delta_out

损失函数是:

Loss = (y_hat - y)^2

先求损失对预测值的导数:

dLoss_dy_hat = 2 * (y_hat - y)

代入数值:

dLoss_dy_hat = 2 * (9520 - 208)
dLoss_dy_hat = 2 * 9312
dLoss_dy_hat = 18624

然后考虑输出层激活函数的导数。

输出层有:

y_hat = leaky_relu(z_out)

因为:

z_out = 9520 > 0

所以:

dy_hat_dz_out = 1

输出层误差信号定义为:

delta_out = dLoss_dy_hat * dy_hat_dz_out

代入数值:

delta_out = 18624 * 1
delta_out = 18624

7. 输出层权重梯度

输出层的公式是:

z_out = sum(a_h_j * W_ho_j) + b_out

对于某个输出层权重 W_ho_j,它的梯度是:

dLoss_dW_ho_j = a_h_j * delta_out

为什么是这样?

因为 W_ho_j 只通过这一项影响输出层:

a_h_j * W_ho_j

所以 W_ho_j 对 z_out 的影响大小就是 a_h_j。再乘上输出层误差信号 delta_out,就得到损失对这个权重的梯度。

输出层 6 个权重的梯度如下:

权重a_hdelta_out梯度 dLoss_dW_ho
W_ho_160186241117440
W_ho_2110186242048640
W_ho_3160186242979840
W_ho_4110186242048640
W_ho_560186241117440
W_ho_6110186242048640

以 W_ho_1 为例:

dLoss_dW_ho_1 = a_h_1 * delta_out
dLoss_dW_ho_1 = 60 * 18624
dLoss_dW_ho_1 = 1117440

8. 输出层偏置梯度

输出层线性组合中,偏置是直接加上的:

z_out = sum(a_h_j * W_ho_j) + b_out

b_out 每增加 1,z_out 就增加 1,所以:

dz_out_db_out = 1

因此输出层偏置梯度就是:

dLoss_db_out = delta_out
dLoss_db_out = 18624

9. 隐藏层误差信号 delta_h

隐藏层梯度要比输出层多一步,因为隐藏层不是直接产生最终预测,而是先影响输出层,再影响损失。

隐藏层误差信号的公式是:

delta_h_j = delta_out * W_ho_j * f_prime_z_h_j

其中:

  • delta_out 表示输出层错了多少。
  • W_ho_j 表示第 j 个隐藏神经元对输出层影响有多大。
  • f_prime_z_h_j 表示隐藏层激活函数在当前位置的导数。

本例中所有隐藏层的 z_h 都是正数,所以 Leaky ReLU 的导数都是:

f_prime_z_h_j = 1

因此隐藏层误差信号变成:

delta_h_j = delta_out * W_ho_j

逐个计算:

隐藏神经元delta_outW_hof_prime_z_hdelta_h
h118624101186240
h218624201372480
h318624101186240
h418624101186240
h518624301558720
h618624201372480

以 h5 为例:

delta_h_5 = delta_out * W_ho_5 * f_prime_z_h_5
delta_h_5 = 18624 * 30 * 1
delta_h_5 = 558720

这说明 h5 这条路径对输出影响很大,因为它连接到输出层的权重 W_ho_5 = 30,所以它分到的误差信号也更大。

10. 隐藏层输入权重梯度

隐藏层线性组合是:

z_h_j = x * W_xh_j + b_h_j

对于隐藏层输入权重 W_xh_j,梯度公式是:

dLoss_dW_xh_j = x * delta_h_j

因为本例中:

x = 5

所以每个隐藏层输入权重梯度就是:

dLoss_dW_xh_j = 5 * delta_h_j

逐个计算:

权重xdelta_h梯度 dLoss_dW_xh
W_xh_15186240931200
W_xh_253724801862400
W_xh_35186240931200
W_xh_45186240931200
W_xh_555587202793600
W_xh_653724801862400

以 W_xh_2 为例:

dLoss_dW_xh_2 = x * delta_h_2
dLoss_dW_xh_2 = 5 * 372480
dLoss_dW_xh_2 = 1862400

11. 隐藏层偏置梯度

隐藏层线性组合中,偏置也是直接加上的:

z_h_j = x * W_xh_j + b_h_j

所以:

dz_h_j_db_h_j = 1

因此隐藏层偏置梯度就是:

dLoss_db_h_j = delta_h_j

逐个结果如下:

偏置梯度 dLoss_db_h
b_h_1186240
b_h_2372480
b_h_3186240
b_h_4186240
b_h_5558720
b_h_6372480

12. 参数更新

参数更新使用同一个规则:

new_parameter = old_parameter - eta * gradient

本例中:

eta = 0.01

所以更新幅度是:

adjustment = eta * gradient

再从旧参数中减去这个调整幅度。

12.1 输出层权重更新

以 W_ho_1 为例:

old W_ho_1 = 10
gradient = 1117440
eta = 0.01
adjustment = 0.01 * 1117440
adjustment = 11174.4
new W_ho_1 = 10 - 11174.4
new W_ho_1 = -11164.4

输出层权重更新结果如下:

参数旧值梯度调整幅度更新后
W_ho_110111744011174.4-11164.4
W_ho_220204864020486.4-20466.4
W_ho_310297984029798.4-29788.4
W_ho_410204864020486.4-20476.4
W_ho_530111744011174.4-11144.4
W_ho_620204864020486.4-20466.4

12.2 输出层偏置更新

输出层偏置更新如下:

old b_out = 20
gradient = 18624
eta = 0.01
adjustment = 0.01 * 18624
adjustment = 186.24
new b_out = 20 - 186.24
new b_out = -166.24

12.3 隐藏层输入权重更新

隐藏层输入权重也使用同一个规则:

new W_xh_j = old W_xh_j - eta * dLoss_dW_xh_j

更新结果如下:

参数旧值梯度调整幅度更新后
W_xh_1109312009312-9302
W_xh_220186240018624-18604
W_xh_3309312009312-9282
W_xh_4209312009312-9292
W_xh_510279360027936-27926
W_xh_620186240018624-18604

12.4 隐藏层偏置更新

隐藏层偏置更新公式:

new b_h_j = old b_h_j - eta * dLoss_db_h_j

更新结果如下:

参数旧值梯度调整幅度更新后
b_h_1101862401862.4-1852.4
b_h_2103724803724.8-3714.8
b_h_3101862401862.4-1852.4
b_h_4101862401862.4-1852.4
b_h_5105587205587.2-5577.2
b_h_6103724803724.8-3714.8

13. 为什么这里的参数会变成很大的负数

有同学可能会疑惑:为什么更新后很多参数一下变成很大的负数?

原因有两个:

第一,本例预测值远大于真实值:

y_hat = 9520
y = 208

误差很大:

y_hat - y = 9312

第二,本例没有做归一化,也没有把损失写成 0.5 * error^2,所以导数比较大:

dLoss_dy_hat = 2 * 9312 = 18624

隐藏层和输出层权重又会继续放大梯度。例如:

dLoss_dW_ho_3 = a_h_3 * delta_out
dLoss_dW_ho_3 = 160 * 18624
dLoss_dW_ho_3 = 2979840

学习率虽然只有 0.01,但乘上几百万级别的梯度后,调整幅度仍然很大:

adjustment = 0.01 * 2979840
adjustment = 29798.4

所以更新后参数会发生大幅变化。

这也是 Excel 教学展示的价值:它能让我们直接看到梯度为什么大、更新为什么大,而不是只记住一个抽象公式。

14. 一句话总结

这份 Excel 展示的是 MLP 的一次完整计算:

前向传播:用当前参数算出预测值和损失。
反向传播:从损失开始,计算每个参数的梯度。
参数更新:用 new = old - eta * gradient 更新参数。

如果只记住一个核心逻辑,可以记成:

先算模型错了多少,再算每个参数对错误负多少责任,最后按责任大小调整参数。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐