上一篇我们看到,线性分类器——无论是 logistic 回归还是 linear SVM——只能画一条直线。对于非线性可分的数据(比如两个新月),它们的准确率卡在 50% 左右,不管你怎么调参。
核技巧可以绕过这个限制:先把数据映射到高维,再在高维画直线。但核函数的映射 ϕ(x) 是预先固定的——你选了 RBF 核就是 RBF,选了多项式核就是多项式。如果你选错了核,分类效果照样差。
那能不能让模型自己学一个最适合这份数据的映射?这就是神经网络的核心想法:
不预设 ϕ,而是用可训练的参数搭一个映射函数,让梯度下降自动找到最好的那个。
一个神经元做的事只有两步:
第一步:线性变换。 把输入向量 x 和权重 w 做点积,加上偏置 b:
z=w⋅x+b
这跟 logistic 回归完全一样——在特征空间里画一条超平面。
第二步:非线性激活。 把 z 喂进一个非线性函数 σ:
a=σ(z)
最常用的激活函数是 ReLU(Rectified Linear Unit):
ReLU(z)=max(0,z)
几何意义:ReLU 把超平面一侧的输出截断为零,另一侧保留原值。整个操作就是「画一条线,然后把一边折下去」。
为什么这个弯至关重要?因为线性变换叠线性变换还是线性变换——两个矩阵相乘 W2W1 等于一个新矩阵 W。没有激活函数,不管堆多少层,效果跟一层一样。正是这个「弯」让叠加有了意义。
ReLU 不是唯一的选择。激活函数的设计直接决定了网络能不能训好、训多快。一个好的激活函数需要满足几件事:
- 非线性 —— 否则多层等于一层。
- 处处可微 —— 否则反向传播在某些点断裂。
- 梯度不消失、不爆炸 —— 导数在大部分区间内接近 1,链式乘法才不会指数衰减或指数放大。
- 计算便宜 —— 一个 LLM 里几十亿个激活值,每次前向都要算。
来看四种最常用的激活函数,以及它们各自的导数行为:
2012+ · AlexNet 到 ResNet 的标配max(0, z)。负侧梯度为零(dead ReLU),正侧梯度恒为 1。
ReLU 在正侧导数恒为 1,梯度能一路流回去。但负侧死区会让部分神经元「永久关闭」。
激活函数对比。点「显示导数」看梯度行为——反向传播时,导数决定了信号能不能流回去。
Sigmoid σ(z)=1/(1+e−z):输出在 (0,1) 之间,天然适合当概率。但两端饱和——当 z 很大或很小时,导数几乎为零。在深层网络里,这些零导数乘起来会让梯度消失,前面的层完全学不动。所以 sigmoid 在隐藏层已经被淘汰了,但最后一层做二分类输出时仍然常见。
Tanh tanh(z)=(ez−e−z)/(ez+e−z):输出在 (−1,1) 之间,零均值,比 sigmoid 好一些。但两端同样饱和。RNN 里还偶尔用,大部分场景已被 ReLU 系列取代。
ReLU max(0,z):正侧导数恒为 1,梯度能一路流回去;计算只需一个比较,极快。问题是负侧死区——一旦某个神经元对所有输入都输出 z<0,它就永久「死了」,梯度永远为零。经验上,约 10–30% 的 ReLU 神经元会在训练中死亡,但通常不影响最终效果。
GELU z⋅Φ(z),其中 Φ(z) 是标准正态的累积分布函数:在 z=0 附近有光滑过渡,负侧保留小梯度,不会完全死掉。计算比 ReLU 贵,但现代 GPU 上差异不大。BERT、GPT、几乎所有 LLM 都用 GELU 或其变体(SwiGLU、GeGLU)。
选择激活函数有一条经验法则:隐藏层用 ReLU 或 GELU,输出层看任务——回归用恒等,二分类用 sigmoid,多分类用 softmax。
把多个神经元并排放在一层:每个神经元画自己的线、折自己的弯。一层 N 个 ReLU 神经元就是 N 条线同时作用——几何上,它们切出一个 N 边形的区域。
数学上,一个隐藏层的前向计算:
h=ReLU(W1x+b1)
y^=w2⊤h+b2
W1 的每一行是一个神经元的权重向量(= 一条线的法向量),b1 是偏置(= 线到原点的距离),w2 控制怎么组合这些「折叠」。
下面的数据是两个同心环——内圈紫色,外圈青色,线性分类器完全没办法。切换隐藏层宽度感受:
线性分类器acc 41%
切换隐藏层宽度。4 个 ReLU 神经元画出正方形,8 个画八边形,16 个接近圆——每个神经元贡献一条直线,合在一起逼近任意形状。
4 个神经元画出正方形(4 条边),只在四个对角方向漏了。8 个画八边形,16 个已经几乎是圆。这就是万能逼近定理(Universal Approximation Theorem)的直觉:
只要隐藏层够宽,一层神经网络就能逼近任意连续函数到任意精度。
实际上,深度网络(多层)比宽网络(一层很宽)更高效——因为叠层是变换的复合 f3∘f2∘f1,每一层可以在前一层的特征上做进一步的「折叠」。同样表达能力,深网络需要的参数更少。这也是为什么 Transformer 要堆 96 层而不是 1 层。
网络搭好了,怎么训练?目标跟以前一样:定义一个损失 L,算出 L 对每个参数的梯度,然后用梯度下降更新参数。
关键问题是:参数藏在很多层里,L 对第一层权重的梯度要穿过后面所有层才能算出来。链式法则告诉我们,把每一段的局部导数乘起来就行:
∂w∂L=∂y^∂L⋅∂z∂y^⋅∂w∂z
反向传播(backpropagation) 就是把这条乘法链从后往前算一遍。
把神经网络的每一次运算都画成一个节点,数据流就是边。这样一个两层网络的计算图长这样:
x×W1+b1z1ReLUh×w2+b2y^(y^−t)2L
前向传播:从左到右,每个节点根据输入算输出。反向传播:从右到左,每个节点根据输出的梯度算输入的梯度。每个节点只需要知道自己的局部导数:
| 节点 | 前向 | 反向(局部导数) |
|---|
| ×W+b | z=Wx+b | ∂x∂z=W⊤,∂W∂z=x⊤ |
| ReLU | h=max(0,z) | ∂zi∂hi=1[zi>0](逐元素 0/1 门控) |
| MSE | L=(y^−t)2 | ∂y^∂L=2(y^−t) |
梯度回传时,每经过一个节点就乘一次局部导数。对于第二层的权重 w2:
∂w2∂L=∂L/∂y^2(y^−t)⋅∂y^/∂w2h⊤
对于第一层的权重 W1,链子更长:
∂W1∂L=∂L/∂y^2(y^−t)⋅∂y^/∂hw2⊤⋅∂h/∂z1diag(1[z1>0])⋅∂z1/∂W1x⊤
这就是梯度消失的数学根源:链式乘法中如果每一项都小于 1(比如 sigmoid 导数峰值只有 0.25),乘几次就趋近零了。ReLU 的正侧导数恒为 1,所以梯度不会在正侧衰减——这是它取代 sigmoid 的根本原因。
来看一个具体例子——单个神经元,输入 x=2,权重 w=0.5,偏置 b=−0.3,目标 t=1:
点「前向传播」看数据怎么流过网络,再点「反向传播」看梯度怎么流回去。
一个单神经元的前向 / 反向传播。真实网络有几十亿参数,但每个参数的梯度都是这条链的一环。
前向传播算出预测 y^=0.70,损失 L=0.09。反向传播从 L 出发,逐层乘回去:∂L/∂w=−1.2。这告诉优化器:w 应该增大(梯度为负,沿负梯度方向走就是增大),从而把预测往目标推近。
真实网络有几十亿参数,但每个参数的梯度都是同样的链式乘法。PyTorch 的 autograd 和 JAX 的 grad 做的事情就是自动地把这条链算出来——你只写前向,它们帮你做反向。
损失函数定义「什么叫好的预测」。神经网络常用的损失有两大类,分别对应回归和分类:
均方误差(MSE)——回归任务的默认选择:
LMSE=n1i=1∑n(y^i−yi)2
MSE 对大误差惩罚很重(平方),对异常值敏感。概率上等价于假设数据有高斯噪声时的极大似然估计。
交叉熵(Cross-Entropy)——分类任务的标配:
LCE=−n1i=1∑nk=1∑Kyiklogp^ik
其中 yik 是 one-hot 标签(正确类别为 1,其余为 0),p^ik 是 softmax 输出的概率。交叉熵衡量的是模型预测的分布和真实分布之间的距离。
为什么分类不用 MSE?回忆 logistic 回归里讲过的:sigmoid/softmax 把输出压到 (0,1) 后,MSE 的梯度在「远端」几乎为零——预测错得很离谱时梯度反而小,训练会卡住。交叉熵配合 softmax 则给出恒定的梯度规模:∂L/∂zk=p^k−yk,干净利落。
| 任务 | 输出层 | 损失 | 概率解释 |
|---|
| 回归 | 恒等 | MSE | 高斯噪声 MLE |
| 二分类 | sigmoid | Binary CE | Bernoulli MLE |
| 多分类 | softmax | Cross-Entropy | Multinomial MLE |
| 语言模型 | softmax(词表维度) | Cross-Entropy | 下一个 token 的 MLE |
LLM 的训练目标就是一个超大规模的交叉熵:给定前面的 token,预测下一个 token 在 50000+ 维词表上的分布。每训练一个 batch 就是在做几十万次 softmax + 交叉熵。
梯度下降需要起点——参数的初始值。随便初始化行不行?不行。
假设一层有 512 个神经元,权重从 N(0,1) 采样。输入 x 的方差是 1,那么 z=∑i=1512wixi 的方差就是 512——z 的绝对值会非常大。经过 ReLU 后,大部分值落在正侧的线性区,网络的有效容量被浪费了;如果用 sigmoid/tanh,z 极大导致输出饱和在 ±1,导数为零,还没开始训梯度就消失了。
解决方案:控制每层输出的方差。
Xavier 初始化(Glorot & Bengio, 2010)——适合 sigmoid/tanh:
W∼N(0,nin+nout2)或U[−nin+nout6,nin+nout6]
直觉:让前向传播的方差和反向传播的方差都保持一致——nin+nout 是两层维度之和。
He 初始化(He et al., 2015)——适合 ReLU:
W∼N(0,nin2)
因为 ReLU 会把一半输出截为零,方差直接砍半,所以分母用 nin 而不是 (nin+nout)/2,把初始化方差放大一倍补偿回来。
偏置 b 通常初始化为零。没有随机性——对称性由权重的随机初始化打破。如果所有权重也初始化为相同的值,同层的所有神经元会做完全一样的事,训练过程中永远不会分化。
经验法则:用 ReLU 就用 He,用 sigmoid/tanh 就用 Xavier,用 GELU 用 Xavier 或 PyTorch 默认的 Kaiming uniform 都行。大多数框架已经帮你做好了默认初始化,但自定义架构时仍然需要注意。
网络训起来之后,有两个问题绕不开:训练不稳定和过拟合。Batch Normalization 解决前者,Dropout 解决后者。
训练过程中,每一层的输入分布在不断变化——前一层的参数在更新,它输出的均值和方差就在漂移。后一层不得不持续适应新的输入范围,训练效率低且不稳定。这种内部协变量偏移(Internal Covariate Shift) 是深度网络难训的核心原因之一。
Batch Normalization(Ioffe & Szegedy, 2015)的做法很直接:在每个 mini-batch 内,把每层的输入拉到均值为零、方差为一,再用两个可学习参数 γ 和 β 恢复表达能力:
x^i=σB2+ϵxi−μB,yi=γx^i+β
其中 μB 和 σB2 是当前 mini-batch 的均值和方差,ϵ 防止除以零。γ 和 β 是可学习参数——如果网络觉得标准化不好,它可以学回原来的分布。
Batch Norm 的好处:
- 允许更大的学习率——标准化让损失面更平滑。
- 减少对初始化的敏感——不管初始值怎么飘,BN 都能拉回来。
- 轻微的正则化效果——mini-batch 的统计量带有噪声,相当于注入了随机性。
但 BN 有一个硬伤:依赖 batch size。当 batch size 很小(比如 1 或 2)时,μB 和 σB2 的估计非常差,效果崩溃。Transformer 训练序列很长、batch size 很小时,BN 就不好用了——这就是为什么 Transformer 选择了 Layer Normalization(对单条样本的所有特征做标准化,而非对 batch 维度)。
Dropout(Srivastava et al., 2014)是另一种正则化手段:训练时,以概率 p(通常 0.1–0.5)随机把一些神经元的输出设为零。推理时不用 dropout,但把所有权重乘以 (1−p) 缩放。
h~=h⊙m,mi∼Bernoulli(1−p)
直觉上,dropout 强迫网络不能依赖任何单个神经元——每个神经元都要在没有同伴的情况下也能工作。效果等价于训练了指数级多个子网络(每个 dropout mask 对应一个子网络),推理时是它们的集成。
Dropout 在 CNN 和传统全连接网络里非常有效。但在 Transformer 里用得比较克制(通常只在 FFN 后加 p=0.1 的 dropout),因为 Layer Norm + 残差连接 + 大数据量本身就提供了足够的正则化。
有了反向传播、好的激活函数、合理的初始化和归一化,理论上可以训练任意深的网络。但实践中还有几个工程上绕不开的问题:
批量梯度下降(mini-batch SGD)——不拿全部数据算一次梯度(太慢),也不只拿一个样本(太噪),而是每次取一个小批量(batch size 64–4096)。噪声反而帮助跳出局部最小值。
学习率调度——开头用小学习率(warm-up),让参数在初始化附近稳定住;中间放大,大步搜索;后期衰减(cosine decay),精细收敛。这套 warm-up + cosine 几乎是 Transformer 训练的标配。
Adam 优化器——不是裸用 −∇L,而是给每个参数维护梯度均值和梯度方差的滑动平均,自适应调步长。LLM 训练几乎全用 AdamW(Adam 加权重衰减)。
残差连接——即使有了好的初始化和 BN,超深网络仍然需要 skip connection y=f(x)+x,让梯度沿着 +x 那条路直接流到底。Transformer 的每个子层都有残差连接。
梯度裁剪——RNN 和 LLM 训练时梯度偶尔会爆炸(loss spike),把超过阈值的梯度范数裁掉就能救回来。简单粗暴但救命。
这些技巧不是装饰——没有它们,GPT 级别的模型根本训不动。