假设你在做一个「今天该不该出门跑步」的判断器,输入有温度、湿度、空气质量三个数。如果只是把它们加权相加得到一个总分,不管这个分多大或多小,你的判断都只能在一条直线上滑动。可现实里的选择并不是直线:温度在 15~25 ℃ 时越接近 22 ℃ 越舒服,可一旦超过 30 ℃,温度越高反而越糟。这种「到达某个阈值后才转向」的判断,正是激活函数要给神经网络装上的能力。
一句话定义
激活函数(activation function)是神经网络每个神经元在「加权求和」之后套上的一个小函数,决定这个神经元的输出长什么样、以什么强度传给下一层。
如果没有它,神经网络无论堆多少层,都等价于一层线性回归——什么复杂问题都学不会。激活函数就是那个让神经网络「能学复杂东西」的关键开关。
为什么神经网络需要非线性
先看一个反直觉的事实:没有激活函数,再深的网络也只是一层线性模型。
假设有一个最简单的两层网络,中间不加激活函数:
把括号展开:
令
图 1:无激活函数时两层线性变换可合并成一层,堆再深也只等价于一条直线。
符号逐项解读:
:这两层线性堆叠后网络的输出向量。 :输入向量(比如一张图片的像素、一句话的词向量)。 :相邻两层之间的权重矩阵,是网络要学的参数。 :偏置,每个神经元自带的「基础值」。 :合并后的等价权重和等价偏置。
线性模型只能解决「一条直线能分开」的问题。可现实里的数据——猫和狗的照片、垃圾邮件和正常邮件——根本不可能用一条直线切开,你需要的是曲面、折线、各种拐弯的边界。激活函数就是那个把直线掰弯、让网络能画曲面的小函数。
ReLU:最简单也最常用的激活函数
ReLU(Rectified Linear Unit,修正线性单元)的公式:
符号逐项解读:
:这个神经元收到的加权求和值(来自上一层的输入乘权重之和)。 :在 0 和 之间取较大者。 - 当
时,输出就是 本身;当 时,输出被「掐」成 0。
直觉:信号大于 0 原样通过,小于等于 0 直接掐掉——就像一个只放行「正信号」的门卫。
图 2:ReLU 的折线形状——正输入原样放行,负输入掐成 0,拐弯处即非线性。
手算小例子
假设某个神经元收到的加权求和是
再来一个
自检:结果要么等于输入(输入为正),要么等于 0(输入非正),符合公式定义。
对应的 PyTorch 片段
import torch.nn as nn # nn 是 PyTorch 的神经网络模块
# 一层线性变换 + ReLU 激活,对应公式 y = ReLU(W x + b)
layer = nn.Linear(3, 1) # 3 个输入 → 1 个输出,内部自带 W 和 b
activation = nn.ReLU() # 激活函数 ReLU
y = activation(layer(x)) # 等价于 max(0, W x + b)为什么是 ReLU 而不是 sigmoid / tanh
ReLU 不是唯一选择。历史上更早用的是 sigmoid 和 tanh,它们都是 S 形曲线。
Sigmoid 把任意实数压到
符号逐个解读:
:sigmoid 函数的输出,把任意实数 压缩到 区间内。 :这个神经元收到的加权求和值。 :自然对数的底(约 2.718),指数函数的底数。
Tanh 把任意实数压到
式中的
它们看起来都很优雅,但在深层网络里会触发一个致命问题——梯度消失。
梯度消失:为什么深层网络「学不动」
神经网络靠反向传播学习,每一层都要乘一次激活函数的导数(链式法则)。当导数小于 1,连乘几次就会迅速缩小:
- sigmoid 的导数是
,最大值只有 0.25(出现在 处),输入稍微偏离 0 就更接近 0。 - tanh 的导数是
,最大值是 1(出现在 处),但两端同样快速趋近 0。
式子右上角的小撇号
假设一个 10 层网络,每层都乘 0.25:
底层收到的梯度几乎是 0,权重根本更新不了——网络「学不动」。这就是梯度消失。
图 3:三种激活的导数对比——sigmoid 峰值仅 0.25,连乘 10 层梯度近乎归零;ReLU 正区恒为 1,梯度一路畅通。
ReLU 的导数则截然不同:
这里的
对正输入,导数恒为 1,连乘 100 次还是 1——梯度可以一路畅通传到底层。这正是 ReLU 让深度学习在 2010 年后突然起飞的原因。
ReLU 的缺点:神经元「死亡」
ReLU 也有短板:负输入的导数是 0,一旦某个神经元的输入长期为负,它就再也得不到梯度更新,等于「死掉」。这就是 Dying ReLU 问题。
图 4:ReLU 负区导数为 0 可能让神经元「死亡」,Leaky ReLU 给负区留小斜率以保留梯度。
后来出现了不少改良版:Leaky ReLU(负输入给一个很小的斜率,比如 0.01)、GELU(BERT、GPT 等模型常用)等,专门弥补这个缺陷。
小结
激活函数是神经网络里那个「把直线掰弯」的关键零件——没有它,再深的网络也只是一条直线。历史上 sigmoid / tanh 的 S 曲线优雅,却容易让深层网络梯度消失;ReLU 用一个极简的
完整代码
下面这段代码定义一个带 ReLU 的两层多层感知机(MLP),用假数据跑一次前向 + 一次训练步,可以直接复制运行。
import torch
import torch.nn as nn
# 定义一个带 ReLU 激活的两层 MLP
class TinyMLP(nn.Module):
def __init__(self, in_dim=3, hid_dim=4, out_dim=1):
super().__init__()
# nn.Linear(in, out) 对应 y = W x + b,内部自带权重 W 和偏置 b
self.fc1 = nn.Linear(in_dim, hid_dim)
# ReLU 激活:max(0, x),夹在两层之间引入非线性
self.act = nn.ReLU()
self.fc2 = nn.Linear(hid_dim, out_dim)
def forward(self, x):
# 前向:线性 → ReLU → 线性
h = self.act(self.fc1(x)) # 第一层 + 激活
out = self.fc2(h) # 输出层(回归任务通常不再加激活)
return out
# 造一批假数据:3 维输入 x,目标值 y
torch.manual_seed(0) # 固定随机种子,结果可复现
x = torch.randn(8, 3) # 8 个样本,每个 3 维
y = torch.randn(8, 1) # 8 个目标值
# 实例化模型、损失函数、优化器
model = TinyMLP()
criterion = nn.MSELoss() # 均方误差损失
optimizer = torch.optim.SGD(model.parameters(), lr=0.05)
# 一次训练步:前向 → 算损失 → 反向传播 → 更新权重
pred = model(x)
loss = criterion(pred, y)
optimizer.zero_grad() # 清空旧梯度
loss.backward() # 反向传播算新梯度(ReLU 让梯度畅通)
optimizer.step() # 用梯度更新 W、b
print(f"loss = {loss.item():.4f}")试着把 self.act = nn.ReLU() 换成 nn.Sigmoid(),再跑一次观察 loss 的下降速度——在深层网络里这个差距会被放大几十倍。
参考资料
- Rectified Linear Units Improve Restricted Boltzmann Machines - Vinod Nair & Geoffrey E. Hinton(ICML 2010) http://www.cs.toronto.edu/~fritz/absps/reluICML.pdf
- Activation Functions and the Vanishing Gradient Problem - Fabrizio Musacchio https://www.fabriziomusacchio.com/teaching/teaching_dimensionality_reduction_in_neuroscience/07_activations_functions_and_vanishing_gradient_problem
- A Gentle Introduction to the Rectified Linear Unit (ReLU) - Machine Learning Mastery https://www.machinelearningmastery.com/rectified-linear-activation-function-for-deep-learning-neural-networks/