























本文介绍了将LS(Large - Small)卷积与YOLO26相结合的方法。LS卷积受人类视觉系统动态异尺度感知能力启发,由大核感知(LKP)和小核聚合(SKA)两部分组成,能实现“广域上下文捕获”和“局部关键特征融合”,解决传统卷积和自注意力在轻量级模型中的局限。我们将集成LS卷积的C3k2_LSConv模块引入YOLO26,对相关代码进行修改和注册,并配置了YOLO26 - C3k2_LSConv.yaml文件。实验脚本表明,该结合方式可应用于目标检测任务。
文章目录: YOLO26改进大全:卷积层、轻量化、注意力机制、损失函数、Backbone、SPPF、Neck、检测头全方位优化汇总
专栏链接: YOLO26改进专栏

视觉网络设计(包括卷积神经网络(CNNs)和视觉Transformer(ViTs))极大推动了计算机视觉领域的发展。然而,其复杂的计算过程给实际部署带来了挑战,在实时应用场景中尤为突出。为解决这一问题,研究者们探索了多种轻量级高效网络设计方案。但现有轻量级模型主要依赖自注意力机制和卷积进行令牌混合(token mixing),这种依赖导致轻量级网络的感知与聚合过程在有效性和效率上存在局限,难以在有限计算预算下平衡性能与效率。本文受高效人类视觉系统中固有的动态异尺度视觉能力启发,提出一种适用于轻量级视觉网络设计的“广域感知、精准聚焦(See Large, Focus Small)”策略。我们引入LS(大-小)卷积,该卷积结合了大核感知与小核聚合的特性,能够高效捕获广泛的感知信息,并针对动态复杂的视觉表征实现精准的特征聚合,从而实现对视觉信息的高效处理。基于LS卷积,我们提出了一个全新的轻量级模型家族LSNet。大量实验表明,在各类视觉任务中,LSNet相较于现有轻量级网络均具有更优的性能和效率。相关代码与模型可通过链接获取:https://github.com/jameslahm/lsnet。
论文地址:论文地址
代码地址:代码地址
LS(Large-Small)卷积是LSNet模型的核心创新组件,其设计灵感源于人类视觉系统的动态异尺度感知能力(外周视觉“看大视野”+ 中央视觉“聚焦细节”),旨在解决传统卷积/自注意力在轻量级模型中“感知范围有限”“聚合冗余”“效率低下”的问题,实现“高效广域感知”与“精准细粒度聚合”的协同,为轻量级视觉网络提供高性能、低计算成本的令牌混合(Token Mixing)方案。
在轻量级视觉模型中,传统令牌混合方式存在显著局限:
LS卷积的核心目标是:在低计算预算下,同时实现“广域上下文捕获”(See Large)和“局部关键特征融合”(Focus Small),打破“感知-聚合尺度绑定”的限制,平衡模型性能与效率。
LS卷积由大核感知(Large-Kernel Perception, LKP) 和小核聚合(Small-Kernel Aggregation, SKA) 两部分组成,二者分工明确且深度协同——LKP负责“看大”(捕获全局上下文),SKA负责“聚焦小”(自适应融合局部关键特征),而非简单叠加大核与小核卷积。
LKP的核心作用是以低计算成本扩展感受野,建模令牌间的全局空间关系,类似人类外周视觉通过视杆细胞感知场景全貌的过程。其具体流程与设计细节如下:
给定输入特征图 ( X \in \mathbb{R}^{H \times W \times C} )(( H,W ) 为空间分辨率,( C ) 为通道数),LKP通过三步生成“上下文自适应权重”,为后续SKA提供指导:
对于单个令牌 ( x_i \in \mathbb{R}^C ),其对应的LKP权重生成过程可表示为: [ \begin{aligned} wi &= \mathcal{P}{ls}\left(xi, \mathcal{N}{K_L}(xi)\right) \ &= PW\left(DW{K_L \times KL}\left(PW\left(\mathcal{N}{K_L}(x_i)\right)\right)\right) \end{aligned} ] 其中:
SKA的核心作用是基于LKP生成的全局上下文权重,在局部小范围(( K_S \times K_S ))内精准融合关键特征,类似人类中央视觉通过视锥细胞聚焦细节的过程。其设计重点是“动态性”与“分组优化”,确保效率与精度的平衡。
SKA以LKP生成的权重 ( W ) 为指导,对输入特征图 ( X ) 进行局部动态聚合,具体步骤如下:
LS卷积的关键优势并非“大核+小核”的简单组合,而是LKP与SKA的深度协同:
class SkaFn(Function):
@staticmethod
@custom_fwd
def forward(ctx, x: torch.Tensor, w: torch.Tensor) -> torch.Tensor:
# 解析参数
ks = int(math.sqrt(w.shape[2])) # 核大小 (K×K)
pad = (ks - 1) // 2 # 填充大小
n, ic, h, wd = x.shape # x形状: (N, C_in, H, W)
_, wc, _, _, _ = w.shape # w形状: (N, C_w, K², H, W)
G = ic // wc # 分组数 (G = C_in / C_w)
# 保存反向传播所需参数
ctx.ks = ks
ctx.pad = pad
ctx.G = G
ctx.save_for_backward(x, w)
# 输入填充与滑动窗口提取
x_padded = torch.nn.functional.pad(x, (pad, pad, pad, pad), mode='constant', value=0.0)
x_windows = x_padded.unfold(2, ks, 1).unfold(3, ks, 1) # (N, C_in, H, W, K, K)
x_windows = x_windows.permute(0, 1, 4, 5, 2, 3).contiguous() # (N, C_in, K, K, H, W)
x_windows = x_windows.view(n, ic, ks*ks, h, wd) # (N, C_in, K², H, W)
# 分组处理与加权聚合
x_grouped = x_windows.view(n, G, wc, ks*ks, h, wd) # (N, G, C_w, K², H, W)
w_grouped = w.view(n, 1, wc, ks*ks, h, wd) # (N, 1, C_w, K², H, W)
out_grouped = torch.sum(x_grouped * w_grouped, dim=3) # 沿K²维度求和
out = out_grouped.view(n, ic, h, wd) # 合并分组: (N, C_in, H, W)
return out
@staticmethod
@custom_bwd
def backward(ctx, go: torch.Tensor) -> tuple:
ks = ctx.ks
pad = ctx.pad
G = ctx.G
x, w = ctx.saved_tensors
n, ic, h, wd = x.shape
_, wc, k_sq, w_h, w_w = w.shape # 解析w的维度: (N, C_w, K², H, W)
# 计算x的梯度 (gx)
gx = None
if ctx.needs_input_grad[0]:
# 填充梯度并提取窗口
go_padded = torch.nn.functional.pad(go, (pad, pad, pad, pad), mode='constant', value=0.0)
go_windows = go_padded.unfold(2, ks, 1).unfold(3, ks, 1) # (N, C_in, H, W, K, K)
go_windows = go_windows.permute(0, 1, 4, 5, 2, 3).contiguous() # (N, C_in, K, K, H, W)
go_windows = go_windows.view(n, ic, ks*ks, h, wd) # (N, C_in, K², H, W)
# 分组处理并求和
go_grouped = go_windows.view(n, G, wc, ks*ks, h, wd) # (N, G, C_w, K², H, W)
w_grouped = w.view(n, 1, wc, ks*ks, h, wd) # (N, 1, C_w, K², H, W)
gx_grouped = torch.sum(go_grouped * w_grouped, dim=3) # 沿K²维度求和
gx = gx_grouped.view(n, ic, h, wd) # 合并分组: (N, C_in, H, W)
# 计算w的梯度 (gw) - 核心修正
gw = None
if ctx.needs_input_grad[1]:
# 填充输入x并提取窗口
x_padded = torch.nn.functional.pad(x, (pad, pad, pad, pad), mode='constant', value=0.0)
x_windows = x_padded.unfold(2, ks, 1).unfold(3, ks, 1) # (N, C_in, H, W, K, K)
x_windows = x_windows.permute(0, 1, 4, 5, 2, 3).contiguous() # (N, C_in, K, K, H, W)
x_windows = x_windows.view(n, ic, ks*ks, h, wd) # (N, C_in, K², H, W)
# 分组处理(严格基于w的维度)
x_grouped = x_windows.view(n, G, wc, ks*ks, h, wd) # (N, G, C_w, K², H, W)
go_grouped = go.view(n, G, wc, 1, h, wd) # (N, G, C_w, 1, H, W) - 扩展K²维度
# 计算分组梯度并聚合(关键:对G维度求和)
gw_grouped = x_grouped * go_grouped # (N, G, C_w, K², H, W)
gw = gw_grouped.sum(dim=1) # 聚合分组维度G: (N, C_w, K², H, W)
# 强制形状匹配(应对极端情况)
if gw.shape != w.shape:
gw = gw[:, :wc, :k_sq, :w_h, :w_w].contiguous()
return gx, gw
class SKA(torch.nn.Module):
def forward(self, x: torch.Tensor, w: torch.Tensor) -> torch.Tensor:
return SkaFn.apply(x, w)
class Conv2d_BN(torch.nn.Sequential):
def __init__(self, a, b, ks=1, stride=1, pad=0, dilation=1,
groups=1, bn_weight_init=1):
super().__init__()
self.add_module('c', torch.nn.Conv2d(
a, b, ks, stride, pad, dilation, groups, bias=False))
self.add_module('bn', torch.nn.BatchNorm2d(b))
torch.nn.init.constant_(self.bn.weight, bn_weight_init)
torch.nn.init.constant_(self.bn.bias, 0)
@torch.no_grad()
def fuse(self):
c, bn = self._modules.values()
w = bn.weight / (bn.running_var + bn.eps)**0.5
w = c.weight * w[:, None, None, None]
b = bn.bias - bn.running_mean * bn.weight / \
(bn.running_var + bn.eps)**0.5
m = torch.nn.Conv2d(w.size(1) * self.c.groups, w.size(
0), w.shape[2:], stride=self.c.stride, padding=self.c.padding, dilation=self.c.dilation, groups=self.c.groups,
device=c.weight.device)
m.weight.data.copy_(w)
m.bias.data.copy_(b)
return m
class LKP(nn.Module):
def __init__(self, dim, lks, sks, groups):
super().__init__()
self.cv1 = Conv2d_BN(dim, dim // 2)
self.act = nn.ReLU()
self.cv2 = Conv2d_BN(dim // 2, dim // 2, ks=lks, pad=(lks - 1) // 2, groups=dim // 2)
self.cv3 = Conv2d_BN(dim // 2, dim // 2)
self.cv4 = nn.Conv2d(dim // 2, sks ** 2 * dim // groups, kernel_size=1)
self.norm = nn.GroupNorm(num_groups=dim // groups, num_channels=sks ** 2 * dim // groups)
self.sks = sks
self.groups = groups
self.dim = dim
def forward(self, x):
x = self.act(self.cv3(self.cv2(self.act(self.cv1(x)))))
w = self.norm(self.cv4(x))
b, _, h, width = w.size()
# 确保w的形状正确:(batch, C_w, K², H, W)
w = w.view(b, self.dim // self.groups, self.sks ** 2, h, width)
return w
class LSConv(nn.Module):
def __init__(self, dim):
super(LSConv, self).__init__()
self.lkp = LKP(dim, lks=7, sks=3, groups=8)
self.ska = SKA()
self.bn = nn.BatchNorm2d(dim)
import warnings
warnings.filterwarnings('ignore')
from ultralytics import YOLO
if __name__ == '__main__':
# 修改为自己的配置文件地址
model = YOLO('./ultralytics/cfg/models/26/yolo26-C3k2_LSConv.yaml')
# 修改为自己的数据集地址
model.train(data='./ultralytics/cfg/datasets/coco8.yaml',
cache=False,
imgsz=640,
epochs=10,
single_cls=False, # 是否是单类别检测
batch=8,
close_mosaic=10,
workers=0,
optimizer='MuSGD',
# optimizer='SGD',
amp=False,
project='runs/train',
name='yolo26-C3k2_LSConv',
)

此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。