Skip to content

多层感知机/前馈神经网络

在绝大多数语境下, 多层感知机 (Multilayer Perception, MLP)全连接神经网络 (Fully Connected Neural Network, FCNN)前馈神经网络 (Feedforward Neural Network, FNN) 说的都是同一类东西。

待办事项/思考:

  • 关注 CrossEntropyLoss(交叉熵),思考它为什么比均方误差(MSE)在分类任务上收敛得更快。
  • 如果输入是高清图片,为什么 MLP 的参数量会迅速失控?
  • 为什么把像素打乱顺序后喂给 MLP,它的表现几乎没有变化?(这说明它无法感知图像的“位置”)。

单层神经网络

神经网络最早于1940年代就被提出,但长期都被否定和边缘化。神经网络的狂热信徒Hinton等人为了区分,提出了“深度学习”的概念。直到2012年AlexNet以压倒性优势获得ImageNet的冠军后,深度神经网络才再次回到人们讨论的主舞台上,并在后续的发展中展现出了惊人的水平。

本章节我将梳理神经网络的组成结构:神经元,以及神经网络的一些相关重要概念,说明神经网络架构如何从底层实现。

线性回归模型

在机器学习的笔记中我们已经讲解了线性回归和一些重要的概念,我们回顾一下最主要的内容。

在机器学习领域,我们通常使用的是高维数据集,建模时采用线性代数表示法会比较方便。当我们的输入包含\(d\)个特征时,我们将预测结果表示为:

\[ \hat{y} = w_1x_1 + \dots + w_dx_d + b. \]

将所有特征放到向量\(\mathbf{x} \in \mathbb{R}^d\)中,并将所有权重放到向量 \(\mathbf{w} \in \mathbb{R}^d\) 中,我们可以用点积形式来简洁地表达模型:

\[ \hat{y} = \mathbf{w}^\top \mathbf{x} + b \]

在上述公式中,向量 \(\mathbf{x}\) 对应于单个数据样本的特征。

用符号表示的矩阵 \(\mathbf{X} \in \mathbb{R}^{n \times d}\) 可以很快捷地引用我们整个数据集的 \(n\) 个样本。其中, \(\mathbf{X}\) 的每一行是一个样本,每一列是一种特征。对于特征集合 \(\mathbf{X}\),预测值 \(\hat{\mathbf{y}} \in \mathbb{R}^n\) 可以通过矩阵-向量乘法表示为:

\[ \hat{\mathbf{y}} = \mathbf{Xw} + b \]

损失函数 (loss function) 能够量化目标的实际值与预测值之间的差距。通常我们会选择非负数作为损失,且数值越小表示损失越小,完美预测时的损失为 0。

回归问题中最常用的损失函数是平方误差函数。当样本 \(i\) 的预测值为 \(\hat{y}^{(i)}\),其相应的真实标签为 \(y^{(i)}\) 时,平方误差可以定义为以下公式:

\[ l^{(i)}(\mathbf{w}, b) = \frac{1}{2} \left( \hat{y}^{(i)} - y^{(i)} \right)^2 \]

由于平方误差函数中的二次方项,估计值 \(\hat{y}^{(i)}\) 和观测值 \(y^{(i)}\) 之间较大的差异将导致更大的损失。为了度量模型在整个数据集上的质量,我们需要计算在训练集 \(n\) 个样本上的损失均值(也等价于求和)。

\[ L(\mathbf{w}, b) = \frac{1}{n} \sum_{i=1}^{n} l^{(i)}(\mathbf{w}, b) = \frac{1}{n} \sum_{i=1}^{n} \frac{1}{2} \left( \mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)} \right)^2 \]

在训练模型时,我们希望寻找一组参数\((\mathbf{w}^*, b^*)\),这组参数能最小化在所有训练样本上的总损失:

\[ \mathbf{w}^*, b^* = \operatorname*{argmin}_{\mathbf{w}, b} L(\mathbf{w}, b) \]

线性回归刚好是一个很简单的优化问题。与我们将在本节中讲到的其他大部分模型不同,线性回归的解可以用一个公式简单地表达出来,这类解叫作解析解 (analytical solution)。

我们将偏置 \(b\) 合并到参数 \(\mathbf{w}\) 中,合并方法是在包含所有参数的矩阵中附加一列。我们的预测问题是最小化 \(\| \mathbf{y} - \mathbf{Xw} \|^2\)。这在损失平面上只有一个临界点,这个临界点对应于整个区域的损失极小点。将损失关于 \(\mathbf{w}\) 的导数设为 0,得到解析解:

\[ \mathbf{w}^* = (\mathbf{X}^\top \mathbf{X})^{-1} \mathbf{X}^\top \mathbf{y} \]

不过,在绝大多数任务上,我们无法得到解析解。我们常用梯度下降的方法来优化深度学习模型,其最简单的用法是计算损失函数(数据集中所有样本的损失均值)关于模型参数的导数(在这里也可以称为梯度)。由于数据集可能很大,直接应用梯度下降会很慢,所以一般每次需要计算更新的时候随机抽取一小批样本,这种变体叫做小批量随机梯度下降 (minibatch stochastic gradient descent)。

在每次迭代中,我们首先随机抽样一个小批量\(\mathcal{B}\),它是由固定数量的训练样本组成的。然后,我们计算小批量的平均损失关于模型参数的导数(也可以称为梯度)。最后,我们将梯度乘以一个预先确定的正数\(\eta\)(被称为学习率),并从当前参数的值中减掉。我们用下面的数学公式来表示这一更新过程(\(\partial\)表示偏导数):

\[ (\mathbf{w}, b) \leftarrow (\mathbf{w}, b) - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{(\mathbf{w}, b)} l^{(i)}(\mathbf{w}, b) \]

总结一下,算法的步骤如下:

(1)初始化模型参数的值,如随机初始化;

(2)从数据集中随机抽取小批量样本且在负梯度的方向上更新参数,并不断迭代这一步骤。对于平方损失和仿射变换,我们可以明确地写成如下形式:

\[ \mathbf{w} \leftarrow \mathbf{w} - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{\mathbf{w}} l^{(i)}(\mathbf{w}, b) = \mathbf{w} - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \mathbf{x}^{(i)} \left( \mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)} \right) \]
\[ b \leftarrow b - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_b l^{(i)}(\mathbf{w}, b) = b - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \left( \mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)} \right) \]

