1. 前向传播
神经元的输入值为$x$,输出值为$f(x+b)$,其中$f$叫做该神经元的激活函数,$b$叫做神经元的阈值(偏置),$f(x+b)$叫神经元的激活值。常用的激活函数有$sigmoid$、$relu$等。
(j)
(layer L ) ....O.......
\
weight(i, j)
\
(layer L+1) .........O..
(i)
我们关注第$(L+1)$层的第$i$个神经元,它与前一层$(L)$的第$j$个神经元相连接,该连接的权重为$\omega_{ij}$,那么神经元$j$的激活值$a^L_j$传导到神经元$i$时为$\omega_{ij}a^L_i$。神经元$i$与第$(L)$层的每一个神经元都有连接,它们对神经元$i$的输入都有贡献,考虑到神经元$i$本身的阈值,第$(L+1)$层神经元$i$最终的激活值为
$$
a^{L+1}_i = f(\sum_{j=1}^{N_L} \omega_{ij}a^L_j + b^{L+1}_i)
$$
写成向量相乘的形式
$$
a^{L+1}_i = f(
\begin{bmatrix}
\omega_{i1} & ... & \omega_{iN_L}
\end{bmatrix}
\begin{bmatrix}
a_1^L \\
... \\
a_{N_L}^L
\end{bmatrix}
+ b_i^{L+1}
)
$$
根据上式,我们就可以写出第$(L+1)$层所有神经元的激活值的矩阵形式
$$
\begin{bmatrix}
a_1^{L+1} \\
... \\
a_{N_{L+1}}^{L+1}
\end{bmatrix}
=
f(
\begin{bmatrix}
\omega_{11} & ... & \omega_{1N_L} \\
... & & ... \\
\omega_{N_{L+1}1} & ... & \omega_{N_{L+1}N_L}
\end{bmatrix}
\begin{bmatrix}
a_1^L \\
... \\
a_{N_L}^L
\end{bmatrix}
+
\begin{bmatrix}
b_1^{L+1} \\
... \\
b_{N_{L+1}^{L+1}}
\end{bmatrix}
)
$$
$$
\vec{a}^{L+1} = f(W \vec{a}^L + \vec{b}^{L+1})
$$
我们为了方便描述,把作用在向量上的激活函数记作如下形式
$$
f(\vec x) = \begin{bmatrix} f(x_1) \\ ... \\ f(x_n) \end{bmatrix}
$$
这样,在已知每一层的权重矩阵和阈值向量的情况下,我们就可以从输入层开始,一层一层地计算每层神经元的激活值。
2. 损失函数
假设神经网络的输出层有$N$个神经元,那么输入给神经网络数据后,我们将最终得到$N$个激活值$a_1$~$a_N$。如何评估神经网络的好坏呢?我们使用神经网络输出的激活值$a_i$与我们希望得到的激活值$y_i$之间的差距来作为评估依据,差距越小,说明网络越好;反之差距越大,说明网络越不好。
直接作差会面临符号问题,所以我们对差进行平方。将输出层所有神经元的计算结果求和,再乘以1/2(后文会说明为什么这样做),给这个量起一个名字,叫做损失函数$C$,它描述了神经网络训练结果的好坏。
$$
C = \frac12 \sum_{i = 1}^N (a_i - y_i)^2
$$
其中,1/2这个常数并不是一开始就存在的,而是人们后续在进行大量的梯度计算时,发现如果损失函数这里多个1/2,就可以把求导生成的2消掉,非常方便,所以就在定义中加入了1/2。
我们训练神经网络时,不能直接调节神经元的激活值,只能调节网络中的权重和阈值。因此损失函数也是神经网络中所有权重和阈值的函数
$$
C = C(\omega_{ij} \space,\space ... \space,\space b_k \space,\space ...)
$$
3. 梯度下降
有了损失函数就可以量化评估神经网络训练结果的好坏了,同时也就有了神经网络的优化指南:我们想让损失函数越小越好。
想让一个函数变得越小越好,我们自然会想直接找到其一阶导数为零的点,这个点就是极小值点,若损失函数本身是凸函数,则该点就是最小值点。但是损失函数是网络中所有权重和阈值的函数,所以直接求解所有偏导数为零的方程组几乎是不可能完成的事情(例如DeepSeek-R1模型有约$6.7\times 10^{11}$个变量)。
我们换一个思路,损失函数有个非常明显的特点——函数值非负。因此我们如果每次都找一个比当前函数值小一点点(非无穷小)的值,那么在有限步数内,一定能找到一个极小值。所以我们只需要在附近找一个比当前函数值更小的点,然后重复这个过程即可。因为梯度向量是函数值上升最快的单位方向向量,所以比起漫无目的地找更小的函数值,我们直接往梯度向量相反的方向去找,效率最高。
$$
f_{min}(\vec{x} + \vec{e}) = f(\vec{x} + (- \nabla f))
$$
上式中的$\alpha \in (0, 1)$,叫做学习率,梯度下降每次只下降一点点,这样步长减小后下降的过程会变得更加稳定。
所谓的梯度下降,用一句话来概括就是:损失函数沿着其梯度相反的方向,函数值下降得最快。
4. 反向传播
我们知道了优化神经网络的核心算法是梯度下降,那具体要怎么做呢?考虑一个简单的例子:包含三层神经元的多层感知机,每层的神经元数量分别为2、3、2。
(k) o o (input layer)
xxx [weight(j, k)]
(j) o o o (hidden layer)
xxx [weight(i, j)]
(i) o o (output layer)
输入层用$K$表示,$k$为该层神经元索引,$b_k$为第$k$个神经元的阈值;隐藏层用$J$表示,$j$为该层神经元索引,$b_j$为第$j$个神经元的阈值;输出层用$I$表示,$i$为该层神经元索引,$b_i$为第$i$个神经元的阈值。
假设我们希望输出层第$i$个神经元的激活值为$y_i$,那么损失函数为
$$
C = \frac12 \sum_{i = 1}^2 (a_i - y_i)^2
$$
根据梯度下降思想,在$(\omega, b, C)$构成的相空间中,我们需要让当前状态往$(-\nabla C)$方向移动一小段距离,虽然梯度是单位向量,本身长度已经足够小,但是为了计算的稳定,我们仍然选择一个非常小的值$\alpha$作为步长,$\alpha$也被称为学习率。
$$
\Delta C (\omega, b) = -\alpha \nabla C
$$
这样我们就能求出每一个参数的调整量了,我们从后往前一层一层地看,这个计算过程叫做反向传播(BP)(Back Propagation)。
4.1 输出层
损失函数是输出层神经元激活值$a_i$的函数,而$a_i$又是其与前一层神经元之间连接的权重、阈值、前一层神经元的激活值的函数$a_i = a_i(\omega_{ij}, b_i, a_j)$。
根据前向传播的定义,
$$
a_i = f(\sum_{j = 1}^3 \omega_{ij}a_j + b_i) = f(z_i)
$$
我们可以求出以下偏导数
$$
\frac{\partial C}{\partial b_i} =
\frac{\partial C}{\partial a_i}
\frac{d a_i}{d z_i} \frac{\partial z_i}{\partial b_i} =
(a_i - y_i)f'(z_i)
$$
$$
\frac{\partial C}{\partial \omega_{ij}} =
\frac{\partial C}{\partial a_i}
\frac{da_i}{dz_i}
\frac{\partial z_i}{\partial \omega_{ij}} =
(a_i - y_i)f'(z_i) a_j
$$
$$
\frac{\partial C}{\partial a_j} =
\sum_{i = 1}^2
\frac{\partial C}{\partial a_i}
\frac{da_i}{dz_i}
\frac{\partial z_i}{\partial a_j} =
\sum_{i = 1}^2 (a_i - y_i)f'(z_i) \omega_{ij}
$$
其中,$f'$表示激活函数$f$的导函数。
对$a_j$求偏导数的时候,要注意到下标$j$与$i$无关,对于$i\in[1, 2]$,每一个$a_i$的计算中都包含了$a_j$,因此,若损失函数对与$i$无关的变量求偏导数,则结果应该是对$i$求和的。
4.2 隐藏层
前面求出了损失函数对隐藏层神经元激活值的偏导数$\frac{\partial C}{\partial a_j}$,但我们不能直接修改神经元的激活值,而应该修改隐藏层神经元激活值所依赖的$(\omega_{jk}, b_j, a_k)$。
$$
a_j = f(\sum_{k = 1}^2 \omega_{jk}a_k + b_j) = f(z_j)
$$
与前一层类似,我们可以求出隐藏层各项参数的偏导数
$$
\frac{\partial C}{\partial b_j} =
\frac{\partial C}{\partial a_j}
\frac{d a_j}{dz_j}
\frac{\partial z_j}{\partial b_j} =
\frac{\partial C}{\partial a_j} f'(z_j)
$$
$$
\frac{\partial C}{\partial \omega_{jk}} =
\frac{\partial C}{\partial a_j}
\frac{d a_j}{dz_j}
\frac{\partial z_j}{\partial \omega_{jk}} =
\frac{\partial C}{\partial a_j} f'(z_j) a_k
$$
$$
\frac{\partial C}{\partial a_k} =
\sum_{j = 1}^3
\frac{\partial C}{\partial a_j}
\frac{d a_j}{d z_j}
\frac{\partial z_j}{\partial a_k} =
\sum_{j = 1}^3
\frac{\partial C}{\partial a_j} f'(z_j) \omega_{jk}
$$
与前文类似,求$a_k$的偏导数时,由于每一个$a_j(j\in[1,3])$中都包含了$a_k$(见$a_j$的计算过程),所以结果应该对$j$求和。若隐藏层不止一层,则每一层的诸项偏导数都可以套用上述形式,换句话说,上述偏导数形式是递归的。
4.3 输入层
该层神经元将输入给神经网络的值直接向后传递,不进行激活操作;换句话说,该层的神经元没有偏置,没有激活函数,也没有与前一层连接的权重。
$$
a_{in} = input
$$
该层无可调节参数,不进行反向传播。
5. 矩阵形式
我们前面提到了神经网络前向传播的矩阵形式,在这个三层MLP的例子中,用$I$、$J$、$K$分别表示输出层、隐藏层、输入层。
5.1 前向传播
$$
\begin{bmatrix}
a_1^I \\
a_2^I
\end{bmatrix}
=
f(
\begin{bmatrix}
\omega_{11}^{IJ} & \omega_{12}^{IJ} & \omega_{13}^{IJ} \\
\omega_{21}^{IJ} & \omega_{22}^{IJ} & \omega_{23}^{IJ} \\
\end{bmatrix}
\begin{bmatrix}
a_1^J \\
a_2^J \\
a_3^J
\end{bmatrix}
+
\begin{bmatrix}
b_1^I \\
b_2^I
\end{bmatrix}
)
$$
$$
\begin{bmatrix}
a_1^J \\
a_2^J \\
a_3^J
\end{bmatrix}
=
f(
\begin{bmatrix}
\omega_{11}^{JK} & \omega_{12}^{JK} \\
\omega_{21}^{JK} & \omega_{22}^{JK} \\
\omega_{31}^{JK} & \omega_{32}^{JK}
\end{bmatrix}
\begin{bmatrix}
a_1^K \\
a_2^K
\end{bmatrix}
+
\begin{bmatrix}
b_1^J \\
b_2^J \\
b_3^J
\end{bmatrix}
)
$$
$$
\begin{bmatrix}
a_1^K \\
a_2^K
\end{bmatrix}
=
\begin{bmatrix}
input_1 \\
input_2
\end{bmatrix}
$$
5.2 反向传播
由损失函数对输出层参数的偏导数构成的2个矩阵为
$$
\Delta W_{IJ} =
\begin{bmatrix}
(a_1^I - y_1)f^{\prime I}(z_1) a_1^J & (a_1 - y_1)f^{\prime I}(z_1) a_2^J & (a_1 - y_1)f^{\prime I}(z_1) a_3^J \\
(a_2^I - y_2)f^{\prime I}(z_2) a_1^J & (a_2 - y_2)f^{\prime I}(z_2) a_2^J & (a_2 - y_2)f^{\prime I}(z_2) a_3^J
\end{bmatrix}
$$
$$
\vec{\Delta b_I} =
\begin{bmatrix}
(a_1^I - y_1)f^{\prime I}(z_1) \\
(a_2^I - y_2)f^{\prime I}(z_2)
\end{bmatrix}
$$
这个形式很有规律,我们可以写成如下形式
$$
\Delta W_{IJ} =
\begin{bmatrix}
(a_1^I - y_1)f^{\prime I}(z_1) \\
(a_2^I - y_2)f^{\prime I}(z_2)
\end{bmatrix}
\begin{bmatrix}
a_1^J & a_2^J & a_3^J
\end{bmatrix} =
\begin{bmatrix}
\delta_1^I \\
\delta_2^I
\end{bmatrix}
\begin{bmatrix}
a_1^J & a_2^J & a_3^J
\end{bmatrix}
$$
$$
\vec{\Delta b_I} =
\begin{bmatrix}
(a_1^I - y_1)f^{\prime I}(z_1) \\
(a_2^I - y_2)f^{\prime I}(z_2)
\end{bmatrix} =
\begin{bmatrix}
\delta_1^I \\
\delta_2^I
\end{bmatrix}
$$
我们把其中的$\begin{bmatrix}\delta^I_1 \\ \delta^I_2\end{bmatrix}$称为误差向量$\vec{\delta^I}$,只与输出层有关;$\begin{bmatrix} a_1^J \\ a_2^J \\ a_3^J \end{bmatrix}$为上一层的激活值向量$\vec{a^J}$。那么偏导数矩阵$\Delta W_{IJ}$实际上是由$\vec{\delta^I}$和$\vec{a^J}$经过外积得到的二阶张量。
我们继续写出隐藏层的偏导数矩阵
$$
\Delta W_{JK} =
\begin{bmatrix}
\frac{\partial C}{\partial a_1^J}f^{\prime J}(z^J_1) a_1^K & \frac{\partial C}{\partial a_1^J}f^{\prime J}(z^J_1) a_2^K \\
\frac{\partial C}{\partial a_2^J}f^{\prime J}(z^J_2) a_1^K & \frac{\partial C}{\partial a_2^J}f^{\prime J}(z^J_2) a_2^K \\
\frac{\partial C}{\partial a_3^J}f^{\prime J}(z^J_3) a_1^K & \frac{\partial C}{\partial a_3^J}f^{\prime J}(z^J_3) a_2^K
\end{bmatrix} =
\begin{bmatrix}
\frac{\partial C}{\partial a_1^J}f^{\prime J}(z^J_1) \\
\frac{\partial C}{\partial a_2^J}f^{\prime J}(z^J_2) \\
\frac{\partial C}{\partial a_3^J}f^{\prime J}(z^J_3)
\end{bmatrix}
\begin{bmatrix}
a_1^K & a_2^K
\end{bmatrix} =
\begin{bmatrix}
\delta_1^J \\
\delta_2^J \\
\delta_3^J
\end{bmatrix}
\begin{bmatrix}
a_1^K & a_2^K
\end{bmatrix}
$$
$$
\vec{\Delta b_{J}} =
\begin{bmatrix}
\frac{\partial C}{\partial a_1^J}f^{\prime J}(z^J_1) \\
\frac{\partial C}{\partial a_2^J}f^{\prime J}(z^J_2) \\
\frac{\partial C}{\partial a_3^J}f^{\prime J}(z^J_3)
\end{bmatrix} =
\begin{bmatrix}
\delta_1^J \\
\delta_2^J \\
\delta_3^J
\end{bmatrix}
$$
其中,误差向量$\delta^J$这样计算
$$
\frac{\partial C}{\partial a_j} =
\sum_{i = 1}^2
\frac{\partial C}{\partial a_i}
\frac{da_i}{dz_i}
\frac{\partial z_i}{\partial a_j} =
\sum_{i = 1}^2 (a_i - y_i)f'(z_i) \omega_{ij}
$$
若隐藏层有多层,则非最末层隐藏层的误差向量$\delta^K$使用下面的形式递归计算
$$
\frac{\partial C}{\partial a_k} =
\sum_{j = 1}^3
\frac{\partial C}{\partial a_j}
\frac{d a_j}{d z_j}
\frac{\partial z_j}{\partial a_k} =
\sum_{j = 1}^3
\frac{\partial C}{\partial a_j} f'(z_j) \omega_{jk}
$$
总结(TODO)