惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
N
News and Events Feed by Topic
D
DataBreaches.Net
MongoDB | Blog
MongoDB | Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Engineering at Meta
Engineering at Meta
T
Tailwind CSS Blog
博客园_首页
Microsoft Azure Blog
Microsoft Azure Blog
Y
Y Combinator Blog
博客园 - Franky
Hugging Face - Blog
Hugging Face - Blog
月光博客
月光博客
A
About on SuperTechFans
I
InfoQ
S
Securelist
Last Week in AI
Last Week in AI
S
Schneier on Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
Hacker News: Ask HN
Hacker News: Ask HN
Schneier on Security
Schneier on Security
Know Your Adversary
Know Your Adversary
腾讯CDC
大猫的无限游戏
大猫的无限游戏
S
Security @ Cisco Blogs
博客园 - 三生石上(FineUI控件)
Simon Willison's Weblog
Simon Willison's Weblog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tor Project blog
美团技术团队
aimingoo的专栏
aimingoo的专栏
G
Google Developers Blog
罗磊的独立博客
Vercel News
Vercel News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
The Cloudflare Blog
S
Secure Thoughts
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Latest news
Latest news
Recent Announcements
Recent Announcements
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
L
LINUX DO - 热门话题
Security Latest
Security Latest
TaoSecurity Blog
TaoSecurity Blog
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队

土法炼钢兴趣小组的算法知识备份

国密算法与国密 TLS 系列索引 【系统架构设计】架构质量属性:不只是"高可用高性能" 【系统架构设计百科】告警策略:如何避免"狼来了" 【系统架构设计】CQRS:读写分离的架构哲学 【系统架构设计】空间架构:极端扩展场景的解法 【系统架构设计】微服务架构深度审视:优势、代价与适用边界 【系统架构设计】扩展性原理:水平、垂直与对角扩展 【系统架构设计】无状态设计:扩展的第一步也是最难的一步 【系统架构设计】缓存架构:从本地到分布式的多级缓存体系 【系统架构设计】管道与过滤器:Unix 哲学的架构表达 【系统架构设计】复杂性管理:架构的核心战场 【系统架构设计】消息队列架构:异步解耦的设计与陷阱 【系统架构设计】CDN 架构:全球加速的设计原理 【系统架构设计】连接池设计:被忽视的性能杀手 【系统架构设计】弹性设计模式:熔断器、舱壁与超时 【系统架构设计】高可用设计模式:冗余、故障转移与仲裁 【系统架构设计】容量规划:从拍脑袋到数据驱动 【系统架构设计】数据库扩展:分库分表的工程实践与替代方案 【系统架构设计】SLO 工程:可靠性的量化管理 【系统架构设计】性能建模:用数学思维分析系统瓶颈 【系统架构设计】混沌工程:主动验证系统的韧性 【系统架构设计】零拷贝与内存映射:数据搬运的极致优化 【系统架构设计】线程模型:从 thread-per-request 到协程 【系统架构设计】容灾架构:多活与灾备设计 【系统架构设计】数据库性能模式:索引、查询与连接管理 【系统架构设计】数据建模:从关系范式到文档模型的真实权衡 【系统架构设计】吞吐量优化:批处理、流水线与并发模型 【系统架构设计】流处理架构:从批处理到实时的范式迁移 【系统架构设计】搜索引擎架构:倒排索引之上的系统设计 【系统架构设计】时序数据架构:监控与 IoT 的存储设计 【系统架构设计】数据迁移与版本化:在线不停机的数据演进 【系统架构设计】数据湖与数据仓库:分析架构的演进路线 【系统架构设计】API 网关设计:入口层的职责边界 【系统架构设计】应用层数据一致性模式:在正确性与性能之间走钢丝 【系统架构设计】多模数据库选型:Polyglot Persistence 的工程实践 【系统架构设计】服务发现与注册:动态拓扑的基础设施 【系统架构设计】配置管理架构:从配置文件到配置中心 【系统架构设计】全链路压测:大规模系统的性能验证 【系统架构设计】幂等性设计:分布式环境下的安全重试 【系统架构设计】契约测试与 Schema 演进:服务间的信任协议 【系统架构设计】长连接与推送架构:WebSocket、SSE 与 MQTT 【系统架构设计】延迟分析:从 P50 到 P999 的全链路追踪 【系统架构设计百科】DDD 战术模式:聚合、实体与值对象 【系统架构设计百科】防腐层与开放主机服务:系统集成的 DDD 方案 【系统架构设计百科】领域事件与事件风暴:从业务到架构的桥梁 【系统架构设计百科】CQRS + Event Sourcing 完整实战:从领域建模到部署 【系统架构设计百科】DDD 与微服务:用领域模型划分服务边界 【系统架构设计】DDD 战略设计:限界上下文与上下文映射 【系统架构设计百科】认证架构:从 Session 到 JWT 到 OIDC 【系统架构设计】API 设计哲学:REST vs GraphQL vs gRPC 的真实权衡 排序算法专题:从 TimSort 到并行排序 【密码学百科】国密算法体系:SM2/SM3/SM4/SM9 全景解读 【密码学百科】承诺方案:Pedersen 承诺、向量承诺与多项式承诺 【密码学百科】不经意传输与隐私信息检索:OT、OT 扩展与 PIR 【密码学百科】门限密码学:门限签名、门限解密与分布式密钥生成 完美哈希:从理论到 gperf 实践 【密码学百科】安全多方计算:从 Yao 的混淆电路到实用 MPC 【密码学百科】同态加密:从 Paillier 到全同态加密(FHE) 【密码学百科】零知识证明系统:zk-SNARKs、zk-STARKs 与 Bulletproofs 【密码学百科】概率论与密码分析:生日攻击、差分分析与线性分析 【密码学百科】计算复杂性与归约:密码安全性证明的基石 【密码学百科】秘密共享:Shamir 方案、VSS 与安全多方计算入口 【密码学百科】椭圆曲线代数:Weierstrass 方程、点群运算与曲线选择 【密码学百科】离散对数与配对密码学:从 DLP 到 BLS 签名 【密码学百科】格密码数学基础:SVP、LWE 与格基约化 【密码学百科】抽象代数:群、环、域的密码学视角 【密码学百科】有限域算术:GF(2^n) 运算与在 AES/ECC 中的应用 【密码学百科】数论进阶:二次剩余、椭圆曲线上的 Weil 配对 【密码学百科】密码学简史:从凯撒密码到量子时代 【密码学百科】威胁模型与安全目标:CIA 三要素之外 【密码学百科】Kerckhoffs 原则与现代密码设计哲学 【密码学百科】随机性:密码学的基石 【密码学百科】信息论入门:熵、完美保密与 Shannon 定理 【密码学百科】分组密码原理:Feistel 网络与 SPN 结构 【密码学百科】AES 逐步拆解:SubBytes 到 MixColumns 的数学 【密码学百科】分组密码工作模式全览:ECB/CBC/CTR/OFB/CFB 【密码学百科】流密码:RC4 的兴衰与 ChaCha20 的崛起 【密码学百科】密码学哈希函数:MD5→SHA-2→SHA-3 的进化之路 【密码学百科】MAC 与 HMAC:消息认证的正确姿势 【密码学百科】认证加密(AEAD):GCM、ChaCha20-Poly1305 与 OCB 【密码学百科】密钥派生函数:HKDF、PBKDF2、Argon2 与密码存储 【密码学百科】公钥密码的数论基础:模运算、群、原根 【密码学百科】RSA 从原理到攻击:教科书 RSA 为什么不安全 【密码学百科】Diffie-Hellman 密钥交换与离散对数问题 【密码学百科】椭圆曲线密码学(ECC):从几何直觉到点群运算 【密码学百科】数字签名:ECDSA、EdDSA 与 Schnorr 签名 【密码学百科】现代密钥交换:X25519、ECDHE 与前向保密 【密码学百科】混合加密与 KEM/DEM 范式:ECIES 与 HPKE 【密码学百科】填充方案:PKCS#1 v1.5、OAEP 与 PSS 【密码学百科】TLS 协议全解析:从握手到 0-RTT 【密码学百科】PKI 与数字证书:信任链的构建与崩塌 【密码学百科】密码认证协议:从 SRP 到 OPAQUE 【密码学百科】零知识证明入门:如何证明你知道而不泄露 【密码学百科】安全信道构造:Noise 协议框架与 Signal 协议 【密码学百科】密钥管理工程:HSM、KMS 与密钥生命周期 【密码学百科】侧信道攻击:从时序攻击到功耗分析 【密码学百科】密码学实现陷阱:三层漏洞分类、审计工具链与系统性预防 密码敏捷性:如何设计可升级的密码系统 【密码学百科】OpenSSL/BoringSSL 架构剖析:ENGINE、Provider 与 FIPS 模块 排序基准测试:用数据说话
【Transformer 与注意力机制】05. 激活函数:让网络「弯下来」的非线性魔法
2026-04-15 · via 土法炼钢兴趣小组的算法知识备份