在训练了预先确定的若干迭代次数后(或者直到满足某些其他停止条件后), 我们记录下模型参数的估计值,表示为\(\hat{\mathbf{w}}, \hat{b}\)。在训练了预先确定的若干迭代次数后(或者直到满足某些其他停止条件后), 我们记录下模型参数的估计值,表示为。 但是,即使我们的函数确实是线性的且无噪声,这些估计值也不会使损失函数真正地达到最小值。 因为算法会使得损失向最小值缓慢收敛,但却不能在有限的步数内非常精确地达到最小值。

线性回归恰好是一个在整个域中只有一个最小值的学习问题。 但是对像深度神经网络这样复杂的模型来说,损失平面上通常包含多个最小值。 深度学习实践者很少会去花费大力气寻找这样一组参数,使得在训练集上的损失达到最小。 事实上,更难做到的是找到一组参数,这组参数能够在我们从未见过的数据上实现较低的损失, 这一挑战被称为 泛化 (generalization)。


在现实中,真实值 \(y\) 和预测值 \(\hat{y}\) 之间总会有误差。我们假设目标值 \(y\) 与特征 \(\mathbf{x}\) 之间存在线性关系,并包含一个随机噪声 \(\epsilon\)

\[ y = \mathbf{w}^\top \mathbf{x} + b + \epsilon \]

其中\(\mathbf{w}, b\) 是我们要学习的参数。\(\epsilon\) 是无法被模型解释的随机噪声。我们假设噪声 \(\epsilon\) 服从 均值为 0、方差为 \(\sigma^2\) 的正态分布

\[ \epsilon \sim \mathcal{N}(0, \sigma^2) \]

这意味着给定输入 \(\mathbf{x}\),观测到特定 \(y\) 的概率密度函数为:

\[ P(y \mid \mathbf{x}) = \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{1}{2\sigma^2} (y - (\mathbf{w}^\top \mathbf{x} + b))^2\right) \]

为了让模型最符合观测到的 \(n\) 个样本,我们需要找到能使 整个数据集出现的联合概率 (似然函数)最大的参数:

\[ P(\mathbf{y} \mid \mathbf{X}) = \prod_{i=1}^{n} P(y^{(i)} \mid \mathbf{x}^{(i)}) \]

直接优化连乘公式很困难,我们通过取负对数(Negative Log) 将其简化为求和形式。最小化负对数似然等价于最大化似然:

\[ - \log P(\mathbf{y} \mid \mathbf{X}) = \sum_{i=1}^{n} \left[ \underbrace{\frac{1}{2}\log(2\pi\sigma^2)}_{\text{常数}} + \underbrace{\frac{1}{2\sigma^2} (y^{(i)} - (\mathbf{w}^\top \mathbf{x}^{(i)} + b))^2}_{\text{平方误差项}} \right] \]

由于第一项和系数 \(\frac{1}{2\sigma^2}\) 不影响 \(\mathbf{w}, b\) 的极值点位置,我们略去常数,这就推导出了在刚才的笔记中提到的损失函数平均平方误差 (MSE)

\[ L(\mathbf{w}, b) = \frac{1}{n} \sum_{i=1}^{n} \frac{1}{2} \left( \mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)} \right)^2 \]

对于简单模型,我们可以通过对公式直接求导并令导数为 0 得到:

\[ \mathbf{w}^* = (\mathbf{X}^\top \mathbf{X})^{-1} \mathbf{X}^\top \mathbf{y} \]

在线性回归中,我们的目标是让损失函数 \(L\) 减小。梯度下降公式如下:

\[ w = w - \eta \cdot \frac{\partial L}{\partial w} \]

梯度下降公式可以理解为反向传播加参数更新:

\[ \underbrace{w_{next}}_{\text{更新后的结果}} = \underbrace{w_{current}}_{\text{当前参数}} - \eta \cdot \underbrace{\frac{\partial L}{\partial w}}_{\text{反向传播}} \]

对于反向传播,一般我们用PyTorch的.backward()来完成。PyTorch 实际上是在执行微积分中的 链式法则 。它从最后的结果 \(L\) 开始,一步步往回求导:

\[ \frac{\partial L}{\partial w} = \underbrace{\frac{\partial L}{\partial \hat{y}}}_{\text{第一层:损失对预测值的导数}} \cdot \underbrace{\frac{\partial \hat{y}}{\partial w}}_{\text{第二层:预测值对参数的导数}} \]

如果我们代入具体的函数定义:

  1. 预测函数:\(\hat{y} = Xw + b\)
  2. 损失函数:\(L = \frac{1}{2}(\hat{y} - y)^2\)

那么对应的链式法则详细标注为:

\[ \frac{\partial L}{\partial w} = \underbrace{(\hat{y} - y)}_{\substack{\text{误差 (Error)} \\ \text{从损失函数求导而来}}} \cdot \underbrace{X}_{\substack{\text{特征输入 (Input)} \\ \text{从预测函数求导而来}}} \]

当神经元变多、层数变深时,链式法则就像一场“导数接力赛”。每一层都计算自己的局部梯度,然后乘以前面传过来的梯度,继续向后传递。假设我们有一个三层的神经网络(输入层 \(\rightarrow\) 隐藏层1 \(\rightarrow\) 隐藏层2 \(\rightarrow\) 输出层),每一层只有单个神经元:

  1. 第一层 (Hidden 1): \(h_1 = \sigma(w_1 \cdot x + b_1)\)
  2. 第二层 (Hidden 2): \(h_2 = \sigma(w_2 \cdot h_1 + b_2)\)
  3. 第三层 (Output): \(\hat{y} = w_3 \cdot h_2 + b_3\)
  4. 最终损失: \(L = \text{Loss}(\hat{y}, y)\)

(注:\(\sigma\) 是激活函数,如 ReLU 或 Sigmoid,这个后面会提到;数据从左往右流动。)

如果我们要计算 第一层权重 \(w_1\)* 的梯度,导数需要跨越整整三层。根据链式法则,*\(\frac{\partial L}{\partial w_1}\) 的完整公式如下:

\[ \frac{\partial L}{\partial w_1} = \underbrace{ \frac{\partial L}{\partial \hat{y}} }_{ \text{1. 损失对输出的导数} } \cdot \underbrace{ \frac{\partial \hat{y}}{\partial h_2} }_{ \text{2. 输出对层2的导数} } \cdot \underbrace{ \frac{\partial h_2}{\partial h_1} }_{ \text{3. 层2对层1的导数} } \cdot \underbrace{ \frac{\partial h_1}{\partial w_1} }_{ \text{4. 层1对参数 } w_1 \text{ 的导数} } \]

我们将每一项展开,每一层“接力棒”是:

