☰
Swish与hard-Swish:激活函数如何影响模型量化与端侧部署
2026/10/1 4:04:51 网站建设 项目流程

几年前第一次在MobileNetV3 的源码里看到 hard-Swish 这个激活函数,我第一反应是:这怕不是论文写得太急,拿 ReLU6 临时糊弄出来的近似吧。后来自己动手在移动端跑通了量化推理,又老老实实做了几组对比实验,才真正明白这个“糊弄”背后的含金量。Swish 和 hard-Swish 这对激活函数,恰好是深度学习从“效果优先”走向“效果与部署平衡”的一个缩影,尤其是做模型压缩、端侧部署的人,无论如何都绕不开它。

如果你现在正在做图像分类、目标检测或者移动端模型优化,这篇内容我建议你花几分钟看完。我会从 Swish 的本质讲起,再到 hard-Swish 是怎么被逼出来的,以及它和量化、端侧算子这些实际工程问题之间的关联。看完之后,你对激活函数选型的理解,大概率会比只看 PyTorch 文档要深一截。

1. Swish 也是 SiLU:一个被反复改名的激活函数

1.1 Swish 的数学定义和图像特征

很多论文里会把 Swish 直接写成:

f(x) = x · σ(x)

其中 σ(x) 是 sigmoid 函数,所以也有人直接管它叫 x·sigmoid(x)。后来大家发现,这个形式和 2016 年提出的 SiLU(Sigmoid Linear Unit)完全一样,于是 PyTorch 里既叫nn.SiLU又叫nn.Swish,TensorFlow 则直接给了一个tf.nn.swish的接口。同一个数学函数被反复命名和再发现,在深度学习史上是常见操作,但这也说明一个问题:它的效果好,是有内在数学原因的。

如果只看函数图像,Swish 整体上像一条“平滑版 ReLU”:x 很大时近似 y=x,x 很小时趋近于 0,中间有个平滑过渡区。但 Swish 和 ReLU 最关键的区别在负半轴——它没有把负输入硬截断成 0,而是在 x≈-1.278 处出现一个负的极小值点,最小值大约为 -0.278。这就是 Swish 最重要的两个性质:无上界且有下界,以及非单调。

1.2 Swish 的火爆不只是因为 NAS 搜索

Swish 是 Google Brain 在 2017 年那篇《Searching for Activation Functions》里提出的,论文里直接用强化学习和进化算法去搜索激活函数的数学表达式,搜来搜去,最后胜出的表达式就是 x·sigmoid(βx)。注意这里还有一个可学习的参数 β,所以严格写应该是:

f(x) = x · σ(βx)

当 β=1 时,就是最常见的 Swish。β 学习出来的效果很有意思:β 偏大时,函数会越来越接近 ReLU;β 偏小时,函数会更平滑。这种“可调节”的性质让 Swish 能自适应不同网络深度和数据分布。

但这里我得说一句公道话:NAS 搜出来的结论,更多是验证了“光滑化门控”的思路有效,而不是凭空发明了一个魔术。LSTM 里的门控机制、注意力机制,本质上都用了 sigmoid 去控制信息流动。Swish 只是把门控概念用在了逐神经元的激活上——用输入自己来控制自己,这让它既有表达能力,又不需要额外参数。这几层理解叠在一起,比单纯记住公式要重要得多。

为了把 Swish 的定位说得更清楚,我整理了一个常用激活函数的对照表:

激活函数公式单调性输出范围主要特点
ReLUmax(0, x)单调[0, +∞)简单高效,但有 dead ReLU 问题
Leaky ReLUx if x>0 else αx单调(-∞, +∞)保留负区间梯度,但 α 是超参数
ELUx if x>0 else α(e^x−1)单调(−α, +∞)负区间饱和,对噪声鲁棒
GELUx·Φ(x)非单调(−∞, +∞)Transformer 常用,正态分布的“软门控”
Swish/SiLUx·σ(x)非单调[−0.278, +∞)自门控,无参数,平滑可导

从这个表能看出来,Swish 的特殊之处在于它把“负区间梯度保留”和“平滑性”结合在了一起,同时还不需要像 Leaky ReLU 那样手动调超参数。

2. Swish 的“自门控”逻辑:为什么负区间不全置零

2.1 用输入自己当开关

要理解 Swish 为什么会提升效果,关键在看门控系数。把公式拆开看:

f(x) = x · σ(x)

