news 2026/9/23 7:56:30

SE模块详解:从Squeeze-and-Excitation原理到代码实现与工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SE模块详解:从Squeeze-and-Excitation原理到代码实现与工程实践

1. 聊聊SSH模型,先把这个名字认清楚

第一次看到“SSH模型”这个标题,你可能会和我当初一样下意识愣了一下——是那个程序员天天连服务器用的SSH?还是什么新的网络协议?实际上这里说的是深度学习里的一个经典结构:Squeeze-and-Excitation Networks,国内一般翻译成“压缩-激发网络”,大家习惯简称为SE模型或SE模块。标题里写成“SSH”大概率是把SE和SENet、或者和某些变体叫法混在一起了,不过核心想讲的还是这一套东西。

为什么这么多年过去了,我还要专门写一篇博客来聊它?因为直到今天,你在MobileNet、EfficientNet、RegNet这些主流轻量化网络里,依然能看到SE模块的影子,或者它的各种改良版本。如果你在工业界做模型压缩、做端侧部署、做分类检测分割任务,SE模块是你绕不开的一个基础组件。它做的事情听起来简单得有点“过分”:就是让网络自动去学习每个特征通道的重要性,然后按照重要性重新加权。但就是这个小改动,当年在ImageNet上把ResNet-50的top-5错误率从6.71%直接压到了5.60%,接近一个点的提升,而计算量几乎可以忽略不计。

这篇博客面向的读者,我觉得有三类:第一类是刚接触深度学习的同学,准备用SE模块给自己的网络提点,但不知道从哪下手;第二类是已经在做模型轻量化的工程师,想在MobileNet这类结构里嵌入注意力机制;第三类是看过SENet论文但没动手复现过,想知道里面的参数怎么设、训练时有什么坑的人。我会把SE模块的原理拆开讲透,给出可以直接复用的代码和工程经验,最后把我踩过的坑也一并交代清楚。

2. 为什么一个简单的“特征重标定”能带来这么大的提升

2.1 一个类比帮你理解SE在干什么

想象你正在做一道复杂的菜,厨房里摆了一排调料罐——盐、糖、生抽、老抽、醋、料酒、蚝油。一个普通的卷积网络在处理图片时,就好比一个新手厨师,每道菜都把面前所有调料平均分配一遍,不管这道菜需不需要。结果呢?红烧肉里放了太多醋,凉拌菜里又忘了放盐,味道自然不对。

SE模块做的事情,就是给这个厨师加了一个“尝味”的步骤。做完一道菜之后,他先尝一口,判断这盘菜里哪个调料放多了、哪个放少了,然后在下一次烹饪时动态调整各调料的比例。放到网络里,这个“尝味”的动作就是全局平均池化,“判断哪个调料重要”就是两个全连接层加激活函数的学习过程,“调整比例”就是把学习到的权重乘回原来的特征图上。

这个类比虽然朴素,但基本把SE的核心逻辑说清楚了。它不改变卷积层本身的结构,只是在卷积计算完之后,额外加了一个“通道注意力”的旁路,让网络自己学会哪些特征图值得关注、哪些可以忽略。这就是为什么SE模块可以被非常方便地插入到ResNet、MobileNet、Inception等几乎任何主流网络结构中,而不需要动网络的骨架。

2.2 说说Squeeze和Excitation到底在计算什么

SE模块全程就三个步骤:Squeeze(压缩)、Excitation(激发)、Scale(缩放)。听起来玄乎,其实每一步都非常好理解。

第一步Squeeze,论文里用的是全局平均池化(Global Average Pooling,简称GAP)。假设输入特征图是H×W×C,经过GAP之后变成1×1×C。这一步的目的,是把每个通道的空间信息汇总成一个数值,相当于统计了整个特征图在每个通道上的“平均响应强度”。论文里把这个过程解释为“获取全局感受野”——因为普通卷积的感受野是局部的,但GAP可以看到整个特征图的信息。为什么要用平均而不是最大池化呢?作者在原论文里专门做过实验,平均池化的效果略优于最大池化,因为最大池化只关注响应最强的那个点,会丢失太多全局统计信息。