\[ \frac{\partial L}{\partial w_1} = \underbrace{ (\hat{y} - y) }_{ \substack{\text{输出层的误差} \\ \text{(最末端的信号)}} } \cdot \underbrace{ w_3 }_{ \substack{\text{第三层的连接权重} \\ \text{(将信号传回层2)}} } \cdot \underbrace{ \sigma'(z_2) \cdot w_2 }_{ \substack{\text{第二层的权重与激活导数} \\ \text{(将信号传回层1)}} } \cdot \underbrace{ \sigma'(z_1) \cdot x }_{ \substack{\text{第一层的激活导数与输入} \\ \text{(最终算达目标 } w_1 \text{)}} } \]

可以看到,所有的反馈都源自最右端的误差 \((\hat{y} - y)\)。如果中间某一层的权重 \(w\) 非常小,或者激活函数的导数 \(\sigma'\) 接近 0,梯度就会在传递过程中消失(这就是著名的 梯度消失问题 )。

在多层网络中,每一层都有自己的权重。比如在三层网络中,有 \(w_3, w_2, w_1\)反向传播时 :信号从 \(L\) 出发,经过 \(w_3\),再经过 \(w_2\),最后到达 \(w_1\)。这一趟跑完,PyTorch 会为每一层都算出一个专属的梯度:\(\frac{\partial L}{\partial w_3}\)\(\frac{\partial L}{\partial w_2}\)\(\frac{\partial L}{\partial w_1}\)

当你调用优化器(比如 sgd)时,它会遍历网络中所有被标记为 requires_grad=True 的参数,并对它们统一执行减法操作:

\[ \begin{cases} w_3 = w_3 - \eta \cdot \frac{\partial L}{\partial w_3} \\ w_2 = w_2 - \eta \cdot \frac{\partial L}{\partial w_2} \\ w_1 = w_1 - \eta \cdot \frac{\partial L}{\partial w_1} \end{cases} \]

这意味着: 只要是从损失函数 \(L\) 能够通过路径回溯到的参数,都会在同一个 batch 训练中被更新。

这里有一个非常关键的点:只有在“计算路径”上的参数才会被更新。

在大模型中,我们通常是完成整条路径的 求导 (算出所有层的梯度),统一修改数值,而不是改完第三层再算第二层。

比方说,在反向传播过程中,所有的 \(w\)\(b\) 都必须保持原样不动。我们只是记录下:

  • \(w_3\) 对应的梯度是 \(g_3\)
  • \(w_2\) 对应的梯度是 \(g_2\)
  • \(w_1\) 对应的梯度是 \(g_1\)

此时,模型里的 \(w\) 还是那个旧的 \(w\)。等到所有人的责任(梯度)都记录在案了,我们才执行 optimizer.step() 或者你写的 sgd() 函数。这时,程序会像发工资(或扣工资)一样,统一执行:

  • \(w_3 = w_3 - \eta \cdot g_3\)
  • \(w_2 = w_2 - \eta \cdot g_2\)
  • \(w_1 = w_1 - \eta \cdot g_1\)

这样做的好处: 确保所有的参数更新都是基于同一时刻的模型状态计算出来的。

在PyTorch代码中,你会发现这两步是严格分行的:

# --- 第一步:求导阶段 ---
l.sum().backward()  
# 这一行执行完:
# w.grad 里面有值了,但 w 的值没变!
# b.grad 里面有值了,但 b 的值没变!

# --- 第二步:修改阶段 ---
sgd([w, b], lr, batch_size) 
# 这一行执行完(在 sgd 函数内部):
# param -= lr * param.grad / batch_size
# w 和 b 的数值才真正被改写了!

大模型有成千上万个层,如果一边算梯度一边改参数,前面的修改会干扰后面的梯度计算,导致整个数学逻辑乱套(这在微积分里叫“数值不稳定”)。


我们回到正题。对于大多数复杂模型,我们可以使用 小批量随机梯度下降 (Minibatch SGD) ,通过迭代不断逼近最小值:

\[ \mathbf{w} \leftarrow \mathbf{w} - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \mathbf{x}^{(i)} \left( \mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)} \right) \]

至此,我们通过统计假设(正态分布)、优化准则(极大似然)、损失函数(平方损失)、计算方法(解析解/随机梯度下降)完整梳理了线性回归模型的整体思路。

举例来说,假设有10000 个数据,1个batch随机选100个数据,那么我们训练一个轮次(Epoch) 需要100个batch。模型通常需要跑几十个甚至上百个 Epoch 才能收敛。

线性神经元

线性回归是一个单层神经网络,是神经网络的重要组成部分。如下图所示:

1769724697174

在上图中,输入层有d个输入变量,这些变量都是特征,即特征维度(feature dimensionality)为d;圆圈 \(o_1\) 就是一个 线性神经元 ,代表着输出的预测值。

上图所示结构也被称为全连接层 (Fully Connected Layer,简称 FC ),也常被称为 稠密层 (Dense Layer),是深度学习和人工神经网络中最基础、最核心的层结构之一。它的特征是:该层中的每一个神经元,都与前一层的所有神经元相连接。

在线性神经元内部,它只做两件事:

  • 加权求和 :把所有输入 \(x_i\) 乘以对应的权重 \(w_i\) 加在一起。
  • 加上偏置 :最后加上 \(b\)

我们可以把图中的连线理解为权重,代表着输入特征与神经元之间的突触强度,决定了每一个输入信号对最终结果的影响程度;而在线性神经元内部又存储了一个偏置b,这个偏置通常被视为神经元o1自身的固有属性。

换句话说,我们说训练一个线性回归模型,其实就是在调整这个线性神经元上的连接权重和偏置。线性神经元决定了 预测值(\(\hat{y}\))是如何产生的 ,而训练优化(平方损失、梯度下降),则是为了 修正这个神经元 :梯度下降算法会计算损失对神经元参数的导数,然后逆向告诉这个神经元如何调整权重参数和偏置,来让最终的损失更小。

之所以叫它“线性神经元”,是因为它的输出仅仅是输入的线性组合。

单层感知机

分类问题的本质是寻找 决策边界 。线性模型的本质是画 直线

康奈尔大学的 Rosenblatt 受大脑神经元启发,发明了单层感知机。单层感知机使用的是 阶跃函数 (Step Function):如果 \(z > 0\) 输出 1,否则输出 0。

由于单层感知机是硬跳变的,数学上不可导,所以没法用梯度下降来精细调优。