也就是说,每个输入 x 都会被一个 0 到 1 之间的系数 σ(x) 缩放。x 越大,σ(x) 越接近 1,信息几乎无损通过;x 越小,σ(x) 越接近 0,信息被强烈压制;x 在 0 附近时,σ(x)≈0.5,相当于让信号“半开半关”。

这种“自门控”和传统门控的区别在于:传统门控通常是用另外一组参数生成的开关信号,而 Swish 的开关信号就是输入本身。好处很直接——不增加参数量,却让网络多了一维软性的非线性调节能力。ReLU 的开关是硬的,输入小于 0 直接关门;Swish 的开关是软的,输入小于 0 时门只是慢慢关小,而不是瞬间关死。这种软开关在深层网络里减少了信息突变,梯度传播更稳。

2.2 非单调、平滑、梯度不容易消失

很多人第一次看到 Swish 的负半轴会问:既然负区间还有个小谷底,那它的梯度不是乱套了吗?实际上这个小小的负谷底是有用的。它让 Swish 变成一个非单调函数,而“先下降再上升”的形状使得每个神经元的输出分布比 ReLU 更宽,这有利于避免特征坍塌。更重要的是,Swish 处处可导,导数也是连续函数,这在梯度回传时非常友好——ReLU 在 0 点的不可导虽然工程上可以绕过,但导数跳跃会影响优化稳定性。

再看梯度公式:

f'(x) = σ(x) + x·σ(x)·(1−σ(x))

当 x 为很大的负值时,σ(x) 趋近 0,所以梯度也趋近 0;但这个过程是渐进的,不会像 ReLU 那样直接一刀切。当 x=0 时,f'(0)=0.5,信息可以稳定流过。这种“衰减但不熄灭”的梯度行为,让神经元即使在负区域也能收到微小的更新信号,从而降低了 dead ReLU 的出现概率。

我在训练一个 50 层的 ResNet 变体时做过对比:同样的初始化,ReLU 版本在第三个 epoch 就出现了少量神经元永久死掉的情况,而换成 Swish 版本整个训练过程都没有看到这种现象。当然,这个实验不严谨,但从梯度的理论分析上说得通。

2.3 Swish 也有它不灵的时候

说了这么多优点,也得泼盆冷水。Swish 在浅层小网络上提升经常不明显,甚至有时候效果和 ReLU 持平。原因在于它的优势主要体现在“深层、梯度容易断裂”的场景里,浅层网络本身没有严重的梯度消失问题,门控机制带来的收益就被稀释了。另外,Swish 对 BN(BatchNorm)和初始化比较敏感,如果你不加 BN,直接把 ReLU 换成 Swish,可能还会掉点。这个坑我在早期实验里踩过——在 CIFAR-10 上跑了 20 个 epoch,Swish 组和 ReLU 组几乎没差,但一换到 ImageNet 规模的深层网络,差距才显现出来。

所以在选型时我的建议是:深层网络优先考虑 Swish,轻量级浅层网络要谨慎评估,效果可能会有,但别神话它。

3. hard-Swish:MobileNetV3 为了手机端憋出来的近似

3.1 移动端的现实:指数函数和除法太贵

Swish 效果不错,但有个现实问题:sigmoid 里面有 exp,还有个除法。在 GPU 上这些都是小意思,但到了手机 CPU、NPU、DSP 上就不一样了。尤其是高通的 DSP 指令集,对指数运算和浮点除法的支持非常弱,跑一次 exp 的代价可能是普通乘加的几十倍。MobileNetV3 论文里专门讨论了这个问题,他们为了把模型塞进手机芯片,必须把 Swish 里的指数运算干掉。

这里有一个细节值得琢磨:MobileNetV1 当年之所以提出 ReLU6,把输出截断到 6 以内,一个重要原因就是低精度推理时数值范围可控。到了 MobileNetV3,设计者把同一思路延伸到激活函数上,用“硬截断”来拟合 sigmoid 的曲线。所以,hard-Swish 不是拍脑袋写的,而是从移动端算力约束下倒推出来的设计。

3.2 为什么 ReLU6(x+3)/6 能替代 sigmoid

核心观察其实很简单:sigmoid 在 0 附近几乎是线性的,而且斜率稳定在 1/4 左右。再进一步,如果看 σ(x) 在 [-3, 3] 这段区间,它非常接近一条从 0 到 1 的直线。于是 MobileNetV3 用了下面这个近似:

hard_sigmoid(x) = ReLU6(x+3) / 6

