用 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 样本和超参数
本例的输入、目标和学习率如下:
| 项目 | 数值 |
|---|---|
输入样本x | 5 |
真实目标y | 208 |
学习率eta | 0.01 |
Leaky ReLU 负半轴斜率alpha | 0.05 |
这里的学习率eta会在参数更新时使用:
new_parameter = old_parameter - eta * gradient3.2 隐藏层参数
每个隐藏层神经元都有两个参数:
W_xh_j 输入 x 到第 j 个隐藏神经元的权重 b_h_j 第 j 个隐藏神经元的偏置Excel 中整理出的初始参数如下:
| 隐藏神经元 | W_xh | b_h |
|---|---|---|
| h1 | 10 | 10 |
| h2 | 20 | 10 |
| h3 | 30 | 10 |
| h4 | 20 | 10 |
| h5 | 10 | 10 |
| h6 | 20 | 10 |
3.3 输出层参数
每个隐藏层神经元到输出层都有一个权重:
W_ho_j 第 j 个隐藏神经元到输出层的权重对应数值如下:
| 隐藏神经元 | W_ho |
|---|---|
| h1 | 10 |
| h2 | 20 |
| h3 | 10 |
| h4 | 10 |
| h5 | 30 |
| h6 | 20 |
输出层还有一个偏置:
b_out = 204.前向传播
前向传播的作用是:把输入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 = 606 个隐藏层神经元的线性组合结果如下:
| 隐藏神经元 | 计算过程 | z_h |
|---|---|---|
| h1 | 5 * 10 + 10 | 60 |
| h2 | 5 * 20 + 10 | 110 |
| h3 | 5 * 30 + 10 | 160 |
| h4 | 5 * 20 + 10 | 110 |
| h5 | 5 * 10 + 10 | 60 |
| h6 | 5 * 20 + 10 | 110 |
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_h | a_h |
|---|---|---|
| h1 | 60 | 60 |
| h2 | 110 | 110 |
| h3 | 160 | 160 |
| h4 | 110 | 110 |
| h5 | 60 | 60 |
| h6 | 110 | 110 |
这里的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_bias4.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_out | 9520 |
y_hat | 9520 |
y | 208 |
Loss | 86713344 |
到这里,模型已经知道“自己预测错了多少”。下一步反向传播要解决的是:每个参数应该为这个错误承担多少责任。
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 = 186247. 输出层权重梯度
输出层的公式是:
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_h | delta_out | 梯度 dLoss_dW_ho |
|---|---|---|---|
| W_ho_1 | 60 | 18624 | 1117440 |
| W_ho_2 | 110 | 18624 | 2048640 |
| W_ho_3 | 160 | 18624 | 2979840 |
| W_ho_4 | 110 | 18624 | 2048640 |
| W_ho_5 | 60 | 18624 | 1117440 |
| W_ho_6 | 110 | 18624 | 2048640 |
以W_ho_1为例:
dLoss_dW_ho_1 = a_h_1 * delta_out dLoss_dW_ho_1 = 60 * 18624 dLoss_dW_ho_1 = 11174408. 输出层偏置梯度
输出层线性组合中,偏置是直接加上的:
z_out = sum(a_h_j * W_ho_j) + b_outb_out每增加 1,z_out就增加 1,所以:
dz_out_db_out = 1因此输出层偏置梯度就是:
dLoss_db_out = delta_out dLoss_db_out = 186249. 隐藏层误差信号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_out | W_ho | f_prime_z_h | delta_h |
|---|---|---|---|---|
| h1 | 18624 | 10 | 1 | 186240 |
| h2 | 18624 | 20 | 1 | 372480 |
| h3 | 18624 | 10 | 1 | 186240 |
| h4 | 18624 | 10 | 1 | 186240 |
| h5 | 18624 | 30 | 1 | 558720 |
| h6 | 18624 | 20 | 1 | 372480 |
以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逐个计算:
| 权重 | x | delta_h | 梯度 dLoss_dW_xh |
|---|---|---|---|
| W_xh_1 | 5 | 186240 | 931200 |
| W_xh_2 | 5 | 372480 | 1862400 |
| W_xh_3 | 5 | 186240 | 931200 |
| W_xh_4 | 5 | 186240 | 931200 |
| W_xh_5 | 5 | 558720 | 2793600 |
| W_xh_6 | 5 | 372480 | 1862400 |
以W_xh_2为例:
dLoss_dW_xh_2 = x * delta_h_2 dLoss_dW_xh_2 = 5 * 372480 dLoss_dW_xh_2 = 186240011. 隐藏层偏置梯度
隐藏层线性组合中,偏置也是直接加上的:
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_1 | 186240 |
| b_h_2 | 372480 |
| b_h_3 | 186240 |
| b_h_4 | 186240 |
| b_h_5 | 558720 |
| b_h_6 | 372480 |
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_1 | 10 | 1117440 | 11174.4 | -11164.4 |
| W_ho_2 | 20 | 2048640 | 20486.4 | -20466.4 |
| W_ho_3 | 10 | 2979840 | 29798.4 | -29788.4 |
| W_ho_4 | 10 | 2048640 | 20486.4 | -20476.4 |
| W_ho_5 | 30 | 1117440 | 11174.4 | -11144.4 |
| W_ho_6 | 20 | 2048640 | 20486.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.2412.3 隐藏层输入权重更新
隐藏层输入权重也使用同一个规则:
new W_xh_j = old W_xh_j - eta * dLoss_dW_xh_j更新结果如下:
| 参数 | 旧值 | 梯度 | 调整幅度 | 更新后 |
|---|---|---|---|---|
| W_xh_1 | 10 | 931200 | 9312 | -9302 |
| W_xh_2 | 20 | 1862400 | 18624 | -18604 |
| W_xh_3 | 30 | 931200 | 9312 | -9282 |
| W_xh_4 | 20 | 931200 | 9312 | -9292 |
| W_xh_5 | 10 | 2793600 | 27936 | -27926 |
| W_xh_6 | 20 | 1862400 | 18624 | -18604 |
12.4 隐藏层偏置更新
隐藏层偏置更新公式:
new b_h_j = old b_h_j - eta * dLoss_db_h_j更新结果如下:
| 参数 | 旧值 | 梯度 | 调整幅度 | 更新后 |
|---|---|---|---|---|
| b_h_1 | 10 | 186240 | 1862.4 | -1852.4 |
| b_h_2 | 10 | 372480 | 3724.8 | -3714.8 |
| b_h_3 | 10 | 186240 | 1862.4 | -1852.4 |
| b_h_4 | 10 | 186240 | 1862.4 | -1852.4 |
| b_h_5 | 10 | 558720 | 5587.2 | -5577.2 |
| b_h_6 | 10 | 372480 | 3724.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 更新参数。如果只记住一个核心逻辑,可以记成:
先算模型错了多少,再算每个参数对错误负多少责任,最后按责任大小调整参数。