明斯基(Minsky)在1969年出版的书中证明了 单层感知机无法解决 XOR(异或)问题 。这不仅是一个数学问题,更是一场灾难。它证明了当时的 AI “智商有限”,直接导致了 AI 的第一个寒冬。

在机器学习监督学习笔记章节中,我们已经学习了解了线性回归和逻辑回归。然而,如果我们想做出一个复杂的分类曲线,我们不能用多个线性层进行叠加,因为无论多少个线性层叠加起来都还是线性层。

最简单的例子就是XOR(异或)问题。XOR(Exclusive OR) 逻辑非常简单: 相同为 0,不同为 1 。我们可以把它看作一个分类任务,坐标点如下:

  • (0, 0) \(\to\) 分类 0
  • (1, 1) \(\to\) 分类 0
  • (0, 1) \(\to\) 分类 1
  • (1, 0) \(\to\) 分类 1

如果你现在拿一张纸,画一个坐标轴,点上这四个点。你会发现,无论你如何尝试, 你都无法画出“一条直线”把 0 和 1 分开 。线性模型(没有 \(\sigma\))只能画直线。单纯的线性模型在面对异或这种简单的逻辑时,准确率最高只能达到 75%(也就是它只能切中 3 个点)。


直到80年代,Hinton 等人复兴了 多层感知机(MLP) ,并普及了反向传播(Backpropagation) 算法。这时人们才意识到,把多个感知机串联起来变成 “多层感知机 (MLP)” ,并把硬跳变的阶跃函数换成平滑的 Sigmoid ,才真正实现了“空间折叠”,解决了 XOR。

Sigmoid函数把 \(z\) 变成 0 到 1 之间的平滑概率。因为平滑可导,所以能配合交叉熵梯度下降大杀四方。

为了捕捉到非线性特征,我们需要引入非线性变换。最常用的就是sigmoid,\(\sigma(z) = \frac{1}{1 + e^{-z}}\)。sigmoid函数虽然看起来很简单,但是它做了两件机器伟大的事情:

  1. 引入了“阈值”和“激活”的概念:线性函数有求必应,但是sigmoid在小于/大于某个阈值的时候基本没反应;只有在0附近才会剧烈反应。这就像是一个开关。
  2. 空间扭曲:经过这种非线性的转换,原本在原始坐标系里无法线性分开的点(比如 XOR 的四个点),在被“折叠”后的新空间里, 正好变得可以被一刀切开了

放到XOR问题中,我们可以把解决 XOR 的过程拆解为:两个“阶梯”的协同与对折。假设我们有两个神经元 \(h_1\)\(h_2\),它们各自带有一个 Sigmoid

  • 神经元 \(h_1\)* :它负责观察 *\(x_1 + x_2\)。当其中一个为 1 时,它开始激活。
  • 神经元 \(h_2\)* :它也负责观察 *\(x_1 + x_2\),但它比较“迟钝”,只有当两个都为 1 时,它才强烈激活。

此时的空间扭曲发生了:我们将原始的 \((x_1, x_2)\) 坐标点,映射到新的隐藏层坐标轴 \((h_1, h_2)\) 上:

  1. 对于 A (0,0) :两个神经元都不动 \(\to\) 新坐标 (0, 0)
  2. 对于 C (0,1) 和 D (1,0)\(h_1\) 激活了,\(h_2\) 还没动 \(\to\) 新坐标 (1, 0)
  3. 对于 B (1,1)\(h_1\)\(h_2\) 都激活了 \(\to\) 新坐标 (1, 1)

现在,请你在大脑里画出新的坐标系 \((h_1, h_2)\),并点上这三个新位置:

  • 红色点:(0, 0)(1, 1)
  • 蓝色点: (1, 0) (原来那两个蓝点重合在一起了)

这三个点现在构成了一个三角形:

  • 底部是 (0,0) 到 (1,0)
  • 侧边是 (1,0) 到 (1,1)

原本在原始空间里,红色点 A 和 B 是隔着蓝色点“遥遥相望”的。但经过 Sigmoid 的非线性挤压:

  • 两个蓝色点被“捏”到了同一个位置 (1, 0)
  • 由于 \(h_2\) 是非线性的(它不是平滑上升,而是像个台阶),它把 (1,1) 这个点向上提拉了,而保持 (1,0) 还在低处。

“解决 XOR 的本质是‘特征空间变换’。每一个带 Sigmoid 的神经元都在原始空间切割并产生一个 非线性的激活波浪 。多个神经元叠加后,相当于将原始的扁平坐标系进行了 非线性弯曲和投影 。在这个被‘揉皱’的新空间里,原本线性不可分的数据点被重新排列,使得它们在新的维度上变得 线性可分 。这就是神经网络通过增加隐藏层来解决复杂问题的底层逻辑。”


在神经网络的术语中,我们通常把 “线性运算(\(wX+b\))+ 非线性激活函数(\(\sigma\))” 的这个组合体,称为一个 非线性神经元

一个典型的非线性神经元(以单个输出为例)的数学表达式为:

\[ y = \sigma(\sum_{i=1}^{n} w_i x_i + b) \]

在 PyTorch 的底层(如 nn.Linear 配合激活函数),为了处理成批的数据,公式通常表示为矩阵形式:

\[ Y = \sigma(XW^\top + \mathbf{b}) \]

通过sigmoid函数,深度神经网络所需的基本元素都已经齐备了。

Softmax回归

至此,我们已经了解了线性回归、线性神经元和非线性神经元。下面介绍一个特殊的、具有“全局观”的非线性激活函数 ,它通常作为一组神经元的最后一道工序,将它们共同转化为一个 非线性神经单元阵列

在分类问题中,如果分类只有两个,那么我们可以使用 1 个输出神经元 + Sigmoid来实现。如果有多个输出神经元,且输出类别不互斥(如:一张图里既有“山”也有“水”),每个神经元独立判断该标签是否存在,那么我们依然可以使用 N 个输出神经元 + Sigmoid

然而,对于多类选一 (Multi-class Classification)的情况,即类别互斥(如:识别猫、狗、猪)的情况,模型必须在这些选项中给出一个概率分布,最终选概率最大的,那么我们就不能再使用Sigmoid函数了。

在深度学习中,Logit 通常指神经网络 最后一层全连接层输出的原始数值 ,也就是在进入激活函数(如 Softmax 或 Sigmoid)之前的结果。

为了计算每个类别的原始得分(即 Logit ),我们为每个输出配置一套独立的权重和偏置:

\[ o_1 = x_1w_{11} + x_2w_{12} + x_3w_{13} + x_4w_{14} + b_1 \]
\[ o_2 = x_1w_{21} + x_2w_{22} + x_3w_{23} + x_4w_{24} + b_2 \]
\[ o_3 = x_1w_{31} + x_2w_{32} + x_3w_{33} + x_4w_{34} + b_3 \]

每个输入计算出的三个结果 \(o_1, o_2, o_3\) 被称为“未规范化的预测(logit)”。这本质上就是一个 仿射函数(Affine Function) ,即线性组合加偏置。

1769737964972

为了在编程和数学推导中更高效,通常将其写为矩阵乘法:

\[ \mathbf{o} = \mathbf{Wx} + \mathbf{b} \]

其中:

  • \(\mathbf{o}\) :输出向量,维度为 \(3 \times 1\)
  • \(\mathbf{W}\) :权重矩阵,维度为 \(3 \times 4\)(对应 12 个标量权重)。
  • \(\mathbf{x}\) :输入特征向量,维度为 \(4 \times 1\)
  • \(\mathbf{b}\) :偏置向量,维度为 \(3 \times 1\)

在图示的例子中,参数规模可以按照如下计算:

\[ \text{总参数量} = (\text{输入特征数} \times \text{输出类别数}) + \text{偏置数量} \]

即4x3+3=15个。GPT等文本生成任务本质上就是一个巨大的多类选一机器,而我们常说的总参数量计算就是上述这样将总参数相加得来的。


我们接着来看一下Softmax函数本身:

\[ \hat{y}_j = \frac{\exp(o_j)}{\sum_k \exp(o_k)} \]

从预测的角度来看,我们不能直接把神经网络的原始输出(Logits)当成概率。我们希望预测结果是概率最高的那个类别,即\(\operatorname{argmax}_j \hat{y}_j\),简单说,就是看哪个分值最高,就选哪个。然而,神经网络最后一层直接输出的数值(记为 \(o\))不能直接叫“概率”,因为它们不符合概率的两大基本原则:非负性和归一化。

非负性指神经网络的原始输出可能是负数,但概率必须 \(\ge 0\)。归一化指原始输出的和不一定等于 \(1\),但所有可能的类别概率之和必须为 \(1\)。由于指数函数是单调递增的,这意味着如果 \(o_1 > o_2\),那么 \(\exp(o_1) > \exp(o_2)\),因此,Softmax不会改变原始预测值之间的大小次序 ,也即:

\[ \operatorname{argmax}_j \hat{y}_j = \operatorname{argmax}_j o_j \]

这意味着预测时直接看原始输出的最大值和看 Softmax 后的最大值,结果是一样的。


对于n个样本的情况,我们可以把 \(n\) 个样本堆叠在一起变成矩阵,通过一次矩阵乘法同时算出所有样本的预测结果。比如说,对于:

  • 特征矩阵 \(\mathbf{X}\)* : 形状为 *\(n \times d\)。这里 \(n\) 是批量大小(一次处理多少个样本),\(d\) 是每个样本的特征数。
  • 权重矩阵 \(\mathbf{W}\)* : 形状为 *\(d \times q\)\(q\) 是输出类别的数量(比如做 10 分类问题,\(q\) 就是 10)。
  • 偏置向量 \(\mathbf{b}\)* : 形状为 *\(1 \times q\)

我们可以得到:

\[ \mathbf{O} = \mathbf{X}\mathbf{W} + \mathbf{b} \]

矩阵乘法 \(\mathbf{X}\mathbf{W}\)* 得到的结果是一个 *\(n \times q\) 的矩阵。注意 \(\mathbf{b}\) 只有一行,但 \(\mathbf{X}\mathbf{W}\)\(n\) 行。在计算时,系统会自动把 \(\mathbf{b}\) 复制到每一行上,以便与矩阵相加,这便是深度学习中非常实用的广播机制 (Broadcasting)

\[ \hat{\mathbf{Y}} = \text{softmax}(\mathbf{O}) \]

对结果矩阵 \(\mathbf{O}\) 的每一行分别做 Softmax,将其转化为概率分布(每行之和为 1),最终得到的 \(\hat{\mathbf{Y}}\) 就是一个包含 \(n\) 行样本预测概率的矩阵。

交叉熵损失函数

我们上面所讲的内容都可以认为是一层神经网络,输入数据通过全连接层进入神经元层,通过参数和偏置的计算得到预测结果,并可以通过softmax来进行多类归一任务。但是,得到预测结果并不是神经网络的重点,我们的目的是得到一组可以用来预测的参数(权重和偏置),而我们无法直接获得参数,必须通过训练来获得。

为了训练模型,我们需要一种方法来衡量预测值 \(\hat{y}\) 与真实值 \(y\) 的匹配程度。

假设整个数据集独立同分布,根据最大似然估计(MLE),我们希望最大化观测到所有真实标签的概率为:

\[ P(\mathbf{Y} \mid \mathbf{X}) = \prod_{i=1}^n P(\mathbf{y}^{(i)} \mid \mathbf{x}^{(i)}) \]

由于乘法计算梯度非常困难,我们取对数将“乘法”转为“加法”,并取负号将“最大化问题”转为“最小化问题”,即最小化负对数似然(NLL):

\[ -\log P(\mathbf{Y} \mid \mathbf{X}) = \sum_{i=1}^n -\log P(\mathbf{y}^{(i)} \mid \mathbf{x}^{(i)}) = \sum_{i=1}^n l(\mathbf{y}^{(i)}, \hat{\mathbf{y}}^{(i)}) \]

由此,我们便得到了交叉熵损失函数(Cross-Entropy Loss),即对于任何标签 \(\mathbf{y}\)(独热编码)和预测概率 \(\hat{\mathbf{y}}\),具体的损失函数表达式为:

\[ l(\mathbf{y}, \hat{\mathbf{y}}) = -\sum_{j=1}^q y_j \log \hat{y}_j \]

注:在分类中,由于 \(y_j\) 只有一个位置是 1,其余是 0,公式实际简化为 \(L = -\log \hat{y}_{\text{correct\_class}}\)

因为 \(y\) 是独热编码(只有一个位置是 1,其余是 0),所以这个公式实际上只关心 正确类别的预测概率 。你对正确类别的把握越大(\(\hat{y}_j\) 越接近 1),损失就越接近 0。

接着,我们将 Softmax 的定义代入交叉熵损失中,我们可以得到损失函数相对于输出层未规范化预测(logit)\(o_j\) 的导数:

\[ l(\mathbf{y}, \hat{\mathbf{y}}) = \log \sum_{k=1}^q \exp(o_k) - \sum_{j=1}^q y_j o_j \]

