Blog
Essay
深度学习:Basics·Chapter 2 of 12

激活函数:神经网络为什么需要非线性

从 XOR 与线性变换的边界出发,理解激活函数怎样改变表示空间,并比较 Sigmoid、Tanh、ReLU 与 GELU 的输出、梯度和使用位置。

deep-learningneural-networkactivation-functionrelugelu

上一篇把逻辑回归拆成了一个可以训练的 Sigmoid 神经元。它接收输入,计算加权和,再输出一个概率。训练能够移动它的决策边界,却不能改变一个事实:这个边界始终是一条直线。

这一篇从四个点开始。我们会先证明,多写几层线性计算仍然只能得到线性模型;再把激活函数放到层与层之间,观察它怎样让网络形成新的表示。最后比较 Sigmoid、Tanh、ReLU 与 GELU:它们不只长得不同,也会改变信号怎样向前流动、梯度怎样向后传播。

一条直线分不开 XOR

XOR 叫作“异或”。两个输入不同时,它输出 11;两个输入相同时,它输出 00

x1x_1x2x_2y=x1XORx2y=x_1\operatorname{XOR}x_2
000000
001111
110011
111100

把四个输入画在平面上,两个类别会交叉落在正方形的对角线上:

x₂
1     ● y=1          ○ y=0

0     ○ y=0          ● y=1
      0              1        x₁

单个神经元先计算:

z=w1x1+w2x2+bz=w_1x_1+w_2x_2+b

如果用 z=0z=0 作为分类边界,它只能在平面上画出一条直线。无论这条线怎样平移或旋转,都无法把两个实心点放在一侧、两个空心点放在另一侧。

这不是参数还没找到,也不是训练轮次不够。模型允许的答案里根本没有正确边界。

多写几层 Linear 也没有用

既然一个线性层不够,最直接的想法是再接一个。第一层把输入变成隐藏表示:

h=W1x+b1\boldsymbol{h}=\boldsymbol{W}_1\boldsymbol{x}+\boldsymbol{b}_1

第二层再根据隐藏表示产生输出:

y=W2h+b2\boldsymbol{y}=\boldsymbol{W}_2\boldsymbol{h}+\boldsymbol{b}_2

把第一条式子代入第二条:

y=W2(W1x+b1)+b2=(W2W1)x+(W2b1+b2)\begin{aligned} \boldsymbol{y} &=\boldsymbol{W}_2(\boldsymbol{W}_1\boldsymbol{x}+\boldsymbol{b}_1)+\boldsymbol{b}_2\\ &=(\boldsymbol{W}_2\boldsymbol{W}_1)\boldsymbol{x} +(\boldsymbol{W}_2\boldsymbol{b}_1+\boldsymbol{b}_2) \end{aligned}

W=W2W1,b=W2b1+b2\boldsymbol{W}=\boldsymbol{W}_2\boldsymbol{W}_1, \qquad \boldsymbol{b}=\boldsymbol{W}_2\boldsymbol{b}_1+\boldsymbol{b}_2

两层计算便重新变成:

y=Wx+b\boldsymbol{y}=\boldsymbol{W}\boldsymbol{x}+\boldsymbol{b}

它和一个更大的线性层具有完全相同的形式。三层、十层也一样:仿射变换的复合仍然是仿射变换。中间虽然出现了许多数字,整个模型却没有获得弯折输入空间的能力。

这里常说的 Linear 实际包含权重与偏置,数学上更准确的名字是仿射变换。深度学习语境通常沿用“线性层”这个名称。本文也采用这一习惯,但上面的合并已经把偏置计算在内。

问题因此变得明确:层与层之间需要一个无法被矩阵乘法和加法吸收的变换。

激活函数放在什么位置

激活函数通常记作 ϕ\phi。一个隐藏层先计算预激活值 z\boldsymbol{z},再逐元素应用 ϕ\phi

z=Wx+b,h=ϕ(z)\boldsymbol{z}=\boldsymbol{W}\boldsymbol{x}+\boldsymbol{b}, \qquad \boldsymbol{h}=\phi(\boldsymbol{z})

“逐元素”表示每个隐藏单元分别处理自己的输入:

ϕ(z)=[ϕ(z1)ϕ(z2)]\phi(\boldsymbol{z})= \begin{bmatrix} \phi(z_1)\\ \phi(z_2)\\ \vdots \end{bmatrix}

两层网络于是变成:

输入 x
  ↓
z₁ = W₁x + b₁
  ↓ 激活函数 φ
h₁ = φ(z₁)
  ↓
z₂ = W₂h₁ + b₂