一、上一篇留下的悬念

上一篇我们论证了一件事——纯线性的网络再深,也只是一个线性变换。把 \(W_2(W_1\mathbf{x} + \mathbf{b}_1) + \mathbf{b}_2\) 展开就是 \(W'\mathbf{x} + \mathbf{b}'\)。线性的复合还是线性,这是线性代数的铁律。

所以神经网络要想拟合「弯曲的」关系,必须引入非线性。引入的方式很巧妙——不是改 \(W\mathbf{x} + \mathbf{b}\) 的形状(那样就不再是线性变换了),而是在两个线性变换之间插入一个逐元素的非线性函数。这个函数叫激活函数(activation function)。

形式化地,一个典型的「线性 + 非线性」三明治长这样:

\[\mathbf{h} = \sigma(W_1 \mathbf{x} + \mathbf{b}_1)\] \[\mathbf{y} = W_2 \mathbf{h} + \mathbf{b}_2\]

其中 \(\sigma\) 是激活函数,逐元素作用——也就是说 \(\sigma(\mathbf{z})\) 表示对 \(\mathbf{z}\) 的每一个元素分别应用 \(\sigma\)

这一篇我们就来仔细看看 \(\sigma\) 这个角色。它看起来微不足道——一个一维标量函数嘛,能有多复杂?但偏偏,激活函数的选择直接决定了深度网络能不能训得动、训得好。一个不合适的激活函数,再大的模型再多的数据都救不了;一个合适的激活函数,能让原本训不动的网络突然焕发生机。

我们会按时间顺序讲:Sigmoid(最早)→ Tanh(改进)→ ReLU(革命)→ Leaky/ELU/SELU(修补)→ Swish/GELU(现代标配)→ SwiGLU/GeGLU(大模型时代)。每一个的诞生都对应着一个时代的痛点,每一次迭代都解决了前一代的某个核心问题。我希望你读完之后,不仅记住每个函数长什么样,更能体会到激活函数演化的逻辑——从生物启发到数值优化,从局部小改到大模型时代的全新设计。

二、为什么必须是「逐元素」的?

这个细节很多教材会一笔带过,但其实值得停下来想一下。

激活函数作用在向量上,但它是「逐元素」作用——也就是说,对向量 \(\mathbf{z} = (z_1, z_2, \cdots, z_h)\),我们有:

\[\sigma(\mathbf{z}) = (\sigma(z_1), \sigma(z_2), \cdots, \sigma(z_h))\]

每一个分量独立处理,互不干扰。这意味着 \(\sigma\) 本质上只是一个一维函数 \(\mathbb{R} \to \mathbb{R}\),被「广播」到向量上。

为什么要这样设计?为什么不用一个「真·向量到向量」的非线性函数(比如对向量做一些复杂的混合)?

有两个原因。

第一,计算简单。逐元素操作可以完美并行——每个元素分配到 GPU 的一个线程,独立计算。如果用复杂的混合非线性,并行度会被破坏。

第二,线性变换已经在做混合了\(W\) 矩阵的每一行都是对输入向量的线性组合。也就是说,「不同维度间的相互作用」这件事,已经由 \(W\) 解决了。激活函数不需要再重复做。

所以神经网络的分工是:\(W\) 负责「不同维度间的混合」(线性,跨元素),\(\sigma\) 负责「单个维度上的弯曲」(非线性,元素内)。两者各司其职,组合出强大的表达能力。

后面我们会看到,这个分工有一个例外——Softmax。Softmax 是非逐元素的非线性(它需要对整个向量归一化),所以它有特殊的地位。但常规激活函数都是逐元素的。

三、Sigmoid:第一代主角

最早的激活函数是 Sigmoid(也叫 Logistic 函数):

\[\sigma(z) = \frac{1}{1 + e^{-z}}\]

它的形状是一条「S」曲线:当 \(z \to -\infty\) 时趋于 0,当 \(z \to +\infty\) 时趋于 1,在 \(z = 0\) 处等于 0.5,左右对称。

Sigmoid 为什么是第一代?因为它有几个让人喜欢的性质。

性质一:输出在 \((0, 1)\) 之间。这个区间天然像「概率」。所以 Sigmoid 在二分类的输出层用得很多——「这是猫的概率」之类。

性质二:处处可导。Sigmoid 处处光滑,没有任何尖角。它的导数还有一个漂亮的形式:

\[\sigma'(z) = \sigma(z)(1 - \sigma(z))\]

也就是说,导数可以由函数值本身算出来——前向算了 \(\sigma(z)\),反向算梯度时直接乘 \(\sigma(z)(1-\sigma(z))\),不用重新算 exp。这在没有自动微分的早期是个工程优势。

性质三:受生物启发。神经元有「激发阈值」——刺激不够就不发放,刺激够了就饱和发放。Sigmoid 的形状刚好像「阈值激发函数的光滑版」。早期人们觉得这种「生物味」是好事。

但 Sigmoid 也有几个致命的缺点。这些缺点直到 2010 年代才被深度学习社区集体抛弃。

四、Sigmoid 的致命问题:梯度消失

Sigmoid 的导数 \(\sigma'(z) = \sigma(z)(1 - \sigma(z))\),最大值出现在 \(z = 0\) 处,等于 \(0.5 \times 0.5 = 0.25\)最大值只有 0.25。当 \(|z|\) 大一点(比如 \(z = 5\)),\(\sigma(z) \approx 0.993\),导数 \(\approx 0.993 \times 0.007 \approx 0.007\)——基本上等于 0。

这意味着什么?意味着 Sigmoid 在「饱和区」(\(|z|\) 较大)的梯度极小

而梯度在反向传播时是「逐层相乘」的。链式法则告诉我们:

\[\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}\]

每一层都要乘一次激活函数的导数。如果每层导数最大才 0.25,10 层之后梯度被乘了 \(0.25^{10} \approx 10^{-6}\)。再乘点权重矩阵,基本就是 0 了。

这就是著名的梯度消失问题(vanishing gradients)。早期网络的浅层(靠近输入)几乎学不到东西——梯度还没传到那里就消失了。1990 年代到 2000 年代,整个深度学习领域被这个问题困住了十几年。

我以前读这段历史,觉得不可思议——一个看起来这么显然的问题,居然困住了一代人。但仔细想想,「显然」是事后的视角。当时人们没有 PyTorch 的可视化工具,没有 TensorBoard 的梯度直方图。他们只能盯着训练曲线发呆——「为什么深网络比浅网络还差?」直到 Hinton 等人在 2006 年用「逐层预训练」绕开这个问题,再到 2012 年 ReLU 直接消解这个问题,整个领域才打开局面。

五、Sigmoid 的另一个问题:输出不以 0 为中心

Sigmoid 的输出范围是 \((0, 1)\)全是正数。这看起来无害,其实有一个微妙的问题——梯度更新会有 zigzag

简单解释:假设第 \(l\) 层的激活是 \(\mathbf{h}_l = \sigma(\cdot)\),全是正数。那么下一层 \(W\) 的梯度更新方向,会受 \(\mathbf{h}_l\) 的符号约束——所有 \(W\) 的元素的梯度同号(要么都正、要么都负)。这意味着参数只能朝某些「象限」移动,要去其他象限就得绕路,形成 zigzag 的更新轨迹。

这听起来抽象,但实际表现就是收敛慢、训练不稳定。

这个问题被叫做「非零中心化」(not zero-centered)问题。要解决它,激活函数的输出最好以 0 为中心,左右对称分布。

六、Tanh:Sigmoid 的孪生兄弟

为了解决「非零中心化」,人们找到了 Tanh:

\[\tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}}\]

它和 Sigmoid 是孪生兄弟——形状几乎一模一样,只是范围从 \((0, 1)\) 拉到了 \((-1, 1)\),并且关于原点对称。事实上:

\[\tanh(z) = 2\sigma(2z) - 1\]

Tanh 解决了 Sigmoid 的「非零中心化」问题。RNN 时代(90 年代到 2010 年代初),Tanh 是 LSTM 和 GRU 内部的标准激活——你今天打开任何一个 RNN 实现,里面到处都是 tanh。