第二步Excitation,这是整个模块里唯一的“学习”环节,由两个全连接层构成。第一层把C维压缩成C/r维,这里的r是缩减比例,论文默认取16;然后经过一个ReLU激活函数;第二层再把C/r维恢复回C维,最后接一个Sigmoid函数,把输出映射到0到1之间。这样每个通道就拿到一个介于0到1的权重,代表这个通道的重要性。为什么要搞两个全连接层而不是直接一个?因为一个全连接层学不到通道之间的非线性关系,而且通过先降维再升维的方式,可以显著减少参数量。举个例子,如果输入是512个通道,r=16,那么中间层就是32个神经元,两个全连接层总共的参数是512×32+32×512=32768个参数,相比直接用一个512×512的全连接层(262144个参数),少了整整八倍。

第三步Scale,就是把这个0到1之间的权重,逐个通道乘回原始的H×W×C特征图上。注意是通道维度上的乘法,相当于把每个特征图整体缩放一下:重要的通道放大一些,不重要的通道缩小一些。

这整个流程可以用一张流程图表示(只能文字描述了):输入特征图→GAP压缩→全连接降维→ReLU→全连接升维→Sigmoid→得到通道权重→乘回原特征图→输出。

2.3 为什么这个“旁路设计”能解决实际问题

深度学习发展到2017年,卷积神经网络在图像识别上已经取得了很好的成绩,但有一个问题一直很困扰研究者:卷积操作是均匀地作用在每个通道上的,网络缺乏对“哪些特征重要”的自适应能力。举个实际场景,在一张猫的图片里,如果网络提取到了猫胡须的纹理特征、毛发的颜色特征、背景的草地特征,按理说毛发和胡须应该是判别的关键,而背景可能造成干扰。但普通卷积不会区分这些,它把所有通道一视同仁地送入下一层。SE模块相当于在每一层都加了一个“通道分配器”,让网络在训练过程中自动学会绝大多数特征图上哪些通道值得多分配一些权重、哪些通道可以压低。

从数学上看,SE模块本质上是在计算特征通道之间的非线性依赖关系。这种依赖关系不是简单统计一下哪个通道方差大,而是通过数据驱动学出来的。这也是为什么SE能带来真实的精度提升——它不是手工设计一个固定的mask,而是让网络根据任务来动态决定通道的去留。

我做过的实验里,在ImageNet分类任务上,把SE模块插入ResNet-50的每个bottleneck里,top-1准确率大概能提升1.5到1.8个百分点。这个提升幅度在分类任务上相当可观了。而在目标检测的backbone里,SE带来的提升更明显,因为检测任务输入图片中包含的背景干扰更大,通道注意力的过滤作用更突出。

3. 手把手实现SE模块,从公式推导到代码落地

3.1 数学表达式的逐步拆解

咱们用数学公式把上面三个步骤精确描述一下。设输入特征图为 (X \in R^{H \times W \times C}),这里的H是高度,W是宽度,C是通道数。

第一步Squeeze,对每个通道求全局平均:

[ z_c = \frac{1}{H \times W} \sum_{i=1}^{H} \sum_{j=1}^{W} u_c(i, j) ]

得到的 (z \in R^C) 是一个长度为C的向量,里面每个元素代表对应通道的全局统计量。

第二步Excitation,先降维再升维,中间加ReLU:

[ s = \sigma(W_2 \cdot \text{ReLU}(W_1 \cdot z)) ]

这里的 (W_1 \in R^{(C/r) \times C}),(W_2 \in R^{C \times (C/r)}),(\sigma) 是Sigmoid函数。最终得到每个通道的权重 (s \in R^C),取值在(0,1)之间。

第三步Scale,把权重乘回特征图:

[ \tilde{X}_c = s_c \cdot X_c ]

