一、上一篇留下的悬念
上一篇我们论证了一件事------纯线性的网络再深,也只是一个线性变换 。把 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 2 ( W 1 x + b 1 ) + b 2 W_2(W_1\mathbf{x} + \mathbf{b}_1) + \mathbf{b}_2 </math>W2(W1x+b1)+b2 展开就是 <math xmlns="http://www.w3.org/1998/Math/MathML"> W ′ x + b ′ W'\mathbf{x} + \mathbf{b}' </math>W′x+b′。线性的复合还是线性,这是线性代数的铁律。
所以神经网络要想拟合「弯曲的」关系,必须引入非线性 。引入的方式很巧妙------不是改 <math xmlns="http://www.w3.org/1998/Math/MathML"> W x + b W\mathbf{x} + \mathbf{b} </math>Wx+b 的形状(那样就不再是线性变换了),而是在两个线性变换之间插入一个逐元素的非线性函数 。这个函数叫激活函数(activation function)。
形式化地,一个典型的「线性 + 非线性」三明治长这样:
<math xmlns="http://www.w3.org/1998/Math/MathML"> h = σ ( W 1 x + b 1 ) \mathbf{h} = \sigma(W_1 \mathbf{x} + \mathbf{b}_1) </math>h=σ(W1x+b1) <math xmlns="http://www.w3.org/1998/Math/MathML"> y = W 2 h + b 2 \mathbf{y} = W_2 \mathbf{h} + \mathbf{b}_2 </math>y=W2h+b2
其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ \sigma </math>σ 是激活函数,逐元素 作用------也就是说 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ ( z ) \sigma(\mathbf{z}) </math>σ(z) 表示对 <math xmlns="http://www.w3.org/1998/Math/MathML"> z \mathbf{z} </math>z 的每一个元素分别应用 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ \sigma </math>σ。
这一篇我们就来仔细看看 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ \sigma </math>σ 这个角色。它看起来微不足道------一个一维标量函数嘛,能有多复杂?但偏偏,激活函数的选择直接决定了深度网络能不能训得动、训得好。一个不合适的激活函数,再大的模型再多的数据都救不了;一个合适的激活函数,能让原本训不动的网络突然焕发生机。
我们会按时间顺序讲:Sigmoid(最早)→ Tanh(改进)→ ReLU(革命)→ Leaky/ELU/SELU(修补)→ Swish/GELU(现代标配)→ SwiGLU/GeGLU(大模型时代)。每一个的诞生都对应着一个时代的痛点,每一次迭代都解决了前一代的某个核心问题。我希望你读完之后,不仅记住每个函数长什么样,更能体会到激活函数演化的逻辑------从生物启发到数值优化,从局部小改到大模型时代的全新设计。
Sigmoid/Tanh 两端「饱和」;ReLU 在正区间不饱和;GELU 则是在保留 ReLU 主体形状的同时,把 0 附近的过渡做得更平滑。
二、为什么必须是「逐元素」的?
这个细节很多教材会一笔带过,但其实值得停下来想一下。
激活函数作用在向量上,但它是「逐元素」作用------也就是说,对向量 <math xmlns="http://www.w3.org/1998/Math/MathML"> z = ( z 1 , z 2 , ⋯ , z h ) \mathbf{z} = (z_1, z_2, \cdots, z_h) </math>z=(z1,z2,⋯,zh),我们有:
<math xmlns="http://www.w3.org/1998/Math/MathML"> σ ( z ) = ( σ ( z 1 ) , σ ( z 2 ) , ⋯ , σ ( z h ) ) \sigma(\mathbf{z}) = (\sigma(z_1), \sigma(z_2), \cdots, \sigma(z_h)) </math>σ(z)=(σ(z1),σ(z2),⋯,σ(zh))
每一个分量独立处理,互不干扰。这意味着 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ \sigma </math>σ 本质上只是一个一维函数 <math xmlns="http://www.w3.org/1998/Math/MathML"> R → R \mathbb{R} \to \mathbb{R} </math>R→R,被「广播」到向量上。
为什么要这样设计?为什么不用一个「真·向量到向量」的非线性函数(比如对向量做一些复杂的混合)?
有两个原因。
第一,计算简单。逐元素操作可以完美并行------每个元素分配到 GPU 的一个线程,独立计算。如果用复杂的混合非线性,并行度会被破坏。
第二,线性变换已经在做混合了 。 <math xmlns="http://www.w3.org/1998/Math/MathML"> W W </math>W 矩阵的每一行都是对输入向量的线性组合。也就是说,「不同维度间的相互作用」这件事,已经由 <math xmlns="http://www.w3.org/1998/Math/MathML"> W W </math>W 解决了。激活函数不需要再重复做。
所以神经网络的分工是:<math xmlns="http://www.w3.org/1998/Math/MathML"> W W </math>W 负责「不同维度间的混合」(线性,跨元素), <math xmlns="http://www.w3.org/1998/Math/MathML"> σ \sigma </math>σ 负责「单个维度上的弯曲」(非线性,元素内)。两者各司其职,组合出强大的表达能力。
后面我们会看到,这个分工有一个例外------Softmax。Softmax 是非逐元素的非线性(它需要对整个向量归一化),所以它有特殊的地位。但常规激活函数都是逐元素的。
三、Sigmoid:第一代主角
最早的激活函数是 Sigmoid(也叫 Logistic 函数):
<math xmlns="http://www.w3.org/1998/Math/MathML"> σ ( z ) = 1 1 + e − z \sigma(z) = \frac{1}{1 + e^{-z}} </math>σ(z)=1+e−z1
它的形状是一条「S」曲线:当 <math xmlns="http://www.w3.org/1998/Math/MathML"> z → − ∞ z \to -\infty </math>z→−∞ 时趋于 0,当 <math xmlns="http://www.w3.org/1998/Math/MathML"> z → + ∞ z \to +\infty </math>z→+∞ 时趋于 1,在 <math xmlns="http://www.w3.org/1998/Math/MathML"> z = 0 z = 0 </math>z=0 处等于 0.5,左右对称。
Sigmoid 为什么是第一代?因为它有几个让人喜欢的性质。
性质一:输出在 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( 0 , 1 ) (0, 1) </math>(0,1) 之间。这个区间天然像「概率」。所以 Sigmoid 在二分类的输出层用得很多------「这是猫的概率」之类。
性质二:处处可导。Sigmoid 处处光滑,没有任何尖角。它的导数还有一个漂亮的形式:
<math xmlns="http://www.w3.org/1998/Math/MathML"> σ ′ ( z ) = σ ( z ) ( 1 − σ ( z ) ) \sigma'(z) = \sigma(z)(1 - \sigma(z)) </math>σ′(z)=σ(z)(1−σ(z))
也就是说,导数可以由函数值本身算出来 ------前向算了 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ ( z ) \sigma(z) </math>σ(z),反向算梯度时直接乘 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ ( z ) ( 1 − σ ( z ) ) \sigma(z)(1-\sigma(z)) </math>σ(z)(1−σ(z)),不用重新算 exp。这在没有自动微分的早期是个工程优势。
性质三:受生物启发 。神经元有「激发阈值」------刺激不够就不发放,刺激够了就饱和发放。Sigmoid 的形状刚好像「阈值激发函数的光滑版」。早期人们觉得这种「生物味」是好事。
但 Sigmoid 也有几个致命的缺点。这些缺点直到 2010 年代才被深度学习社区集体抛弃。
四、Sigmoid 的致命问题:梯度消失
Sigmoid 的导数 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ ′ ( z ) = σ ( z ) ( 1 − σ ( z ) ) \sigma'(z) = \sigma(z)(1 - \sigma(z)) </math>σ′(z)=σ(z)(1−σ(z)),最大值出现在 <math xmlns="http://www.w3.org/1998/Math/MathML"> z = 0 z = 0 </math>z=0 处,等于 <math xmlns="http://www.w3.org/1998/Math/MathML"> 0.5 × 0.5 = 0.25 0.5 \times 0.5 = 0.25 </math>0.5×0.5=0.25。最大值只有 0.25 。当 <math xmlns="http://www.w3.org/1998/Math/MathML"> ∣ z ∣ |z| </math>∣z∣ 大一点(比如 <math xmlns="http://www.w3.org/1998/Math/MathML"> z = 5 z = 5 </math>z=5), <math xmlns="http://www.w3.org/1998/Math/MathML"> σ ( z ) ≈ 0.993 \sigma(z) \approx 0.993 </math>σ(z)≈0.993,导数 <math xmlns="http://www.w3.org/1998/Math/MathML"> ≈ 0.993 × 0.007 ≈ 0.007 \approx 0.993 \times 0.007 \approx 0.007 </math>≈0.993×0.007≈0.007------基本上等于 0。
这意味着什么?意味着 Sigmoid 在「饱和区」( <math xmlns="http://www.w3.org/1998/Math/MathML"> ∣ z ∣ |z| </math>∣z∣ 较大)的梯度极小。
而梯度在反向传播时是「逐层相乘」的。链式法则告诉我们:
<math xmlns="http://www.w3.org/1998/Math/MathML"> ∂ L ∂ W 1 = ∂ L ∂ h L ⋅ ∏ l ∂ h l + 1 ∂ h l \frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial \mathbf{h}L} \cdot \prod{l} \frac{\partial \mathbf{h}_{l+1}}{\partial \mathbf{h}_l} </math>∂W1∂L=∂hL∂L⋅∏l∂hl∂hl+1
每一层都要乘一次激活函数的导数。如果每层导数最大才 0.25,10 层之后梯度被乘了 <math xmlns="http://www.w3.org/1998/Math/MathML"> 0.2 5 10 ≈ 1 0 − 6 0.25^{10} \approx 10^{-6} </math>0.2510≈10−6。再乘点权重矩阵,基本就是 0 了。
这就是著名的梯度消失问题(vanishing gradients)。早期网络的浅层(靠近输入)几乎学不到东西------梯度还没传到那里就消失了。1990 年代到 2000 年代,整个深度学习领域被这个问题困住了十几年。
我以前读这段历史,觉得不可思议------一个看起来这么显然的问题,居然困住了一代人。但仔细想想,「显然」是事后的视角。当时人们没有 PyTorch 的可视化工具,没有 TensorBoard 的梯度直方图。他们只能盯着训练曲线发呆------「为什么深网络比浅网络还差?」直到 Hinton 等人在 2006 年用「逐层预训练」绕开这个问题,再到 2012 年 ReLU 直接消解这个问题,整个领域才打开局面。
五、Sigmoid 的另一个问题:输出不以 0 为中心
Sigmoid 的输出范围是 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( 0 , 1 ) (0, 1) </math>(0,1),全是正数 。这看起来无害,其实有一个微妙的问题------梯度更新会有 zigzag。
简单解释:假设第 <math xmlns="http://www.w3.org/1998/Math/MathML"> l l </math>l 层的激活是 <math xmlns="http://www.w3.org/1998/Math/MathML"> h l = σ ( ⋅ ) \mathbf{h}_l = \sigma(\cdot) </math>hl=σ(⋅),全是正数。那么下一层 <math xmlns="http://www.w3.org/1998/Math/MathML"> W W </math>W 的梯度更新方向,会受 <math xmlns="http://www.w3.org/1998/Math/MathML"> h l \mathbf{h}_l </math>hl 的符号约束------所有 <math xmlns="http://www.w3.org/1998/Math/MathML"> W W </math>W 的元素的梯度同号(要么都正、要么都负)。这意味着参数只能朝某些「象限」移动,要去其他象限就得绕路,形成 zigzag 的更新轨迹。
这听起来抽象,但实际表现就是收敛慢、训练不稳定。
这个问题被叫做「非零中心化」(not zero-centered)问题。要解决它,激活函数的输出最好以 0 为中心,左右对称分布。
六、Tanh:Sigmoid 的孪生兄弟
为了解决「非零中心化」,人们找到了 Tanh:
<math xmlns="http://www.w3.org/1998/Math/MathML"> tanh ( z ) = e z − e − z e z + e − z \tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}} </math>tanh(z)=ez+e−zez−e−z
它和 Sigmoid 是孪生兄弟 ------形状几乎一模一样,只是范围从 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( 0 , 1 ) (0, 1) </math>(0,1) 拉到了 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( − 1 , 1 ) (-1, 1) </math>(−1,1),并且关于原点对称。事实上:
<math xmlns="http://www.w3.org/1998/Math/MathML"> tanh ( z ) = 2 σ ( 2 z ) − 1 \tanh(z) = 2\sigma(2z) - 1 </math>tanh(z)=2σ(2z)−1
Tanh 解决了 Sigmoid 的「非零中心化」问题。RNN 时代(90 年代到 2010 年代初),Tanh 是 LSTM 和 GRU 内部的标准激活------你今天打开任何一个 RNN 实现,里面到处都是 tanh。
但 Tanh 没有解决梯度消失问题 。它的导数 <math xmlns="http://www.w3.org/1998/Math/MathML"> tanh ′ ( z ) = 1 − tanh 2 ( z ) \tanh'(z) = 1 - \tanh^2(z) </math>tanh′(z)=1−tanh2(z),最大值是 1(在 <math xmlns="http://www.w3.org/1998/Math/MathML"> z = 0 z = 0 </math>z=0 处),但两端饱和时仍然趋于 0。所以 Tanh 比 Sigmoid 好一点,但深度网络仍然训不深。
到这里,「梯度消失」已经像一座大山压在深度学习的头上。整个领域亟需一个全新的激活函数。
七、ReLU:一记神来之笔
2010 年前后,一个看似「大道至简」的激活函数横空出世------
<math xmlns="http://www.w3.org/1998/Math/MathML"> ReLU ( z ) = max ( 0 , z ) \text{ReLU}(z) = \max(0, z) </math>ReLU(z)=max(0,z)
「整流线性单元」(Rectified Linear Unit)。它的形状简单到不像「现代神经网络」该有的东西:负数砍成零,正数原样保留。一条折线,仅此而已。
但就是这样一个朴素的函数,彻底改变了深度学习。
ReLU 的优势:
第一,正区间梯度恒为 1 。在 <math xmlns="http://www.w3.org/1998/Math/MathML"> z > 0 z > 0 </math>z>0 时, <math xmlns="http://www.w3.org/1998/Math/MathML"> ReLU ′ ( z ) = 1 \text{ReLU}'(z) = 1 </math>ReLU′(z)=1。梯度不会衰减 。这意味着深层网络的梯度可以原样传到底层。一记神来之笔。
第二,计算极快 。max(0, z) 是一个 if-else 分支,比 exp 快几十倍。这在 GPU 上是巨大优势。
第三,稀疏性。负输入直接归零,意味着每次前向时,约一半神经元处于「不激活」状态。稀疏激活对生物神经元而言是常态,对网络的容量也有帮助。
第四,不饱和 。Sigmoid/Tanh 在 <math xmlns="http://www.w3.org/1998/Math/MathML"> ∣ z ∣ |z| </math>∣z∣ 大时饱和(输出贴近边界,梯度趋零);ReLU 在正区间永不饱和。
ReLU 不是 2010 年才被发明的。最早 Hahnloser 在 2000 年的论文里就提到过它。但真正让它流行起来的,是 2010 年 Glorot & Bengio 的《Deep Sparse Rectifier Neural Networks》,以及 2012 年 Krizhevsky 用 ReLU 训出的 AlexNet------一举打破了 ImageNet 的所有纪录,深度学习从此进入「ReLU 时代」。
八、ReLU 的「副作用」:Dying ReLU
ReLU 这么好,有缺点吗?有。最有名的叫Dying ReLU(死亡 ReLU)。
如果某个神经元的输入 <math xmlns="http://www.w3.org/1998/Math/MathML"> z z </math>z 始终为负,那么 ReLU 输出始终为 0,对应的梯度也是 0。梯度为 0 意味着参数不会更新 。这个神经元就「死了」------再也不会激活,再也不会学习。
死亡 ReLU 在训练初期最容易发生:参数初始化不好,或者学习率太大,导致某些神经元的输入被推到一直为负,从此再也救不回来。研究发现,一个训练完的 ReLU 网络里,可能有 10%-40% 的神经元已经死了。
这是 ReLU 的「副作用」,但通常不致命------其他 60%-90% 的神经元仍然在工作,整体性能仍然很好。但在一些极端情况下,死亡 ReLU 会显著降低模型容量。
为了缓解这个问题,人们提出了一系列「ReLU 变种」。
九、Leaky ReLU 与 PReLU:让负数也有一点点导数
第一个补丁是 Leaky ReLU:
<math xmlns="http://www.w3.org/1998/Math/MathML"> LeakyReLU ( z ) = { z z > 0 α z z ≤ 0 \text{LeakyReLU}(z) = \begin{cases} z & z > 0 \\ \alpha z & z \le 0 \end{cases} </math>LeakyReLU(z)={zαzz>0z≤0
这里 <math xmlns="http://www.w3.org/1998/Math/MathML"> α \alpha </math>α 是个小常数,通常取 0.01。负数区间也有一个小斜率,梯度不再为零。即使神经元的输入长期为负,参数也仍然在更新------它有机会「复活」。
PReLU (Parametric ReLU)把 <math xmlns="http://www.w3.org/1998/Math/MathML"> α \alpha </math>α 也变成可学习参数,每个神经元自己学一个最合适的负斜率。He 等人的工作(2015)显示这在某些任务上略有提升。
Leaky/PReLU 实战中的表现......不算特别好。它们解决了 Dying ReLU 的理论问题,但实际收益不太明显。多数大模型仍然用普通的 ReLU 或后面要讲的 GELU,而不是 Leaky 系。
十、ELU 与 SELU:试图同时解决多个问题
ELU(Exponential Linear Unit, Clevert et al. 2015)走得更远:
<math xmlns="http://www.w3.org/1998/Math/MathML"> ELU ( z ) = { z z > 0 α ( e z − 1 ) z ≤ 0 \text{ELU}(z) = \begin{cases} z & z > 0 \\ \alpha(e^z - 1) & z \le 0 \end{cases} </math>ELU(z)={zα(ez−1)z>0z≤0
ELU 在负区间用 <math xmlns="http://www.w3.org/1998/Math/MathML"> α ( e z − 1 ) \alpha(e^z - 1) </math>α(ez−1),让输出能取到接近 <math xmlns="http://www.w3.org/1998/Math/MathML"> − α -\alpha </math>−α 的负值。这有两个好处:
- 零中心化------输出可以是负数,整体均值可以接近 0。
- 平滑 ------在 <math xmlns="http://www.w3.org/1998/Math/MathML"> z = 0 z = 0 </math>z=0 处可导(一阶导连续),不像 ReLU/Leaky 有尖角。
SELU (Scaled ELU, Klambauer et al. 2017)在 ELU 基础上加了一个缩放因子 <math xmlns="http://www.w3.org/1998/Math/MathML"> λ \lambda </math>λ:
<math xmlns="http://www.w3.org/1998/Math/MathML"> SELU ( z ) = λ ⋅ ELU ( z ) \text{SELU}(z) = \lambda \cdot \text{ELU}(z) </math>SELU(z)=λ⋅ELU(z)
并精心选取 <math xmlns="http://www.w3.org/1998/Math/MathML"> λ ≈ 1.0507 \lambda \approx 1.0507 </math>λ≈1.0507 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> α ≈ 1.6733 \alpha \approx 1.6733 </math>α≈1.6733,使得网络在前向传播时自归一化------激活的均值和方差自动保持在 0 和 1 附近。理论很漂亮,工程上也省了 BatchNorm。
但 SELU 的限制很多(要求特定权重初始化、特定层结构),使用门槛高。最终它没能成为主流。
ELU/SELU 的故事告诉我们一件事------激活函数的「数学优雅」不一定等于「工程实用」。一个函数能不能流行,要看它在大量真实任务上的综合表现,不只是看它有多少漂亮的性质。
十一、Swish 与 SiLU:从神经架构搜索来的「光滑 ReLU」
2017 年,Google 用神经架构搜索(NAS)寻找更好的激活函数。算法跑了几天,吐出了一个让人意外的结果:
<math xmlns="http://www.w3.org/1998/Math/MathML"> Swish ( z ) = z ⋅ σ ( z ) \text{Swish}(z) = z \cdot \sigma(z) </math>Swish(z)=z⋅σ(z)
其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ \sigma </math>σ 是 Sigmoid。这个函数也叫 SiLU(Sigmoid Linear Unit)。
它的形状像「光滑版的 ReLU」------大于 0 时趋近 <math xmlns="http://www.w3.org/1998/Math/MathML"> z z </math>z,小于 0 时趋近 0,中间过渡平滑、非单调(在某个负值附近有个小凹陷)。
Swish 的优势是「软」------不像 ReLU 在 0 处有尖角,Swish 处处光滑。这对优化曲面有微妙的好处,让训练更稳。
更妙的是,Swish 的形式非常简单------只是 <math xmlns="http://www.w3.org/1998/Math/MathML"> z z </math>z 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ ( z ) \sigma(z) </math>σ(z) 的乘积,几乎不增加计算量。
Swish 在 EfficientNet 等模型里效果不错,逐渐流行起来。但更广为人知的是它的「亲戚」------GELU。
十二、GELU:Transformer 的标准激活
GELU(Gaussian Error Linear Unit, Hendrycks & Gimpel, 2016)的形式是:
<math xmlns="http://www.w3.org/1998/Math/MathML"> GELU ( z ) = z ⋅ Φ ( z ) \text{GELU}(z) = z \cdot \Phi(z) </math>GELU(z)=z⋅Φ(z)
其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> Φ ( z ) \Phi(z) </math>Φ(z) 是标准正态分布的累积分布函数(CDF)。
直观地讲,GELU 把 <math xmlns="http://www.w3.org/1998/Math/MathML"> z z </math>z 乘以「 <math xmlns="http://www.w3.org/1998/Math/MathML"> z z </math>z 是正的概率」(在标准正态假设下)。如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> z z </math>z 很大,概率接近 1,GELU 输出 <math xmlns="http://www.w3.org/1998/Math/MathML"> ≈ z \approx z </math>≈z;如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> z z </math>z 很负,概率接近 0,GELU 输出 <math xmlns="http://www.w3.org/1998/Math/MathML"> ≈ 0 \approx 0 </math>≈0。它和 ReLU 的差别在中间------ReLU 是「硬开关」(要么 0 要么 1),GELU 是「软概率」(连续从 0 过渡到 1)。
GELU 的精确公式比较麻烦,所以工程实现常用近似:
<math xmlns="http://www.w3.org/1998/Math/MathML"> GELU ( z ) ≈ 0.5 z ( 1 + tanh ( 2 π ( z + 0.044715 z 3 ) ) ) \text{GELU}(z) \approx 0.5 z \left(1 + \tanh\left(\sqrt{\frac{2}{\pi}}(z + 0.044715 z^3)\right)\right) </math>GELU(z)≈0.5z(1+tanh(π2 (z+0.044715z3)))
PyTorch 提供了精确版(用 erf 函数)和 tanh 近似版两种。
GELU 的真正分量在哪里?------它是 Transformer 的标准激活函数。从原始的 BERT 论文(2018)开始,几乎所有后续 Transformer 模型(GPT-2、T5、ViT、CLIP 等)都用 GELU。它和 Swish/SiLU 形状几乎一样,效果也接近,但在 Transformer 上 GELU 的实证表现略好,于是成为 de facto 标准。
我自己的理解:GELU 在 ReLU 和 Swish 中间找到一个甜蜜点------它是「带概率解释的 Swish」「光滑版的 ReLU」。它没有 ReLU 的尖角问题,又有 ReLU 的稀疏激活倾向,还多了一个「概率」的诠释(虽然这个诠释主要是事后归因)。
十三、SwiGLU:LLaMA 时代的新王
Transformer 的 FFN 部分原本是这样:
<math xmlns="http://www.w3.org/1998/Math/MathML"> FFN ( x ) = W 2 ⋅ GELU ( W 1 x + b 1 ) + b 2 \text{FFN}(x) = W_2 \cdot \text{GELU}(W_1 x + b_1) + b_2 </math>FFN(x)=W2⋅GELU(W1x+b1)+b2
这是一个「线性 → 激活 → 线性」的标准结构。
LLaMA(2023)和它的后继者用了一个不一样的结构------SwiGLU(Shazeer 2020 提出,LLaMA 2 普及):
<math xmlns="http://www.w3.org/1998/Math/MathML"> SwiGLU ( x ) = ( Swish ( W 1 x ) ⊙ ( W 3 x ) ) W 2 \text{SwiGLU}(x) = (\text{Swish}(W_1 x) \odot (W_3 x)) W_2 </math>SwiGLU(x)=(Swish(W1x)⊙(W3x))W2
这里 <math xmlns="http://www.w3.org/1998/Math/MathML"> ⊙ \odot </math>⊙ 是逐元素相乘。
注意它有三个权重矩阵 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 1 , W 2 , W 3 W_1, W_2, W_3 </math>W1,W2,W3,比标准 FFN 多了一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 3 W_3 </math>W3。整体结构是:
- 用 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 1 W_1 </math>W1 投影一次,过 Swish(其实就是 SiLU)。
- 用 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 3 W_3 </math>W3 投影一次,不过激活。
- 把两个结果逐元素相乘------这是「门控」(gating)。
- 再过 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 2 W_2 </math>W2 投影回去。
这种「用一路当门控乘另一路 」的设计叫门控线性单元(GLU, Gated Linear Unit)。最早出现在 Dauphin 等人的 LSTM 改造里,2017 年被引入 Transformer,2020 年 Shazeer 系统比较了各种 GLU 变种(GeGLU、ReGLU、SwiGLU 等),SwiGLU 综合表现最好。
LLaMA 把 SwiGLU 用了之后,开源社区基本一边倒地跟上------Mistral、Qwen、DeepSeek、Yi 都用 SwiGLU。它已经替代 GELU 成为现代 LLM 的 FFN 标准。
为什么 SwiGLU 比 GELU 好?没有非常完整的理论解释。Shazeer 在论文最后一段写了一句很有名的话:「我们没有理由解释为什么这些架构有效。我们把它归因于神圣的恩赐(divine benevolence)」------这是激活函数研究里的一句神回复,意思是「就是 work,问就是玄学」。
但有一些直觉可以建立:
- 门控带来更强的表达能力 。SwiGLU 比 GELU 多一倍的参数量(多了一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 3 W_3 </math>W3),所以表达力更强。
- 门控让网络能动态决定信息流------某些维度被门「关上」,某些被「打开」。这种数据相关的稀疏性比 ReLU 的固定稀疏更灵活。
- 乘法操作引入二阶相互作用------比单层 MLP 的线性 + 一元激活表达更复杂。
我的看法:SwiGLU 的成功很可能是「容量增加」+「门控形式」+「Swish 激活」三者的合力。其中哪个占主因,研究界还没共识。
十四、激活函数家谱
整理一下整个家族:
| 时代 | 激活函数 | 主要优势 | 主要问题 |
|---|---|---|---|
| 1990s | Sigmoid | 概率解释、光滑 | 梯度消失、非零中心 |
| 1990s-2010s | Tanh | 零中心 | 仍然梯度消失 |
| 2012- | ReLU | 简单、不饱和、稀疏 | Dying ReLU、非光滑 |
| 2014- | Leaky/PReLU | 缓解 Dying ReLU | 实证收益小 |
| 2015- | ELU/SELU | 自归一化(SELU) | 限制太多 |
| 2017- | Swish/SiLU | 光滑、轻微非单调 | 略有计算开销 |
| 2018- | GELU | Transformer 标配 | 和 Swish 实质等价 |
| 2020- | SwiGLU/GeGLU | LLM 时代标配 | 参数量多 50% |
这条演化线有几个有意思的观察:
第一 ,前期演化由「梯度消失」驱动。从 Sigmoid 到 Tanh 到 ReLU,每一步都在让梯度更好传播。
第二 ,中期演化由「Dying ReLU」驱动。从 ReLU 到 Leaky/ELU/Swish,每一步都在让负区间也有梯度。
第三 ,后期演化由「Transformer + LLM」驱动。从 GELU 到 SwiGLU,是为了在大模型上挤出最后一点性能。这个阶段的进步不是「修补已知问题」,而是「在已经很好的基础上找更好的」------所以提升幅度小、解释难。
第四 ,最简单的方案不是最优的 。ReLU 简单,但不是终点;GELU/SwiGLU 比 ReLU 复杂得多,但确实更好。这驳斥了「最简单的就是最好的」这种朴素观点------在工程上,一点复杂换一点性能经常是值得的。
十五、推导:为什么 n 层线性等价于 1 层线性
上一篇我们口头说「线性的复合还是线性」。这里给一个严格推导,让结论无可辩驳。
定义两层无激活的全连接:
<math xmlns="http://www.w3.org/1998/Math/MathML"> h = W 1 x + b 1 \mathbf{h} = W_1 \mathbf{x} + \mathbf{b}_1 </math>h=W1x+b1 <math xmlns="http://www.w3.org/1998/Math/MathML"> y = W 2 h + b 2 \mathbf{y} = W_2 \mathbf{h} + \mathbf{b}_2 </math>y=W2h+b2
代入:
<math xmlns="http://www.w3.org/1998/Math/MathML"> y = W 2 ( W 1 x + b 1 ) + b 2 = W 2 W 1 x + W 2 b 1 + b 2 \mathbf{y} = W_2 (W_1 \mathbf{x} + \mathbf{b}_1) + \mathbf{b}_2 = W_2 W_1 \mathbf{x} + W_2 \mathbf{b}_1 + \mathbf{b}_2 </math>y=W2(W1x+b1)+b2=W2W1x+W2b1+b2
记 <math xmlns="http://www.w3.org/1998/Math/MathML"> W ′ = W 2 W 1 W' = W_2 W_1 </math>W′=W2W1, <math xmlns="http://www.w3.org/1998/Math/MathML"> b ′ = W 2 b 1 + b 2 \mathbf{b}' = W_2 \mathbf{b}_1 + \mathbf{b}_2 </math>b′=W2b1+b2,则:
<math xmlns="http://www.w3.org/1998/Math/MathML"> y = W ′ x + b ′ \mathbf{y} = W' \mathbf{x} + \mathbf{b}' </math>y=W′x+b′
这就是单层线性。归纳法可以推出 n 层无激活的复合也等价于单层。
数学上严谨多了。插入非线性 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ \sigma </math>σ 之后,因为 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ \sigma </math>σ 不可交换出来 ( <math xmlns="http://www.w3.org/1998/Math/MathML"> σ ( W x ) ≠ W σ ( x ) \sigma(W\mathbf{x}) \ne W \sigma(\mathbf{x}) </math>σ(Wx)=Wσ(x) 一般情况下),整个表达就不再能折叠成单层了。这就是非线性的本质作用------打破线性变换的「可交换+可吸收」性质。
十六、激活函数与「初始化」的纠缠
激活函数和参数初始化是耦合的。同一个网络,初始化变了,激活函数的合适选择也会变。
具体说,初始化的目标是让前向传播时每层激活的方差稳定 (不爆炸、不消失),同时让反向传播时梯度方差稳定。不同激活函数对这两个目标的影响不同:
- Sigmoid/Tanh :用 Xavier/Glorot 初始化 ------ <math xmlns="http://www.w3.org/1998/Math/MathML"> W ∼ Uniform ( − 6 / ( n i n + n o u t ) , + 6 / ( n i n + n o u t ) ) W \sim \text{Uniform}(-\sqrt{6/(n_{in}+n_{out})}, +\sqrt{6/(n_{in}+n_{out})}) </math>W∼Uniform(−6/(nin+nout) ,+6/(nin+nout) )。
- ReLU :用 He/Kaiming 初始化 ------ <math xmlns="http://www.w3.org/1998/Math/MathML"> W ∼ N ( 0 , 2 / n i n ) W \sim \mathcal{N}(0, \sqrt{2/n_{in}}) </math>W∼N(0,2/nin )。多了一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 \sqrt{2} </math>2 来补偿 ReLU 把一半激活归零的损失。
这些是经典论文里推出来的。有兴趣可以读 He et al. 2015《Delving Deep into Rectifiers》------里面把 ReLU 初始化的方差推导得很清楚。
初始化错了,再好的激活函数也救不了你。这是新手常踩的坑------下载了别人代码,把 ReLU 换成 Sigmoid 就完蛋,因为初始化没跟着换。
十七、激活函数与「归一化」的关系
激活函数还和归一化(Normalization)层紧密相关。
BatchNorm(2015)和 LayerNorm(2016)的出现,让激活函数饱和的问题得到了缓解------归一化把每一层的输入拉到一个合理范围,避免输入过大导致 Sigmoid/Tanh 饱和。
有趣的是,归一化层的存在让一些「老」激活函数重新焕发生机------比如在 Transformer 里,Pre-LayerNorm + GELU 工作良好;在 ViT 里,Pre-LayerNorm + GELU 也是标配。如果没有 LayerNorm,单纯的 GELU 也很难训稳大型 Transformer。
激活 + 归一化是「深度学习训练稳定性的双轮」。讨论激活函数时,不能脱离归一化谈。
十八、梯度爆炸怎么办?激活函数能管吗?
激活函数主要解决梯度消失 。但爆炸呢?
爆炸通常不是激活函数的问题,是权重过大 或循环结构(RNN)的问题。激活函数有界(Sigmoid/Tanh)能从源头限制爆炸,但代价是梯度消失。无界激活(ReLU)不限制爆炸,但配合归一化和权重初始化通常没事。
实战中对抗梯度爆炸的工具:
- 梯度裁剪(gradient clipping):把梯度的范数限制在一个阈值内。LLM 训练标配。
- 学习率 warmup:训练前期用很小的学习率,让网络稳定下来。
- 权重衰减(weight decay):限制权重不要长得太大。
激活函数本身能做的有限。它的主战场仍然是梯度消失。
十九、Softmax:一个特殊的「广义激活」
我们之前说「激活函数都是逐元素的」。唯一的例外是 Softmax。
<math xmlns="http://www.w3.org/1998/Math/MathML"> Softmax ( z ) i = e z i ∑ j e z j \text{Softmax}(\mathbf{z})_i = \frac{e^{z_i}}{\sum_j e^{z_j}} </math>Softmax(z)i=∑jezjezi
它把一个向量映射到「概率分布」------所有输出非负、和为 1。它是非逐元素的(每个输出依赖整个输入向量)。
Softmax 在 Transformer 里有两个关键位置:
- 注意力里,对 attention scores 做 Softmax 转成权重分布。
- 输出层,把 logits 转成下一个 token 的概率分布。
Softmax 我们留到「第八篇·Softmax 与概率分布」专门讲。这里只是提一句它的特殊地位------它不算狭义的「激活函数」,但它做的事情类似(引入非线性、把一个向量「整形」成有特定语义的输出)。
二十、激活函数的导数和「梯度流」
激活函数的导数决定了梯度怎么流。简单整理:
| 激活 | 函数 | 导数 |
|---|---|---|
| Sigmoid | <math xmlns="http://www.w3.org/1998/Math/MathML"> σ ( z ) = 1 / ( 1 + e − z ) \sigma(z) = 1/(1+e^{-z}) </math>σ(z)=1/(1+e−z) | <math xmlns="http://www.w3.org/1998/Math/MathML"> σ ′ ( z ) = σ ( z ) ( 1 − σ ( z ) ) \sigma'(z) = \sigma(z)(1-\sigma(z)) </math>σ′(z)=σ(z)(1−σ(z)) |
| Tanh | <math xmlns="http://www.w3.org/1998/Math/MathML"> tanh ( z ) \tanh(z) </math>tanh(z) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 − tanh 2 ( z ) 1 - \tanh^2(z) </math>1−tanh2(z) |
| ReLU | <math xmlns="http://www.w3.org/1998/Math/MathML"> max ( 0 , z ) \max(0, z) </math>max(0,z) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 ( z > 0 ) \mathbb{1}(z > 0) </math>1(z>0) |
| Leaky ReLU | <math xmlns="http://www.w3.org/1998/Math/MathML"> max ( α z , z ) \max(\alpha z, z) </math>max(αz,z) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 ( z > 0 ) + α 1 ( z ≤ 0 ) \mathbb{1}(z > 0) + \alpha \mathbb{1}(z \le 0) </math>1(z>0)+α1(z≤0) |
| ELU | <math xmlns="http://www.w3.org/1998/Math/MathML"> z z </math>z 或 <math xmlns="http://www.w3.org/1998/Math/MathML"> α ( e z − 1 ) \alpha(e^z - 1) </math>α(ez−1) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1 或 <math xmlns="http://www.w3.org/1998/Math/MathML"> α e z \alpha e^z </math>αez |
| Swish | <math xmlns="http://www.w3.org/1998/Math/MathML"> z ⋅ σ ( z ) z \cdot \sigma(z) </math>z⋅σ(z) | <math xmlns="http://www.w3.org/1998/Math/MathML"> σ ( z ) + z σ ( z ) ( 1 − σ ( z ) ) \sigma(z) + z \sigma(z)(1-\sigma(z)) </math>σ(z)+zσ(z)(1−σ(z)) |
| GELU | <math xmlns="http://www.w3.org/1998/Math/MathML"> z ⋅ Φ ( z ) z \cdot \Phi(z) </math>z⋅Φ(z) | <math xmlns="http://www.w3.org/1998/Math/MathML"> Φ ( z ) + z ϕ ( z ) \Phi(z) + z \phi(z) </math>Φ(z)+zϕ(z) |
注意 Swish/GELU 的导数都不是「函数本身的简单变换」------它们包含 <math xmlns="http://www.w3.org/1998/Math/MathML"> z z </math>z 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ ( z ) / Φ ( z ) \sigma(z)/\Phi(z) </math>σ(z)/Φ(z) 的乘积。这意味着反向传播时既要算 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ \sigma </math>σ 又要算 <math xmlns="http://www.w3.org/1998/Math/MathML"> σ ′ \sigma' </math>σ′,比 ReLU 贵一点。但在 GPU 上这点开销几乎可忽略。
「梯度流 」(gradient flow)是分析深度网络训练的一个视角。我们关心:每过一层,梯度的「典型大小」会变成多少?如果大于 1,可能爆炸;小于 1,可能消失;接近 1,最稳。激活函数选得好,能让梯度流尽量保持「接近 1」。这就是从 Sigmoid 到 ReLU 到 GELU 演化的内在逻辑。
二十一、动手实验:在 PyTorch 里换激活
文字讲多了,回到代码感受一下。一个最小的两层 MLP:
python
import torch
import torch.nn as nn
class MLP(nn.Module):
def __init__(self, in_dim, hidden, out_dim, act='relu'):
super().__init__()
self.fc1 = nn.Linear(in_dim, hidden)
self.fc2 = nn.Linear(hidden, out_dim)
self.act = {
'sigmoid': nn.Sigmoid(),
'tanh': nn.Tanh(),
'relu': nn.ReLU(),
'leaky': nn.LeakyReLU(0.01),
'elu': nn.ELU(),
'gelu': nn.GELU(),
'silu': nn.SiLU(),
}[act]
def forward(self, x):
return self.fc2(self.act(self.fc1(x)))
用同一份训练代码、同一个数据集(比如 MNIST 或 fashion-MNIST),跑 Sigmoid / Tanh / ReLU / GELU 四个版本------你会清楚地看到:
- Sigmoid 收敛最慢,loss 震荡。
- Tanh 比 Sigmoid 快一点,但深网络仍训不动。
- ReLU 显著快,loss 下降平滑。
- GELU 比 ReLU 略好(但差距不大)。
我建议每个学深度学习的人都自己跑一遍这个实验。理论看得再多,不如自己跑一次的肌肉记忆深刻。Karpathy 的 makemore 系列有一节专门讲激活函数的实证对比,强烈推荐。
二十二、为什么大模型几乎都用 SwiGLU 而不是 GELU?
这是最近(2023 年开始)一个有趣的现象。GPT-3 还用 GELU,但 LLaMA 系列、Mistral、Qwen、DeepSeek、Yi 全用了 SwiGLU。这不是巧合。
主要原因有几条。
第一,效果实测稍好。Shazeer 2020 的论文《GLU Variants Improve Transformer》对比了多种 GLU 变体在 T5 上的表现,SwiGLU 略胜 GeLU。LLaMA 团队复现了这个结论,于是采用。
第二,参数效率 。SwiGLU 多一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 3 W_3 </math>W3 矩阵,但 LLaMA 把 hidden_dim 略微缩小,使总参数量保持原样。同样参数预算下 SwiGLU 略好。
第三,惯性效应 。LLaMA 是开源大模型的标杆,它怎么做后面的人就跟着做。这种「事实标准的传递」是开源社区的常见现象------一旦某个实践被证明有效且开源可见,其他人不会冒险换。
但 SwiGLU 也不是没缺点:
- 多了一个矩阵,实现稍复杂。
- 三路计算 + 乘法,不如 GELU 那么「干净」。
- 在小模型上的优势不明显,主要在大模型上显现。
我的判断:SwiGLU 不是终点。未来可能有更好的 FFN 结构(比如 Mixture of Experts、LayerScale 等替代)。但短期内 SwiGLU 是大模型的实战标配。
二十三、激活函数的「死信」与「活档」
说一段题外话。
我喜欢把激活函数想象成「邮局窗口」------输入是一封信,激活函数是窗口工作人员,决定这封信是「寄出去 」还是「扔掉」。
- Sigmoid:怕担责,所有信都半寄半扔(输出都是 0 到 1 之间的小数)。
- ReLU:果断,正面信件全寄,负面信件全扔。但有时把误判扔了的信救不回来(Dying ReLU)。
- Leaky ReLU:负面信件偶尔象征性留一份副本( <math xmlns="http://www.w3.org/1998/Math/MathML"> α z \alpha z </math>αz)。
- GELU:根据信件的「概率得分」决定要不要寄、寄多少。
- SwiGLU:两个窗口对照------一个看内容,一个发门票。两个一致才寄。
这是个调皮的比喻,但帮我记住了不同激活的「精神」。读者可以自己创造比喻------让抽象概念在脑子里有形象,是学习深度学习的关键习惯。
二十四、从激活函数看深度学习的「大道」
激活函数这个领域,给我最大的启发是:
深度学习的进步,往往来自「把一个简单的细节做到极致」。
激活函数有什么大不了?不就是一个一维标量函数嘛。但从 Sigmoid 到 ReLU,整个深度学习领域换了几代------这是一个看似微小的细节,却带动了整个学科的变革。
类似的「小细节大革命」还有很多:
- 初始化:Xavier → He → 各种 LSUV,深度网络从「训不动」变「能训」。
- 归一化:BatchNorm → LayerNorm → RMSNorm,让千亿参数模型的训练稳定。
- 优化器:SGD → Momentum → Adam → AdamW,让大模型能在合理时间内收敛。
- 位置编码:绝对 → 相对 → RoPE,让 Transformer 能处理长序列。
每一个细节,单独看都不起眼;合起来,把深度学习从「学术玩具」变成了「工业级技术」。
这给我们一个研究方法论:关注「不起眼」的小问题,往里面深挖 。激活函数研究了三十年,仍然有人在 ICLR/NeurIPS 发新激活函数的论文。这不是「卷」,这是「重要的小事」。
二十五、关键概念回顾
回顾一下本篇的脉络。
**为什么需要激活函数?**因为线性的复合还是线性,单纯堆叠 <math xmlns="http://www.w3.org/1998/Math/MathML"> W x + b W\mathbf{x} + \mathbf{b} </math>Wx+b 表达力极有限。激活函数引入非线性,把「分段线性」「光滑曲线」等表达能力带给神经网络。每一层的激活函数都是「弯曲」的来源。
**激活函数家谱怎么看?**早期 Sigmoid(光滑、概率解释),改进 Tanh(零中心),革命 ReLU(不饱和、训得动深),修补 Leaky/ELU(缓解 Dying),现代 Swish/GELU(光滑 ReLU),LLM 时代 SwiGLU(门控 + Swish)。每一代都解决了上一代的某个核心问题。
**激活函数的「形状」决定什么?**决定梯度怎么流。两端饱和会梯度消失;中段陡峭会梯度爆炸;恒等斜率(ReLU 正半轴)让梯度原样传递。深网络能不能训,根本上是「梯度流稳不稳」的问题。
激活函数与其他组件的耦合 :和初始化耦合(不同激活配不同初始化);和归一化耦合(LayerNorm 让饱和问题缓解);和优化器耦合(不同激活的 loss 曲面不同,最佳学习率也不同)。孤立讨论激活函数没有意义,要把它当作整个系统的一部分。
**Transformer 用什么?**GELU 是经典选择;SwiGLU 是最新事实标准。两者实质都属于「光滑、近似 ReLU」家族。
把这五点串起来,你对激活函数的理解就完整了。
二十六、常见误解
误解一:激活函数就是「神经元的开关」。
部分对。Sigmoid 和 Tanh 像「软开关」,ReLU 像「硬开关」。但 Swish/GELU 的「开关」是平滑过渡的,没有明显的「关」状态。激活函数本质是非线性函数,不必都按「开关」理解。
误解二:越复杂的激活函数效果越好。
错。ReLU 是最简单的之一,却是十年来最成功的激活函数。SwiGLU 比 GELU 复杂,但提升幅度有限。复杂不等于好------简单 + 工程实用,往往胜过复杂 + 数学优雅。
误解三:训练时用哪个激活函数差别不大。
大错。激活函数能让训练快 10 倍或慢 10 倍。Sigmoid 和 ReLU 在深网络上的差距是「能训」与「训不动」的差距。激活函数是深度学习能 work 的关键之一。
误解四:激活函数只在隐藏层有用,输出层不需要。
要看任务。回归任务输出层通常不加激活 (让输出可以是任意实数);分类任务输出层加 Softmax (变概率);二分类输出层加 Sigmoid。所谓「输出层不需要激活」是回归的特例,不是通则。
误解五:Sigmoid 已经被淘汰了。
不完全。Sigmoid 仍然在二分类输出层、注意力门控(如 LSTM)、某些特殊场景里活跃。它在隐藏层确实被 ReLU 系替代,但不等于全面淘汰。
误解六:所有 Transformer 都用 GELU。
不对。原始 Transformer(2017)用 ReLU;BERT/GPT 系列用 GELU;LLaMA 系列用 SwiGLU。激活函数选择随时代演进,不要被「Transformer 用 X」一概而论。
误解七:激活函数「越接近 ReLU」越好。
部分对。形状像 ReLU(一边趋零、另一边趋恒等)的激活,确实在深网络上表现普遍较好。但「光滑」「零中心」「门控」等其他属性也重要。不要只盯一个维度优化。
误解八:激活函数是网络的「主要参数」。
错。激活函数本身是固定的非线性函数,几乎没有可学习参数 (除了 PReLU 等少数变种)。可学习参数都在 <math xmlns="http://www.w3.org/1998/Math/MathML"> W W </math>W 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> b \mathbf{b} </math>b 里。
误解九:换一个激活函数就能让模型大幅提升。
实际中,从 ReLU 换 GELU 的提升通常 < 1%。从 GELU 换 SwiGLU 类似。激活函数的边际收益有限------但如果你的模型在百亿参数级别,这 1% 就是值钱的。
误解十:激活函数研究已经做完了。
未必。每年仍有新的激活函数论文。比如 Mish、Hardswish、SquaredReLU、Geglu 等等。虽然「大革命」可能不会再有,但「小优化」一直在继续。
二十七、下一步
下一篇(第六篇)我们讲梯度下降与反向传播。
我们会从最朴素的梯度下降开始,逐步讲到 SGD、Momentum、Adam,看看「怎么用数据找好参数」这件事的工程实现。然后我们会讲反向传播------链式法则的工程化版本,让自动微分成为可能。
之后第七篇是 Softmax 与概率分布------它是注意力机制的核心齿轮之一。第八篇之后我们就开始接触向量空间、嵌入、位置编码,正式踏上注意力机制的核心。
激活函数这一篇,是「神经网络的非线性来源」。Softmax 那一篇,是「注意力的概率化齿轮」。这两个非线性合起来,构成了 Transformer 的全部非线性来源。记住一句话:Transformer 的非线性,在 FFN 的激活(GELU/SwiGLU)和 Attention 的 Softmax 这两个地方。
二十八、参考文献
- Glorot, X. & Bengio, Y. (2010). Understanding the difficulty of training deep feedforward neural networks. AISTATS.
- Glorot, X., Bordes, A., Bengio, Y. (2011). Deep Sparse Rectifier Neural Networks. AISTATS.
- Krizhevsky, A., Sutskever, I., Hinton, G. (2012). ImageNet Classification with Deep Convolutional Neural Networks. NeurIPS(AlexNet).
- He, K. et al. (2015). Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. ICCV(PReLU + He 初始化).
- Clevert, D. et al. (2015). Fast and Accurate Deep Network Learning by Exponential Linear Units (ELUs). arXiv:1511.07289.
- Klambauer, G. et al. (2017). Self-Normalizing Neural Networks. NeurIPS(SELU).
- Ramachandran, P. et al. (2017). Searching for Activation Functions. arXiv:1710.05941(Swish).
- Hendrycks, D. & Gimpel, K. (2016). Gaussian Error Linear Units (GELUs). arXiv:1606.08415.
- Shazeer, N. (2020). GLU Variants Improve Transformer. arXiv:2002.05202.
- Touvron, H. et al. (2023). LLaMA: Open and Efficient Foundation Language Models. arXiv:2302.13971.
- Karpathy, A. Neural Networks: Zero to Hero 视频系列里的激活函数对比章节。
上一篇 :04. 从函数到神经网络 下一篇 :06. 梯度下降与反向传播