激活函数:让网络学会弯曲 本节摘要:没有非线性,你那 100 层的网络不过是一次花哨的矩阵乘法。激活函数(Activation Function)就是那道门,让神经网络能用曲线思考。把两个线性变换堆起来:y = W2(W1x + b1) + b2,展开后就是 y = Ax + c——一次线性变换。无论你堆多少层线性层,结果都坍缩成一次矩阵乘法,你那 100 层网络的表达力和单层毫无区别,既学不会 XOR,也分不开螺旋数据,更认不出人脸。激活函数打破线性:它把每一层输出扭过一个非线性函数,赋予网络弯曲决策边界、逼近任意函数、真正学习的能力。但选错激活,梯度要么消失到零(深网里的 sigmoid),要么爆炸到无穷(初始化不当的无界激活),要么神经元永久死亡(大负偏置下的 ReLU)。
本节摘要:没有非线性,你那 100 层的网络不过是一次花哨的矩阵乘法。激活函数(Activation Function)就是那道门,让神经网络能用曲线思考。把两个线性变换堆起来:y = W2(W1x + b1) + b2,展开后就是 y = Ax + c——一次线性变换。无论你堆多少层线性层,结果都坍缩成一次矩阵乘法,你那 100 层网络的表达力和单层毫无区别,既学不会 XOR,也分不开螺旋数据,更认不出人脸。激活函数打破线性:它把每一层输出扭过一个非线性函数,赋予网络弯曲决策边界、逼近任意函数、真正学习的能力。但选错激活,梯度要么消失到零(深网里的 sigmoid),要么爆炸到无穷(初始化不当的无界激活),要么神经元永久死亡(大负偏置下的 ReLU)。激活函数的选择,直接决定你的网络到底学不学得动。本节将从零实现 sigmoid、tanh、ReLU、Leaky ReLU、GELU、Swish、softmax 及其导数,实测梯度在 10 层以上网络里的衰减,检测 ReLU 的死神经元,并给出按架构选激活的决策图。
阅读完本节,你应当能够:
堆两个线性变换:y = W2(W1x + b1) + b2,展开:y = W2W1x + W2b1 + b2,就是 y = Ax + c——一次线性变换。无论堆多少层线性层,结果都坍缩成一次矩阵乘法,你那 100 层网络和单层表达力相同。
这不是理论奇谈,它意味着深线性网络根本学不会 XOR、分不开螺旋、认不出人脸。没有激活函数,深度就是幻觉。
激活函数打破线性,把每层输出扭过非线性函数,赋予网络弯曲决策边界、逼近任意函数、真正学习的能力。但选错激活,梯度要么消失到零(深网 sigmoid),要么爆炸到无穷,要么神经元永久死掉(大负偏置 ReLU)。激活函数的选择,直接决定网络能否学起来。
矩阵乘法可组合:向量先乘 A 再乘 B,等价于乘 AB。这意味着堆十个线性层,数学上等价于一个带大矩阵的线性层——所有参数、所有深度全白费。必须有东西打断这条链,那就是激活函数。
证明:线性层算 f(x) = Wx + b,堆两层:
第 1 层: h = W1 * x + b1 第 2 层: y = W2 * h + b2
代入:
y = W2 * (W1 * x + b1) + b2 y = (W2 * W1) * x + (W2 * b1 + b2) y = A * x + c
一层了。在层间插入非线性激活 g():
h = g(W1 * x + b1) y = W2 * h + b2
代入被打断,W2 × g(W1 × x + b1) + b2 无法化简为单次线性变换。网络能表达非线性函数,每多一个带激活的层就多一份表达力。
神经网络最早的激活函数。
sigmoid(x) = 1 / (1 + e^(-x))
输出范围 (0, 1),光滑、可导,把任意实数映射到类概率值。导数:
sigmoid'(x) = sigmoid(x) * (1 - sigmoid(x))
这个导数的最大值是 0.25,出现在 x = 0。反向传播里梯度逐层相乘,十层 sigmoid 意味着梯度至多被乘 0.25 十次:
0.25^10 = 0.000000953674
不到原始信号的百万分之一。这就是梯度消失:早期层梯度小到权重几乎不更新,网络表面在学(后期层损失在降),第一层却冻死了。深 sigmoid 网络根本训不动。
额外毛病:sigmoid 输出恒正(0 到 1),权重的梯度永远同号,导致梯度下降来回之字形震荡。
sigmoid 的居中版。
tanh(x) = (e^x - e^(-x)) / (e^x + e^(-x))
输出范围 (−1, 1),零均值,消除了之字形震荡。导数:
tanh'(x) = 1 - tanh(x)^2
最大导数 1.0(x = 0 处)——比 sigmoid 好四倍。但梯度消失仍在:大正或大负输入时导数趋零,十层照样把梯度压瘪,只是没那么狠。
修正线性单元(Rectified Linear Unit),由 Nair 与 Hinton 在 2010 年为深度学习推广(函数本身可追溯到 Fukushima 1969 年的工作),它改变了一切。
relu(x) = max(0, x)
输出范围 [0, +∞)。导数简单至极:
relu'(x) = 1 若 x > 0 0 若 x <= 0
正输入无梯度消失——梯度恒为 1,直通而过。这就是深网变得可训的原因:ReLU 在层间保持梯度幅度。
但它有失败模式:死神经元问题。若某神经元的加权输入恒为负(大负偏置或不巧的初始化),它的输出恒为 0、梯度恒为 0,永不更新,永久死亡。实践中 ReLU 网络训练时 10~40% 的神经元可能死掉。
死神经元最简解法。
leaky_relu(x) = x 若 x > 0 alpha * x 若 x <= 0
alpha 是小常数(典型 0.01)。负侧给个小斜率而非零,死神经元仍能收到梯度信号、有机会复活。
高斯误差线性单元(Gaussian Error Linear Unit),2016 年由 Hendrycks 与 Gimpel 提出,BERT、GPT 及多数现代 Transformer 的默认激活。
gelu(x) = x * Phi(x)
Phi(x) 是标准正态分布的累积分布函数,实践中用近似:
gelu(x) ~= 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))
GELU 处处光滑、允许小的负值通过(不像 ReLU 硬截到 0)、有概率解释——它按输入在正态分布下为正的概率给输入加权。这种光滑门控在 Transformer 架构里优于 ReLU,因为它提供更好的梯度流,并彻底避免死神经元。
Ramachandran 等人 2017 年通过自动化搜索发现的自门控激活。
swish(x) = x * sigmoid(x)
Swish 形式是 x × sigmoid(x)。Google 通过对激活函数空间做自动搜索发现它——神经网络在为自己设计部件。和 GELU 一样光滑、非单调、允许小负值。差别很微妙:Swish 用 sigmoid 做门控,GELU 用高斯 CDF。实践上性能几乎相同,Swish 用于 EfficientNet 等视觉模型,GELU 主宰语言模型。
不在隐藏层用。softmax 把一组原始分数(logits)转成概率分布。
softmax(x_i) = e^(x_i) / sum(e^(x_j) 对所有 j)
每个输出在 0 到 1 之间,所有输出之和为 1。它是多分类的标配最终激活:最大 logit 拿最高概率,但与 argmax 不同,softmax 可导且保留相对置信度信息。
完整代码见原课程 phases/03-deep-learning-core/04-activation-functions/code/ 相应文件。
每个函数接收一个浮点数返回一个浮点数,导数函数同理。
import math def sigmoid(x): x = max(-500, min(500, x)) return 1.0 / (1.0 + math.exp(-x)) def sigmoid_derivative(x): s = sigmoid(x) return s * (1 - s) def relu(x): return max(0.0, x) def relu_derivative(x): return 1.0 if x > 0 else 0.0 def gelu(x): return 0.5 * x * (1 + math.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * x ** 3))) def softmax(xs): max_x = max(xs) exps = [math.exp(x - max_x) for x in xs] total = sum(exps) return [e / total for e in exps]
在 -5 到 5 之间均匀取 100 个点,统计每个激活的导数有多少点接近零,打印文字直方图,直观看到 sigmoid/tanh 的死区。
把一个信号依次穿过 N 层(分别用 sigmoid、ReLU、GELU),测量每层激活幅度的变化。sigmoid 在深网里迅速衰减,ReLU 与 GELU 维持幅度——这是肉眼可见的差距。
搭一个 ReLU 网络,灌随机输入,统计有多少神经元从不激活(输出恒为 0)。打印报告:死亡(从不激活)、虚弱(低于 5%)、健康各多少个,并画出每个神经元的激活频次条形图。
用同一个两层网络在圆形数据集(圆内为类 1、圆外为类 0)上配三种激活训练,比较收敛速度。GELU 与 ReLU 通常明显快于 sigmoid,这正是上一节梯度消失的实战后果。
PyTorch 把这些激活同时提供为函数式和模块式两种形式:
import torch import torch.nn as nn import torch.nn.functional as F x = torch.randn(4, 10) relu_out = F.relu(x) gelu_out = F.gelu(x) sigmoid_out = torch.sigmoid(x) swish_out = F.silu(x) logits = torch.randn(4, 5) probs = F.softmax(logits, dim=1) model = nn.Sequential( nn.Linear(10, 64), nn.GELU(), nn.Linear(64, 32), nn.GELU(), nn.Linear(32, 5), )
经验法则:Transformer 隐藏层用 GELU;CNN 隐藏层用 ReLU;分类输出层用 softmax;回归输出层不加激活(线性);概率输出用 sigmoid。先从这些默认值起步,有了证据再改。RNN/LSTM 隐藏态用 tanh、门用 sigmoid,但今天从零搭基本不会用 RNN。若 ReLU 网络里神经元在死,换 GELU——它同时解决死神经元并给更好梯度流,不必动辄上 Leaky ReLU。
本节产出(位于原课程 outputs/):
prompt-activation-selector.md:一个可复用提示,帮你在任意架构里挑对激活函数。下一节,我们吃透损失函数——它才是模型真正优化的唯一目标,选错损失,模型会把力气全花在钻空子上。