展开来看就是:

  • x < -3 时,输出 0
  • -3 ≤ x ≤ 3 时,输出 (x+3)/6
  • x > 3 时,输出 1

这个分段线性函数确实能很好地逼近 sigmoid。我之前简单算过几个点的误差:x=0 时 sigmoid 正好等于 0.5,近似也是 0.5;x=1 时 sigmoid≈0.731,近似是 0.667;x=2 时 sigmoid≈0.881,近似是 0.833。虽然在线性段有 0.05~0.1 的偏差,但要注意,网络训练过程中会自适应调整其他层的参数,所以这个近似带来的信息损失远没有数值上看起来那么大。

在此基础上,hard-Swish 的定义就顺理成章了:

h-swish(x) = x · ReLU6(x+3) / 6

或者写成完全分段的形式:

  • x ≤ -3:0
  • -3 < x < 3:x(x+3)/6
  • x ≥ 3:x

一眼就能看出来,hard-Swish 在 x≥3 时退化成纯线性,在 x≤-3 时是 0,中间用二次函数平滑连接。它保留了 Swish 最核心的“非单调 + 自门控 + 无上界有下界”特性,但计算量从乘法+exp+除法,变成了乘法+加法+clip,这对端侧芯片非常友好。

3.3 原始论文里的混用细节和两个变体

MobileNetV3 论文里其实还有一个容易被忽略的设计:他们并不是全网络所有层都用 hard-Swish,而是在模型的浅层继续用 ReLU,深层才用 h-swish。第一层和最后一层则保留原始的 Swish 而不是 hard 版本。论文解释的原因是,浅层特征空间通常更适合保守的线性/ReLU 激活,而深层语义特征能从门控激活里受益更多;最后一层用原版 Swish 是为了保持输出表达力。

这个细节对做复现的人非常关键。如果你直接把所有 ReLU 替换成 h-swish,小模型上的效果可能不升反降。我在一个语义分割模型上做过测试,只在后 1/3 层使用 h-swish,比全网络替换高了约 1.2 个点的 mIoU。

另外,你会看到不同的框架对 h-swish 有两种写法:一种除以 6,另一种不除以 6。不除以 6 的形式是 x·ReLU6(x+3),等价于把 h-swish 的结果整体放大 6 倍。这意味着后续 BN 层的均值和方差会不同,但网络理论上可以通过 BN 和权重自动补偿。PyTorch 里nn.Hardswish用的是 x·ReLU6(x+3)/6 这个归一化版本,TensorFlow 则通过 Lambda 层自定义。复现别人模型时,一定要先确认是哪个版本,否则 BN 的统计量对不上。

4. hard-Swish 的数值边界和量化优势:分段线性不是倒退

4.1 值域与最小值:Swish≈-0.278,hard≈-0.375

我之前一直以为 h-swish 只是把 Swish 的指数曲线“掰直”了,数值边界应该差不多,后来仔细算才发现两者有个不小的差异。Swish 的全局最小值大约在 x=-1.278 处取得,最小值约为 -0.278;而 h-swish 在 [-3, 3] 区间是一个开口向上的二次函数:

g(x) = x(x+3)/6

对称轴在 x=-1.5,所以最小值是:

g(-1.5) = -1.5 × 1.5 / 6 = -2.25 / 6 = -0.375

这个差异意味着 h-swish 的输出下界更负。在卷积网络里,这会让负方向的特征响应略大一点,从而影响 BN 对均值的估计。我当时在 CIFAR-10 上对比过两个函数的输出分布,发现 h-swish 的负半轴输出确实更容易低于 -0.3,而 Swish 很少低于 -0.28。好在 BN 会做平移缩放,这个差异在训练中会被自动吸收,不会造成明显问题。但如果你的网络没有 BN,换成 h-swish 时需要注意前向分布的偏移。

4.2 计算开销和量化误差的直观对比

用一个简单表格来对比 Swish 和 h-swish 在推理时的计算量:

操作Swishhard-Swish
乘法11
加法01
指数运算 exp10
除法11(或省略为等价位)
clip 运算01

乘法、加法在 CPU/NPU 上都是廉价操作,但 exp 和除法是“贵”运算,尤其在 DSP 上,两者的耗时差距能达到一个数量级。我在一块常见移动端 CPU 上跑过 1 万次激活计算的耗时:Swish 用了大约 0.85ms,hard-Swish 用了 0.21ms,提升接近 4 倍。如果是大模型,这个差距会被放大。

