上一篇把逻辑回归拆成了一个可以训练的 Sigmoid 神经元。它接收输入,计算加权和,再输出一个概率。训练能够移动它的决策边界,却不能改变一个事实:这个边界始终是一条直线。
这一篇从四个点开始。我们会先证明,多写几层线性计算仍然只能得到线性模型;再把激活函数放到层与层之间,观察它怎样让网络形成新的表示。最后比较 Sigmoid、Tanh、ReLU 与 GELU:它们不只长得不同,也会改变信号怎样向前流动、梯度怎样向后传播。
一条直线分不开 XOR
XOR 叫作“异或”。两个输入不同时,它输出 ;两个输入相同时,它输出 :
把四个输入画在平面上,两个类别会交叉落在正方形的对角线上:
x₂
1 ● y=1 ○ y=0
0 ○ y=0 ● y=1
0 1 x₁
单个神经元先计算:
如果用 作为分类边界,它只能在平面上画出一条直线。无论这条线怎样平移或旋转,都无法把两个实心点放在一侧、两个空心点放在另一侧。
这不是参数还没找到,也不是训练轮次不够。模型允许的答案里根本没有正确边界。
多写几层 Linear 也没有用
既然一个线性层不够,最直接的想法是再接一个。第一层把输入变成隐藏表示:
第二层再根据隐藏表示产生输出:
把第一条式子代入第二条:
令
两层计算便重新变成:
它和一个更大的线性层具有完全相同的形式。三层、十层也一样:仿射变换的复合仍然是仿射变换。中间虽然出现了许多数字,整个模型却没有获得弯折输入空间的能力。
这里常说的 Linear 实际包含权重与偏置,数学上更准确的名字是仿射变换。深度学习语境通常沿用“线性层”这个名称。本文也采用这一习惯,但上面的合并已经把偏置计算在内。
问题因此变得明确:层与层之间需要一个无法被矩阵乘法和加法吸收的变换。
激活函数放在什么位置
激活函数通常记作 。一个隐藏层先计算预激活值 ,再逐元素应用 :
“逐元素”表示每个隐藏单元分别处理自己的输入:
两层网络于是变成:
输入 x
↓
z₁ = W₁x + b₁
↓ 激活函数 φ
h₁ = φ(z₁)
↓
z₂ = W₂h₁ + b₂
引入非线性的 后,网络不再能对所有输入和任意参数统一压缩成一次矩阵乘法。第一层可以为输入建立若干新的坐标,激活函数对这些坐标作截断、压缩或平滑加权,下一层再组合变换后的结果。某组特殊参数仍可能碰巧表达线性函数,ReLU 网络在单个局部区域内也仍是线性的;新增的是表达非线性函数的可能性。
激活函数的作用不是单纯“让某个神经元亮起来”,也不一定把值限制在 到 。它在网络中的核心职责,是让多层函数的复合不再退化为一个仿射函数。
ReLU 怎样让 XOR 可表示
先用最简单的 ReLU 看一次具体构造:
对于 XOR 的四个输入,只需先计算它们的和:
再建立两个隐藏单元:
最后组合为:
把 分别代入:
网络现在恰好得到 XOR 的四个答案。关键不是“用了两个神经元”本身,而是 ReLU 在 和 处让计算规则发生了转折:
原来的输入空间经过隐藏层后,获得了可以被最后一层线性组合的新表示。这个例子使用手工指定的参数来证明“可以表示”,还没有说明训练怎样自动找到这些参数;那需要前向传播、损失与反向传播共同工作。
比较函数时要看什么
一条激活曲线至少需要从四个角度观察。
第一是输出范围。输出是否有上下界、是否以 为中心,会影响下一层收到的数值分布。
第二是梯度。反向传播会乘上激活函数的导数。导数长期接近 时,前面的层可能很难收到调整信号;导数特别大时,信号又可能变得不稳定。
第三是平滑性。函数是否处处可导、导数是否连续,会改变优化时看到的局部地形。不过,单个点不可导并不等于无法训练。
第四是使用位置。隐藏层需要建立表示,输出层需要表达任务要求。适合输出概率的函数,未必适合反复放在许多隐藏层里。
下面沿这四个角度比较四种常见函数。
Sigmoid:适合概率出口,容易在隐藏层饱和
Sigmoid 在上一篇已经出现:
它把整个实数轴平滑压缩到 ,导数可以只用输出本身表示:
导数在 时取得最大值 。当 很大时,输出接近 ;当 很小时,输出接近 。两端的曲线越来越平,导数也越来越接近 ,这种状态叫作饱和。
z 很小 z≈0 z 很大
输出接近 0 输出约 0.5 输出接近 1
梯度接近 0 梯度最大 0.25 梯度接近 0
Sigmoid 放在二分类输出端时有清楚的概率含义:模型把 作为 logit, 再把它还原为类别 的概率。实际计算损失时,通常直接把 logit 交给数值稳定的“Sigmoid 与二元交叉熵”组合,而不是先算出一个极接近 或 的概率再取对数。
它在隐藏层里的处境不同。若误差信号穿过许多 Sigmoid,每一层都要乘上一个不超过 的激活导数。只看六个这样的导数,相乘最多只有:
完整梯度还会受到权重矩阵等因素影响,这个数不是对所有网络的最终梯度预测;它展示的是一条持续缩短信号的通道。输入落入饱和区后,缩短会更加明显。这正是深层隐藏层较少默认使用 Sigmoid 的重要原因。
此外,Sigmoid 输出始终为正,不以 为中心。下一层收到的激活可能整体偏向同一方向。Glorot 与 Bengio 对深层前馈网络的分析展示了 Logistic Sigmoid 的均值和饱和如何妨碍训练,也推动了与激活函数相匹配的初始化研究。
Tanh:以零为中心,但仍会饱和
双曲正切函数写作:
它与 Sigmoid 形状相似,但输出范围变为 ,并且以原点为中心:
导数为:
在 附近,Tanh 的导数可以接近 ,信号比 Sigmoid 更容易穿过这一段;正输入与负输入也能产生不同符号的激活。需要表达“增加或减少”“朝两个方向偏移”时,这种对称范围很自然。
但是 Tanh 没有消除饱和。 增大后,输出分别靠近 或 ,导数仍会靠近 。如果许多层的输入持续落在两端,梯度一样可能逐层变小。
Tanh 至今仍适合某些需要有界、带符号状态的位置。例如经典循环网络会用它限制候选状态的范围。它不是因为“比 ReLU 老”而失效;选择取决于这一层希望表示什么,以及训练信号需要怎样通过。
ReLU:正半轴保持线性
ReLU 是 Rectified Linear Unit 的缩写:
它的规则非常直接:负数变成 ,正数保持不变。除 外,导数为:
函数在 处没有唯一导数。软件实现会约定一个可用的次梯度,常见选择是 。训练不会因此停摆:精确落在这个转折点只是参数空间中的特殊位置,优化算法也本来就在使用局部近似。
ReLU 的正半轴不饱和。只要 ,激活导数就是 ,反向信号不会仅仅因为这个激活函数而缩小。它还会把负输入精确变成 ,让一部分单元在给定样本上保持静默,形成稀疏激活。2011 年的 Deep Sparse Rectifier Neural Networks 系统展示了 Rectifier 在深层监督网络中的训练表现,以及这种精确为零的稀疏表示。
代价也来自同一条负半轴。若某个单元对所有训练样本都得到 ,它的局部梯度会一直为 ,参数无法靠当前信号回到正区间。这通常被称为 dying ReLU。过大的学习率、不合适的初始化或持续负偏置都可能增加这种风险。
ReLU 的输出没有上界,也不直接表示概率。它常用于隐藏层,不应因为名字里有“激活”就机械地接到所有输出端。
GELU:平滑地为输入加权
GELU 是 Gaussian Error Linear Unit 的缩写,定义为:
其中 是标准正态分布的累积分布函数。 位于 和 之间,因此可以把 GELU 看成用一个随输入平滑变化的系数缩放 :
较大的负数 → 系数接近 0 → 输出接近 0
接近 0 → 系数约为 0.5
较大的正数 → 系数接近 1 → 输出接近输入
它与 ReLU 的差别集中在原点附近。ReLU 用符号作硬切换:负数全部归零,正数全部保留。GELU 使用 作平滑权重,允许一小段负输入以较小幅度通过,正输入也不是立刻完整通过。
GELU 的导数为:
这里的 是标准正态分布的概率密度函数。导数连续,但并不始终位于 和 之间;函数在一小段负区间内并非严格单调。这些细节说明,“平滑版 ReLU”可以帮助形成直觉,却不是完整定义。
Hendrycks 与 Gimpel 在 GELU 原始论文中把这种区别概括为:ReLU 按输入的符号门控,GELU 按输入的数值加权。GELU 后来常见于 Transformer 的前馈子层。它的平滑性并不保证在每个网络、每份数据上都优于 ReLU;架构、初始化、归一化、优化器与计算成本仍然共同决定结果。
把四条曲线放在一起
| 函数 | 公式 | 输出范围 | 饱和区域 | 常见位置 |
|---|---|---|---|---|
| Sigmoid | 正、负两端 | 二分类输出 | ||
| Tanh | 正、负两端 | 有界且带符号的状态 | ||
| ReLU | 整个负半轴梯度为零 | MLP、卷积网络隐藏层 | ||
| GELU | 约 | 很负时梯度接近零 | Transformer 隐藏层 |
“饱和”与“梯度为零”需要分清。Sigmoid 和 Tanh 在两端逐渐变平,导数无限接近 ;ReLU 在负半轴直接给出 ,正半轴保持导数 ;GELU 在两者之间采用平滑过渡。
再观察几个点上的输出:
| Sigmoid | Tanh | ReLU | GELU(约) | |
|---|---|---|---|---|
同一个预激活值经过不同函数后,会给下一层完全不同的尺度、符号和稀疏性。激活函数不是独立于网络其余部分的装饰;权重初始化、归一化与学习率都要和这些数值特征一起考虑。
梯度怎样穿过激活函数
假设某层计算:
后面的网络最终产生损失 。反向传播到这一层时,链式法则给出:
表示逐元素相乘。前一项是后面传回来的误差信号,后一项是当前激活函数在每个单元上的局部斜率。
这条式子解释了为什么曲线形状会影响训练:
- Sigmoid 或 Tanh 进入平坦区时, 很小,传回前层的信号被削弱;
- ReLU 单元位于负区间时,,这条路径暂时不传信号;
- ReLU 位于正区间时,,信号原样通过激活节点;
- GELU 在原点附近连续改变缩放比例,反向信号也随之平滑变化。
梯度消失不能只归咎于一种激活函数。深度、权重矩阵、初始化、归一化、残差连接与损失函数都会改变整条路径的雅可比矩阵。激活函数提供的是每一层中的一个局部因子。
隐藏层和输出层回答不同问题
“哪种激活函数最好”缺少一个必要条件:它放在哪里?
隐藏层的输出是网络内部表示。这里通常希望保留有效梯度,同时让多层组合具有非线性。ReLU 是普通前馈网络的可靠起点;GELU 常见于 Transformer;Tanh 适合需要有界、带符号状态的结构。具体选择仍应通过训练稳定性与验证集表现检查。
输出层则要匹配任务:
| 任务 | 最后一层产生什么 | 训练时怎样处理 |
|---|---|---|
| 二分类 | 一个 logit | 与二元交叉熵的稳定实现组合 |
| 互斥的多分类 | 每个类别一个 logit | 用 Softmax 交叉熵统一比较 |
| 多标签分类 | 每个标签一个独立 logit | 每个位置使用 Sigmoid 二元交叉熵 |
| 不受范围限制的回归 | 一个或多个实数 | 通常不加输出激活 |
| 必须非负或落在固定区间的预测 | 满足约束的数值 | 根据约束选择 Softplus、Sigmoid 等变换 |
表里的“一个 logit”表示先保留未压缩的分数。推理时可以再用 Sigmoid 或 Softmax 转成概率;训练时让损失函数直接接收 logits,通常能避免指数、对数和有限浮点精度造成的不稳定。
因此,不能用一条“隐藏层全部 ReLU,输出层全部 Sigmoid”的口诀覆盖所有任务。输出的数学含义应由任务和损失共同决定。
用 NumPy 写出四种函数
下面的实现直接对应公式:
import numpy as np
def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))
def tanh(z):
return np.tanh(z)
def relu(z):
return np.maximum(0.0, z)
def gelu(z):
return 0.5 * z * (
1.0
+ np.tanh(
np.sqrt(2.0 / np.pi) * (z + 0.044715 * z**3)
)
)
最后一段是 GELU 常用的 Tanh 近似。它避免显式计算正态分布累积分布函数。若要实现定义本身,可以借助 erf 写成:
在一个隐藏层中,激活函数只占一行:
z1 = x @ w1 + b1
h1 = relu(z1)
logits = h1 @ w2 + b2
但这一行改变了整个模型的函数族。删掉 relu 后,w1 与 w2 可以合并,网络重新退化为单个仿射变换;保留它以后,不同隐藏单元能在输入空间的不同区域开启,后面的层再把这些局部响应拼成更复杂的函数。
选择激活函数时的检查顺序
面对一个新网络,可以按下面的顺序判断:
- 先确定输出语义。 是概率、互斥类别、多个独立标签,还是不受限的连续值?输出激活与损失必须一起设计。
- 再选隐藏层基线。 普通 MLP 或卷积网络可以先用 ReLU;Transformer 类结构通常沿用其架构指定的 GELU 或其他门控函数。
- 检查激活与梯度分布。 大量单元是否长期为零?Sigmoid 或 Tanh 是否集中在饱和区?不同层的数值是否迅速放大或缩小?
- 把初始化、归一化和学习率一起看。 激活异常有时是上游尺度不合适的结果,替换函数不一定触及原因。
- 用验证结果决定。 函数的理论性质提供候选方向,不能替代针对数据和架构的实验。
这套顺序比背诵一张排行榜更可靠。激活函数并不存在脱离位置、结构和目标的全局冠军。
非线性带来了什么
现在可以把开头的问题完整复述一遍:
只有线性层
↓ 可以代数合并
无论多深,仍是一次仿射变换
在线性层之间加入非线性激活
↓ 不能再合并
隐藏层形成新的表示
↓ 后续层继续组合
得到分段、弯曲或更复杂的函数
XOR 展示了最小的能力差异:单条直线无法完成分类,两个 ReLU 隐藏单元却能构造出正确答案。Sigmoid、Tanh、ReLU 与 GELU 都能提供非线性,但它们对输出范围、信号中心、饱和区域和局部梯度作出了不同选择。
激活函数解决了“多层为什么不等于一层”的问题,却没有自动回答“应该有多少层、每层多少单元、参数怎样组织”。下一篇会继续追问为什么需要深度:宽度与深度各自怎样增加表示能力,多层组合又怎样逐步形成层级表示。随后我们再把这些结构正式写成多层感知机,沿张量形状追踪一批输入的前向计算。