多层感知机/前馈神经网络
在绝大多数语境下, 多层感知机 (Multilayer Perception, MLP) 、全连接神经网络 (Fully Connected Neural Network, FCNN) 和 前馈神经网络 (Feedforward Neural Network, FNN) 说的都是同一类东西。
待办事项/思考:
- 关注
CrossEntropyLoss(交叉熵),思考它为什么比均方误差(MSE)在分类任务上收敛得更快。 - 如果输入是高清图片,为什么 MLP 的参数量会迅速失控?
- 为什么把像素打乱顺序后喂给 MLP,它的表现几乎没有变化?(这说明它无法感知图像的“位置”)。
单层神经网络
神经网络最早于1940年代就被提出,但长期都被否定和边缘化。神经网络的狂热信徒Hinton等人为了区分,提出了“深度学习”的概念。直到2012年AlexNet以压倒性优势获得ImageNet的冠军后,深度神经网络才再次回到人们讨论的主舞台上,并在后续的发展中展现出了惊人的水平。
本章节我将梳理神经网络的组成结构:神经元,以及神经网络的一些相关重要概念,说明神经网络架构如何从底层实现。
线性回归模型
在机器学习的笔记中我们已经讲解了线性回归和一些重要的概念,我们回顾一下最主要的内容。
在机器学习领域,我们通常使用的是高维数据集,建模时采用线性代数表示法会比较方便。当我们的输入包含\(d\)个特征时,我们将预测结果表示为:
将所有特征放到向量\(\mathbf{x} \in \mathbb{R}^d\)中,并将所有权重放到向量 \(\mathbf{w} \in \mathbb{R}^d\) 中,我们可以用点积形式来简洁地表达模型:
在上述公式中,向量 \(\mathbf{x}\) 对应于单个数据样本的特征。
用符号表示的矩阵 \(\mathbf{X} \in \mathbb{R}^{n \times d}\) 可以很快捷地引用我们整个数据集的 \(n\) 个样本。其中, \(\mathbf{X}\) 的每一行是一个样本,每一列是一种特征。对于特征集合 \(\mathbf{X}\),预测值 \(\hat{\mathbf{y}} \in \mathbb{R}^n\) 可以通过矩阵-向量乘法表示为:
损失函数 (loss function) 能够量化目标的实际值与预测值之间的差距。通常我们会选择非负数作为损失,且数值越小表示损失越小,完美预测时的损失为 0。
回归问题中最常用的损失函数是平方误差函数。当样本 \(i\) 的预测值为 \(\hat{y}^{(i)}\),其相应的真实标签为 \(y^{(i)}\) 时,平方误差可以定义为以下公式:
由于平方误差函数中的二次方项,估计值 \(\hat{y}^{(i)}\) 和观测值 \(y^{(i)}\) 之间较大的差异将导致更大的损失。为了度量模型在整个数据集上的质量,我们需要计算在训练集 \(n\) 个样本上的损失均值(也等价于求和)。
在训练模型时,我们希望寻找一组参数\((\mathbf{w}^*, b^*)\),这组参数能最小化在所有训练样本上的总损失:
线性回归刚好是一个很简单的优化问题。与我们将在本节中讲到的其他大部分模型不同,线性回归的解可以用一个公式简单地表达出来,这类解叫作解析解 (analytical solution)。
我们将偏置 \(b\) 合并到参数 \(\mathbf{w}\) 中,合并方法是在包含所有参数的矩阵中附加一列。我们的预测问题是最小化 \(\| \mathbf{y} - \mathbf{Xw} \|^2\)。这在损失平面上只有一个临界点,这个临界点对应于整个区域的损失极小点。将损失关于 \(\mathbf{w}\) 的导数设为 0,得到解析解:
不过,在绝大多数任务上,我们无法得到解析解。我们常用梯度下降的方法来优化深度学习模型,其最简单的用法是计算损失函数(数据集中所有样本的损失均值)关于模型参数的导数(在这里也可以称为梯度)。由于数据集可能很大,直接应用梯度下降会很慢,所以一般每次需要计算更新的时候随机抽取一小批样本,这种变体叫做小批量随机梯度下降 (minibatch stochastic gradient descent)。
在每次迭代中,我们首先随机抽样一个小批量\(\mathcal{B}\),它是由固定数量的训练样本组成的。然后,我们计算小批量的平均损失关于模型参数的导数(也可以称为梯度)。最后,我们将梯度乘以一个预先确定的正数\(\eta\)(被称为学习率),并从当前参数的值中减掉。我们用下面的数学公式来表示这一更新过程(\(\partial\)表示偏导数):
总结一下,算法的步骤如下:
(1)初始化模型参数的值,如随机初始化;
(2)从数据集中随机抽取小批量样本且在负梯度的方向上更新参数,并不断迭代这一步骤。对于平方损失和仿射变换,我们可以明确地写成如下形式:
在训练了预先确定的若干迭代次数后(或者直到满足某些其他停止条件后), 我们记录下模型参数的估计值,表示为\(\hat{\mathbf{w}}, \hat{b}\)。在训练了预先确定的若干迭代次数后(或者直到满足某些其他停止条件后), 我们记录下模型参数的估计值,表示为。 但是,即使我们的函数确实是线性的且无噪声,这些估计值也不会使损失函数真正地达到最小值。 因为算法会使得损失向最小值缓慢收敛,但却不能在有限的步数内非常精确地达到最小值。
线性回归恰好是一个在整个域中只有一个最小值的学习问题。 但是对像深度神经网络这样复杂的模型来说,损失平面上通常包含多个最小值。 深度学习实践者很少会去花费大力气寻找这样一组参数,使得在训练集上的损失达到最小。 事实上,更难做到的是找到一组参数,这组参数能够在我们从未见过的数据上实现较低的损失, 这一挑战被称为 泛化 (generalization)。
在现实中,真实值 \(y\) 和预测值 \(\hat{y}\) 之间总会有误差。我们假设目标值 \(y\) 与特征 \(\mathbf{x}\) 之间存在线性关系,并包含一个随机噪声 \(\epsilon\):
其中\(\mathbf{w}, b\) 是我们要学习的参数。\(\epsilon\) 是无法被模型解释的随机噪声。我们假设噪声 \(\epsilon\) 服从 均值为 0、方差为 \(\sigma^2\) 的正态分布 :
这意味着给定输入 \(\mathbf{x}\),观测到特定 \(y\) 的概率密度函数为:
为了让模型最符合观测到的 \(n\) 个样本,我们需要找到能使 整个数据集出现的联合概率 (似然函数)最大的参数:
直接优化连乘公式很困难,我们通过取负对数(Negative Log) 将其简化为求和形式。最小化负对数似然等价于最大化似然:
由于第一项和系数 \(\frac{1}{2\sigma^2}\) 不影响 \(\mathbf{w}, b\) 的极值点位置,我们略去常数,这就推导出了在刚才的笔记中提到的损失函数平均平方误差 (MSE) :
对于简单模型,我们可以通过对公式直接求导并令导数为 0 得到:
在线性回归中,我们的目标是让损失函数 \(L\) 减小。梯度下降公式如下:
梯度下降公式可以理解为反向传播加参数更新:
对于反向传播,一般我们用PyTorch的.backward()来完成。PyTorch 实际上是在执行微积分中的 链式法则 。它从最后的结果 \(L\) 开始,一步步往回求导:
如果我们代入具体的函数定义:
- 预测函数:\(\hat{y} = Xw + b\)
- 损失函数:\(L = \frac{1}{2}(\hat{y} - y)^2\)
那么对应的链式法则详细标注为:
当神经元变多、层数变深时,链式法则就像一场“导数接力赛”。每一层都计算自己的局部梯度,然后乘以前面传过来的梯度,继续向后传递。假设我们有一个三层的神经网络(输入层 \(\rightarrow\) 隐藏层1 \(\rightarrow\) 隐藏层2 \(\rightarrow\) 输出层),每一层只有单个神经元:
- 第一层 (Hidden 1): \(h_1 = \sigma(w_1 \cdot x + b_1)\)
- 第二层 (Hidden 2): \(h_2 = \sigma(w_2 \cdot h_1 + b_2)\)
- 第三层 (Output): \(\hat{y} = w_3 \cdot h_2 + b_3\)
- 最终损失: \(L = \text{Loss}(\hat{y}, y)\)
(注:\(\sigma\) 是激活函数,如 ReLU 或 Sigmoid,这个后面会提到;数据从左往右流动。)
如果我们要计算 第一层权重 \(w_1\)* 的梯度,导数需要跨越整整三层。根据链式法则,*\(\frac{\partial L}{\partial w_1}\) 的完整公式如下:
我们将每一项展开,每一层“接力棒”是:
可以看到,所有的反馈都源自最右端的误差 \((\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 的参数,并对它们统一执行减法操作:
这意味着: 只要是从损失函数 \(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) ,通过迭代不断逼近最小值:
至此,我们通过统计假设(正态分布)、优化准则(极大似然)、损失函数(平方损失)、计算方法(解析解/随机梯度下降)完整梳理了线性回归模型的整体思路。
举例来说,假设有10000 个数据,1个batch随机选100个数据,那么我们训练一个轮次(Epoch) 需要100个batch。模型通常需要跑几十个甚至上百个 Epoch 才能收敛。
线性神经元
线性回归是一个单层神经网络,是神经网络的重要组成部分。如下图所示:

在上图中,输入层有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函数虽然看起来很简单,但是它做了两件机器伟大的事情:
- 引入了“阈值”和“激活”的概念:线性函数有求必应,但是sigmoid在小于/大于某个阈值的时候基本没反应;只有在0附近才会剧烈反应。这就像是一个开关。
- 空间扭曲:经过这种非线性的转换,原本在原始坐标系里无法线性分开的点(比如 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)\) 上:
- 对于 A (0,0) :两个神经元都不动 \(\to\) 新坐标 (0, 0)
- 对于 C (0,1) 和 D (1,0) :\(h_1\) 激活了,\(h_2\) 还没动 \(\to\) 新坐标 (1, 0)
- 对于 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\))” 的这个组合体,称为一个 非线性神经元 。
一个典型的非线性神经元(以单个输出为例)的数学表达式为:
在 PyTorch 的底层(如 nn.Linear 配合激活函数),为了处理成批的数据,公式通常表示为矩阵形式:
通过sigmoid函数,深度神经网络所需的基本元素都已经齐备了。
Softmax回归
至此,我们已经了解了线性回归、线性神经元和非线性神经元。下面介绍一个特殊的、具有“全局观”的非线性激活函数 ,它通常作为一组神经元的最后一道工序,将它们共同转化为一个 非线性神经单元阵列 。
在分类问题中,如果分类只有两个,那么我们可以使用 1 个输出神经元 + Sigmoid来实现。如果有多个输出神经元,且输出类别不互斥(如:一张图里既有“山”也有“水”),每个神经元独立判断该标签是否存在,那么我们依然可以使用 N 个输出神经元 + Sigmoid 。
然而,对于多类选一 (Multi-class Classification)的情况,即类别互斥(如:识别猫、狗、猪)的情况,模型必须在这些选项中给出一个概率分布,最终选概率最大的,那么我们就不能再使用Sigmoid函数了。
在深度学习中,Logit 通常指神经网络 最后一层全连接层输出的原始数值 ,也就是在进入激活函数(如 Softmax 或 Sigmoid)之前的结果。
为了计算每个类别的原始得分(即 Logit ),我们为每个输出配置一套独立的权重和偏置:
每个输入计算出的三个结果 \(o_1, o_2, o_3\) 被称为“未规范化的预测(logit)”。这本质上就是一个 仿射函数(Affine Function) ,即线性组合加偏置。