进而得到 梯度公式 (最关键的结论):

\[ \partial_{o_j} l(\mathbf{y}, \hat{\mathbf{y}}) = \text{softmax}(\mathbf{o})_j - y_j \]

这个导数(梯度)恰好是 模型预测的概率与实际观察值之间的差异 。这种极其简单的形式(预测值 - 真实值)使得梯度计算在实践中变得非常容易且高效。

对于一个神经网络,我们把输入数据经过神经网络来得到输出结果的过程称为前向传播;接着,我们对比预测值和真实值的差异,在机器学习的笔记中已经介绍了这一思想,即使用损失函数来量化评估这一差异;最后,我们根据损失函数的结果,对其进行求导,并顺着梯度下降的方向更新参数,来让参数向我们目标的参数移动——求导与链式法则的部分称为反向传播,更新参数的部分称为优化。

这里要注意,在矢量化计算中,我们通常会对一个小批量(batch)的 \(n\) 个样本计算平均损失:

\[ L = \frac{1}{n} \sum_{i=1}^n l(\mathbf{y}^{(i)}, \hat{\mathbf{y}}^{(i)}) \]

这是因为梯度的量级如果随 batch size 剧烈波动,会导致优化不稳定。

在实际编程中,直接计算 \(\exp(o_k)\) 容易导致数值溢出。因此,成熟的深度学习框架(如 PyTorch)通常会将 Softmax 和 Cross-Entropy 合并为一个函数,利用 LogSumExp 技巧来保证计算的稳定性。


值得注意的是,虽然交叉熵是训练模型时的 损失函数(Loss Function) ,但它不够直观。在评估阶段,我们使用精度 (Accuracy)。

\[ \text{Accuracy} = \frac{1}{n} \sum_{i=1}^{n} \mathbb{1}(\hat{y}_i = y_i) \]

其中:

  • \(\hat{y}_i = \operatorname{argmax}_j Q(y_{ij})\):预测概率最大的那个类别。
  • \(\mathbb{1}(\cdot)\) 是指示函数,条件成立为 1,否则为 0。

信息论的角度来看,我们可以做如下总结:

概念 公式简述 物理意义
信息量 \(-\log P(x)\) 单个事件带来的“惊异度”
\(\sum P \log \frac{1}{P}\) 整个系统平均的不确定性
交叉熵 \(\sum P \log \frac{1}{Q}\) 用预测分布\(Q\)表达真实分布\(P\)的成本
精度 正确数 / 总数 模型在实际决策中的表现

具体参见信息论笔记。

多层神经网络

上面我们已经学习了单层神经网络,并了解了如何前向传播(输入数据经过全连接层的权重和神经元自身的偏置得到预测数据),也了解了如何衡量预测结果(对于分类任务,一般用交叉熵损失函数),以及如何通过结果来更新参数(梯度下降、反向传播、参数更新)。

同时,我们也了解了激活函数和非线性神经元,接下来,我们来设计多层的神经网络。

通用近似定理

1989年,Cybenko 等人正式发表论文,从数学上证明了,只要有足够多的非线性激活函数神经元,我们可以拟合出 世界上任何一种连续函数 。这就是通用近似定理(Universal Approximation Theorem)。

证明了堆叠方法的万能性质后,人工智能从数学上被证明,无论如何复杂的曲线,都可以通过足够多的非线性激活单元来模拟出来。从数学角度来看,无论参数空间多么巨大,都一定存在一组参数 \((w, b)\) 能够完美模拟那条复杂的曲线。

即便是到了2020年代的Transformer大模型时代,其数学原理依然是在一个极其宏大的高维空间里,画出无数条极其复杂的“分割线”。因为GPT 生成下一个字的过程,本质上是在做 全词汇表分类 (比如 5 万个词选 1 个)。Transformer 把你输入的每一个词,映射成一个 12288 维(以 GPT-3 为例)的向量。在最后输出层(Linear + Softmax)之前,模型已经把“逻辑”理顺了。这时,空间中存在着 50,000 个区域,每一块区域对应一个词。Transformer 的所有计算(Self-Attention),都是为了 在空间中不断移动这个向量的位置 ,直到它落入那个代表正确答案的“分割区域”里。更进一步地,Transformer会根据你输入的上下文(比如“苹果”后面跟的是“公司”还是“手机”), 实时地调整空间折叠的方式 。这种“注意力机制”让模型可以在每一层都对空间进行一次“全局大挪移”,从而在极高维的空间里刻画出精细到极致的分割线。

总而言之,通用近似定理证明了:只要给单层隐藏层提供足够多的神经元和 非线性激活函数 ,它就可以以任意精度拟合任何闭区间内的连续函数。

特征层次结构

虽然通用近似定理(1989年)证明了单层无限多的可行性下限,然而我们所熟悉的神经网络架构基本都是多层的。事实上,在通用近似定理出现之前,科学家们就已经在尝试多层结构了。

1965年,苏联数学家 IvakhnenkoLapa 提出了“数据处理分组方法”(GMDH),这被认为是世界上第一个真正的深度学习算法。他们通过多层多项式神经元来拟合数据,每一层都试图筛选出最好的特征传递给下一层。

1980年,日本科学家 福岛邦彦(Kunihiko Fukushima) 提出了 神经认知机(Neocognitron) 。他受生物视觉启发,设计了多个卷积层和池化层的交替结构。这直接影响了后来著名的卷积神经网络(CNN)。

1986年,深度学习三巨头之一 Geoffrey Hinton 等人重新推广了 反向传播算法(Backpropagation) 。这是历史性的转折点,因为它解决了“多层网络怎么训练”的问题。

从发展历程中可以看到,多层的使用来自于生物直觉。

1950年代,Hubel & Wiesel的生理学实验发现,猫的视觉皮层是分层的:有的细胞只对线条敏感,有的对形状敏感,有的对复杂物体敏感。深度神经网络模仿了这种组合式(Compositional) 的逻辑:复杂系统的本质是层级化的。通过多层堆叠,网络可以先学习“局部特征”,再学习“全局结构”。这种从局部到全局的演化,是单层网络(即使是非线性的)在逻辑上极难实现的。

另外一种假说则是关于“空间变换”的。流形分布假说(Manifold Hypothesis)认为,现实世界的高维数据(比如图片、语音)实际上都分布在低维的流形(Manifold)上。通俗点说,数据像是一张被揉皱在三维空间里的二维纸团。这种“逐层展开”的过程被称为 解纠缠(Disentanglement) 。多层的意义在于,单次变换很难把极度复杂的扭曲一次性拉平,必须通过多层连续平滑的映射,才能将复杂的流形变换到线性可分的状态。