但 Tanh 没有解决梯度消失问题。它的导数 \(\tanh'(z) = 1 - \tanh^2(z)\),最大值是 1(在 \(z = 0\) 处),但两端饱和时仍然趋于 0。所以 Tanh 比 Sigmoid 好一点,但深度网络仍然训不深。

到这里,「梯度消失」已经像一座大山压在深度学习的头上。整个领域亟需一个全新的激活函数。

七、ReLU:一记神来之笔

2010 年前后,一个看似「大道至简」的激活函数横空出世——

\[\text{ReLU}(z) = \max(0, z)\]

「整流线性单元」(Rectified Linear Unit)。它的形状简单到不像「现代神经网络」该有的东西:负数砍成零,正数原样保留。一条折线,仅此而已。

但就是这样一个朴素的函数,彻底改变了深度学习

ReLU 的优势:

第一,正区间梯度恒为 1。在 \(z > 0\) 时,\(\text{ReLU}'(z) = 1\)梯度不会衰减。这意味着深层网络的梯度可以原样传到底层。一记神来之笔

第二,计算极快max(0, z) 是一个 if-else 分支,比 exp 快几十倍。这在 GPU 上是巨大优势。

第三,稀疏性。负输入直接归零,意味着每次前向时,约一半神经元处于「不激活」状态。稀疏激活对生物神经元而言是常态,对网络的容量也有帮助。

第四,不饱和。Sigmoid/Tanh 在 \(|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)。

如果某个神经元的输入 \(z\) 始终为负,那么 ReLU 输出始终为 0,对应的梯度也是 0。梯度为 0 意味着参数不会更新这个神经元就「死了」——再也不会激活,再也不会学习

死亡 ReLU 在训练初期最容易发生:参数初始化不好,或者学习率太大,导致某些神经元的输入被推到一直为负,从此再也救不回来。研究发现,一个训练完的 ReLU 网络里,可能有 10%-40% 的神经元已经死了。

这是 ReLU 的「副作用」,但通常不致命——其他 60%-90% 的神经元仍然在工作,整体性能仍然很好。但在一些极端情况下,死亡 ReLU 会显著降低模型容量。

为了缓解这个问题,人们提出了一系列「ReLU 变种」。

九、Leaky ReLU 与 PReLU:让负数也有一点点导数

第一个补丁是 Leaky ReLU

\[\text{LeakyReLU}(z) = \begin{cases} z & z > 0 \\ \alpha z & z \le 0 \end{cases}\]

这里 \(\alpha\) 是个小常数,通常取 0.01。负数区间也有一个小斜率,梯度不再为零。即使神经元的输入长期为负,参数也仍然在更新——它有机会「复活」。

PReLU(Parametric ReLU)把 \(\alpha\) 也变成可学习参数,每个神经元自己学一个最合适的负斜率。He 等人的工作(2015)显示这在某些任务上略有提升。

Leaky/PReLU 实战中的表现……不算特别好。它们解决了 Dying ReLU 的理论问题,但实际收益不太明显。多数大模型仍然用普通的 ReLU 或后面要讲的 GELU,而不是 Leaky 系。

十、ELU 与 SELU:试图同时解决多个问题

ELU(Exponential Linear Unit, Clevert et al. 2015)走得更远:

\[\text{ELU}(z) = \begin{cases} z & z > 0 \\ \alpha(e^z - 1) & z \le 0 \end{cases}\]

ELU 在负区间用 \(\alpha(e^z - 1)\),让输出能取到接近 \(-\alpha\) 的负值。这有两个好处:

  1. 零中心化——输出可以是负数,整体均值可以接近 0。
  2. 平滑——在 \(z = 0\) 处可导(一阶导连续),不像 ReLU/Leaky 有尖角。

SELU(Scaled ELU, Klambauer et al. 2017)在 ELU 基础上加了一个缩放因子 \(\lambda\)

\[\text{SELU}(z) = \lambda \cdot \text{ELU}(z)\]

并精心选取 \(\lambda \approx 1.0507\)\(\alpha \approx 1.6733\),使得网络在前向传播时自归一化——激活的均值和方差自动保持在 0 和 1 附近。理论很漂亮,工程上也省了 BatchNorm。

但 SELU 的限制很多(要求特定权重初始化、特定层结构),使用门槛高。最终它没能成为主流。

ELU/SELU 的故事告诉我们一件事——激活函数的「数学优雅」不一定等于「工程实用」。一个函数能不能流行,要看它在大量真实任务上的综合表现,不只是看它有多少漂亮的性质。

十一、Swish 与 SiLU:从神经架构搜索来的「光滑 ReLU」

2017 年,Google 用神经架构搜索(NAS)寻找更好的激活函数。算法跑了几天,吐出了一个让人意外的结果:

\[\text{Swish}(z) = z \cdot \sigma(z)\]

其中 \(\sigma\) 是 Sigmoid。这个函数也叫 SiLU(Sigmoid Linear Unit)。

它的形状像「光滑版的 ReLU」——大于 0 时趋近 \(z\),小于 0 时趋近 0,中间过渡平滑、非单调(在某个负值附近有个小凹陷)。

Swish 的优势是「」——不像 ReLU 在 0 处有尖角,Swish 处处光滑。这对优化曲面有微妙的好处,让训练更稳。

更妙的是,Swish 的形式非常简单——只是 \(z\)\(\sigma(z)\) 的乘积,几乎不增加计算量。

Swish 在 EfficientNet 等模型里效果不错,逐渐流行起来。但更广为人知的是它的「亲戚」——GELU。

十二、GELU:Transformer 的标准激活

GELU(Gaussian Error Linear Unit, Hendrycks & Gimpel, 2016)的形式是:

\[\text{GELU}(z) = z \cdot \Phi(z)\]

其中 \(\Phi(z)\) 是标准正态分布的累积分布函数(CDF)。

直观地讲,GELU 把 \(z\) 乘以「\(z\) 是正的概率」(在标准正态假设下)。如果 \(z\) 很大,概率接近 1,GELU 输出 \(\approx z\);如果 \(z\) 很负,概率接近 0,GELU 输出 \(\approx 0\)。它和 ReLU 的差别在中间——ReLU 是「硬开关」(要么 0 要么 1),GELU 是「软概率」(连续从 0 过渡到 1)。

GELU 的精确公式比较麻烦,所以工程实现常用近似:

\[\text{GELU}(z) \approx 0.5 z \left(1 + \tanh\left(\sqrt{\frac{2}{\pi}}(z + 0.044715 z^3)\right)\right)\]

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 部分原本是这样:

\[\text{FFN}(x) = W_2 \cdot \text{GELU}(W_1 x + b_1) + b_2\]

这是一个「线性 → 激活 → 线性」的标准结构。

LLaMA(2023)和它的后继者用了一个不一样的结构——SwiGLU(Shazeer 2020 提出,LLaMA 2 普及):

\[\text{SwiGLU}(x) = (\text{Swish}(W_1 x) \odot (W_3 x)) W_2\]

这里 \(\odot\) 是逐元素相乘。

注意它有三个权重矩阵 \(W_1, W_2, W_3\),比标准 FFN 多了一个 \(W_3\)。整体结构是:

  1. \(W_1\) 投影一次,过 Swish(其实就是 SiLU)。
  2. \(W_3\) 投影一次,不过激活
  3. 把两个结果逐元素相乘——这是「门控」(gating)。
  4. 再过 \(W_2\) 投影回去。

这种「用一路当门控乘另一路」的设计叫门控线性单元(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,问就是玄学」。

但有一些直觉可以建立:

  1. 门控带来更强的表达能力。SwiGLU 比 GELU 多一倍的参数量(多了一个 \(W_3\)),所以表达力更强。
  2. 门控让网络能动态决定信息流——某些维度被门「关上」,某些被「打开」。这种数据相关的稀疏性比 ReLU 的固定稀疏更灵活。
  3. 乘法操作引入二阶相互作用——比单层 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 层线性

上一篇我们口头说「线性的复合还是线性」。这里给一个严格推导,让结论无可辩驳。

定义两层无激活的全连接:

\[\mathbf{h} = W_1 \mathbf{x} + \mathbf{b}_1\] \[\mathbf{y} = W_2 \mathbf{h} + \mathbf{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\]

\(W' = W_2 W_1\)\(\mathbf{b}' = W_2 \mathbf{b}_1 + \mathbf{b}_2\),则:

\[\mathbf{y} = W' \mathbf{x} + \mathbf{b}'\]

这就是单层线性。归纳法可以推出 n 层无激活的复合也等价于单层。

数学上严谨多了。插入非线性 \(\sigma\) 之后,因为 \(\sigma\) 不可交换出来\(\sigma(W\mathbf{x}) \ne W \sigma(\mathbf{x})\) 一般情况下),整个表达就不再能折叠成单层了。这就是非线性的本质作用——打破线性变换的「可交换+可吸收」性质

十六、激活函数与「初始化」的纠缠

激活函数和参数初始化是耦合的。同一个网络,初始化变了,激活函数的合适选择也会变。

具体说,初始化的目标是让前向传播时每层激活的方差稳定(不爆炸、不消失),同时让反向传播时梯度方差稳定。不同激活函数对这两个目标的影响不同:

  • Sigmoid/Tanh:用 Xavier/Glorot 初始化——\(W \sim \text{Uniform}(-\sqrt{6/(n_{in}+n_{out})}, +\sqrt{6/(n_{in}+n_{out})})\)
  • ReLU:用 He/Kaiming 初始化——\(W \sim \mathcal{N}(0, \sqrt{2/n_{in}})\)。多了一个 \(\sqrt{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

\[\text{Softmax}(\mathbf{z})_i = \frac{e^{z_i}}{\sum_j e^{z_j}}\]

它把一个向量映射到「概率分布」——所有输出非负、和为 1。它是非逐元素的(每个输出依赖整个输入向量)。

Softmax 在 Transformer 里有两个关键位置:

  1. 注意力里,对 attention scores 做 Softmax 转成权重分布。
  2. 输出层,把 logits 转成下一个 token 的概率分布。

Softmax 我们留到「第八篇·Softmax 与概率分布」专门讲。这里只是提一句它的特殊地位——它不算狭义的「激活函数」,但它做的事情类似(引入非线性、把一个向量「整形」成有特定语义的输出)。

二十、激活函数的导数和「梯度流」

激活函数的导数决定了梯度怎么流。简单整理:

激活 函数 导数
Sigmoid \(\sigma(z) = 1/(1+e^{-z})\) \(\sigma'(z) = \sigma(z)(1-\sigma(z))\)
Tanh \(\tanh(z)\) \(1 - \tanh^2(z)\)
ReLU \(\max(0, z)\) \(\mathbb{1}(z > 0)\)
Leaky ReLU \(\max(\alpha z, z)\) \(\mathbb{1}(z > 0) + \alpha \mathbb{1}(z \le 0)\)
ELU \(z\)\(\alpha(e^z - 1)\) \(1\)\(\alpha e^z\)
Swish \(z \cdot \sigma(z)\) \(\sigma(z) + z \sigma(z)(1-\sigma(z))\)
GELU \(z \cdot \Phi(z)\) \(\Phi(z) + z \phi(z)\)

注意 Swish/GELU 的导数都不是「函数本身的简单变换」——它们包含 \(z\)\(\sigma(z)/\Phi(z)\) 的乘积。这意味着反向传播时既要算 \(\sigma\) 又要算 \(\sigma'\),比 ReLU 贵一点。但在 GPU 上这点开销几乎可忽略。

梯度流」(gradient flow)是分析深度网络训练的一个视角。我们关心:每过一层,梯度的「典型大小」会变成多少?如果大于 1,可能爆炸;小于 1,可能消失;接近 1,最稳。激活函数选得好,能让梯度流尽量保持「接近 1」。这就是从 Sigmoid 到 ReLU 到 GELU 演化的内在逻辑。

二十一、动手实验:在 PyTorch 里换激活

文字讲多了,回到代码感受一下。一个最小的两层 MLP:

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 多一个 \(W_3\) 矩阵,但 LLaMA 把 hidden_dim 略微缩小,使总参数量保持原样。同样参数预算下 SwiGLU 略好。

第三,惯性效应。LLaMA 是开源大模型的标杆,它怎么做后面的人就跟着做。这种「事实标准的传递」是开源社区的常见现象——一旦某个实践被证明有效且开源可见,其他人不会冒险换。

但 SwiGLU 也不是没缺点:

  • 多了一个矩阵,实现稍复杂
  • 三路计算 + 乘法,不如 GELU 那么「干净」
  • 在小模型上的优势不明显,主要在大模型上显现。

我的判断:SwiGLU 不是终点。未来可能有更好的 FFN 结构(比如 Mixture of Experts、LayerScale 等替代)。但短期内 SwiGLU 是大模型的实战标配。

二十三、激活函数的「死信」与「活档」

说一段题外话。

我喜欢把激活函数想象成「邮局窗口」——输入是一封信,激活函数是窗口工作人员,决定这封信是「寄出去」还是「扔掉」。

  • Sigmoid:怕担责,所有信都半寄半扔(输出都是 0 到 1 之间的小数)。
  • ReLU:果断,正面信件全寄,负面信件全扔。但有时把误判扔了的信救不回来(Dying ReLU)。
  • Leaky ReLU:负面信件偶尔象征性留一份副本(\(\alpha z\))。
  • GELU:根据信件的「概率得分」决定要不要寄、寄多少。
  • SwiGLU:两个窗口对照——一个看内容,一个发门票。两个一致才寄。

这是个调皮的比喻,但帮我记住了不同激活的「精神」。读者可以自己创造比喻——让抽象概念在脑子里有形象,是学习深度学习的关键习惯。

二十四、从激活函数看深度学习的「大道」

激活函数这个领域,给我最大的启发是:

深度学习的进步,往往来自「把一个简单的细节做到极致」

激活函数有什么大不了?不就是一个一维标量函数嘛。但从 Sigmoid 到 ReLU,整个深度学习领域换了几代——这是一个看似微小的细节,却带动了整个学科的变革。

类似的「小细节大革命」还有很多:

  • 初始化:Xavier → He → 各种 LSUV,深度网络从「训不动」变「能训」。
  • 归一化:BatchNorm → LayerNorm → RMSNorm,让千亿参数模型的训练稳定。
  • 优化器:SGD → Momentum → Adam → AdamW,让大模型能在合理时间内收敛。
  • 位置编码:绝对 → 相对 → RoPE,让 Transformer 能处理长序列。

每一个细节,单独看都不起眼;合起来,把深度学习从「学术玩具」变成了「工业级技术」。

这给我们一个研究方法论:关注「不起眼」的小问题,往里面深挖。激活函数研究了三十年,仍然有人在 ICLR/NeurIPS 发新激活函数的论文。这不是「卷」,这是「重要的小事」。

二十五、关键概念回顾

回顾一下本篇的脉络。

为什么需要激活函数?因为线性的复合还是线性,单纯堆叠 \(W\mathbf{x} + \mathbf{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 等少数变种)。可学习参数都在 \(W\)\(\mathbf{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. 梯度下降与反向传播


后记一:为什么激活函数让我着迷

写完正文,我想抒情一下。

激活函数是我学深度学习时最早「惊艳到我」的概念之一。它的设计不靠数学推导,不靠理论证明,全靠实证选优——人们试了很多个,看哪个 work,就用哪个。这种「实验科学」的色彩,和我之前接触的纯数学非常不同。

后来我发现整个深度学习都是这种风格。先做实验,再补理论;先 work,再 understand。激活函数只是这种风格最浓缩的体现。

这种风格让一些纯数学背景的人不舒服。他们觉得「不优雅」「不严格」。但我觉得这恰恰是深度学习有意思的地方——它逼迫我们承认:复杂系统的某些性质,可能永远超出现有的数学工具。我们能做的,是先把它跑起来,再慢慢理解它为什么 work。这是科学研究的另一种形态。

后记二:选激活的实战建议

如果你正在写代码、纠结选哪个激活函数,下面是一份实战清单:

MLP / 简单分类:用 ReLU。无脑选,省得纠结。

CNN:用 ReLU 或它的变种(Leaky/PReLU)。ReLU 仍然是 CNN 的标配

Transformer 隐藏层:用 GELU 或 SwiGLU。GELU 经典,SwiGLU 现代

RNN(如果你还在用):用 Tanh。LSTM 内部的门控用 Sigmoid,cell 状态用 Tanh,沿用经典

输出层: - 回归:不加激活。 - 二分类:Sigmoid。 - 多分类:Softmax。

调试 Dying ReLU:换 Leaky ReLU 或 ELU 试试。通常治标不治本,根本解决在初始化和学习率

模型很深、训练不稳:先检查 LayerNorm/BatchNorm 是不是加对了;激活函数本身换不大可能解决问题。

到这一篇为止,「数学与基础」部分(第 1-8 篇)完成了一半多。接下来:

  • 第 6 篇:梯度下降与反向传播。——「网络怎么学」的细节。
  • 第 7 篇:Softmax 与概率分布。——注意力的关键齿轮。
  • 第 8 篇:向量空间与嵌入。——把离散 token 变成连续向量。

之后我们会进入「注意力机制原理」篇章(第 11-18 篇),这是整个系列的核心。我会保持「长篇叙述 + 直觉为先」的风格,希望大家能跟着走完。

激活函数这一篇是「细节但关键」。下一篇梯度下降会更工程化一点——我们会真正面对优化器、学习率、动量这些训练时最常碰到的概念。

下一篇见。


后记四:再多说几句

最后再多絮叨几句。

关于本系列的定位:我希望本系列既能让初学者「建立直觉」,也能让有经验的人「重温细节」。所以正文偏向叙述和直觉,每篇配若干 SVG 直观图;后记和参考文献则偏向深入和工程。读者可以根据自己的水平选择性阅读。

关于「篇幅长」的辩护:本系列每一篇都接近一万字。这不是为了凑字数,而是因为深度学习的概念之间高度耦合——讲一个概念时不可避免会牵出旁系。我宁可一篇长一点把背景讲透,也不愿写五篇短的让读者跳来跳去。

关于「不卖弄」的承诺:我尽量避免「炫技式」的表述——故意用难懂的术语显得自己专业。我希望本系列读起来像「学长在跟你聊天」,而不是「教授在台上讲课」。如果某些段落让你觉得「在卖弄」,欢迎指出,我会改。

关于「错误」的免责:本系列我自己一遍一遍校对,但不可避免会有小错(公式手误、年份记错、术语用错)。如果你发现,请告诉我。AI 时代,唯一不变的是「人会犯错」——但人也会改。

那么,激活函数这一篇就到这里。

下一篇见。

后记五:激活函数的「数值稳定性」陷阱

讲完原理和家谱,我想专门挑出一个小坑——数值稳定性

激活函数在数学上是良好定义的,但在浮点运算里会翻车。最经典的例子是 Sigmoid

\[\sigma(z) = \frac{1}{1 + e^{-z}}\]

\(z\) 很大(比如 \(z = 1000\))时,\(e^{-z} \approx 0\)\(\sigma(z) \approx 1\),没问题。

但当 \(z\) 很负(比如 \(z = -1000\))时,\(e^{-z} = e^{1000}\)——浮点数会溢出(overflow)。结果是 NaN 或 inf。

工程实现里,Sigmoid 常用 torch.sigmoid 或者数值稳定的写法:

def stable_sigmoid(z):
    return torch.where(z >= 0,
                       1 / (1 + torch.exp(-z)),
                       torch.exp(z) / (1 + torch.exp(z)))

类似的,Softmax 必须减去最大值再做 exp:

def stable_softmax(z):
    z = z - z.max(dim=-1, keepdim=True).values
    e = torch.exp(z)
    return e / e.sum(dim=-1, keepdim=True)

否则 \(e^z\) 容易溢出。

GELUSwish 也有类似的稳定性问题。PyTorch 的内置函数都已经处理好了,但如果你手写实现,要小心。

这些坑通常在 fp16/bf16 训练时更严重——半精度的范围更小,更容易溢出。所以训练大模型时,激活函数的数值实现细节非常重要

后记六:激活函数与「混合精度训练」

混合精度训练(mixed precision training)是大模型时代的必备技能。它在 fp16 或 bf16 下做矩阵乘法,节省一半显存和加快一倍速度,同时在 fp32 下做 loss 计算和参数更新,保证精度。

激活函数在这种训练里要特别小心:

Sigmoid/Tanh:在 fp16 下饱和区会精度损失。极端值附近导数接近零,fp16 表示不出来差异。 ReLU:基本无影响,因为它只判断符号。 GELU/Swish:要看具体实现。tanh 近似版本在 fp16 下可能略不稳定;erf 精确版本一般稳定。 Softmax:必须用稳定写法,否则 fp16 下溢出概率非常高。

实际经验:Transformer 在 bf16 下训练时,GELU/SwiGLU 都没问题;在 fp16 下偶尔会出现 NaN,需要 loss scaling 或者切换到 bf16

这些不是激活函数本身的设计问题,而是它和硬件、框架配合时的问题。但作为工程实践者,必须知道。

后记七:激活函数的「可视化技巧」

如果你想直观理解激活函数,最好的方式是画图

我给学生上课时常做一个练习:让学生用 matplotlib 画出每个激活函数的「函数曲线」和「导数曲线」,并并排对比。这个练习极其有效——画完之后,学生对各激活的形状一辈子忘不掉。

代码极简:

import torch
import matplotlib.pyplot as plt

z = torch.linspace(-5, 5, 200)
fns = {
    'Sigmoid': torch.sigmoid,
    'Tanh': torch.tanh,
    'ReLU': torch.relu,
    'GELU': torch.nn.functional.gelu,
    'SiLU': torch.nn.functional.silu,
}

fig, axes = plt.subplots(2, len(fns), figsize=(15, 5))
for i, (name, fn) in enumerate(fns.items()):
    z_grad = z.clone().requires_grad_(True)
    y = fn(z_grad)
    y.sum().backward()
    axes[0, i].plot(z.numpy(), y.detach().numpy())
    axes[0, i].set_title(name)
    axes[1, i].plot(z.numpy(), z_grad.grad.numpy())
    axes[1, i].set_title(name + " derivative")
plt.tight_layout()
plt.show()

跑一下,你会看到 Sigmoid 的导数像个钟形(峰值 0.25),Tanh 的导数像个钟形(峰值 1),ReLU 的导数是阶跃,GELU/SiLU 的导数是「光滑阶跃」。视觉化让抽象概念变具体

后记八:激活函数的「微分几何」视角

最后讲一个高级一点的视角——微分几何

神经网络可以看作 \(\mathbb{R}^n \to \mathbb{R}^m\) 的映射。激活函数让这个映射「弯曲」。从微分几何看,网络的每一层都在变形输入空间的几何结构——把曲线变直,把直线变曲,把流形变形。

线性层做「线性变形」(保持平直,只做拉伸/旋转);激活函数做「逐点弯曲」(把每个点单独弯一下)。两者交替,整个网络逐层把输入空间「揉」成对任务有用的形状。

这个视角在「几何深度学习」(Geometric Deep Learning)和「信息几何」(Information Geometry)领域有专门的研究。如果你对数学感兴趣,强烈推荐 Bronstein 等人的综述《Geometric Deep Learning: Grids, Groups, Graphs, Geodesics, and Gauges》。

我在这里只能蜻蜓点水。激活函数的「几何效果」,是深度学习理论最迷人的方向之一

后记九:激活函数与「神经形态硬件」

最后一个题外话——激活函数在硬件上的实现

CPU/GPU 上,激活函数是软件实现的——一个数学函数,调用 exp/erf/max 等指令算出来。但有些专用硬件(TPU、神经形态芯片、模拟计算芯片)会把激活函数固化到电路里。

固化的好处是速度极快——不用走指令解码,电路直接给结果。坏处是不灵活——一旦想换激活,硬件要重新设计。

这就是为什么很多专用芯片只支持 ReLU 或者 GELU 的近似版本——这些激活足够稳定、足够普及,硬件厂商敢押宝。激活函数的选择,会反过来影响硬件设计。这是一个软件和硬件互相塑造的有趣例子。

未来如果有了「神经形态计算」的大规模应用,激活函数甚至可能不再是「函数」——而是某种模拟电路的物理特性。这是个值得关注的方向。

后记十:写在最后

终于到末尾了。本篇我从「为什么需要非线性」一路讲到 SwiGLU,从原理讲到工程,从数学讲到硬件。希望你读完之后,对激活函数这个看似不起眼的小角色有了全新的尊重。

激活函数是深度学习里最简单的概念之一,也是最容易被低估的概念之一。一个一维标量函数,背后是三十年的演化、无数次的实验、整个学科的范式转移。

下一篇我们讲梯度下降与反向传播。如果激活函数是「让网络能弯曲」,那梯度下降就是「让网络能学习」。两者合起来,就是神经网络的「形 + 神」。

谢谢你又一次读到这里。我们下一篇见。

后记十一:激活函数的「鲜为人知的小变种」

正文里我们讲了主流激活函数。这里再聊几个「不太主流但值得知道」的变种。

Mish(Misra, 2019):

\[\text{Mish}(z) = z \cdot \tanh(\ln(1 + e^z))\]

形状非常像 Swish,但导数稍微不同。在 YOLOv4 等目标检测模型里被使用,效果略好。但在 LLM 上没普及。

Hardswish / HardSigmoid(MobileNetV3):

\[\text{Hardswish}(z) = z \cdot \frac{\text{ReLU}(z + 3)}{6}\]

这是 Swish 的「分段线性近似」。在移动端部署时,分段线性比 Swish 的 sigmoid 计算便宜很多。是「精度与速度的权衡」的典型例子。

SquaredReLU

\[\text{SquaredReLU}(z) = \max(0, z)^2\]

把 ReLU 的输出再平方一下。在某些 Transformer 变体里被用过,效果略有差异。

Softplus

\[\text{Softplus}(z) = \ln(1 + e^z)\]

「光滑版的 ReLU」,但梯度下降不如 ReLU 高效,所以没流行起来。

Maxout(Goodfellow 2013):

\[\text{Maxout}(z) = \max(W_1 z + b_1, W_2 z + b_2, \cdots, W_k z + b_k)\]

它是「可学习的分段线性」。理论上很强(可以近似任何凸函数),但参数量是普通激活的 k 倍,没法 scale,被遗忘。

Snake(Liu et al. 2020):

\[\text{Snake}(z) = z + \frac{1}{a}\sin^2(az)\]

试图引入周期性。在某些时序信号建模任务里有用。

这些「小变种」大多数没成为主流,但每一个都解决了某个具体问题,值得作为「激活函数的多样性」的注脚记下来。

激活函数研究的状态:主流稳定(ReLU 系),但小变种层出不穷。每年几篇新激活函数的论文,但真正能推翻 ReLU/GELU/SwiGLU 三巨头的,至今没出现。

后记十二:激活函数与「初始化」的更深关系

正文里我提到激活函数和初始化耦合。这里展开一点。

He 初始化的推导大致如下。考虑一层 \(y = Wx\),假设 \(W\) 的元素独立同分布、\(x\) 的元素独立同分布、均值为 0。则:

\[\text{Var}(y_i) = \sum_j W_{ij}^2 \cdot \text{Var}(x_j) = n \cdot \text{Var}(W) \cdot \text{Var}(x)\]

如果想让 \(\text{Var}(y) = \text{Var}(x)\)(前向方差稳定),需要 \(\text{Var}(W) = 1/n\),也就是 Xavier 初始化。

但这个推导假设 \(x\) 没有经过激活。如果有 ReLU,\(x\) 一半被砍成 0,方差减半。要补偿这个减半,应该让 \(W\) 的方差加倍——\(\text{Var}(W) = 2/n\)。这就是 He 初始化。

如果有 Tanh/Sigmoid,因为它们近似线性(在 0 附近),方差几乎不变,所以仍用 Xavier。

这种推导在更深的网络上还要考虑「方差的链式累积」。He 论文里证明:用 Xavier + ReLU 训 30 层 CNN,方差会指数衰减,无法训练;用 He + ReLU 则方差稳定,可以训得动。初始化与激活函数必须配套

后来的工作(如 LSUV、深度残差网络)让初始化的重要性稍微降低(因为 LayerNorm/BatchNorm 自动归一化了激活),但「激活与初始化的相互作用」始终是一个核心议题。

后记十三:激活函数在 Mixture of Experts 里的作用

最近 LLM 流行 Mixture of Experts (MoE) 架构(如 Mixtral、DeepSeek-MoE)。MoE 里有一个「路由器」决定 token 走哪个 expert。这个路由器本身用了激活函数——通常是 Softmax + Top-K 选择

每个 expert 内部仍然是 SwiGLU 结构。所以 MoE 模型里有两层激活:

  • 路由层:Softmax,做稀疏选择。
  • Expert 内部:SwiGLU,做特征变换。

激活函数不仅出现在 FFN 里,只要涉及非线性映射或概率分布的地方,都有它的影子。注意力的 Softmax、Embedding 的某些归一化、损失的 Sigmoid/Softmax、输出的最终归一化……一个 LLM 里至少有几十种「激活」类操作。

这给我一个新的视角:激活函数不是某一种「层」,而是「贯穿模型的非线性原语」。理解它,就是理解模型的「弯曲」如何在不同层次发生。

后记十四:为什么本篇这么长

最后辩护一下篇幅。

激活函数表面是个「一维标量函数」,但它牵涉到:

  1. 非线性的本质(数学)。
  2. 梯度消失/爆炸(优化理论)。
  3. 初始化和归一化(数值稳定性)。
  4. 不同时代的演化(学科史)。
  5. 大模型时代的工程(实战)。
  6. 硬件和数值精度(系统)。

每一个都值得展开。把它们打包到一篇里,当然就长了。但也只有打包,读者才能看到「激活函数」作为一个概念,是怎么和深度学习的其他部分纠缠在一起的。

我希望你读完之后的感受不是「学到了 ReLU 是 max(0, z)」,而是「激活函数是深度学习的一个缩影——简单到极致,又深远到惊人」。

如果有这种感受,本篇就值了。

下一篇见。

后记十五:致敬

本篇内容大量参考了:

  • Hendrycks 的 GELU 论文(写得极清晰)。
  • Shazeer 的 GLU 变体论文(结论简洁有力)。
  • Karpathy 的 makemore 系列里的激活函数讨论。
  • 花书第六章(基础但全面)。
  • He 等人的 ReLU 初始化论文(数学优雅)。

向所有把这些精彩工作分享出来的研究者致敬。本系列只是站在他们的肩膀上的二次创作。

下一篇见。

后记十六:激活函数的常见调试场景

把工程经验再细化一下。下面列几种「训练出问题,可能和激活有关」的典型场景,以及怎么排查。

场景 1:loss 一直不降,几乎是常数

可能:激活全部饱和或全部死亡。Sigmoid 的输入值过大导致 \(\sigma' \approx 0\);ReLU 的所有神经元输入都为负。

排查:打印每层激活的均值、方差、min、max。如果均值都接近 0 或接近边界,就是饱和/死亡。 对策:换激活(ReLU → GELU 之类);调小学习率;改初始化;加 LayerNorm。

场景 2:loss 突然变 NaN

可能:激活溢出。Sigmoid/Softmax 在极大输入下会 inf,进而 NaN。

排查:开启 torch.autograd.set_detect_anomaly(True);打印梯度统计。 对策:加梯度裁剪;用稳定版的 Softmax/Sigmoid;用 bf16 替代 fp16。

场景 3:浅层学得快,深层学不动

可能:梯度消失。激活的导数累乘到深层时太小。

排查:打印每层的梯度范数。如果从输出到输入梯度逐层减小到接近 0,就是消失。 对策:加残差连接(Residual);改 ReLU 系激活;改初始化;加 LayerNorm。

场景 4:激活值分布不正常(部分维度全 0 或全 max)

可能:Dying ReLU 或某些维度饱和。

排查:可视化每层激活的直方图(用 TensorBoard 或类似工具)。 对策:换 LeakyReLU/GELU;调初始化;加 dropout 让激活更分散。

场景 5:训练正常但推理时模型行为奇怪

可能:你忘了 model.eval(),BatchNorm 和 Dropout 仍在训练模式;或者激活函数有非确定性(极少见)。

排查:检查 model.train() vs model.eval() 切换。 对策:永远在推理前调 .eval()。

这些场景全是我自己踩过的坑。激活函数虽然是一个「小细节」,但在调试时是「第一战场」之一。希望这些经验对你有用。

后记十七:再见

行了,这次真的最后一段。

本篇 28 节正文 + 17 节后记,写下来已经接近书的章节体量。激活函数这个题目,如果让我再写,我可能还能扩展出十几节——它真的太「纠缠」了,牵一发而动全身。

但适可而止。读者要消化、我也要喘口气。剩下的细节,留给下一次系统化的「深度学习系统手册」类的写作(如果有那一天)。

激活函数到此为止。下一篇梯度下降与反向传播,我们继续。

谢谢读到这里的每一个你。下一篇见。


到这里整个第 5 篇就完整了。从「为什么需要激活函数」到「激活函数的家谱」,从「数学性质」到「工程细节」,从「Sigmoid 的过去」到「SwiGLU 的现在」,希望对你深入理解深度学习有一些帮助。

再见。

后记十八:一个不算总结的总结

写完十七篇后记,我自己回看也乐了——「后记比正文还长」是作者最不该犯的毛病。但激活函数这个话题真的容易勾出闲话。每写一段就想到一个相关的故事或经验,刹不住车。

为了不再失控,给本篇收一个真正的尾。

激活函数到底是什么?——一个让网络具备「弯曲表达力」的逐元素非线性函数。

为什么它重要?——没有它,网络再深也只能拟合超平面;有它,两层就能逼近任意连续函数(理论上)。

怎么选?——隐藏层用 ReLU 系(ReLU/GELU/SwiGLU),输出层根据任务选(无激活/Sigmoid/Softmax)。

演化方向?——从「光滑」到「不饱和」到「门控」,每一步都解决了上一代的某个核心痛点。

未来呢?——大变革可能不会再有,但小优化(针对特定任务、特定硬件)会持续。

把这五点记住,本篇的核心就拿到了。剩下的都是细节。

下一篇我们正式进入梯度下降。从「网络的形」(结构 + 激活)转到「网络的神」(学习 + 优化)。这是另一座大山,等你来爬。

再再再见。

后记十九:写完时的雪夜

写完这篇时已经是深夜,窗外飘了点小雪。我合上笔记本电脑,给自己倒了杯茶。

回想我第一次接触激活函数,是在大学三年级一门人工智能选修课上。老师在黑板上画 Sigmoid 曲线,说「这个 S 形,是神经网络非线性的来源」。当时我觉得「不就是一条 S 嘛,有什么了不起」。

十几年过去了,我从来没想到这条 S 形——以及它的兄弟姐妹们——会带给我这么多思考。简单的东西,往往是深邃的入口。激活函数让我看到了这一点。

如果本篇对你有所启发,那就把这种启发用在你接下来要学的东西上。保持对「简单细节」的敬畏,不要轻易跳过。深度学习里没有真正不重要的细节——只是有些细节,重要性藏得深一点,需要你慢慢发掘。

茶喝完了。明天继续写第六篇梯度下降。

晚安。


完。

后记二十:给本系列读者的一封短信

亲爱的读者:

如果你顺序读到了这一篇,意味着你已经走过了向量、点积、矩阵乘法、神经网络、激活函数这五块「砖头」。这些都是 Transformer 的最基础积木。

接下来我们要进入「学习的机制」——梯度下降、反向传播、Softmax、嵌入、位置编码。这些会更工程化、更实战。等到第 11 篇我们会正式进入「注意力机制」——这是整个系列的核心,也是我个人最想讲清楚的部分。

如果你觉得到目前为止的内容慢、节奏拖沓——我承认有这种风险。本系列追求的是「让你建立直觉」,而不是「快速过完知识点」。如果你想快,强烈推荐 Karpathy 的视频——他三小时能讲完我五篇文章的内容,效率高很多。但慢有慢的好处:你会记得更牢

如果你觉得到目前为止的内容快、跟不上——也欢迎反馈。我会在后续篇章里调整节奏。

总之:慢慢来,比较快。深度学习不是百米冲刺,是马拉松。一年的扎实学习胜过三个月的速成。本系列陪你走得稍远一点。

下一篇见。

——ltl

后记二十一:彩蛋——激活函数的一段对话

最后留一段虚构的小对话,算彩蛋。

——「老师,为什么不直接用最复杂的激活函数?比如三次多项式 + 周期项 + 指数衰减?」

——「你试过吗?」

——「没试过,我觉得理论上更复杂的应该更强。」

——「这就是问题。深度学习里,没试过就不算证据。你试一下就会发现,复杂激活的训练很不稳定,而且收益寥寥。」

——「但理论上……」

——「理论上 ReLU 不应该比 Sigmoid 好。但实际上 ReLU 大杀四方。理论是地图,实证是道路。」

——「那 SwiGLU 呢?为什么它就 work?」

——「因为有人试过了,并且发现 work。Shazeer 在论文里说『divine benevolence』,半开玩笑半认真——它就是 work,深层原因还在研究。」

——「我不太能接受这种『就是 work』的研究方式。」

——「这是大多数从纯数学过来的人最初的反应。但科学有两条路——演绎(从公理推导)和归纳(从实验提炼)。物理学起步靠归纳(伽利略、开普勒),后来才有牛顿做演绎。深度学习现在还在「伽利略阶段」——大量做实验、归纳规律。理论体系会慢慢建立,但不要等理论建好再用工具。」

——「我懂了。所以激活函数的研究,是一种实验科学。」

——「对。这也是它的魅力——任何人,只要敢做实验,都有机会发现新东西。你不必是数学天才,也能为这个领域做贡献。」

——「我可以试试发明一个新激活函数?」

——「试试看。但记住三件事:1)证明它在多个任务上稳定优于 ReLU/GELU;2)解释它为什么 work(哪怕只是直觉);3)保持谦虚——大多数新激活只是「和现有的差不多」,不是「革命」。」

——「好的,我去试了。」

——「祝你好运。如果发明出来了,记得让我用上。」

这段对话浓缩了我对激活函数研究态度的总结。深度学习是「敢做实验的人的乐园」——不要被「我不会数学」「我不是天才」吓住。实验本身就是一种思考

下一篇真的真的见。

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。