为了在编程和数学推导中更高效,通常将其写为矩阵乘法:
其中:
- \(\mathbf{o}\) :输出向量,维度为 \(3 \times 1\)。
- \(\mathbf{W}\) :权重矩阵,维度为 \(3 \times 4\)(对应 12 个标量权重)。
- \(\mathbf{x}\) :输入特征向量,维度为 \(4 \times 1\)。
- \(\mathbf{b}\) :偏置向量,维度为 \(3 \times 1\)。
在图示的例子中,参数规模可以按照如下计算:
即4x3+3=15个。GPT等文本生成任务本质上就是一个巨大的多类选一机器,而我们常说的总参数量计算就是上述这样将总参数相加得来的。
我们接着来看一下Softmax函数本身:
从预测的角度来看,我们不能直接把神经网络的原始输出(Logits)当成概率。我们希望预测结果是概率最高的那个类别,即\(\operatorname{argmax}_j \hat{y}_j\),简单说,就是看哪个分值最高,就选哪个。然而,神经网络最后一层直接输出的数值(记为 \(o\))不能直接叫“概率”,因为它们不符合概率的两大基本原则:非负性和归一化。
非负性指神经网络的原始输出可能是负数,但概率必须 \(\ge 0\)。归一化指原始输出的和不一定等于 \(1\),但所有可能的类别概率之和必须为 \(1\)。由于指数函数是单调递增的,这意味着如果 \(o_1 > o_2\),那么 \(\exp(o_1) > \exp(o_2)\),因此,Softmax不会改变原始预测值之间的大小次序 ,也即:
这意味着预测时直接看原始输出的最大值和看 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{X}\mathbf{W}\)* 得到的结果是一个 *\(n \times q\) 的矩阵。注意 \(\mathbf{b}\) 只有一行,但 \(\mathbf{X}\mathbf{W}\) 有 \(n\) 行。在计算时,系统会自动把 \(\mathbf{b}\) 复制到每一行上,以便与矩阵相加,这便是深度学习中非常实用的广播机制 (Broadcasting) 。
对结果矩阵 \(\mathbf{O}\) 的每一行分别做 Softmax,将其转化为概率分布(每行之和为 1),最终得到的 \(\hat{\mathbf{Y}}\) 就是一个包含 \(n\) 行样本预测概率的矩阵。
交叉熵损失函数
我们上面所讲的内容都可以认为是一层神经网络,输入数据通过全连接层进入神经元层,通过参数和偏置的计算得到预测结果,并可以通过softmax来进行多类归一任务。但是,得到预测结果并不是神经网络的重点,我们的目的是得到一组可以用来预测的参数(权重和偏置),而我们无法直接获得参数,必须通过训练来获得。
为了训练模型,我们需要一种方法来衡量预测值 \(\hat{y}\) 与真实值 \(y\) 的匹配程度。
假设整个数据集独立同分布,根据最大似然估计(MLE),我们希望最大化观测到所有真实标签的概率为:
由于乘法计算梯度非常困难,我们取对数将“乘法”转为“加法”,并取负号将“最大化问题”转为“最小化问题”,即最小化负对数似然(NLL):
由此,我们便得到了交叉熵损失函数(Cross-Entropy Loss),即对于任何标签 \(\mathbf{y}\)(独热编码)和预测概率 \(\hat{\mathbf{y}}\),具体的损失函数表达式为:
注:在分类中,由于 \(y_j\) 只有一个位置是 1,其余是 0,公式实际简化为 \(L = -\log \hat{y}_{\text{correct\_class}}\)。
因为 \(y\) 是独热编码(只有一个位置是 1,其余是 0),所以这个公式实际上只关心 正确类别的预测概率 。你对正确类别的把握越大(\(\hat{y}_j\) 越接近 1),损失就越接近 0。
接着,我们将 Softmax 的定义代入交叉熵损失中,我们可以得到损失函数相对于输出层未规范化预测(logit)\(o_j\) 的导数:
进而得到 梯度公式 (最关键的结论):
这个导数(梯度)恰好是 模型预测的概率与实际观察值之间的差异 。这种极其简单的形式(预测值 - 真实值)使得梯度计算在实践中变得非常容易且高效。
对于一个神经网络,我们把输入数据经过神经网络来得到输出结果的过程称为前向传播;接着,我们对比预测值和真实值的差异,在机器学习的笔记中已经介绍了这一思想,即使用损失函数来量化评估这一差异;最后,我们根据损失函数的结果,对其进行求导,并顺着梯度下降的方向更新参数,来让参数向我们目标的参数移动——求导与链式法则的部分称为反向传播,更新参数的部分称为优化。
这里要注意,在矢量化计算中,我们通常会对一个小批量(batch)的 \(n\) 个样本计算平均损失:
这是因为梯度的量级如果随 batch size 剧烈波动,会导致优化不稳定。
在实际编程中,直接计算 \(\exp(o_k)\) 容易导致数值溢出。因此,成熟的深度学习框架(如 PyTorch)通常会将 Softmax 和 Cross-Entropy 合并为一个函数,利用 LogSumExp 技巧来保证计算的稳定性。
值得注意的是,虽然交叉熵是训练模型时的 损失函数(Loss Function) ,但它不够直观。在评估阶段,我们使用精度 (Accuracy)。
其中:
- \(\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年,苏联数学家 Ivakhnenko 和 Lapa 提出了“数据处理分组方法”(GMDH),这被认为是世界上第一个真正的深度学习算法。他们通过多层多项式神经元来拟合数据,每一层都试图筛选出最好的特征传递给下一层。
1980年,日本科学家 福岛邦彦(Kunihiko Fukushima) 提出了 神经认知机(Neocognitron) 。他受生物视觉启发,设计了多个卷积层和池化层的交替结构。这直接影响了后来著名的卷积神经网络(CNN)。
1986年,深度学习三巨头之一 Geoffrey Hinton 等人重新推广了 反向传播算法(Backpropagation) 。这是历史性的转折点,因为它解决了“多层网络怎么训练”的问题。
从发展历程中可以看到,多层的使用来自于生物直觉。
1950年代,Hubel & Wiesel的生理学实验发现,猫的视觉皮层是分层的:有的细胞只对线条敏感,有的对形状敏感,有的对复杂物体敏感。深度神经网络模仿了这种组合式(Compositional) 的逻辑:复杂系统的本质是层级化的。通过多层堆叠,网络可以先学习“局部特征”,再学习“全局结构”。这种从局部到全局的演化,是单层网络(即使是非线性的)在逻辑上极难实现的。
另外一种假说则是关于“空间变换”的。流形分布假说(Manifold Hypothesis)认为,现实世界的高维数据(比如图片、语音)实际上都分布在低维的流形(Manifold)上。通俗点说,数据像是一张被揉皱在三维空间里的二维纸团。这种“逐层展开”的过程被称为 解纠缠(Disentanglement) 。多层的意义在于,单次变换很难把极度复杂的扭曲一次性拉平,必须通过多层连续平滑的映射,才能将复杂的流形变换到线性可分的状态。
梯度消失问题
虽然科学家们的生物直觉引导他们尝试多层的神经网络,但是多层神经网络在训练时出现了一种“失传”现象:当网络层数太深时,底层的神经元(靠近输入层的那些)由于接收不到有效的“修改建议”,导致它们停止学习。
在神经网络中,我们使用反向传播来更新参数。根据 链式法则 ,深层神经元的梯度是前面每一层梯度的 乘积 。
早期的神经网络常用 Sigmoid 激活函数。它的导数最大值只有 0.25 (当输入为 0 时)。
- 如果你的网络有 20 层,梯度传到最前面就要乘以 20 个小于 0.25 的数。
- \(0.25^{20}\) 是一个极小的数字,几乎等于 0。
此外,如果每一层的权重 \(W\) 初始化得非常小,连乘效应也会迅速让梯度归零。梯度消失问题导致靠近输出层的几层学得很好,但靠近输入的底层权重几乎不动。这意味着网络 无法提取基础特征 ,深度架构的优势荡然无存。
为了解决梯度消失问题,人们先后提出了如下解决方案:
- 逐层预训练
- ReLU(Rectified Linear Unit)
- 改进权重初始化(Xavier & He Initialization)
- 批归一化(Batch Normalization)
- 残差连接 (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激活函数
至此,我们已经从历史和理论的角度,分别了解了多层结构的来源和发展,下面我们就来学习一下多层次结构设计。
无论神经网络有多少层,我们都把该网络分为下面三个部分:
- 输入层
- 隐藏层
- 输出层

上图展示了一个单隐藏层的多层感知机,具有五个隐藏单元。
之所以叫“隐藏”,是因为在训练和预测过程中,你只能看到输入和输出,中间层的中间计算结果(特征表示)对外部用户来说是“不可见”的黑盒。
如果我们使用线性函数,无论叠加多少层,其都能合并为一层,因为他们总是线性变换的。因此,我们引入了激活函数,这一点已经反复见过很多遍,不再赘述。
我们已经介绍过Sigmoid激活函数,并在梯度消失问题那一章节中介绍了Sigmoid激活函数的问题:由于两端太水平,容易导致梯度消失。
除了Sigmoid外,早期常见的还有tanh函数:
然而,在上一小节中我们已经提到,最终让深度学习在2012年一炮而红的,是ReLU激活函数。可以说,ReLU激活函数就是现代深度学习的标配。
ReLU 的全称是 Rectified Linear Unit(修正线性单元) ,它的数学公式简单到让人怀疑人生:
- 如果输入 \(x > 0\): 直接输出 \(x\),不做任何处理。
- 如果输入 \(x \le 0\): 输出 \(0\),直接“掐死”信号。
在 ReLU 普及之前,大家都在用 Sigmoid 或 Tanh 。ReLU 的出现解决了三大痛点:
- 彻底解决“梯度消失”(在正区间)——只要 \(x > 0\),它的导数恒等于 1 。这意味着无论网络叠到 10 层还是 100 层,梯度都能在正区间内“原封不动”地传回来。
- 计算机只需要判断一下
if (x > 0)。这种极致的简单,让训练大型网络的成本大幅下降。 - ReLU 会把所有负信号都变成 0。这意味着在同一时刻,只有一部分神经元是被激活的,而大部分在“睡觉”。这非常符合生物大脑的工作方式——我们思考时,并不是全脑神经元都在同时疯狂放电。这种稀疏性(Sparsity)让模型具有更好的泛化能力,不容易过拟合。
激活函数是神经网络的灵魂。没有激活函数,就没有多层结构。
当然,ReLU也有个致命伤,如果一个神经元的权重更新得太猛,导致对于所有的训练数据,它的输入 \(x\) 永远小于 0,那么:
- 这个神经元的输出永远是 0。
- 它的梯度也永远是 0。
- 它从此 再也无法更新 ,变成了一个“死”掉的神经元。
为了解决“Dead ReLU”问题,科学家们又发明了一些变体:
- Leaky ReLU: 给负区间一点点生路(比如 \(0.01x\)),不让它彻底死掉。
- PReLU: 负区间的斜率是学出来的,不是固定的。
- GeLU (Gaussian Error Linear Unit): 在 \(0\) 点附近更加平滑。这是目前 GPT、BERT 等大模型最喜欢的激活函数。
多层全连接神经网络
至此,我们已经跟随前人的足迹,设计出了多层感知机(Multi-Layer Perceptron, 简称 MLP)。简单来说,MLP就是由若干个全连接层组成的神经网络。多层结构的全连接神经网络也称为多层感知机MLP、全连接神经网络FCNN、前馈神经网络FNN。

我们可以看到这一类神经网络的特点:
- 信号流动总是从输入层进入,经过隐藏层,最后到达输出层。因此也被称为前馈(Feedforward) 神经网络。
- 全连接:第 \(n\) 层的每一个神经元都和第 \(n-1\) 层的所有神经元相连。
- 每一层之间的连接都可以用一个巨大的矩阵\(W\)来表示。
- 每一层的输出都是 \(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}\)。
每一层其实就做两件事:线性变换 + 非线性激活。
- 线性加权和: \(z^{(l)} = W^{(l)} a^{(l-1)} + b^{(l)}\)
- 激活处理: \(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\) 对参数的偏导数(梯度)。
-
输出层的误差 (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\) 是学习率,决定了步子跨多大)
。