激活函数是每个神经元的门,它决定两件事:非线性够不够强,梯度好不好传。 核心数字是导数上限:sigmoid 只有 0.25,tanh 是 1,ReLU 在正区间恒为 1。 反传是连乘。固定权重灌一个梯度进去,走完三层后 sigmoid 剩 5.39e-05, 而它进最后一层时是 1.91e-03——掉了三十多倍。 tanh 和 ReLU 只掉三四倍。 实际训练同一个网络:sigmoid 测试 0.5475,tanh 0.9437,softsign 0.9175,ReLU 0.9587。 sigmoid 只用了两层隐层就落后四十个百分点。
第 2 章讲误差反向传播时提过激活函数,但没讲为什么换一个就训练不动。 这一节把它补上:关键在「连乘」。 这一节也是全章唯一一处「不改数据、不改结构、只换一行代码」的改动。 代价最低,但踩坑也最容易——ReLU 的学习率容忍范围比 tanh 窄得多。
一、四种门,四种形状
四个候选,都是老面孔:
| 名字 | 输出范围 | 导数最大 | 两头会饱和吗 |
|---|---|---|---|
| sigmoid | 0 到 1 | 0.25 | 会,两头趋近 0 |
| tanh | -1 到 1 | 1.0 | 会,两头趋近 0 |
| ReLU | 0 到正无穷 | 1.0(正侧) | 不会,但负侧梯度直接是 0 |
| softsign | -1 到 1 | 1.0 | 会,但尾巴更厚 |
| 具体数值看一眼更直观。 | |||
| x 等于 0 时,sigmoid 输出 0.500、导数 0.250;tanh 输出 0.000、导数 1.000; | |||
| softsign 也是导数 1.000。 | |||
| x 到正负 4 时,sigmoid 和 tanh 的导数都掉到 0.02 以下,softsign 还剩 0.04。 | |||
| softsign 的尾巴比 tanh 厚一倍,这是它唯一的优势:饱和得慢一点。 | |||
| 而 ReLU 在正半轴压根不饱和,导数恒为 1——这是它统治现代网络的直接原因。 |
二、梯度消失就是连乘
反向传播每往回走一层,梯度要乘上这一层的导数。 层数一多,就是连乘一串小于 1 的数。 0.25 乘十次约等于 0.000001,这就是「消失」。 实测的办法是把权重随机定死、不再更新,只在输出端灌一个固定梯度,看它传到每一层剩多少:
| 激活函数 | 第一层 | 第二层 | 第三层 |
|---|---|---|---|
| sigmoid | 5.393e-05 | 3.528e-04 | 1.908e-03 |
| tanh | 2.096e-03 | 4.293e-03 | 7.137e-03 |
| softsign | 9.956e-04 | 2.952e-03 | 6.068e-03 |
| ReLU | 1.149e-03 | 1.752e-03 | 4.539e-03 |
| 从右往左读:sigmoid 每过一层掉一截,走完三层只剩不到四十分之一。 | |||
| tanh 和 softsign 也掉,但只掉三四倍,温和得多。 | |||
| 要诚实说一句:这个比较里混着权重尺度的影响,不能全算到激活函数头上。 | |||
| 但 sigmoid 掉得最狠这一点是稳的——它的导数上限天生只有 0.25,一乘就缩水。 | |||
| 层数越多,这种连乘越致命。 |
三、实际训练一遍
光看梯度还不够,得看它能不能真的学出来。 同一个网络 64 到 32 到 16 到 10,学习率 0.1,1500 步:
| 激活函数 | 训练准确率 | 测试准确率 |
|---|---|---|
| sigmoid | 0.5975 | 0.5475 |
| tanh | 1.0000 | 0.9437 |
| softsign | 1.0000 | 0.9175 |
| ReLU | 1.0000 | 0.9587 |
| sigmoid 连训练集都只学到 0.5975,测试只有一半——它不是「差一点」,是根本学不动。 | ||
| tanh 和 softsign 都过了 0.9,ReLU 最高。 | ||
| 注意这只用了两层隐层,sigmoid 就落后四十个百分点。 | ||
| 层数再深,它只会更糟;tanh 也会开始饱和。 | ||
| 这就是为什么 2012 年之后的新网络几乎全用 ReLU 系。 |
四、ReLU 的坑:学习率一给大就全塌
ReLU 不是没有代价。 它的负半轴导数是 0,一旦某个单元的输出恒为 0,它的梯度也恒为 0,这块参数就永久报废。 这叫 dying ReLU。 先看学习率对它的影响:
| 学习率 | 第一层死单元 | 第二层死单元 | 测试准确率 |
|---|---|---|---|
| 0.1 | 0 / 32 | 0 / 16 | 0.9700 |
| 0.2 | 0 / 32 | 0 / 16 | 0.9725 |
| 0.3 | 0 / 32 | 0 / 16 | 0.1000 |
| 0.5 | 0 / 32 | 0 / 16 | 0.1000 |
| 学习率到 0.3,准确率直接塌到 0.1000——800 张图全猜成同一个数字。 | |||
| 可死单元那一列还是 0:这不是一个个单元慢慢死,是整锅一起炸了。 | |||
| 步子太大,一轮更新就把权重推到很远处,所有单元全卡住,输出变成一个常数。 | |||
| 所以这次没测出真正的 dying ReLU,如实说明:要复现它得用更细的调参——小学习率、长时间训练, | |||
| 让个别单元在训练中被推到负半轴再也没回来。 | |||
| 原理本身是成立的,只是这次实验的粒度不够。 |
五、动手 Demo:四种激活函数的梯度实测、训练对比、以及 ReLU 的崩塌点
数据 400 张训练、800 张测试,先做标准化。
四层网络 64 到 32 到 32 到 16 到 10,只用于梯度那一节;
训练那一节用 64 到 32 到 16 到 10 的两层网络。
代码里最值得留意的是 DACT 那四个 lambda:四个导数都能只用「输出值 a」写出来,
反传时不必再存 z。
ACT = {
u"sigmoid": lambda z: 1.0 / (1.0 + np.exp(-np.clip(z, -60, 60))),
u"tanh": np.tanh,
u"ReLU": lambda z: np.maximum(z, 0.0),
u"softsign": lambda z: z / (1.0 + np.abs(z)),
}
# 四个导数都能只用「输出值 a」写出来,反传时不必再存 z
DACT = {
u"sigmoid": lambda a: a * (1.0 - a),
u"tanh": lambda a: 1.0 - a * a,
u"ReLU": lambda a: (a > 0).astype(float),
u"softsign": lambda a: (1.0 - np.abs(a)) ** 2,
}
# 权重随机定死、不再更新,只在输出端灌一个固定梯度,
# 看它倒着传到每一层还剩多少
d = np.full((len(Xtrn), 10), 0.01) @ Ws[-1].T
norms = []
for i in range(len(Ws) - 2, -1, -1):
d = d * DACT[nm](As[i + 1])
norms.append(float(np.abs(d).mean()))
if i:
d = d @ Ws[i].T
# 实际训练时只换激活函数,其他一概不动
for nm in (u"sigmoid", u"tanh", u"softsign", u"ReLU"):
m = fit(Xtrn, ytr, act=nm, steps=1500, lr=0.1, seed=8)
print(nm, acc(Xtrn, ytr, m, nm), acc(Xten, yte, m, nm))
实跑输出:
--------------------------------------------------------------------
激活函数:门的形状决定梯度能不能传回去
--------------------------------------------------------------------
[1] 激活函数就是每个神经元的「门」,它决定信号怎么过去
下面四个都算一遍:在几个关键点上输出多少、斜率多少
x sigmoid tanh ReLU softsign
-4.0 0.018 / 0.018 -0.999 / 0.001 0.000 / 0.000 -0.800 / 0.040
-2.0 0.119 / 0.105 -0.964 / 0.071 0.000 / 0.000 -0.667 / 0.111
-1.0 0.269 / 0.197 -0.762 / 0.420 0.000 / 0.000 -0.500 / 0.250
0.0 0.500 / 0.250 0.000 / 1.000 0.000 / 0.000 0.000 / 1.000
1.0 0.731 / 0.197 0.762 / 0.420 1.000 / 1.000 0.500 / 0.250
2.0 0.881 / 0.105 0.964 / 0.071 2.000 / 1.000 0.667 / 0.111
4.0 0.982 / 0.018 0.999 / 0.001 4.000 / 1.000 0.800 / 0.040
(每格是「输出值 / 导数」)
sigmoid 的导数最大只有 0.25,两头还趋近 0
tanh 的导数最大是 1,两头同样趋近 0
ReLU 正的这边导数恒为 1,负的那边恒为 0
softsign 导数最大 1,但尾巴比 tanh 更厚,掉得慢一点
[2] 导数上限为什么重要:反向传播是连乘
搭一个四层网络 64-32-32-16-10,权重随机定死不再更新,
只在输出端灌进一个固定的梯度,看它传到每一层还剩多少:
激活函数 隐层 1 隐层 2 隐层 3
sigmoid 5.393e-05 3.528e-04 1.908e-03
tanh 2.096e-03 4.293e-03 7.137e-03
softsign 9.956e-04 2.952e-03 6.068e-03
ReLU 1.149e-03 1.752e-03 4.539e-03
从右往左读:sigmoid 每过一层就掉一截,走完三层剩不到四十分之一
tanh 和 softsign 也掉,但只掉三四倍,比 sigmoid 温和得多
ReLU 这一轮掉得跟 tanh 差不多,因为它有一半单元在负半轴、梯度直接是 0
这个比较里混着权重尺度的影响,不能全算到激活函数头上,
但 sigmoid 掉得最狠这一点是稳的:它的导数上限天生只有 0.25,一乘就缩水
层数越多,这种连乘越致命,这就是梯度消失
[3] 换成实际训练,四种激活各跑一遍
同一个网络 64-32-16-10,学习率 0.1,1500 步:
激活函数 训练准确率 测试准确率
sigmoid 0.5975 0.5475
tanh 1.0000 0.9437
softsign 1.0000 0.9175
ReLU 1.0000 0.9587
sigmoid 明显落后,测试只有一半:它在这个深度上已经学不动了
tanh 和 softsign 都在 0.9 以上,ReLU 最高
注意这只用了两层隐层,sigmoid 就落后四十个百分点
层数再深,它只会更糟;tanh 也会开始饱和,ReLU 才是现在的主流
[4] ReLU 的坑:学习率一给大就全塌
学习率往上加,看有多少个隐单元在全部训练样本上都输出 0:
学习率 第一层死单元 第二层死单元 测试准确率
0.1 0 / 32 0 / 16 0.9700
0.2 0 / 32 0 / 16 0.9725
0.3 0 / 32 0 / 16 0.1000
0.5 0 / 32 0 / 16 0.1000
学习率到 0.3,准确率直接塌到 0.1000——800 张图全猜成同一个数字
可死单元那一列还是 0:这不是一个个单元慢慢死,是整锅一起炸了
步子太大,一轮更新就把权重推到很远处,所有单元全卡在饱和区,
输出变成一个常数,梯度再也拉不回来
这次没测出真正的 dying ReLU,如实说明:要复现它得用更细的调参——
小学习率、长训练,让个别单元在训练中被推到负半轴再也没回来
原理本身是成立的:单元一旦对所有样本都输出 0,它的导数恒为 0,
梯度也恒为 0,这块参数等于永久报废,再也活不过来
对策:学习率压小,或者换负半轴留一点斜率的 Leaky ReLU
[5] 小结
激活函数影响两件事:非线性够不够强,梯度好不好传
梯度好不好传,看导数上限和饱和区——连乘的层数越多越致命
下一节讲 Dropout:不动激活函数,改「训练时让一部分单元随机停工」
DACT 那四个式子值得单独验一遍。
sigmoid 的导数 a 乘 (1 减 a),tanh 是 1 减 a 平方,ReLU 是 a 大于 0,
softsign 是 (1 减 |a|) 的平方。
四个都只依赖输出,这是它们能写成查表形式的原因。
梯度那段的关键是「权重定死」。
如果让权重一起更新,各层的梯度会被训练过程搅在一起,就看不出连乘的效果了。
定死权重,等于只测「传导」这一个环节。
第三段那张表里,sigmoid 的训练准确率只有 0.5975。
这不是过拟合,是欠拟合——连见过的题都没学会。
判断方法很简单:训练低、测试也低,就是欠拟合;训练高、测试低,才是过拟合。
30 秒实验
- 把 ReLU 那组的学习率从 0.1 改成 0.01,再跑一遍死单元统计。 准确率会稳在 0.97 附近,死单元仍然是 0——这份数据太「干净」,不容易死。
- 把
DACT里 sigmoid 的导数故意写成 a 乘 a,再跑梯度那段。 各层梯度会整体变小,但「越往前越小」的趋势不变——说明趋势来自连乘,不是数值。 - 给 sigmoid 那组把步数从 1500 加到 8000。 准确率会往上爬,但爬得很慢;这就是「能用,但划不来」。
常见误区
- 「ReLU 比 sigmoid 好,换了就行。」 同一份代码里换 ReLU,学习率必须跟着压小,否则一轮就炸(0.3 时掉到 0.1000)。 「好」是有前提的,前提是超参数跟着调。
- 「梯度消失是激活函数造成的。」 激活函数只是一半原因,另一半是权重初始化。 初始权重太小,连乘一样会缩水;初始权重太大,tanh 和 sigmoid 会直接饱和。 这也是为什么有了 Xavier 和 He 初始化这两套公式。
- 「sigmoid 已经淘汰了,不能用。」 它在浅层网络、二分类输出层、门控结构里仍然常见。 准确的说法是:在深层网络的中坚层里,它的连乘代价太大。
自测三问(点开对答案)
1. **为什么 sigmoid 的导数上限是 0.25,这个数怎么来的?** —— **sigmoid 的导数是 a 乘 (1 减 a),a 的取值范围是 0 到 1。** **这个二次函数在 a 等于 0.5 时取最大值 0.25。** **所以不管输入是什么,梯度传过 sigmoid 最多只剩四分之一。** 2. **ReLU 正半轴导数是 1,为什么梯度还是会变小?** —— **因为负半轴的单元导数是 0,它们这一轮完全不传梯度。** **另外权重矩阵本身也会缩放梯度,所以「导数为 1」只保证不额外缩小,不保证梯度守恒。** 3. **训练准确率 0.5975、测试 0.5475,这是过拟合吗?** —— **不是,是欠拟合。** **过拟合的特征是训练高、测试低;这里是两边都低。** **欠拟合该做的是加容量、加步数、换激活函数,而不是加 Dropout。**激活函数改的是每个单元「怎么响」。 下一节改的是「这一轮谁上班」——训练时随机让一部分单元停工,这就是 Dropout。
- ☐ (本节完,下一节 6.4 Dropout)