""" YOLOv8目标检测模型 包含改进的CSPDarknet骨干网络、PANet neck和检测头 添加了CBAM注意力机制作为改进 """ import torch import torch.nn as nn import torch.nn.functional as F import math from config import MODEL_CONFIG class Conv(nn.Module): """标准卷积层:Conv + BN + SiLU""" def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=None, groups=1, bias=False): super().__init__() if padding is None: padding = kernel_size // 2 self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, groups=groups, bias=bias) self.bn = nn.BatchNorm2d(out_channels) self.act = nn.SiLU() # Swish激活函数 def forward(self, x): return self.act(self.bn(self.conv(x))) def forward_fuse(self, x): """融合BN后的前向传播(推理加速)""" return self.act(self.conv(x)) class DWConv(nn.Module): """深度可分离卷积""" def __init__(self, in_channels, out_channels, kernel_size=3, stride=1): super().__init__() self.conv = Conv(in_channels, out_channels, kernel_size, stride, groups=in_channels) def forward(self, x): return self.conv(x) class Bottleneck(nn.Module): """标准瓶颈块""" def __init__(self, in_channels, out_channels, shortcut=True, groups=1, expansion=0.5): super().__init__() hidden_channels = int(out_channels * expansion) self.conv1 = Conv(in_channels, hidden_channels, 1, 1) self.conv2 = Conv(hidden_channels, out_channels, 3, 1, groups=groups) self.add = shortcut and in_channels == out_channels def forward(self, x): return x + self.conv2(self.conv1(x)) if self.add else self.conv2(self.conv1(x)) class C2f(nn.Module): """C2f模块:改进的CSP Bottleneck,带2个卷积""" def __init__(self, in_channels, out_channels, n=1, shortcut=False, groups=1, expansion=0.5): super().__init__() self.out_channels = out_channels self.hidden_channels = int(out_channels * expansion) self.conv1 = Conv(in_channels, 2 * self.hidden_channels, 1, 1) self.conv2 = Conv((2 + n) * self.hidden_channels, out_channels, 1, 1) self.m = nn.ModuleList(Bottleneck(self.hidden_channels, self.hidden_channels, shortcut, groups, expansion=1.0) for _ in range(n)) def forward(self, x): y = list(self.conv1(x).chunk(2, 1)) y.extend(m(y[-1]) for m in self.m) return self.conv2(torch.cat(y, 1)) def forward_split(self, x): y = list(self.conv1(x).split((self.hidden_channels, self.hidden_channels), 1)) y.extend(m(y[-1]) for m in self.m) return self.conv2(torch.cat(y, 1)) class SPPF(nn.Module): """快速空间金字塔池化""" def __init__(self, in_channels, out_channels, kernel_size=5): super().__init__() hidden_channels = in_channels // 2 self.conv1 = Conv(in_channels, hidden_channels, 1, 1) self.conv2 = Conv(hidden_channels * 4, out_channels, 1, 1) self.m = nn.MaxPool2d(kernel_size=kernel_size, stride=1, padding=kernel_size // 2) def forward(self, x): x = self.conv1(x) y1 = self.m(x) y2 = self.m(y1) return self.conv2(torch.cat([x, y1, y2, self.m(y2)], 1)) class CBAM(nn.Module): """ CBAM注意力机制 (Convolutional Block Attention Module) 包含通道注意力和空间注意力两个子模块 这是对原始YOLOv8的改进 """ def __init__(self, channels, reduction=16, kernel_size=7): super().__init__() # 通道注意力 self.avg_pool = nn.AdaptiveAvgPool2d(1) self.max_pool = nn.AdaptiveMaxPool2d(1) self.fc = nn.Sequential( nn.Conv2d(channels, channels // reduction, 1, bias=False), nn.ReLU(), nn.Conv2d(channels // reduction, channels, 1, bias=False) ) # 空间注意力 self.conv_spatial = nn.Conv2d(2, 1, kernel_size, padding=kernel_size // 2, bias=False) self.sigmoid = nn.Sigmoid() def forward(self, x): # 通道注意力 avg_out = self.fc(self.avg_pool(x)) max_out = self.fc(self.max_pool(x)) channel_att = self.sigmoid(avg_out + max_out) x = x * channel_att # 空间注意力 avg_out = torch.mean(x, dim=1, keepdim=True) max_out, _ = torch.max(x, dim=1, keepdim=True) spatial_att = self.sigmoid(self.conv_spatial(torch.cat([avg_out, max_out], dim=1))) x = x * spatial_att return x class Backbone(nn.Module): """CSPDarknet骨干网络(带CBAM注意力机制)""" def __init__(self, base_channels=64, base_depth=3): super().__init__() # P1/2 self.stem = Conv(3, base_channels, kernel_size=3, stride=2) # P2/4 self.stage1 = nn.Sequential( Conv(base_channels, base_channels * 2, 3, 2), C2f(base_channels * 2, base_channels * 2, n=base_depth, shortcut=True) ) # P3/8 self.stage2 = nn.Sequential( Conv(base_channels * 2, base_channels * 4, 3, 2), C2f(base_channels * 4, base_channels * 4, n=base_depth * 2, shortcut=True) ) # P4/16 self.stage3 = nn.Sequential( Conv(base_channels * 4, base_channels * 8, 3, 2), C2f(base_channels * 8, base_channels * 8, n=base_depth * 2, shortcut=True) ) # P5/32 self.stage4 = nn.Sequential( Conv(base_channels * 8, base_channels * 16, 3, 2), C2f(base_channels * 16, base_channels * 16, n=base_depth, shortcut=True), SPPF(base_channels * 16, base_channels * 16, kernel_size=5) ) # CBAM注意力模块(改进点) self.cbam3 = CBAM(base_channels * 4) # P3 self.cbam4 = CBAM(base_channels * 8) # P4 self.cbam5 = CBAM(base_channels * 16) # P5 def forward(self, x): x = self.stem(x) x = self.stage1(x) x = self.stage2(x) c3 = self.cbam3(x) # P3/8 x = self.stage3(x) c4 = self.cbam4(x) # P4/16 x = self.stage4(x) c5 = self.cbam5(x) # P5/32 return c3, c4, c5 class Neck(nn.Module): """PANet特征融合网络""" def __init__(self, base_channels=64, base_depth=3): super().__init__() # 上采样融合 self.upsample = nn.Upsample(scale_factor=2, mode='nearest') # P5 -> P4 self.c2f_p4 = C2f(base_channels * 24, base_channels * 8, n=base_depth, shortcut=False) # P4 -> P3 self.c2f_p3 = C2f(base_channels * 12, base_channels * 4, n=base_depth, shortcut=False) # P3 -> P4 self.conv_p3 = Conv(base_channels * 4, base_channels * 4, 3, 2) self.c2f_p4_down = C2f(base_channels * 12, base_channels * 8, n=base_depth, shortcut=False) # P4 -> P5 self.conv_p4 = Conv(base_channels * 8, base_channels * 8, 3, 2) self.c2f_p5 = C2f(base_channels * 24, base_channels * 16, n=base_depth, shortcut=False) def forward(self, c3, c4, c5): # 自顶向下 p5 = c5 x = self.upsample(p5) x = torch.cat([x, c4], dim=1) p4 = self.c2f_p4(x) x = self.upsample(p4) x = torch.cat([x, c3], dim=1) p3 = self.c2f_p3(x) # 自底向上 x = self.conv_p3(p3) x = torch.cat([x, p4], dim=1) p4_out = self.c2f_p4_down(x) x = self.conv_p4(p4_out) x = torch.cat([x, p5], dim=1) p5_out = self.c2f_p5(x) return p3, p4_out, p5_out class TransformerBlock(nn.Module): """Transformer编码器块 - 引入自注意力机制""" def __init__(self, channels, num_heads=8, mlp_ratio=4., dropout=0.1): super().__init__() self.channels = channels self.num_heads = num_heads self.head_dim = channels // num_heads # Layer Normalization self.norm1 = nn.LayerNorm(channels) self.norm2 = nn.LayerNorm(channels) # Multi-Head Self-Attention self.qkv = nn.Linear(channels, channels * 3, bias=False) self.proj = nn.Linear(channels, channels) self.attn_dropout = nn.Dropout(dropout) self.proj_dropout = nn.Dropout(dropout) # MLP mlp_hidden = int(channels * mlp_ratio) self.mlp = nn.Sequential( nn.Linear(channels, mlp_hidden), nn.GELU(), nn.Dropout(dropout), nn.Linear(mlp_hidden, channels), nn.Dropout(dropout) ) def forward(self, x): B, C, H, W = x.shape # 将特征图转换为序列: (B, C, H, W) -> (B, H*W, C) x_flat = x.flatten(2).transpose(1, 2) # Self-Attention with residual x_norm = self.norm1(x_flat) qkv = self.qkv(x_norm).reshape(B, H * W, 3, self.num_heads, self.head_dim).permute(2, 0, 3, 1, 4) q, k, v = qkv[0], qkv[1], qkv[2] # 计算注意力 attn = (q @ k.transpose(-2, -1)) * (self.head_dim ** -0.5) attn = attn.softmax(dim=-1) attn = self.attn_dropout(attn) # 应用注意力 x_attn = (attn @ v).transpose(1, 2).reshape(B, H * W, C) x_attn = self.proj(x_attn) x_attn = self.proj_dropout(x_attn) x_flat = x_flat + x_attn # MLP with residual x_flat = x_flat + self.mlp(self.norm2(x_flat)) # 转换回特征图: (B, H*W, C) -> (B, C, H, W) x_out = x_flat.transpose(1, 2).reshape(B, C, H, W) return x_out class MambaBlock(nn.Module): """Mamba块 - 选择性状态空间模型""" def __init__(self, channels, d_state=16, d_conv=4, expand=2): super().__init__() self.channels = channels self.d_state = d_state self.d_conv = d_conv self.expand = expand self.d_inner = int(self.expand * channels) # 输入投影 self.in_proj = nn.Linear(channels, self.d_inner * 2, bias=False) # 因果卷积 self.conv1d = nn.Conv1d( in_channels=self.d_inner, out_channels=self.d_inner, kernel_size=d_conv, padding=d_conv - 1, groups=self.d_inner, bias=True ) # 选择性参数 self.x_proj = nn.Linear(self.d_inner, d_state * 2, bias=False) self.dt_proj = nn.Linear(self.d_inner, self.d_inner, bias=True) # A参数(状态空间模型) A = torch.arange(1, d_state + 1, dtype=torch.float32).repeat(self.d_inner, 1) self.A_log = nn.Parameter(torch.log(A)) self.D = nn.Parameter(torch.ones(self.d_inner)) # 输出投影 self.out_proj = nn.Linear(self.d_inner, channels, bias=False) self.act = nn.SiLU() def forward(self, x): B, C, H, W = x.shape L = H * W # 转换为序列: (B, C, H, W) -> (B, L, C) x = x.flatten(2).transpose(1, 2) # 输入投影 x_and_res = self.in_proj(x) # (B, L, 2 * d_inner) x, res = x_and_res.split([self.d_inner, self.d_inner], dim=-1) # 因果卷积 x = x.transpose(1, 2) # (B, d_inner, L) x = self.conv1d(x)[:, :, :L] x = x.transpose(1, 2) # (B, L, d_inner) x = self.act(x) # SSM参数 A = -torch.exp(self.A_log.float()) D = self.D.float() # 选择性扫描(简化版) x_ssm = self.selective_scan(x, A, D) # 门控 x = x_ssm * F.silu(res) # 输出投影 x = self.out_proj(x) # 转换回特征图: (B, L, C) -> (B, C, H, W) x = x.transpose(1, 2).reshape(B, C, H, W) return x def selective_scan(self, x, A, D): """选择性扫描 - 状态空间模型核心""" B, L, d = x.shape # 离散化 dt = F.softplus(self.dt_proj(x)) # 状态空间模型计算(简化实现) # 实际应用中应使用CUDA优化的选择性扫描 x_proj = self.x_proj(x) B_param, C_param = x_proj.split([self.d_state, self.d_state], dim=-1) # 简化的状态空间计算 y = x * D.unsqueeze(0).unsqueeze(0) return y class Detect(nn.Module): """检测头(支持Transformer和Mamba两种前沿架构)""" def __init__(self, num_classes=80, anchors=(), base_channels=64, use_transformer=True, use_mamba=False): super().__init__() self.num_classes = num_classes self.num_outputs = num_classes + 5 # x, y, w, h, obj + classes self.num_layers = len(anchors) self.use_transformer = use_transformer self.use_mamba = use_mamba self.anchors = anchors self.anchor_grid = [torch.zeros(1)] * self.num_layers # Transformer模块(前沿架构1:自注意力机制) if use_transformer: self.transformer_blocks = nn.ModuleList([ TransformerBlock(base_channels * 4 * (2 ** i), num_heads=8) for i in range(self.num_layers) ]) # Mamba模块(前沿架构2:选择性状态空间模型) if use_mamba: self.mamba_blocks = nn.ModuleList([ MambaBlock(base_channels * 4 * (2 ** i), d_state=16, expand=2) for i in range(self.num_layers) ]) # 为每个尺度创建检测头,每个anchor预测num_outputs个值 self.m = nn.ModuleList( nn.Conv2d(base_channels * 4 * (2 ** i), 3 * self.num_outputs, 1) for i in range(self.num_layers) ) def forward(self, x): outputs = [] for i in range(self.num_layers): # 应用Transformer自注意力(前沿架构1) if self.use_transformer: x[i] = self.transformer_blocks[i](x[i]) # 应用Mamba选择性状态空间模型(前沿架构2) if self.use_mamba: x[i] = self.mamba_blocks[i](x[i]) # 检测头 x[i] = self.m[i](x[i]) outputs.append(x[i]) return outputs class YOLOv8(nn.Module): """YOLOv8目标检测模型(支持CBAM、Transformer、Mamba多种改进)""" def __init__(self, num_classes=20, base_channels=64, base_depth=3, use_transformer=True, use_mamba=False): super().__init__() self.num_classes = num_classes # 骨干网络 self.backbone = Backbone(base_channels, base_depth) # 特征融合网络 self.neck = Neck(base_channels, base_depth) # 检测头(支持Transformer和Mamba) anchors = MODEL_CONFIG['anchors'] self.head = Detect(num_classes, anchors, base_channels, use_transformer=use_transformer, use_mamba=use_mamba) # 初始化权重 self._initialize_weights() def _initialize_weights(self): """初始化模型权重""" for m in self.modules(): if isinstance(m, nn.Conv2d): nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') if m.bias is not None: nn.init.constant_(m.bias, 0) elif isinstance(m, nn.BatchNorm2d): nn.init.constant_(m.weight, 1) nn.init.constant_(m.bias, 0) elif isinstance(m, nn.Linear): nn.init.normal_(m.weight, 0, 0.01) if m.bias is not None: nn.init.constant_(m.bias, 0) def forward(self, x): # 骨干网络 c3, c4, c5 = self.backbone(x) # 特征融合 p3, p4, p5 = self.neck(c3, c4, c5) # 检测头 outputs = self.head([p3, p4, p5]) return outputs def build_model(num_classes=20, use_transformer=True, use_mamba=False): """构建YOLOv8模型(支持多种前沿架构) Args: num_classes: 类别数量 use_transformer: 是否使用Transformer自注意力机制 use_mamba: 是否使用Mamba选择性状态空间模型 Returns: model: YOLOv8模型 """ model = YOLOv8(num_classes=num_classes, use_transformer=use_transformer, use_mamba=use_mamba) return model if __name__ == '__main__': # 测试模型 model = build_model(num_classes=20) # 统计参数量 total_params = sum(p.numel() for p in model.parameters()) trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad) print(f"总参数量: {total_params / 1e6:.2f}M") print(f"可训练参数量: {trainable_params / 1e6:.2f}M") # 测试前向传播 x = torch.randn(1, 3, 640, 640) outputs = model(x) for i, out in enumerate(outputs): print(f"输出{i+1}形状: {out.shape}")