梯度消失问题

虽然科学家们的生物直觉引导他们尝试多层的神经网络,但是多层神经网络在训练时出现了一种“失传”现象:当网络层数太深时,底层的神经元(靠近输入层的那些)由于接收不到有效的“修改建议”,导致它们停止学习。

在神经网络中,我们使用反向传播来更新参数。根据 链式法则 ,深层神经元的梯度是前面每一层梯度的 乘积

\[ \frac{\partial Loss}{\partial w_{early}} = \frac{\partial Loss}{\partial y} \cdot \frac{\partial y}{\partial h_n} \cdot ... \cdot \frac{\partial h_2}{\partial h_1} \cdot \frac{\partial h_1}{\partial w_{early}} \]

早期的神经网络常用 Sigmoid 激活函数。它的导数最大值只有 0.25 (当输入为 0 时)。

  • 如果你的网络有 20 层,梯度传到最前面就要乘以 20 个小于 0.25 的数。
  • \(0.25^{20}\) 是一个极小的数字,几乎等于 0。

此外,如果每一层的权重 \(W\) 初始化得非常小,连乘效应也会迅速让梯度归零。梯度消失问题导致靠近输出层的几层学得很好,但靠近输入的底层权重几乎不动。这意味着网络 无法提取基础特征 ,深度架构的优势荡然无存。

为了解决梯度消失问题,人们先后提出了如下解决方案:

  1. 逐层预训练
  2. ReLU(Rectified Linear Unit)
  3. 改进权重初始化(Xavier & He Initialization)
  4. 批归一化(Batch Normalization)
  5. 残差连接 (ResNet / Residual Learning)

在 2006 年之前,深度学习正处于“寒冬”,因为当时的人们发现,直接用随机初始化去训练超过 3 层的神经网络,效果通常还不如简单的 SVM。2006年,Hinton 提出“深度信念网络(Deep Belief Networks, DBN)的贪婪逐层预训练”。Hinton 认为,深层网络之所以练不动,是因为 初始化的位置太差了 。在深不见底的参数空间里,随机初始化就像把你蒙着眼丢进喜马拉雅山脉,你根本找不到那个叫“全局最优”的深谷。

Hinton的工作从哲学、信心和技术路径上彻底改写了 AI 的命运。在 2006 年之前,主流学术界(如支持向量机 SVM 的支持者)认为神经网络叠深了没用,只会带来难以优化的非线性混乱。然而,Hinton 通过实验数据向全世界证明:只要能练成,深度架构在处理复杂模式(如手写数字识别)时的上限远高于浅层架构。 这直接开启了“深度学习(Deep Learning)”这个名词的时代。

同时,以前的计算机视觉需要专家手动设计特征(比如怎么定义圆圈、怎么定义边缘)。逐层预训练展示了一种可能性:模型可以 通过观察数据自行理解特征的层级 。虽然是无监督的,但它证明了机器可以自动构建从“像素”到“概念”的阶梯。

2010年,在论文 《Rectified Linear Units Improve Restricted Boltzmann Machines》 中,Nair 和 Hinton 首次在受限玻尔兹曼机(RBM)中引入了 ReLU。当时 Hinton 还在完善他的预训练理论。他发现,相比于传统的 Sigmoid,这种“一半是零,一半是线性”的函数能更好地模拟生物神经元的激活特性。实验结果证明,ReLU 不仅让训练变快了,还解决了一些困扰已久的坐标偏移问题。

2011年,深度学习三巨头之一的 Yoshua Bengio 及其学生 Xavier Glorot 发表了 《Deep Sparse Rectifier Neural Networks》 。他们从数学和仿生学角度深入论证了为什么 ReLU 好用。他们指出,ReLU 带来的 稀疏性(Sparsity) ——即大部分神经元处于关闭状态——使得网络更像人类大脑,且能有效缓解梯度消失。

2012年,在 ImageNet 大赛中,AlexNet 采用了 ReLU 激活函数。Alex 后来在论文中给出了一个震撼的对比图:在同样的任务下,达到相同误差率,使用 ReLU 的网络比使用 Tanh 的网络快了 6 倍!同时,AlexNet 用了 8 层结构(在当时已经是深得惊人),以绝对优势碾压了所有传统的浅层算法。这一刻,工业界和学术界达成共识:深度(Depth)比宽度(Width)更能捕捉复杂的空间特征。

2015年何恺明带领的微软亚洲研究院 (MSRA) 团队提出残差连接是深度学习史上最重要的突破之一。在此之前,20 层以上的网络就很难训练了;ResNet 出现后,人类第一次成功训练了 152 层 (甚至上千层)的神经网络。它通过简单的“恒等映射”让梯度可以在网络中无损流动。

随着深度学习的兴起,数学家们在 2017 年左右(如 Hanin 等人)证明了“深度版本”的通用近似定理:即使每一层的神经元数量是 有限的 (甚至是固定的,只要稍微比输入维度多一点),只要层数(深度) 可以无限增加,同样可以以任意精度拟合任何函数。

ReLU激活函数

至此,我们已经从历史和理论的角度,分别了解了多层结构的来源和发展,下面我们就来学习一下多层次结构设计。

无论神经网络有多少层,我们都把该网络分为下面三个部分:

  1. 输入层
  2. 隐藏层
  3. 输出层

1769742862550

上图展示了一个单隐藏层的多层感知机,具有五个隐藏单元。

之所以叫“隐藏”,是因为在训练和预测过程中,你只能看到输入和输出,中间层的中间计算结果(特征表示)对外部用户来说是“不可见”的黑盒。

如果我们使用线性函数,无论叠加多少层,其都能合并为一层,因为他们总是线性变换的。因此,我们引入了激活函数,这一点已经反复见过很多遍,不再赘述。

我们已经介绍过Sigmoid激活函数,并在梯度消失问题那一章节中介绍了Sigmoid激活函数的问题:由于两端太水平,容易导致梯度消失

除了Sigmoid外,早期常见的还有tanh函数:

\[ \tanh(x) = \frac{1 - \exp(-2x)}{1 + \exp(-2x)} \]

然而,在上一小节中我们已经提到,最终让深度学习在2012年一炮而红的,是ReLU激活函数。可以说,ReLU激活函数就是现代深度学习的标配。

ReLU 的全称是 Rectified Linear Unit(修正线性单元) ,它的数学公式简单到让人怀疑人生:

\[ f(x) = \max(0, x) \]
  • 如果输入 \(x > 0\) 直接输出 \(x\),不做任何处理。
  • 如果输入 \(x \le 0\) 输出 \(0\),直接“掐死”信号。

在 ReLU 普及之前,大家都在用 SigmoidTanh 。ReLU 的出现解决了三大痛点:

  1. 彻底解决“梯度消失”(在正区间)——只要 \(x > 0\),它的导数恒等于 1 。这意味着无论网络叠到 10 层还是 100 层,梯度都能在正区间内“原封不动”地传回来。
  2. 计算机只需要判断一下 if (x > 0)。这种极致的简单,让训练大型网络的成本大幅下降。
  3. ReLU 会把所有负信号都变成 0。这意味着在同一时刻,只有一部分神经元是被激活的,而大部分在“睡觉”。这非常符合生物大脑的工作方式——我们思考时,并不是全脑神经元都在同时疯狂放电。这种稀疏性(Sparsity)让模型具有更好的泛化能力,不容易过拟合。

激活函数是神经网络的灵魂。没有激活函数,就没有多层结构。

当然,ReLU也有个致命伤,如果一个神经元的权重更新得太猛,导致对于所有的训练数据,它的输入 \(x\) 永远小于 0,那么:

  1. 这个神经元的输出永远是 0。
  2. 它的梯度也永远是 0。
  3. 它从此 再也无法更新 ,变成了一个“死”掉的神经元。

为了解决“Dead ReLU”问题,科学家们又发明了一些变体:

  • Leaky ReLU: 给负区间一点点生路(比如 \(0.01x\)),不让它彻底死掉。
  • PReLU: 负区间的斜率是学出来的,不是固定的。
  • GeLU (Gaussian Error Linear Unit):\(0\) 点附近更加平滑。这是目前 GPT、BERT 等大模型最喜欢的激活函数。

多层全连接神经网络

至此,我们已经跟随前人的足迹,设计出了多层感知机(Multi-Layer Perceptron, 简称 MLP)。简单来说,MLP就是由若干个全连接层组成的神经网络。多层结构的全连接神经网络也称为多层感知机MLP、全连接神经网络FCNN、前馈神经网络FNN。

1769743836138

我们可以看到这一类神经网络的特点:

  1. 信号流动总是从输入层进入,经过隐藏层,最后到达输出层。因此也被称为前馈(Feedforward) 神经网络。
  2. 全连接:第 \(n\) 层的每一个神经元都和第 \(n-1\) 层的所有神经元相连。
  3. 每一层之间的连接都可以用一个巨大的矩阵\(W\)来表示。
  4. 每一层的输出都是 \(Output = \sigma(W \cdot Input + b)\),其中 \(\sigma\) 是激活函数。

这里提一下ResNet。上面我们提到,残差连接的提出彻底开启了多层级的时代。ResNet的原理十分简单:

  • 普通前馈层: \(y = f(x)\)
  • 残差前馈层: \(y = f(x) + x\)

由于ResNet应用基本都在CNN中,因此我会在CNN的笔记中着重讲解ResNet。

神经网络的学习过程

整个学习过程也被称为Optimization(优化),具体的内容可以参见优化笔记,这里只是做一个初步的讲解。之所以叫做优化,是因为在数学和工程上,它就是一个标准的 最优化问题 (Optimization Problem)

初始化

在学习开始前,我们需要给网络中的所有参数(权重 \(W\) 和偏置 \(b\))赋予初始值。通常不能全部设为 0(否则神经元会出现对称性问题),而是采用随机初始化(如 Gaussian 分布)或专门的方法(如 Xavier 或 He 初始化)。

  • He 初始化 (常用): \(W \sim N(0, \frac{2}{n_{in}})\)

目的是防止在深度网络中信号消失或爆炸。

前向传播

数据从输入层进入,一层层向后流动。

  • 线性计算 :每一层都会计算 \(z = Wx + b\)
  • 非线性激活 :通过激活函数(如 ReLU、Sigmoid)产生输出。
  • 得到结果 :最终输出一个预测值 \(\hat{y}\)

每一层其实就做两件事:线性变换 + 非线性激活。

  1. 线性加权和: \(z^{(l)} = W^{(l)} a^{(l-1)} + b^{(l)}\)
  2. 激活处理: \(a^{(l)} = \sigma(z^{(l)})\)
    • \(\sigma\) 是激活函数,如 ReLU: \(\max(0, z)\)

损失计算

我们将预测值 \(\hat{y}\) 与真实标签 \(y\) 进行对比,用一个损失函数 (Loss Function) 来衡量模型预测得有多差,即将“误差”量化为一个具体的数字。

常见函数:均方误差 (MSE) 用于回归,交叉熵 (Cross-Entropy) 用于分类。

  • 均方误差 (MSE, 回归用): \(L = \frac{1}{2} \|y - \hat{y}\|^2\)
  • 交叉熵 (Cross-Entropy, 分类用): \(L = -\sum y \log(\hat{y})\)

反向传播

这是最关键的一步,目的是计算 梯度

  • 链式法则 :从输出层开始,利用微积分的链式法则计算损失函数对每一层每一个参数的偏导数(即梯度)。
  • 分配责任 :梯度告诉我们,如果某个权重改变一点点,总误差会随之改变多少。

这一步的核心是 链式法则 ,求损失 \(L\) 对参数的偏导数(梯度)。

  1. 输出层的误差 (Error):

    $$ \delta^{(L)} = \frac{\partial L}{\partial a^{(L)}} \cdot \sigma'(z^{(L)}) $$

    (即:损失对输出的导数 \(\times\) 激活函数的导数) 2. 梯度的层层传递:

    $$ \delta^{(l)} = ((W^{(l+1)})^T \delta^{(l+1)}) \cdot \sigma'(z^{(l)}) $$

    (这一步体现了“反向”:第 \(l\) 层的误差由第 \(l+1\) 层的误差回传得到) 3. 最终参数梯度:

    $$ \frac{\partial L}{\partial W^{(l)}} = \delta^{(l)} (a^{(l-1)})^T, \quad \frac{\partial L}{\partial b^{(l)}} = \delta^{(l)} $$

.

参数更新

利用计算出的梯度,向误差减小的方向迈出一小步。

  • 随机梯度下降 (SGD):

    \[ W_{new} = W_{old} - \eta \cdot \frac{\partial L}{\partial W} \]

    \(\eta\) 是学习率,决定了步子跨多大)


评论 #