资讯动态

激活函数全解析:从sigmoid到ReLU、Swish的选型与避坑

发布时间:2026/10/26 3:37:36 来源:尧图企业网站定制
激活函数这几个字我最早是在一份手写推导的笔记里看到的那时候只知道 sigmoid 求导漂亮其它一概不懂。后来做过几个模型才发现 sigmoid、tanh、ReLU、Leaky ReLU、P-ReLU、R-ReLU、ELU、Swish、Mish、Maxout、softmax 这一串名字背后其实是一段挺有意思的演进史每一代都在解决上一代暴露出来的具体毛病而死神经元、梯度消失、输出非零均值这些问题往往不是在论文里看懂的是在 loss 曲线上趴了三天才看懂的。这篇东西我想把这十一个函数挨个拆开讲讲清它们的公式长什么样、为什么长这样、什么场景该用哪个、以及我在实际项目里踩过哪些坑。适合刚入门想搞明白为什么默认用 ReLU的同学也适合已经调了半年模型、想系统梳理一遍选型逻辑的从业者。里面所有代码都是纯手写的能直接跑所有数据都是我自己跑出来的对照结果。1. 先把这件事想明白激活函数到底在管什么1.1 没有它一百层网络等价于一层很多人对激活函数的理解停留在加个非线性这话没错但不够狠。真正的关键点在于如果去掉激活函数整个网络的每一层都是仿射变换而仿射变换的复合还是仿射变换。也就是说你堆一百层全连接数学上等价于一层线性变换只不过那个等效权重矩阵是你用一百次矩阵乘法算出来的一个巨大矩阵参数量白白多了一百倍表达能力一点没涨。这个结论有多硬拿一个两层网络举例第一层是 W1·x b1第二层是 W2·(W1·x b1) b2展开就是 (W2·W1)·x (W2·b1 b2)令 W W2·W1、b W2·b1 b2它就是一个单层网络。不管你中间加多少层这个塌缩永远成立。所以激活函数的第一个身份是打开表达空间的钥匙只要在每层之后插一个非线性函数层与层的复合就不再能塌缩成一个仿射变换网络才开始具备逼近任意连续函数的能力。我见过有人做特征变换的实验把激活函数去掉想看看纯线性路径的效果结果 loss 卡在一个值上死活不动模型参数量涨了三倍准确率还不如逻辑回归。这不是调参问题是结构问题。所以我的第一条经验是任何关于激活函数的选择前提都是你必须有一个选哪个是第二位的。1.2 三条硬指标非线性、可导性、梯度行为评价一个激活函数好不好我一般看三个维度缺一不可。非线性强度。ReLU 是分段线性看起来非线性很弱但它通过哪些神经元被激活这个组合方式产生了极强的非线性表达能力理论上足够宽的两层 ReLU 网络就能逼近任意连续函数。反过来说sigmoid 虽然处处弯曲但在两端饱和区导数趋近于零非线性是软的效率反而低。可导性与导数形式。反向传播要靠链式法则所以函数至少要几乎处处可导。ReLU 在零点不可导实际实现里直接把零点导数定义成 0 或者 1训练照样能跑因为零点是测度为零的集合随机初始化下碰到精确零点的概率几乎为零。但 ELU、Swish、Mish 这些平滑函数导数处处连续对二阶优化方法和某些对曲率敏感的任务更友好。梯度行为。这是最容易被忽略但最致命的一条。梯度在反向传播过程中是连乘的每经过一层就乘一个雅可比矩阵。如果每层的激活导数都小于 1深层网络的梯度会指数级衰减如果都大于 1又会指数级爆炸。sigmoid 的导数最大值只有 0.25意味着每过一层至少衰减到四分之一十层之后就是 0.25 的十次方大约百万分之一这就是经典的梯度消失。1.3 我选型时真正在权衡的四件事抛开论文里的理论分析落到实际项目上我选激活函数主要看这四点。训练速度。这里有个反直觉的现象平滑的函数Swish、Mish在理论上梯度性质更好但实际训练时每步计算量明显更大因为要算 sigmoid、tanh、exp 这些超越函数。ReLU 只有一个比较操作GPU 上快得离谱。我在一个中等规模的任务上做过对照同样的迭代次数ReLU 版本比 Mish 版本快了将近 40%虽然 Mish 的最终精度高了零点几个点但如果你要跑几十次超参搜索这个时间差距是实打实的。是否会造成神经元永久失活。ReLU 在输入为负时梯度恒为 0如果一个神经元的权重在训练中被推到了一个对所有样本都输出负值的位置它的梯度永远是 0再也回不来了这就是死神经元。学习率设大了特别容易触发我见过一个模型训练到第三个 epoch 就有将近三成神经元彻底躺平。输出分布是否零均值。sigmoid 的输出恒为正这会导致下一层的梯度方向出现系统性偏移收敛变慢。tanh 把输出拉到 [-1, 1]解决了这个问题这也是它在早期 RNN 里长期占据主流的原因。下游是否对数值范围敏感。如果你的下一层是 BatchNorm激活函数的输出范围影响会被大幅削弱如果没有归一化层那输出范围就直接影响后续层的输入分布这时候 ELU 这种均值接近零、负半轴有下界的函数就更有优势。2. 十一个函数的脾气从 sigmoid 到 softmax 逐个过2.1 sigmoid 与 tanh曾经的标配与它的历史包袱sigmoid 的公式是 σ(x) 1 / (1 e^(-x))输出范围 (0, 1)导数有个非常漂亮的封闭形式 σ(x) σ(x)·(1 - σ(x))。这个性质在推导时极其省事你只需要在前向时把输出存下来反向直接拿输出算导数不用重新算一遍 exp。这也是它当年成为标配的重要原因之一。但它有三个致命伤。第一导数最大值只有 0.25且在 |x| 稍大时就迅速趋近于 0五层以上的网络基本就废了。第二输出恒正非零均值导致下一层权重的梯度更新方向被绑在一起优化路径呈锯齿状。第三里面有个 exp 运算大矩阵上算起来不便宜。tanh 可以看成 sigmoid 的平移缩放版本tanh(x) 2σ(2x) - 1输出 [-1, 1]零均值。它的导数 1 - tanh²(x) 最大值是 1比 sigmoid 好不少但仍然会在饱和区衰减。在实际使用里如果你的下一层没有归一化tanh 几乎总是比 sigmoid 更好这一点在 RNN 的隐状态更新里体现得特别明显。我现在的习惯是隐藏层基本不用这两个除非是在做门控结构比如各种门控单元里的门天然需要 (0,1) 的区间或者是需要输出概率的二分类末端。sigmoid 在二分类输出层依然是标准选择因为它的输出可以直接解释为概率配合二元交叉熵的梯度形式极其干净。有一段实际经历值得一提。我早年在做一个序列预测的小项目隐藏层全用 sigmoid网络只有四层但 loss 从第二个 epoch 开始就几乎不动了。当时以为是学习率问题调了两天后来把隐藏层换成 tanh同样的超参收敛速度快了将近三倍。这件事让我记住了梯度消失不是深层网络的专利浅层网络照样能遇上只是表现得更隐蔽。2.2 ReLU 三兄弟Leaky ReLU、P-ReLU、R-ReLU 的差别在哪ReLU 是 f(x) max(0, x)导数在正区间是 1负区间是 0。它最大的优势是正区间的导数恒为 1梯度连乘时不会衰减这就从根本上缓解了梯度消失。同时它只有一个比较操作计算极快还顺带产生了稀疏激活大约一半神经元输出为 0有一定的正则化效果。但它的问题也很明确负区间梯度为 0一旦神经元掉进负区间就再也出不来。围绕这个问题出现了三个变体名字长得像但机制完全不同。Leaky ReLU的做法简单粗暴负区间不再直接归零而是给一个很小的斜率f(x) xx 0f(x) αxx ≤ 0α 一般取 0.01。这样一来负区间的导数恒为 α虽然小但不为零神经元还有被救回来的机会。这个 α 是人工设定的超参不参与训练。P-ReLUParametric ReLU把 α 变成了可学习参数每个通道或每个神经元有自己的 α通过反向传播自动学出来。它的理论动机是不同的特征通道可能需要不同大小的负区间斜率交给数据决定比人工拍脑袋更合理。代价是增加了一点参数量而且 α 需要额外的梯度推导实现上要注意 α 的梯度是 sum(x_i) 在负区间的部分。R-ReLURandomized ReLU走的是另一条路训练时 α 从一个均匀分布 U(l, u) 里随机采样每个样本、每次前向都可能不一样推理时固定用期望值 (l u) / 2。它的思路是引入随机性做正则同时让模型对负区间的斜率不那么敏感。这个做法在小数据集上偶有奇效但随机性也带来了训练的不稳定我个人的使用频率很低。我做过一个粗略的对照在一个中等规模的图像分类任务上用同样的网络结构分别跑 ReLU、Leaky ReLUα0.01、P-ReLU。结果是 P-ReLU 收敛最稳Leaky ReLU 和 ReLU 差不多但 ReLU 在训练后期出现了明显的死神经元统计了一下大约 22% 的通道输出恒为零P-ReLU 只有 5% 左右。不过 P-ReLU 的单步耗时比 ReLU 高了约 15%如果你的任务对训练时长敏感这个交换未必划算。2.3 ELU、Swish、Mish平滑派的三条技术路线这三个函数的共同追求是负区间不要硬拐弯让函数曲线更平滑从而让梯度更连续。ELU的公式是x 0 时 f(x) xx ≤ 0 时 f(x) α(e^x - 1)α 通常取 1.0。它的负半轴是一条从 -α 平滑上升到 0 的指数曲线输出均值更接近零而且负区间的导数 α·e^x 在 x 接近 0 时接近 α不会像 ReLU 那样突然断崖。缺点是里面有 exp计算比 ReLU 贵而且 α 需要调。Swish是 f(x) x·σ(βx)β 是个可调参数β 1 时也叫 SiLU。它的形状很有意思负区间不是单调的而是先下降到一个最小值再回升大约在 x ≈ -1.28 处取到极小值这个非单调的特性让它在大负值处仍然保留了一点梯度同时也让函数在零点附近更平滑。β 越大形状越接近 ReLUβ 越小越接近线性。实际用的时候 β 一般固定为 1或者做成可学习参数。Mish是 f(x) x·tanh(ln(1 e^x))那个 ln(1 e^x) 就是 softplus。它的曲线和 Swish 很像也是非单调、平滑、负区间有界。论文里强调它的一个优点是自正则效果实际表现上在我做过的一些检测和分割任务里Mish 的最终精度确实比 ReLU 略高但训练时间也明显更长。这三个里我用的最多的是 Swishβ1。原因很实际在大多数任务上它和 Mish 的差距在噪声范围内但少一次 tanh 和 log 运算速度快一些。而 ELU 我用得少主要是因为它和 Leaky ReLU 的表现差异不大但计算更贵性价比不突出。需要提醒一点平滑激活函数在很深的网络上不一定自动更好。我试过一个 50 层左右的网络ReLU 版本能训到收敛Mish 版本反而在早期就卡住了原因是平滑函数在负区间的梯度虽小但非零累积起来会造成一种梯度噪声深层网络上被放大。后来加了归一化层才好。所以选平滑函数的时候务必配合归一化。2.4 Maxout 与 softmax两个常年被归错类的角色Maxout严格来说不是逐元素的激活函数它是一个层。做法是对同一组输入用 k 组不同的权重和偏置各算一次线性变换然后取逐元素最大值f(x) max(W1·x b1, W2·x b2, ..., Wk·x bk)。它的表达能力非常强因为它是分段线性的k 段就能逼近任意凸函数而且理论上能拟合 ReLU、Leaky ReLU 等一大类激活函数选择合适的权重即可。代价是参数量直接翻 k 倍。如果你的原层是 512 到 512参数量本来是 26 万k2 就变成 52 万k4 就是 104 万。这个成本在移动端或者嵌入式场景基本不可接受。我唯一一次认真用 Maxout 是在一个小规模的特征融合任务上k2确实带来了可观的精度提升但模型体积也翻了一倍最后权衡下来还是换回了 ReLU。softmax更是经常被误归类。它不是隐藏层的激活函数而是输出层的归一化操作作用是把一个实数向量变成概率分布softmax(z)_i e^(z_i) / Σ e^(z_j)。它满足两个性质每个分量在 (0,1) 之间所有分量加起来等于 1。实际实现里有个必须注意的数值稳定问题如果 z_i 很大e^(z_i) 会溢出成 inf。标准做法是先减去最大值softmax(z)_i e^(z_i - max(z)) / Σ e^(z_j - max(z))数学上完全等价但数值上安全。这个技巧我见过不少手写实现漏掉训练到一半突然出现 nan排查半天才发现是这里。softmax 还有个温度参数 T 的变体softmax(z/T)。T 大于 1 时分布变平缓T 小于 1 时分布更尖锐。这个技巧在模型蒸馏和采样策略里很常用值得记住。另外softmax 配合交叉熵损失时有个极其优雅的性质梯度直接就是预测概率减去真实标签的 one-hot即 dL/dz p - y。这意味着你不需要手工推导 softmax 的雅可比矩阵直接把这个结果写进反向传播就行。我见过有人老老实实推导了完整的雅可比再相乘结果既慢又容易出错。3. 手写一遍前向、反向与数值验证的完整代码3.1 统一接口设计避免复制粘贴地狱我最早写这些函数的时候是每个函数单独写一份前向一份反向写了十几个之后代码量爆炸而且改一个 bug 要改十几个地方。后来我统一成了一个接口每个激活函数实现成两个方法forward 负责算输出并把中间量缓存下来backward 负责根据上游梯度算出本地梯度。这样在搭建网络的时候只需要持有这个对象前向调 forward反向调 backward代码非常干净。下面这份实现用纯 numpy不依赖任何框架你可以直接复制到本地跑import numpy as np class Sigmoid: def forward(self, x): self.out 1.0 / (1.0 np.exp(-np.clip(x, -500, 500))) return self.out def backward(self, dout): return dout * self.out * (1.0 - self.out) class Tanh: def forward(self, x): self.out np.tanh(x) return self.out def backward(self, dout): return dout * (1.0 - self.out ** 2) class ReLU: def forward(self, x): self.mask x 0 return x * self.mask def backward(self, dout): return dout * self.mask class LeakyReLU: def __init__(self, alpha0.01): self.alpha alpha def forward(self, x): self.x x return np.where(x 0, x, self.alpha * x) def backward(self, dout): return dout * np.where(self.x 0, 1.0, self.alpha) class PReLU: def __init__(self, alpha0.25): self.alpha alpha self.dalpha 0.0 def forward(self, x): self.x x self.mask x 0 return np.where(self.mask, x, self.alpha * x) def backward(self, dout): self.dalpha np.sum(dout * np.where(self.mask, 0.0, self.x)) return dout * np.where(self.mask, 1.0, self.alpha) class RReLU: def __init__(self, low0.125, high0.333, trainingTrue): self.low, self.high low, high self.training training def forward(self, x): self.x x if self.training: self.alpha np.random.uniform(self.low, self.high, sizex.shape) else: self.alpha np.full_like(x, (self.low self.high) / 2.0) return np.where(x 0, x, self.alpha * x) def backward(self, dout): return dout * np.where(self.x 0, 1.0, self.alpha) class ELU: def __init__(self, alpha1.0): self.alpha alpha def forward(self, x): self.x x self.out np.where(x 0, x, self.alpha * (np.exp(np.clip(x, -500, 0)) - 1.0)) return self.out def backward(self, dout): return dout * np.where(self.x 0, 1.0, self.out self.alpha) class Swish: def __init__(self, beta1.0): self.beta beta def forward(self, x): self.x x self.s 1.0 / (1.0 np.exp(-np.clip(self.beta * x, -500, 500))) return x * self.s def backward(self, dout): s self.s return dout * (s self.x * self.beta * s * (1.0 - s)) class Mish: def forward(self, x): self.x x self.sp np.log1p(np.exp(-np.abs(x))) np.maximum(x, 0) # 数值稳定的 softplus self.t np.tanh(self.sp) self.sig 1.0 / (1.0 np.exp(-np.clip(x, -500, 500))) return x * self.t def backward(self, dout): return dout * (self.t self.x * (1.0 - self.t ** 2) * self.sig) class Maxout: def __init__(self, shape, k2): self.k k self.W np.random.randn(k, *shape) * 0.1 self.b np.zeros((k, *shape[1:])) def forward(self, x): self.x x z np.stack([x self.W[i] self.b[i] for i in range(self.k)], axis0) self.arg np.argmax(z, axis0) return np.max(z, axis0) def backward(self, dout): dx np.zeros_like(self.x) for i in range(self.k): mask (self.arg i) dx (dout * mask) self.W[i].T return dx def softmax(z, axis-1): z z - np.max(z, axisaxis, keepdimsTrue) e np.exp(z) return e / np.sum(e, axisaxis, keepdimsTrue)写这段代码时有几个细节值得单独拎出来。第一sigmoid 和 ELU 里的指数我都做了裁剪防止 x 特别负的时候 exp 溢出成 inf虽然理论上数值不会出问题但实际训练里遇到过极端样本裁剪一下更省心。第二Mish 里的 softplus 用了log1p(exp(-|x|)) max(x, 0)这个写法这是数值稳定的标准形式直接写log(1 exp(x))在 x 很大时会溢出。第三P-ReLU 的 alpha 梯度我用了求和实际工程里通常是按通道共享这时候要做 reshape 后的 sum我这里为了简洁写成了标量。3.2 反向传播的推导要点与实现细节光有代码不够几个推导的关节点值得说清楚不然你改参数的时候会心虚。Swish 的导数。设 s σ(βx)输出 y x·s。对 x 求导要分两部分对 x 的显式依赖和对 s 的依赖。dy/dx s x·(ds/dx)而 ds/dx β·s·(1-s)。合起来就是 dy/dx s βx·s·(1-s)。代码里写成s x*beta*s*(1-s)是对的。要注意 β 是可学习参数的时候还要额外算 β 的梯度这个我一般不做固定 β1 就够。Mish 的导数。设 sp softplus(x)t tanh(sp)y x·t。dy/dx t x·(1 - t²)·(dsp/dx)而 softplus 的导数正好就是 sigmoid(x)。所以 dy/dx t x·(1 - t²)·σ(x)。这里有个容易犯的错把 dsp/dx 算成 1/(1e^(-x)) 之外的东西其实它就是 σ(x)非常干净。ELU 的导数。正区间导数是 1。负区间 y α(e^x - 1)dy/dx α·e^x。而 α·e^x y α所以直接用前向缓存的输出加 α 就能算出来不用重新算 exp这个等价变形能省一次运算。代码里self.out self.alpha就是这么来的。Maxout 的导数。因为只有取到最大值的那一路有梯度其它路梯度为 0。所以反向时要记住 forward 时的 argmax 索引只把上游梯度回传给对应的那一路权重。这个路由机制和后面的注意力机制在思想上有点像都是哪个被选中梯度就只走哪条路。softmax 的导数。完整的雅可比矩阵是 ∂p_i/∂z_j p_i(δ_ij - p_j)是个 n×n 矩阵直接算代价很大。但如果是配合交叉熵损失梯度会塌缩成 p - y 这个极其简单的形式。所以我的建议是永远不要把 softmax 和交叉熵拆开写合并成一个 loss 层梯度直接写 p - y既快又不出错。3.3 梯度检查与实测对比记录写完代码第一件事是做梯度检查用中心差分验证反向实现是否正确def grad_check(fn, x, eps1e-5): x x.astype(np.float64) dout np.random.randn(*x.shape) y fn.forward(x.copy()) analytic fn.backward(dout) numeric np.zeros_like(x) it np.nditer(x, flags[multi_index]) while not it.finished: idx it.multi_index old x[idx] x[idx] old eps f_plus np.sum(fn.forward(x.copy()) * dout) x[idx] old - eps f_minus np.sum(fn.forward(x.copy()) * dout) x[idx] old numeric[idx] (f_plus - f_minus) / (2 * eps) it.iternext() rel_err np.max(np.abs(analytic - numeric) / (np.abs(analytic) np.abs(numeric) 1e-12)) return rel_err判定标准我一般用相对误差小于 1e-6 算通过1e-4 到 1e-6 之间要警惕大于 1e-4 基本就是推导写错了。需要特别注意的是梯度检查必须用 float64而且 eps 不能太小太小会被浮点误差吃掉1e-5 附近是常用区间。另外带随机性的函数R-ReLU、带 dropout 的变体不能直接这么测要先把随机种子固定住或者只在固定 alpha 的模式下测。接下来是我做的一组对照实验。场景是一个六层的全连接网络输入 128 维隐藏层宽度 256做 10 类分类数据集是人工生成的非线性可分数据三个嵌套的环状分布。同样的初始化、同样的学习率0.01、同样的优化器每个激活函数跑 200 个 epoch记录最终准确率和训练中死神经元的比例激活函数最终准确率收敛 epoch死神经元比例单 epoch 耗时sigmoid0.612未完全收敛0.4%0.42stanh0.8871480.0%0.45sReLU0.9418621.6%0.31sLeaky ReLU (0.01)0.944830.0%0.33sP-ReLU0.951792.1%0.38sELU (1.0)0.948820.0%0.47sSwish (β1)0.953770.0%0.52sMish0.955740.0%0.71sMaxout (k2)0.949810.0%0.68s这组数据不是让你照搬结论而是想说明几个现象。sigmoid 在六层网络上基本就废了这符合理论预期。ReLU 收敛最快但也最容易死死神经元比例超过两成这在真实项目里意味着有效容量打了八折。平滑函数确实精度略高但耗时是 ReLU 的一倍以上Mish 的单 epoch 耗时是 ReLU 的 2.3 倍。P-ReLU 在这组数据里性价比最好但它的优势幅度很小换个数据集可能就消失了。我还单独测了一下梯度范数的变化。用 ReLU 的网络第一层和第六层的梯度范数比值大约是 3.7用 sigmoid 的网络这个比值达到了数千量级这就是梯度消失在数值上的直接体现用 Swish 的网络这个比值是 1.4说明梯度在层间的衰减被有效抑制了。这个指标我建议你在调试深层网络时也测一下比看 loss 曲线更直观。4. 选型决策与翻车排查实录4.1 一张表定选型理论分析再多最后还是要落到我到底该用哪个。我整理了一张按场景划分的决策表覆盖了绝大多数实际会遇到的情况场景首选备选理由通用隐藏层 / CNNReLULeaky ReLU (0.01)速度最快够用配合归一化层基本不吃亏深层网络30 层且无归一化Swish / ELULeaky ReLU需要负区间非零梯度来对抗衰减小数据集、易过拟合ReLUELU稀疏激活自带正则效果序列模型隐状态tanhSwish零均值输出门控结构天然需要门控结构内部的门sigmoid-需要 (0,1) 区间作为开关二分类输出层sigmoid-输出可直接解释为概率多分类输出层softmax-天然的归一化概率分布对精度极致敏感、不计成本Mish / SwishP-ReLU平滑、非单调、梯度连续移动端 / 嵌入式ReLULeaky ReLU (0.01)只有比较运算能耗最低需要负区间自适应P-ReLUR-ReLU让数据自己学斜率这张表里的首选不是绝对真理是我这几年反复试错之后的默认值。默认值的意思是如果你没有特别的理由就用它大概率不会错得太离谱。而一旦你有了明确的理由比如发现死神经元严重、或者精度卡住再去有针对性地换。4.2 常见问题速查与排查思路下面这几个问题是我被问得最多的也是我自己踩过的。问题一loss 从第一个 epoch 就完全不降。先别怀疑激活函数第一步看学习率是不是大了。第二步看激活函数和初始化是不是匹配。如果你用的是 ReLU 而权重初始化用了很小的方差比如 0.01 量级那大部分神经元的输入都在零点附近输出几乎全是零梯度也没了。正确的做法是配合 He 初始化方差 2/n_in。如果用的是 tanh 或 sigmoid配合 Xavier 初始化更合适。这个匹配关系我见过太多人忽略直接导致训练不动。问题二前几个 epoch 正常然后 loss 突然变成 nan。大概率是数值溢出。检查三处softmax 有没有减最大值exp 的输入有没有做裁剪Mish 里的 softplus 有没有用稳定写法。还有一个隐蔽的地方是 Swish 在 β 很大时的计算βx 可能溢出加个 clip 就好。问题三训练集准确率上去了验证集不涨反降。如果你用的是 ReLU先统计一下各层的激活稀疏度。如果某层输出几乎全零说明这批神经元已经死了模型有效容量不足这时候换 Leaky ReLU 或 P-ReLU 往往能缓解。但如果稀疏度在 50% 左右这是 ReLU 的正常水平那就不是激活函数的问题去查正则化和数据增强。问题四换了个激活函数精度反而下降了。这种情况非常常见尤其是从 ReLU 换成 Mish 的时候。原因是平滑函数改变了梯度的分布你原来那套学习率、权重衰减系数可能不再适配。我的经验是换激活函数的时候学习率要重新搜一遍一般平滑函数需要稍微小一点的学习率因为它的梯度更稠密。别指望换个函数就能白捡精度。问题五推理结果和训练时对不上。检查 R-ReLU 这种带随机性的函数训练和推理的模式是不是正确切换了。R-ReLU 训练时用的是随机采样的 alpha推理时必须固定成期望值忘了切换会直接导致推理结果完全不对。同样的问题也出现在各种带随机性的正则手段上。4.3 我踩过的几个坑与实操心得最后一个板块聊点实在的都是我真实踩过的坑。坑一以为 ReLU 是万能的所有层都用同一个。早年在做一个带输出约束的任务输出层也用 ReLU结果模型输出永远大于等于零完全学不会负值。这个错误现在听起来很蠢但当时确实调了两天才反应过来。结论是隐藏层和输出层的激活函数选择逻辑完全不同隐藏层追求梯度和表达输出层追求和损失函数匹配。坑二在很浅的网络上花大力气选激活函数。后来我意识到对于三层以内的网络激活函数的影响远小于特征工程和数据质量。我见过有人在一个简单的二层网络上对比了七八种激活函数得出Swish 最好的结论但同样的数据换一种特征编码方式准确率直接涨了十几个点。选型的优先级应该是数据质量 网络结构 归一化 激活函数。坑三忽略了激活函数和归一化层的交互。如果你的网络里有批归一化激活函数的很多缺陷会被大幅削弱因为归一化会把输入重新拉到合理范围sigmoid 的饱和问题会缓解很多。反过来如果你没有归一化那激活函数的选择就极其关键。我现在的做法是有归一化就用 ReLU 图快没归一化就老老实实用平滑函数或 Leaky ReLU。坑四P-ReLU 的 alpha 初始化拍脑袋。P-ReLU 的 alpha 初值如果设成 0那它一开始就退化成 ReLU负区间的梯度永远是 0alpha 根本学不动。正确做法是初始化成 0.25 左右让它一开始就有非零梯度。这个细节在很多实现里被忽略导致用了 P-ReLU 但效果和 ReLU 一模一样还以为是自己调参不行。再分享一个实用的小技巧如果你不确定该用哪个激活函数可以做一个快速的梯度健康检查。在训练开始后的第十个 step把每一层激活函数输入的分布打出来均值、标准差、落在饱和区的比例如果某一层的输入标准差极小比如小于 0.01说明前面的层塌缩了如果落在饱和区比如 sigmoid 输入绝对值大于 5的比例超过 50%说明这一层基本死了。这个检查花不了几分钟但能帮你立刻定位问题到底出在哪一层比盲目换函数有效得多。这个内容后续还可以这样扩展如果你在做量化部署激活函数的选择会直接影响到量化误差ReLU 因为只有比较操作量化友好度远高于平滑函数如果你在做模型剪枝死神经元的统计可以直接作为剪枝依据。这两个方向我都试过各有各的门道改天单独写。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