(X_c) 表示第c个通道的特征图。这个乘法是逐元素相乘,也就是每个通道的每个空间位置都乘上同一个标量 (s_c)。

整个过程到这里就结束了,是不是比你想象中简单得多?整个模块除了两个全连接层之外,几乎没有引入任何新的运算,这也是它计算开销极低的原因之一。

3.2 完整的PyTorch代码实现

直接上代码,这个实现基本就是原论文的标准复刻,我加了详细的注释方便阅读:

import torch import torch.nn as nn class SEModule(nn.Module): def __init__(self, channels, reduction=16): super(SEModule, self).__init__() # 第一个全连接层:C -> C/r,作用是降维 self.fc1 = nn.Linear(channels, channels // reduction) # ReLU激活 self.relu = nn.ReLU(inplace=True) # 第二个全连接层:C/r -> C,作用是升维 self.fc2 = nn.Linear(channels // reduction, channels) # Sigmoid激活,输出0-1之间的通道权重 self.sigmoid = nn.Sigmoid() def forward(self, x): # x 的维度是 [N, C, H, W] # 第一步:Squeeze,全局平均池化 [N, C, H, W] -> [N, C, 1, 1] squeeze = torch.mean(x, dim=[2, 3], keepdim=True) # 为了输入全连接层,需要展平成 [N, C] squeeze = squeeze.view(squeeze.size(0), -1) # 第二步:Excitation,先降维,再升维,最后sigmoid excitation = self.fc2(self.relu(self.fc1(squeeze))) excitation = self.sigmoid(excitation) # 恢复维度,方便广播乘法 [N, C] -> [N, C, 1, 1] excitation = excitation.view(excitation.size(0), excitation.size(1), 1, 1) # 第三步:Scale,把权重乘回原特征图 scale = x * excitation return scale

这段代码非常简洁,总共加起来也就二三十行。但有几个细节我要特别提醒你:

  • torch.mean(x, dim=[2, 3], keepdim=True)这一步就是GAP,直接用keepdim=True保持维度为[N,C,1,1],方便后面广播乘法。
  • 两个全连接层必须放在nn.Sequential外面分开写,因为中间要插ReLU,如果你想写得简洁也可以用nn.Sequential串起来,效果一样。
  • Sigmoid之后不需要做归一化处理,论文中也没有对权重做L2归一化,直接乘就行。

3.3 在ResNet中嵌入SE模块的标准做法

只实现SE模块还不够,更关键的是知道怎么把它嵌入到现有网络里。这里以ResNet-50为例说一下标准做法。在ResNet的Bottleneck结构中,标准的残差单元结构是:1×1卷积降维→3×3卷积→1×1卷积升维。SE模块的位置就在最后一个1×1卷积之后、残差相加之前。

具体来说,一个Bottleneck的计算流程变成这样:

class SEBottleneck(nn.Module): def __init__(self, inplanes, planes, stride=1, downsample=None, reduction=16): super(SEBottleneck, self).__init__() # 原ResNet的Bottleneck结构 self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) self.bn1 = nn.BatchNorm2d(planes) self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(planes) self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) self.bn3 = nn.BatchNorm2d(planes * 4) self.relu = nn.ReLU(inplace=True) # 在最后一个卷积后插入SE模块 self.se = SEModule(planes * 4, reduction) self.downsample = downsample self.stride = stride def forward(self, x): identity = x out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) out = self.relu(out) out = self.conv3(out) out = self.bn3(out) # SE模块介入点 out = self.se(out) if self.downsample is not None: identity = self.downsample(x) out += identity out = self.relu(out) return out

这里的关键点是SE模块输入输出的通道数都是planes * 4,因为ResNet的Bottleneck最后一个1×1卷积会把通道数扩大4倍。如果你是在MobileNet这类depthwise可分离卷积的结构里嵌入SE,通常的做法是放在depthwise卷积和1×1 pointwise卷积之间,或者放在整个深度可分离卷积之后,比如MobileNetV3就是放在depthwise卷积之后、pointwise卷积之前,并且只对expansion后的通道做SE。

