ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

YOLOv26改进 | 主干/Backbone篇 | 添加最新的LSKNet遥感目标检测网络主干 (可根据yolov26版本自行放缩通道数独家创新)

2026/8/3 10:26:18 拓冰建站 浏览量
YOLOv26改进 | 主干/Backbone篇 | 添加最新的LSKNet遥感目标检测网络主干 (可根据yolov26版本自行放缩通道数独家创新) 开始正文前先向大家推荐我的YOLO专栏系列。本人持续更新 YOLOv8、YOLO11、YOLO26 等热门模型内容覆盖图像分类、目标检测、实例分割、多目标跟踪、姿态估计与关键点检测重点讲解 小目标检测、注意力机制、特征融合、损失函数改进、自定义数据集训练、消融实验及论文代码复现。同时分享如何使用 OpenAI Codex辅助撰写论文、配置实验环境、调试项目、改进模型和分析实验结果。 专栏目前正在进行限时优惠每周更新 5–7篇最新论文机制、YOLO改进方法和实战教程。订阅后可获得包含本人全部改进方案的代码与配置文件并加入专属技术交流群。我也会定期在群内分享 YOLO论文选题、创新点设计、实验方案、论文写作与投稿发表经验欢迎大家订阅交流一、本文介绍本文给大家带来的改进机制是面向遥感目标检测设计的主干网络——LSKNetLarge Selective Kernel Network。遥感图像通常具有场景范围大、背景复杂以及目标尺度差异明显等特点因此网络不仅需要关注目标本身还需要充分利用目标周围的空间上下文信息。LSKNet通过引入大核选择机制根据输入特征动态选择合适的空间感受野从而更好地捕捉不同尺度目标及其周围环境之间的关系。在实验部分我在一个包含三十多个类别的数据集上进行了测试数据中同时包含大目标和小目标。根据实际实验结果引入LSKNet后模型mAP的提升幅度大多在0.04—0.10之间不过受数据集、模型规模和训练参数等因素影响也存在极少数没有明显提升的情况具体效果还需要大家结合自己的任务进行验证。官方代码主要提供了一个规模较大的网络版本为了方便不同算力条件下的读者使用本文在原始结构的基础上进一步适配并提供了一个轻量化版本方便大家在检测精度、参数量和计算量之间进行选择。本文将首先展示不同版本的对比实验结果供大家参考随后介绍LSKNet的主要原理并结合代码讲解如何将其添加到现有目标检测网络中。本文内容可根据yolov11的N、S、M、L、X进行二次缩放轻量化更上一层。专栏链接YOLOv26有效涨点专栏包含Conv、注意力机制、主干/Backbone、损失函数、优化器、后处理等改进机制目录一、本文介绍二、LSKNet原理2.1 LSKNet的基本原理2.2 大型核选择LK Selection子块2.3 前馈网络FFN子块三、LSKNet核心代码四、手把手教你添加LSKNet4.1 修改一4.2 修改二4.3 修改三4.4 修改四4.5 修改五4.6 修改六4.7 修改七4.8 修改八4.9 修改九五、LSKNet的yaml文件5.1 LSKNet的yaml文件5.2 训练文件的代码六、成功运行记录七、本文总结二、LSKNet原理​论文地址官方论文地址代码地址官方代码地址​2.1 LSKNet的基本原理LSKNetLarge Selective Kernel Network是一种专为遥感目标检测设计的网络架构其核心优势在于能够动态调整其大的空间感受野以更好地捕捉遥感场景中不同对象的范围上下文。这是第一次在遥感目标检测领域探索大型和选择性核机制。LSKNet大型选择性核网络的基本原理包括以下关键组成部分1. 大型核选择LK Selection子块这个子块能够动态地调整网络的感受野以便根据需要捕获不同尺度的上下文信息。这使得网络能够根据遥感图像中对象的不同尺寸和复杂性调整其处理能力。2. 前馈网络FFN子块该子块用于通道混合和特征精炼。它由一个完全连接的层、一个深度卷积、一个GELU激活函数以及第二个完全连接的层组成。这些组件一起工作提高了特征的质量并为分类和检测提供了必要的信息。这两个子块共同构成LSKNet块能够提供大范围的上下文信息同时保持对细节的敏感度这对于遥感目标检测尤其重要。下面我将为大家展示四种不同的选择性机制模块的架构比较​对于LSK模块1. 有一个分解步骤似乎是用来处理大尺寸的卷积核Large K。2. 接着是一个空间选择*步骤可能用于选择或优化空间信息的特定部分。这与其他三种模型的架构相比较显示了LSK模块在处理空间信息方面可能有其独特的方法。具体来说LSK模块似乎强调了在大尺寸卷积核上进行操作这可能有助于捕获遥感图像中较大范围的上下文信息这对于检测图像中的对象特别有用。空间选择步骤可能进一步增强了模型对于输入空间特征的选择能力从而使其能够更加有效地聚焦于图像的重要部分。2.2大型核选择LK Selection子块LSKNet的大型核选择Large Kernel Selection, LK Selection子块是其架构的核心组成部分之一。这个子块的功能是根据需要动态调整网络的感受野大小。通过这种方式LSKNet能够根据遥感图像中不同对象的大小和上下文范围调整处理这些对象所需的空间信息范围。大型核选择子块与前馈网络Feed-forward Network, FFN子块一起工作。FFN子块用于通道混合和特征细化它包括一个序列这个序列由一个全连接层、一个深度卷积、一个GELU激活函数以及第二个全连接层组成。这种设计允许LSKNet块进行特征深度融合和增强进一步提升了遥感目标检测的性能。下面我将通过LSKLarge Selective Kernel模块的概念性插图展示LSKNet如何通过大型核选择子块和空间选择机制来处理遥感数据从而使网络能够适应不同对象的长范围上下文需求。​1. Large Kernel Decomposition原始输入​经过大核分解使用两种不同的大型卷积核Large K进行处理以捕获不同尺度的空间信息。2. Channel Concatenation两个不同的卷积输出​和​通过通道拼接组合在一起这样可以在后续步骤中同时利用不同的空间特征。3. Mixed Pooling拼接后的特征图经过平均池化和最大池化的组合操作然后与自注意力SA机制一起使用以进一步强化特征图的关键区域。4. Convolution and Spatial Selection通过卷积操作和自注意力SA生成新的特征图然后通过空间选择机制进一步增强对目标区域的关注。5. Element Product and Sigmoid使用Sigmoid函数生成一个掩码​然后将这个掩码与特征图​进行元素乘积操作得到最终的输出特征图​。这一步骤用于加权特征图中更重要的区域以增强网络对遥感图像中特定对象的检测能力。整个LSK模块的设计强调了对遥感图像中不同空间尺度和上下文信息的有效捕获这对于在复杂背景下准确检测小型或密集排布的目标至关重要。通过上述步骤的复合操作LSK模块能够提升遥感目标检测的性能。2.3前馈网络FFN子块LSKNet的前馈网络Feed-forward Network, FFN子块用于通道混合和特征精炼。该子块包含以下组成部分1. 全连接层用于特征变换提供网络额外的学习能力。2. 深度卷积depth-wise convolution用于在通道间独立地应用空间滤波减少参数量的同时保持效果。3. GELU激活函数一种高斯误差线性单元用于引入非线性提高模型的表达能力。4. 第二个全连接层进一步变换和精炼特征。这个FFN子块紧随LK Selection子块之后作用是在保持特征空间信息的同时增强网络在特征通道上的表示能力。通过这种设计FFN子块有效地对输入特征进行了深度加工提升了最终特征的质量从而有助于提高整个网络在遥感目标检测任务中的性能。三、LSKNet核心代码将此代码复制粘贴到ultralytics/nn/modules目录下新建一个py文件我这里起名字为LSKNet.py然后把代码复制粘贴进去即可使用教程看章节四。import torch import torch.nn as nn from torch.nn.modules.utils import _pair as to_2tuple from timm.models.layers import DropPath, to_2tuple from functools import partial import warnings __all__ [ LSKNET_Tiny, LSKNET_Large ] class Mlp(nn.Module): def __init__(self, in_features, hidden_featuresNone, out_featuresNone, act_layernn.GELU, drop0.): super().__init__() out_features out_features or in_features hidden_features hidden_features or in_features self.fc1 nn.Conv2d(in_features, hidden_features, 1) self.dwconv DWConv(hidden_features) self.act act_layer() self.fc2 nn.Conv2d(hidden_features, out_features, 1) self.drop nn.Dropout(drop) def forward(self, x): x self.fc1(x) x self.dwconv(x) x self.act(x) x self.drop(x) x self.fc2(x) x self.drop(x) return x class LSKblock(nn.Module): def __init__(self, dim): super().__init__() self.conv0 nn.Conv2d(dim, dim, 5, padding2, groupsdim) self.conv_spatial nn.Conv2d(dim, dim, 7, stride1, padding9, groupsdim, dilation3) self.conv1 nn.Conv2d(dim, dim // 2, 1) self.conv2 nn.Conv2d(dim, dim // 2, 1) self.conv_squeeze nn.Conv2d(2, 2, 7, padding3) self.conv nn.Conv2d(dim // 2, dim, 1) def forward(self, x): attn1 self.conv0(x) attn2 self.conv_spatial(attn1) attn1 self.conv1(attn1) attn2 self.conv2(attn2) attn torch.cat([attn1, attn2], dim1) avg_attn torch.mean(attn, dim1, keepdimTrue) max_attn, _ torch.max(attn, dim1, keepdimTrue) agg torch.cat([avg_attn, max_attn], dim1) sig self.conv_squeeze(agg).sigmoid() attn attn1 * sig[:, 0, :, :].unsqueeze(1) attn2 * sig[:, 1, :, :].unsqueeze(1) attn self.conv(attn) return x * attn class Attention(nn.Module): def __init__(self, d_model): super().__init__() self.proj_1 nn.Conv2d(d_model, d_model, 1) self.activation nn.GELU() self.spatial_gating_unit LSKblock(d_model) self.proj_2 nn.Conv2d(d_model, d_model, 1) def forward(self, x): shorcut x.clone() x self.proj_1(x) x self.activation(x) x self.spatial_gating_unit(x) x self.proj_2(x) x x shorcut return x class Block(nn.Module): def __init__(self, dim, mlp_ratio4., drop0., drop_path0., act_layernn.GELU, norm_cfgNone): super().__init__() if norm_cfg: self.norm1 nn.BatchNorm2d(norm_cfg, dim) self.norm2 nn.BatchNorm2d(norm_cfg, dim) else: self.norm1 nn.BatchNorm2d(dim) self.norm2 nn.BatchNorm2d(dim) self.attn Attention(dim) self.drop_path DropPath(drop_path) if drop_path 0. else nn.Identity() mlp_hidden_dim int(dim * mlp_ratio) self.mlp Mlp(in_featuresdim, hidden_featuresmlp_hidden_dim, act_layeract_layer, dropdrop) layer_scale_init_value 1e-2 self.layer_scale_1 nn.Parameter( layer_scale_init_value * torch.ones((dim)), requires_gradTrue) self.layer_scale_2 nn.Parameter( layer_scale_init_value * torch.ones((dim)), requires_gradTrue) def forward(self, x): x x self.drop_path(self.layer_scale_1.unsqueeze(-1).unsqueeze(-1) * self.attn(self.norm1(x))) x x self.drop_path(self.layer_scale_2.unsqueeze(-1).unsqueeze(-1) * self.mlp(self.norm2(x))) return x class OverlapPatchEmbed(nn.Module): Image to Patch Embedding def __init__(self, img_size224, patch_size7, stride4, in_chans3, embed_dim768, norm_cfgNone): super().__init__() patch_size to_2tuple(patch_size) self.proj nn.Conv2d(in_chans, embed_dim, kernel_sizepatch_size, stridestride, padding(patch_size[0] // 2, patch_size[1] // 2)) if norm_cfg: self.norm nn.BatchNorm2d(norm_cfg, embed_dim) else: self.norm nn.BatchNorm2d(embed_dim) def forward(self, x): x self.proj(x) _, _, H, W x.shape x self.norm(x) return x, H, W class LSKNet(nn.Module): def __init__(self, factor0.5, img_size224, in_chans3, dimNone, embed_dims[64, 128, 256, 512], mlp_ratios[8, 8, 4, 4], drop_rate0., drop_path_rate0., norm_layerpartial(nn.LayerNorm, eps1e-6), depths[3, 4, 6, 3], num_stages4, pretrainedNone, init_cfgNone, norm_cfgNone): super().__init__() assert not (init_cfg and pretrained), \ init_cfg and pretrained cannot be set at the same time if isinstance(pretrained, str): warnings.warn(DeprecationWarning: pretrained is deprecated, please use init_cfg instead) self.init_cfg dict(typePretrained, checkpointpretrained) elif pretrained is not None: raise TypeError(pretrained must be a str or None) self.depths depths self.num_stages num_stages embed_dims [int(dim * factor) for dim in embed_dims] dpr [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))] # stochastic depth decay rule cur 0 for i in range(num_stages): patch_embed OverlapPatchEmbed(img_sizeimg_size if i 0 else img_size // (2 ** (i 1)), patch_size7 if i 0 else 3, stride4 if i 0 else 2, in_chansin_chans if i 0 else embed_dims[i - 1], embed_dimembed_dims[i], norm_cfgnorm_cfg) block nn.ModuleList([Block( dimembed_dims[i], mlp_ratiomlp_ratios[i], dropdrop_rate, drop_pathdpr[cur j], norm_cfgnorm_cfg) for j in range(depths[i])]) norm norm_layer(embed_dims[i]) cur depths[i] setattr(self, fpatch_embed{i 1}, patch_embed) setattr(self, fblock{i 1}, block) setattr(self, fnorm{i 1}, norm) self.width_list [i.size(1) for i in self.forward(torch.randn(1, 3, 640, 640))] def freeze_patch_emb(self): self.patch_embed1.requires_grad False torch.jit.ignore def no_weight_decay(self): return {pos_embed1, pos_embed2, pos_embed3, pos_embed4, cls_token} # has pos_embed may be better def get_classifier(self): return self.head def reset_classifier(self, num_classes, global_pool): self.num_classes num_classes self.head nn.Linear(self.embed_dim, num_classes) if num_classes 0 else nn.Identity() def forward_features(self, x): B x.shape[0] outs [] for i in range(self.num_stages): patch_embed getattr(self, fpatch_embed{i 1}) block getattr(self, fblock{i 1}) norm getattr(self, fnorm{i 1}) x, H, W patch_embed(x) for blk in block: x blk(x) x x.flatten(2).transpose(1, 2) x norm(x) x x.reshape(B, H, W, -1).permute(0, 3, 1, 2).contiguous() outs.append(x) return outs def forward(self, x): x self.forward_features(x) # x self.head(x) return x class DWConv(nn.Module): def __init__(self, dim768): super(DWConv, self).__init__() self.dwconv nn.Conv2d(dim, dim, 3, 1, 1, biasTrue, groupsdim) def forward(self, x): x self.dwconv(x) return x def _conv_filter(state_dict, patch_size16): convert patch embedding weight from manual patchify linear proj to conv out_dict {} for k, v in state_dict.items(): if patch_embed.proj.weight in k: v v.reshape((v.shape[0], 3, patch_size, patch_size)) out_dict[k] v return out_dict def LSKNET_Tiny(factor): model LSKNet(factorfactor, depths [2, 2, 2, 2]) return model def LSKNET_Large(factor): model LSKNet(factorfactor, depths[3, 4, 6, 3]) return model if __name__ __main__: model LSKNET_Large(factor0.5) inputs torch.randn((1, 3, 640, 640)) for i in model(inputs): print(i.size())四、手把手教你添加LSKNet4.1 修改一我们复制网络结构代码到“ultralytics/nn”目录下创建一个py文件复制粘贴进去 。​4.2 修改二第二步我们在该目录下创建一个新的py文件名字为__init__.py(用群内的文件的话已经有了无需新建)然后在其内部导入我们的检测头如下图所示。4.3 修改三第三步我门中到如下文件ultralytics/nn/tasks.py进行导入和注册我们的模块(用群内的文件的话已经有了无需重新导入直接开始第四步即可)从今天开始以后的教程就都统一成这个样子了因为我默认大家用了我群内的文件来进行修改4.4 修改四添加如下两行代码​4.5 修改五找到1600多行大概把具体看图片按照图片来修改就行添加红框内的部分注意没有()只是函数名我这里只添加了部分的版本这个还有更多的版本可以添加可以看后面章节五看我给的代码函数头即可。​elif m in {自行添加对应的模型即可下面都是一样的}: m m(*args) c2 m.width_list # 返回通道列表 backbone True4.6 修改六按图修改。​if isinstance(c2, list): m_ m m_.backbone True else: m_ nn.Sequential(*(m(*args) for _ in range(n))) if n 1 else m(*args) # module t str(m)[8:-2].replace(__main__., ) # module type m.np sum(x.numel() for x in m_.parameters()) # number params m_.i, m_.f, m_.type i 4 if backbone else i, f, t # attach index, from index, type4.7 修改七如下的也需要修改全部按照我的来。​代码如下把原先的代码替换了即可。if verbose: LOGGER.info(f{i:3}{str(f):20}{n_:3}{m.np:10.0f} {t:45}{str(args):30}) # print save.extend(x % (i 4 if backbone else i) for x in ([f] if isinstance(f, int) else f) if x ! -1) # append to savelist layers.append(m_) if i 0: ch [] if isinstance(c2, list): ch.extend(c2) if len(c2) ! 5: ch.insert(0, 0) else: ch.append(c2)4.8 修改八修改七和前面的都不太一样需要修改前向传播中的一个部分 已经离开了parse_model方法了。可以在图片中看代码行数没有离开task.py文件都是同一个文件。 同时这个部分有好几个前向传播都很相似大家不要看错了是160多行左右的不同仓库版本可能有些差异同时我后面提供了代码大家直接复制粘贴即可不会修改联系博主获取视频教程。​​代码如下-def _predict_once(self, x, profileFalse, visualizeFalse, embedNone): Perform a forward pass through the network. Args: x (torch.Tensor): The input tensor to the model. profile (bool): Print the computation time of each layer if True. visualize (bool): Save the feature maps of the model if True. embed (list, optional): A list of layer indices to return embeddings from. Returns: (torch.Tensor): The last output of the model. y, dt, embeddings [], [], [] # outputs embed frozenset(embed) if embed is not None else {-1} max_idx max(embed) for m in self.model: if m.f ! -1: # if not from previous layer x y[m.f] if isinstance(m.f, int) else [x if j -1 else y[j] for j in m.f] # from earlier layers if profile: self._profile_one_layer(m, x, dt) if hasattr(m, backbone): x m(x) if len(x) ! 5: # 0 - 5 x.insert(0, None) for index, i in enumerate(x): if index in self.save: y.append(i) else: y.append(None) x x[-1] # 最后一个输出传给下一层 else: x m(x) # run y.append(x if m.i in self.save else None) # save output if visualize: feature_visualization(x, m.type, m.i, save_dirvisualize) if embed and m.i in embed: embeddings.append(nn.functional.adaptive_avg_pool2d(x, (1, 1)).squeeze(-1).squeeze(-1)) # flatten if m.i max_idx: return torch.unbind(torch.cat(embeddings, 1), dim0) return x4.9 修改九我们找到如下文件ultralytics/utils/torch_utils.py按照如下的图片进行修改否则容易打印不出来计算量。​五、LSKNet的yaml文件5.1 LSKNet的yaml文件训练信息YOLO26-Backbone-LSKNet summary: 346 layers, 2,140,396 parameters, 2,140,396 gradients, 5.2 GFLOPs# Ultralytics AGPL-3.0 License - https://ultralytics.com/license # Ultralytics YOLO26 object detection model with P3/8 - P5/32 outputs # Model docs: https://docs.ultralytics.com/models/yolo26 # Task docs: https://docs.ultralytics.com/tasks/detect # Parameters nc: 80 # number of classes end2end: True # whether to use end-to-end mode reg_max: 1 # DFL bins scales: # model compound scaling constants, i.e. modelyolo26n.yaml will call yolo26.yaml with scale n # [depth, width, max_channels] n: [0.50, 0.25, 1024] # summary: 260 layers, 2,572,280 parameters, 2,572,280 gradients, 6.1 GFLOPs s: [0.50, 0.50, 1024] # summary: 260 layers, 10,009,784 parameters, 10,009,784 gradients, 22.8 GFLOPs m: [0.50, 1.00, 512] # summary: 280 layers, 21,896,248 parameters, 21,896,248 gradients, 75.4 GFLOPs l: [1.00, 1.00, 512] # summary: 392 layers, 26,299,704 parameters, 26,299,704 gradients, 93.8 GFLOPs x: [1.00, 1.50, 512] # summary: 392 layers, 58,993,368 parameters, 58,993,368 gradients, 209.5 GFLOPs # 我提供了版本分别是对应是 [LSKNET_Tiny, LSKNET_Large] # YOLO26 backbone backbone: # [from, repeats, module, args] - [-1, 1, LSKNET_Tiny, [0.25]] # 0-4 P1/2 这里是四层大家不要被yaml文件限制住了思维不会画图进群看视频. # 注意args位置的参数对应模型的通道放缩系数width在上面scales位置, 假设你用yolov26n那么可以设置0.25 如果你用yolov26s可以设置0.5 - [-1, 1, SPPF, [1024, 5, 3, True]] # 5 - [-1, 2, C2PSA, [1024]] # 6 # YOLO26 head head: - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 3], 1, Concat, [1]] # cat backbone P4 - [-1, 2, C3k2, [512, True]] # 9 - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 2], 1, Concat, [1]] # cat backbone P3 - [-1, 2, C3k2, [256, True]] # 12 (P3/8-small) - [-1, 1, Conv, [256, 3, 2]] - [[-1, 9], 1, Concat, [1]] # cat head P4 - [-1, 2, C3k2, [512, True]] # 15 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] - [[-1, 6], 1, Concat, [1]] # cat head P5 - [-1, 2, C3k2, [1024, True, 0.5, True]] # 18 (P5/32-large) - [[12, 15, 18], 1, Detect, [nc]] # Detect(P3, P4, P5)5.2 训练文件的代码可以复制我的运行文件进行运行。import warnings warnings.filterwarnings(ignore) from ultralytics import YOLO if __name__ __main__: model YOLO(yolo26-Backbone-LSKNet.yaml) # 如何切换模型版本, 上面的ymal文件可以改为 yolo26s.yaml就是使用的26s, # 类似某个改进的yaml文件名称为yolo26-XXX.yaml那么如果想使用其它版本就把上面的名称改为yolo26l-XXX.yaml即可改的是上面YOLO中间的名字不是配置文件的 # model.load(yolo26n.pt) # 是否加载预训练权重,科研不建议大家加载否则很难提升精度 model.train( datarC:\Users\Administrator\Desktop\CSDNWork\ProjectFiles\YOLOv26\NEU-DET\data.yaml, # 如果大家任务是其它的ultralytics/cfg/default.yaml找到这里修改task可以改成detect, segment, classify, pose cacheFalse, imgsz640, epochs100, single_clsFalse, # 是否是单类别检测 batch16, close_mosaic0, workers0, device0, optimizerSGD, # using SGD/MuSGD # resume, # 这里是填写last.pt地址 ampTrue, # 如果出现训练损失为Nan可以关闭amp projectruns/train, nameexp, )六、成功运行记录下面是成功运行的截图已经完成了有1个epochs的训练图片太大截不全第2个epochs了。​七、本文总结到此本文的正式分享内容就结束了在这里给大家推荐我的YOLOv26改进有效涨点专栏本专栏目前为新开的平均质量分98分后期我会根据各种最新的前沿顶会进行论文复现也会对一些老的改进机制进行补充如果大家觉得本文帮助到你了订阅本专栏关注后续更多的更新~专栏链接YOLOv26有效涨点专栏包含Conv、注意力机制、主干/Backbone、损失函数、优化器、后处理等改进机制