引入非线性的 ϕ\phi 后,网络不再能对所有输入和任意参数统一压缩成一次矩阵乘法。第一层可以为输入建立若干新的坐标,激活函数对这些坐标作截断、压缩或平滑加权,下一层再组合变换后的结果。某组特殊参数仍可能碰巧表达线性函数,ReLU 网络在单个局部区域内也仍是线性的;新增的是表达非线性函数的可能性。

激活函数的作用不是单纯“让某个神经元亮起来”,也不一定把值限制在 0011。它在网络中的核心职责,是让多层函数的复合不再退化为一个仿射函数。

ReLU 怎样让 XOR 可表示

先用最简单的 ReLU 看一次具体构造:

ReLU(z)=max(0,z)\operatorname{ReLU}(z)=\max(0,z)

对于 XOR 的四个输入,只需先计算它们的和:

s=x1+x2s=x_1+x_2

再建立两个隐藏单元:

h1=ReLU(s),h2=ReLU(s1)h_1=\operatorname{ReLU}(s), \qquad h_2=\operatorname{ReLU}(s-1)

最后组合为:

y^=h12h2\hat y=h_1-2h_2

s=0,1,2s=0,1,2 分别代入:

(x1,x2)(x_1,x_2)ssh1h_1h2h_2y^=h12h2\hat y=h_1-2h_2
(0,0)(0,0)00000000
(0,1)(0,1)11110011
(1,0)(1,0)11110011
(1,1)(1,1)22221100

网络现在恰好得到 XOR 的四个答案。关键不是“用了两个神经元”本身,而是 ReLU 在 s=0s=0s=1s=1 处让计算规则发生了转折:

y^={s,0s12s,1<s2\hat y= \begin{cases} s, & 0\le s\le1\\ 2-s, & 1<s\le2 \end{cases}

原来的输入空间经过隐藏层后,获得了可以被最后一层线性组合的新表示。这个例子使用手工指定的参数来证明“可以表示”,还没有说明训练怎样自动找到这些参数;那需要前向传播、损失与反向传播共同工作。

比较函数时要看什么

一条激活曲线至少需要从四个角度观察。

第一是输出范围。输出是否有上下界、是否以 00 为中心,会影响下一层收到的数值分布。

第二是梯度。反向传播会乘上激活函数的导数。导数长期接近 00 时,前面的层可能很难收到调整信号;导数特别大时,信号又可能变得不稳定。

第三是平滑性。函数是否处处可导、导数是否连续,会改变优化时看到的局部地形。不过,单个点不可导并不等于无法训练。

第四是使用位置。隐藏层需要建立表示,输出层需要表达任务要求。适合输出概率的函数,未必适合反复放在许多隐藏层里。

下面沿这四个角度比较四种常见函数。

Sigmoid:适合概率出口,容易在隐藏层饱和

Sigmoid 在上一篇已经出现:

σ(z)=11+ez\sigma(z)=\frac{1}{1+e^{-z}}

它把整个实数轴平滑压缩到 (0,1)(0,1),导数可以只用输出本身表示:

σ(z)=σ(z)(1σ(z))\sigma'(z)=\sigma(z)(1-\sigma(z))

导数在 z=0z=0 时取得最大值 0.250.25。当 zz 很大时,输出接近 11;当 zz 很小时,输出接近 00。两端的曲线越来越平,导数也越来越接近 00,这种状态叫作饱和

z 很小                 z≈0                  z 很大
输出接近 0             输出约 0.5           输出接近 1
梯度接近 0             梯度最大 0.25        梯度接近 0

Sigmoid 放在二分类输出端时有清楚的概率含义:模型把 zz 作为 logit,σ(z)\sigma(z) 再把它还原为类别 11 的概率。实际计算损失时,通常直接把 logit 交给数值稳定的“Sigmoid 与二元交叉熵”组合,而不是先算出一个极接近 0011 的概率再取对数。

它在隐藏层里的处境不同。若误差信号穿过许多 Sigmoid,每一层都要乘上一个不超过 0.250.25 的激活导数。只看六个这样的导数,相乘最多只有:

0.256=140960.25^6=\frac{1}{4096}

完整梯度还会受到权重矩阵等因素影响,这个数不是对所有网络的最终梯度预测;它展示的是一条持续缩短信号的通道。输入落入饱和区后,缩短会更加明显。这正是深层隐藏层较少默认使用 Sigmoid 的重要原因。

此外,Sigmoid 输出始终为正,不以 00 为中心。下一层收到的激活可能整体偏向同一方向。Glorot 与 Bengio 对深层前馈网络的分析展示了 Logistic Sigmoid 的均值和饱和如何妨碍训练,也推动了与激活函数相匹配的初始化研究。

Tanh:以零为中心,但仍会饱和

双曲正切函数写作:

tanh(z)=ezezez+ez\tanh(z)=\frac{e^z-e^{-z}}{e^z+e^{-z}}

它与 Sigmoid 形状相似,但输出范围变为 (1,1)(-1,1),并且以原点为中心:

tanh(z)=2σ(2z)1\tanh(z)=2\sigma(2z)-1

导数为:

ddztanh(z)=1tanh2(z)\frac{d}{dz}\tanh(z)=1-\tanh^2(z)

z=0z=0 附近,Tanh 的导数可以接近 11,信号比 Sigmoid 更容易穿过这一段;正输入与负输入也能产生不同符号的激活。需要表达“增加或减少”“朝两个方向偏移”时,这种对称范围很自然。

但是 Tanh 没有消除饱和。z|z| 增大后,输出分别靠近 1-111,导数仍会靠近 00。如果许多层的输入持续落在两端,梯度一样可能逐层变小。

Tanh 至今仍适合某些需要有界、带符号状态的位置。例如经典循环网络会用它限制候选状态的范围。它不是因为“比 ReLU 老”而失效;选择取决于这一层希望表示什么,以及训练信号需要怎样通过。

ReLU:正半轴保持线性

ReLU 是 Rectified Linear Unit 的缩写:

ReLU(z)=max(0,z)\operatorname{ReLU}(z)=\max(0,z)

它的规则非常直接:负数变成 00,正数保持不变。除 z=0z=0 外,导数为:

ReLU(z)={0,z<01,z>0\operatorname{ReLU}'(z)= \begin{cases} 0,&z<0\\ 1,&z>0 \end{cases}

函数在 z=0z=0 处没有唯一导数。软件实现会约定一个可用的次梯度,常见选择是 00。训练不会因此停摆:精确落在这个转折点只是参数空间中的特殊位置,优化算法也本来就在使用局部近似。

ReLU 的正半轴不饱和。只要 z>0z>0,激活导数就是 11,反向信号不会仅仅因为这个激活函数而缩小。它还会把负输入精确变成 00,让一部分单元在给定样本上保持静默,形成稀疏激活。2011 年的 Deep Sparse Rectifier Neural Networks 系统展示了 Rectifier 在深层监督网络中的训练表现,以及这种精确为零的稀疏表示。

代价也来自同一条负半轴。若某个单元对所有训练样本都得到 z<0z<0,它的局部梯度会一直为 00,参数无法靠当前信号回到正区间。这通常被称为 dying ReLU。过大的学习率、不合适的初始化或持续负偏置都可能增加这种风险。

ReLU 的输出没有上界,也不直接表示概率。它常用于隐藏层,不应因为名字里有“激活”就机械地接到所有输出端。

GELU:平滑地为输入加权

GELU 是 Gaussian Error Linear Unit 的缩写,定义为:

GELU(z)=zΦ(z)\operatorname{GELU}(z)=z\Phi(z)

其中 Φ(z)\Phi(z) 是标准正态分布的累积分布函数。Φ(z)\Phi(z) 位于 0011 之间,因此可以把 GELU 看成用一个随输入平滑变化的系数缩放 zz

较大的负数 → 系数接近 0 → 输出接近 0
接近 0     → 系数约为 0.5
较大的正数 → 系数接近 1 → 输出接近输入

它与 ReLU 的差别集中在原点附近。ReLU 用符号作硬切换:负数全部归零,正数全部保留。GELU 使用 Φ(z)\Phi(z) 作平滑权重,允许一小段负输入以较小幅度通过,正输入也不是立刻完整通过。

GELU 的导数为:

GELU(z)=Φ(z)+zφ(z)\operatorname{GELU}'(z)=\Phi(z)+z\,\varphi(z)

这里的 φ(z)\varphi(z) 是标准正态分布的概率密度函数。导数连续,但并不始终位于 0011 之间;函数在一小段负区间内并非严格单调。这些细节说明,“平滑版 ReLU”可以帮助形成直觉,却不是完整定义。

Hendrycks 与 Gimpel 在 GELU 原始论文中把这种区别概括为:ReLU 按输入的符号门控,GELU 按输入的数值加权。GELU 后来常见于 Transformer 的前馈子层。它的平滑性并不保证在每个网络、每份数据上都优于 ReLU;架构、初始化、归一化、优化器与计算成本仍然共同决定结果。

把四条曲线放在一起

函数公式输出范围饱和区域常见位置
Sigmoid1/(1+ez)1/(1+e^{-z})(0,1)(0,1)正、负两端二分类输出
Tanhtanh(z)\tanh(z)(1,1)(-1,1)正、负两端有界且带符号的状态
ReLUmax(0,z)\max(0,z)[0,)[0,\infty)整个负半轴梯度为零MLP、卷积网络隐藏层
GELUzΦ(z)z\Phi(z)[0.17,)[-0.17,\infty)很负时梯度接近零Transformer 隐藏层

“饱和”与“梯度为零”需要分清。Sigmoid 和 Tanh 在两端逐渐变平,导数无限接近 00;ReLU 在负半轴直接给出 00,正半轴保持导数 11;GELU 在两者之间采用平滑过渡。

再观察几个点上的输出:

zzSigmoidTanhReLUGELU(约)
2-20.1190.1190.964-0.964000.046-0.046
000.5000.500000000
220.8810.8810.9640.964221.9541.954

同一个预激活值经过不同函数后,会给下一层完全不同的尺度、符号和稀疏性。激活函数不是独立于网络其余部分的装饰;权重初始化、归一化与学习率都要和这些数值特征一起考虑。

梯度怎样穿过激活函数

假设某层计算:

z=Wx+b,h=ϕ(z)\boldsymbol{z}=\boldsymbol{W}\boldsymbol{x}+\boldsymbol{b}, \qquad \boldsymbol{h}=\phi(\boldsymbol{z})

后面的网络最终产生损失 LL。反向传播到这一层时,链式法则给出:

Lz=Lhϕ(z)\frac{\partial L}{\partial \boldsymbol{z}} = \frac{\partial L}{\partial \boldsymbol{h}} \odot \phi'(\boldsymbol{z})

\odot 表示逐元素相乘。前一项是后面传回来的误差信号,后一项是当前激活函数在每个单元上的局部斜率。

这条式子解释了为什么曲线形状会影响训练:

  • Sigmoid 或 Tanh 进入平坦区时,ϕ(z)\phi'(z) 很小,传回前层的信号被削弱;
  • ReLU 单元位于负区间时,ϕ(z)=0\phi'(z)=0,这条路径暂时不传信号;
  • ReLU 位于正区间时,ϕ(z)=1\phi'(z)=1,信号原样通过激活节点;
  • 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 写成:

GELU(z)=z2(1+erf(z2))\operatorname{GELU}(z) =\frac{z}{2}\left(1+\operatorname{erf}\left(\frac{z}{\sqrt2}\right)\right)

在一个隐藏层中,激活函数只占一行:

z1 = x @ w1 + b1
h1 = relu(z1)
logits = h1 @ w2 + b2

但这一行改变了整个模型的函数族。删掉 relu 后,w1w2 可以合并,网络重新退化为单个仿射变换;保留它以后,不同隐藏单元能在输入空间的不同区域开启,后面的层再把这些局部响应拼成更复杂的函数。

选择激活函数时的检查顺序

面对一个新网络,可以按下面的顺序判断:

  1. 先确定输出语义。 是概率、互斥类别、多个独立标签,还是不受限的连续值?输出激活与损失必须一起设计。
  2. 再选隐藏层基线。 普通 MLP 或卷积网络可以先用 ReLU;Transformer 类结构通常沿用其架构指定的 GELU 或其他门控函数。
  3. 检查激活与梯度分布。 大量单元是否长期为零?Sigmoid 或 Tanh 是否集中在饱和区?不同层的数值是否迅速放大或缩小?
  4. 把初始化、归一化和学习率一起看。 激活异常有时是上游尺度不合适的结果,替换函数不一定触及原因。
  5. 用验证结果决定。 函数的理论性质提供候选方向,不能替代针对数据和架构的实验。

这套顺序比背诵一张排行榜更可靠。激活函数并不存在脱离位置、结构和目标的全局冠军。

非线性带来了什么

现在可以把开头的问题完整复述一遍:

只有线性层
  ↓ 可以代数合并
无论多深,仍是一次仿射变换

在线性层之间加入非线性激活
  ↓ 不能再合并
隐藏层形成新的表示
  ↓ 后续层继续组合
得到分段、弯曲或更复杂的函数

XOR 展示了最小的能力差异:单条直线无法完成分类,两个 ReLU 隐藏单元却能构造出正确答案。Sigmoid、Tanh、ReLU 与 GELU 都能提供非线性,但它们对输出范围、信号中心、饱和区域和局部梯度作出了不同选择。

激活函数解决了“多层为什么不等于一层”的问题,却没有自动回答“应该有多少层、每层多少单元、参数怎样组织”。下一篇会继续追问为什么需要深度:宽度与深度各自怎样增加表示能力,多层组合又怎样逐步形成层级表示。随后我们再把这些结构正式写成多层感知机,沿张量形状追踪一批输入的前向计算。