工程上我强烈建议你把SE模块封装成独立的类,做成即插即用的通用组件,这样在尝试不同网络结构时只需要一行代码就能完成嵌入,不用反复修改网络定义的代码。实际部署时,由于SE模块只包含两个全连接层和几个简单算子,几乎不会给推理延迟带来明显影响,NPU上更是可以完美支持。

4. 核心参数的选择逻辑,以及不同任务下的表现差异

4.1 缩减比例r:16是最好吗?不一定

SE模块里最关键的参数就是缩减比例reduction,通常用r表示。论文里默认r=16,但这不代表所有场景下r=16都是最优的。r控制的是中间瓶颈层的宽度——它直接决定了SE模块引入的额外参数量的多少。

看一个具体数据:假设你嵌入SE的层通道数是1024,那么SE模块的参数量大约是 (2 \times 1024 \times (1024/r))(两个全连接层,第一层C→C/r,第二层C/r→C)。当r=16时,引入参数量大约是131072;当r=64时,只有32768。参数量少了,但理论上周转空间也变小了,非线性表达能力会被削弱。

从实际实验来看,r=16在大多数中小型网络里是一个平衡点。但如果你的网络本身很大、层数很深,比如ResNet-200或者EfficientNet-B7这种级别,我建议你把r调到32甚至64,主要目的是控制SE模块累积引入的额外参数总量,让它在整个网络中的参数量占比保持在5%以内。如果你是在MobileNet这种做端侧部署的轻量化网络上嵌入SE,r建议设成4或者8,因为轻量化网络的通道数本来就少,r太大降维后会丢失太多信息,效果打折。

这个表我从几个公开实验和自测数据里整理出来的,可以作为参考:

网络结构推荐r值说明
ResNet-5016论文默认值,效果好且稳定
ResNet-200+32防止参数过多
MobileNetV24或8通道数少,r不宜过大
EfficientNet16或24论文原文的自适应缩放
轻量级目标检测head4特征图分辨率高,通道少

4.2 放在“哪里插入”最合适

除了r值,SE模块插入位置的选择也直接影响效果。这其实是一个非常容易被忽视的实操细节。我见过不少新手直接把SE模块套在网络最后几层,效果却不理想,原因就是插入位置不合理。

我把常见的几种插入方式对比一下:

插入位置适用场景效果表现缺点
每个残差块的末尾(标准SENet做法)分类、检测主干网络精度提升稳定,收益约1~2个百分点所有block都插,计算量略有增加
仅插在网络的最后几个stage大模型训练,算力有限缩短训练时间,精度提升约0.5~1个百分点前面层没有注意力,效果打折
插在主干stage的最后一层轻量化部署,延迟敏感计算量开销最小,效果约0.3~0.5个百分点提升幅度有限
插在检测/分割neck的通道融合前目标检测、语义分割多尺度特征融合更充分,收益明显会增加一定显存占用

如果你在训练目标检测模型,我的经验是backbone里的SE模块不要省,neck部分插不插可以视显存和推理速度预算来定。检测任务里backbone做特征提取,SE模块能让提取出来的特征更“干净”,抑制背景干扰,这对后续的bbox回归和分类都很有帮助。而在语义分割任务里,SE模块插在ASPP(空洞空间金字塔池化)模块之前效果最好,因为分割任务需要同时在多个尺度上整合全局信息,SE能帮网络自动决定哪个尺度的通道更重要。

4.3 训练时要注意的初始化技巧

我踩过的一个真实的坑是SE模块的初始化问题。用PyTorch默认的全连接层初始化方式,也就是Kaiming均匀分布初始化,SE模块在训练初期会出现一个非常尴尬的现状:两个全连接层的初始权重是随机的,Sigmoid的输出也大概率在0.5附近震荡。这会导致网络初期所有通道都被乘以0.5左右的权重,相当于特征值普遍衰减了一半,网络收敛速度变慢。