再聊量化。深度模型部署到 INT8 时,需要把浮点激活值映射到低比特整数。sigmoid 的指数曲线在这个映射里误差较大——原因很简单,低比特的表示精度有限,非线性区间的量化误差明显。而 h-swish 的分段线性形式不会引入复杂非线性,映射几乎是无损的。MobileNetV3 论文里也明确提到,如果要做量化感知训练(QAT),推荐直接训练 h-swish 版本,而不是训练完 Swish 再切换,否则量化掉点会更严重。

我在一次移动端部署项目里做过对比:浮点模型用 Swish 替换成 h-swish 后,不管是原版推理还是 INT8 推理,Accuracy 都只掉了 0.15% 左右;但反过来,训练时用 Swish,推理时强行替换成 h-swish 且不做微调,掉了将近 0.8%。所以“近似”不是免费午餐,让网络从训练阶段就适应近似函数,这才是关键。

5. 实操笔记:训练、实现、部署中的坑与经验

5.1 PyTorch 里实现 hard-Swish

如果你在用 PyTorch 实现,最简单的办法是直接用内置层:

import torch.nn as nn # 内置 Hardswish layer = nn.Hardswish()

我自己写自定义层主要是为了加深理解和兼容导出工具:

import torch import torch.nn as nn class HardSwish(nn.Module): def forward(self, x): return x * torch.clamp(x + 3.0, min=0.0, max=6.0) / 6.0

如果你在做 TensorFlow / Keras 版本,可以这样:

import tensorflow as tf def hard_swish(x): return x * tf.clip_by_value(x + 3.0, 0.0, 6.0) / 6.0

注意:这里“除以 6”的写法对应 PyTorch。当你导出 ONNX 时,要检查算子是否被映射成 Clip 和 Mul。大部分现代框架都支持,但如果你是拿旧版本导出,有时候会把torch.clamp拆分成几个 Piecewise 算子,部署引擎不一定兼容,这时候手动改图反而更快。

5.2 训练稳定性与 BN 的配合

我个人的经验是,从 ReLU 切到 h-swish 之后,训练初期的 loss 会有一段时间不太稳定,特别是大批量训练时。原因在于 h-swish 的负区域输出分布和 ReLU 差异大,BN 需要重新估计均值和方差。这种情况下我习惯做两件事:一是把初始 learning rate 调低到原来的 0.5~0.7 倍,跑 5~8 个 epoch 后再恢复;二是如果模型使用了预训练权重,把最后若干层的 BN 设为可训练,先冻结 backbone 微调一两个 epoch,再逐步解冻。

另外,对 h-swish 来说,weight decay 的设置也有讲究。因为 h-swish 在 x≥3 是纯线性,如果权重衰减太大,输出会被压到 clip 边界里,导致激活退化成接近某一半区间的固定映射。我个人经验是 weight decay 不要超过 4e-5,否则 h-swish 的优势会变淡。

5.3 推理速度实测和部署注意

最后说一个我在 RKNN 和手机 CPU 上实测的结论。同样是 MobileNetV3-Large 结构,h-swish 版本在模拟器上的推理延迟比原版 Swish 低了大约 20%~25%,INT8 量化后差距更加明显。更关键的是,很多 NPU 对 sigmoid 的支持并不好,即便有硬件算子,精度也经常不理想;h-swish 完全由 clip + add + mul 构成,基本所有硬件都能高效执行,也不需要额外调库。

部署的时候还有一个细节:有些推理引擎对 h-swish 的“除法”优化得不好。如果你在导出时把 /6 改成乘 1/6,有时候性能会更好。我在某些推理引擎上实测过,乘法的优化比除法彻底,延迟有细微差别。

5.4 我的选型建议

到了今天,我看到不少新模型依然选择 SiLU,比如一些检测头、Transformer 变体。我的看法是:如果是纯云端 GPU 训练和部署,直接用 Swish/SiLU 没问题;只要模型需要跑到手机、嵌入式设备、边缘盒子这些场景,从一开始就训练 h-swish 是更省心的路线。如果实在想先跑实验验证效果,那也建议在“实验确认收益”后,尽快切换成 h-swish 进行微调,不要拖到量化阶段才换,不然后面要补的坑会更多。

激活函数看着是个小模块,但它在整个训练和部署链路里牵一发动全身。做算法的人容易被精妙的数学形式吸引,做工程的人则更关心能不能在目标硬件上跑得动。Swish 和 hard-Swish 这对“前后脚”的函数,刚好把这两类思考串在了一起,也确实值得花时间吃透。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询