解决办法很简单,给SE模块加一个偏置初始化偏置项:把第二个全连接层的偏置初始化成0,把批归一化层(如果有的话)的gamma初始化成适当的值。更标准一点的做法是参考后来的ECA模块或者SKNet的做法,对第二个全连接层使用较小的初始化权重,让Sigmoid输出尽量接近1,这样SE模块在一开始基本退化为单位映射,相当于网络在没有SE的情况下正常训练,再由梯度逐步调整通道权重。这个“热身”策略在实际训练中让我少走了不少弯路——初期loss曲线更平滑,收敛速度大概能快10%左右。

4.4 各类任务上的实际收益如何

说一个我自己实测过的例子。在Cityscapes语义分割数据集上,我用DeepLabV3+作为基线模型,backbone是ResNet-101。加SE模块之后,mIoU从78.3%提升到了79.1%,提升将近0.8个百分点。看起来不多,但在分割任务中0.5个百分点就已经是明显的差异了,而且SE模块增加的推理时间只有不到2毫秒。

在COCO目标检测数据集上,我用Faster R-CNN配合ResNet-50做backbone,加SE后mAP从37.4%提升到了38.9%,提升1.5个百分点。检测任务的提升幅度通常大于分类任务,因为检测输入图片的分辨率更高,SE模块利用全局池化所获取的全局信息,能更好地帮助区分前景和背景。

而如果是在轻量化模型比如MobileNetV2上面加SE,提升幅度大概在0.5~1个百分点之间。轻量化网络通道数本来就少,SE模块能提供的信息增益有限,但配合NAS搜出来的EfficientNet架构上SE模块用得更加灵活,效果反而更好。总的规律是:网络越深、通道数越大,SE模块带来的提升越明显;网络本身已经很小的时候,SE的收益会越来越小,这时可以考虑用ECA模块替代,ECA把全连接层换成了1D卷积,大幅减少了计算量,在小网络上效果反而更好。

5. 训练经验与常见问题排查实录

5.1 训练时Loss不降反升?大概率是学习率没有适配

我在实际项目中发现,SE模块加入之后,整个模型的最优学习率范围会比不加SE时偏小一些,大约要调低到原来的0.5~0.8倍。原因在于SE模块里的两个全连接层是随机初始化的,它们在学习初期对梯度的贡献是很大的,如果整体学习率太高,这些新加入的参数会干扰网络原本已经收敛好的特征表示。

我现在习惯的做法是:在原有训练配置的基础上,把初始学习率从0.1(针对分类任务)降到0.05~0.08,同时将SE模块中两个全连接层的学习率单独设为主干的0.1倍。PyTorch里可以用参数分组来实现:

optimizer = torch.optim.SGD([ # 主干网络参数,正常学习率 {'params': backbone_params, 'lr': 0.08}, # SE模块参数,学习率调低 {'params': se_params, 'lr': 0.008}, ], momentum=0.9, weight_decay=1e-4)

这个做法在有预训练权重时尤其管用。如果你是把SE模块插入到一个已经在ImageNet上预训练好的ResNet中做微调,SE的全连接层属于新加参数,如果学习率设置过高,很容易破坏预训练主干已经学好的低层特征。让SE参数慢一点学习,主干参数维持原有的微调节奏,整体效果会好很多。

5.2 部署到端侧时SE模块性能比预期差怎么办

SE模块在GPU上跑起来附加开销很小,但在CPU和NPU上部署时偶尔会出现性能反而不如直接剪掉SE的情况。我排查过几次,问题基本都出在网络结构的碎片化上。SE模块在全连接层和池化层之间来回reshape和view,如果部署框架没有做充分的图优化,这些细碎算子反而会成为推理瓶颈。

解决办法有两个方向:

一是提前把SE模块做算子融合。在推理框架里,尽量把相邻的卷积、BN、ReLU、Sigmoid合并成单个算子。全连接层在推理时可以折叠成1×1卷积,这样整个SE模块可以视为“GAP→1×1卷积→ReLU→1×1卷积→Sigmoid→Scale”,而1×1卷积在大多数推理框架里都是高度优化的算子,性能自然就上去了。

二是考虑部署时只保留SE模块学到的权重,不保留模块本身的结构,把通道权重在离线阶段算好之后固化下来。不过这种做法等于把动态注意力变成了静态剪枝,模型的适应能力会有所下降,多数场景下不推荐。

5.3 一条被忽视的细节:Squeeze里用GAP还是GMP

原论文在Squeeze步骤里使用了全局平均池化,但我在实际测试中发现在有些任务上,全局最大池化(Global Max Pooling,GMP)反而更好。比如在细粒度图像分类任务里,类别之间的差异通常体现在局部关键区域(比如鸟的喙、车的轮毂),GMP能保留这些最强的局部响应,而GAP会把它们平均掉一部分。

后来我也参考了文献review和各种改进版本的对比,有个经验法则:对整体语义信息敏感的任务(大类别分类、场景分类)用GAP;对局部细节信息敏感的任务(细粒度分类、异常检测、小目标检测)可以考虑GMP,或者更进一步,把GAP和GMP两个池化结果拼接在一起使用。拼接的做法在实际上是让网络同时获得“平均水平响应”和“最大响应”两种信息,想怎么组合由网络自己学,效果通常比单一池化更好,代价只是SE模块中输入通道数翻倍,参数量相应增加一倍。

# GAP + GMP 拼接的Squeeze用法 gap = torch.mean(x, dim=[2, 3], keepdim=True) gmp = torch.max(x, dim=2, keepdim=True)[0] gmp = torch.max(gmp, dim=3, keepdim=True)[0] squeeze = torch.cat([gap, gmp], dim=1) # 通道数翻倍为2C squeeze = squeeze.view(squeeze.size(0), -1)

这个方法在你任务效果长期卡在某个点上不动时,值得尝试一下。

5.4 常见问题速查表

为了方便大家快速排查,我把SE模块从训练到部署的常见问题整理成一个速查表:

现象可能原因解决办法
精度提升不明显SE模块插入位置不对,或r值过大尝试在残差块末尾插入,调小r到8或4
训练初期Loss下降慢SE全连接层初始化导致权重输出接近0.5给第二个全连接层做零偏置初始化
微调预训练模型效果反而更差整体学习率太高,破坏了预训练特征将SE模块参数学习率单独调低为主干的0.1倍
训练正常但测试时显存OOMSE模块在neck或head中重复插入过多只保留backbone中的SE,删掉neck部分的
部署到NPU速度反而更慢图优化不到位,算子碎片化严重将全连接层改成1×1卷积并做算子融合
与BatchNorm冲突SE模块放在BN之前或之后会影响统计量一般情况下放在BN之后,残差相加之前

5.5 组合多个注意力模块?先想清楚目的

最后分享一个我自己的经验。近两年出现了很多注意力机制的变体,比如CBAM是在空间维度加了一个空间注意力分支,ECA用一个1D卷积替代了两个全连接层,SKNet可以自适应选择多个感受野大小的卷积核。不少同学一上来就想“我全都要”,把SE、CBAM、ECA一股脑全塞进网络里。这种做法在绝大多数情况下是不推荐的——模块叠加带来的计算开销成倍增加,但精度收益是递减的,甚至因为过拟合和梯度流动受阻,效果反而不如只用其中一个模块。

我的建议是:如果你面对的是一个常规任务,没有明显的小目标、遮挡等特殊挑战,直接用SE最稳,它结构简单、参数可控、泛化能力好;如果你面对的是多尺度目标频繁出现的检测分割任务,可以尝试SKNet的自适应感受野机制;如果网络已经很小、算力极度紧张,ACC或者ECA模块的轻量特性更值得优先考虑。模型设计永远是减法思维好于加法思维。你在一个地方加了东西,就要考虑在另一个地方减掉点什么,否则网络会越来越冗余,训练难度也会随之上升。

6. 从SE模块出发,还能往哪个方向继续深入

SE模块最吸引我的地方在于它的极致简洁性,但简洁不等于肤浅。它的本质是让网络具备了一种“自我观察、自我调整”的能力,这种思路影响了后来很多注意力机制的设计。顺着这条线深入下去,你可以研究的方向有很多。

比如,把SE模块中的两个全连接层替换成1D卷积的ECA模块,参数量更少且效果相当;把Squeeze步骤中的GAP换成更复杂的特征统计方式(比如二阶统计或者空间金字塔池化)的模块;在SE的基础上引入空间注意力来补充通道注意力的CBAM;用NAS(神经架构搜索)自动搜索SE模块的最佳插入位置和r值,EfficientNet就是这方面的代表作;把SE的思想用到Transformer的通道维度上,可以提升自注意力机制的特征表达能力。

如果你正在做一个新模型,我建议你先用一张流程图认真画出SE模块的应用位置和参数配置,再动手写代码。模型的精度提升从来都不是靠一个模块解决的,它更需要你把握清楚整个网络中不同模块之间的协同关系。把SE模块的1到2个点提好了,再配合更好的数据增强、更优的优化器策略,整体效果才会稳步向上提升。

在这里再把全流程梳理一遍:SE模块的核心是Squeeze、Excitation、Scale三步;在代码上实现只需要在原有网络里插入一个十行左右的模块;训练时注意学习率缩水和初始化策略;部署时做好算子融合以避免性能损失。只要这几步把控好,SE模块基本就是你模型精度提升的一个稳定燃料。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 7:56:21

无线网络连接手写实现:3步搞定吞吐量瓶颈

无线网络连接手写实现:3步搞定吞吐量瓶颈 官方文档翻了三遍还是没看懂?别急,直接上代码。 很多开发者在调试无线网络连接时,总被官方长篇大论的协议文档劝退。TCP握手、RTT计算、拥塞窗口,术语堆砌让人头大。其实核心逻辑没那么复杂,今天我们就用 手写实现…

作者头像 李华
网站建设 2026/9/23 7:56:12

土间埋源码剖析:3个实战项目避坑指南

土间埋源码剖析:3个实战项目避坑指南 别再看那些云里雾里的理论了。如果你还在为“土间埋”相关的逻辑卡壳,或者明明照着教程敲代码却跑不通,问题通常不出在语法,而出在你没看懂底层是怎么流转的。我见过太多开发者在 Stack Overflow…

作者头像 李华
网站建设 2026/9/23 7:56:05

onmeasure手写实现:3个致命坑点避坑指南

onmeasure手写实现:3个致命坑点避坑指南 复制来的 onMeasure 代码直接扔进项目,编译通过但界面全乱了?别急,这根本不是玄学,是 Android…

作者头像 李华
网站建设 2026/9/23 7:55:51

3个致命细节:何不秉烛游避坑指南,别等出事才后悔

3个致命细节:何不秉烛游避坑指南,别等出事才后悔 官方文档那几百页的规范,谁看谁头大,根本抓不住重点。 干了十年房建,见过太多因为不懂“何不秉烛游”这类合规操作,最后项目停摆、个人背锅的案例。 这篇避坑指南不整虚的,直接拆解岗位边界、证书补办和现场违规三大雷区,保你少踩坑。…

作者头像 李华
网站建设 2026/9/23 7:55:44

自建AI出图平台存储选型实战:从NAS到iSCSI企业级存储的完整方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/23 7:55:40

面试被问信用卡号码校验优化答不上?一文搞懂3个提速点

面试被问信用卡号码校验优化答不上?一文搞懂3个提速点 上周陪一个后端同学面大厂,面试官问:“高并发下处理一百万条信用卡号码,你的校验逻辑怎么优化?”他愣住,支支吾吾说“加索引”“用缓存”,完全没抓住核心。面试官没再追问,直接说“下一位”。这就是典型的 面试被问原理答不上来…

作者头像 李华