1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
| """ GDAU (Guided Dual-Attention Unit) 架构复现
论文核心模块: 1. Position-Aware Spatial Attention(位置感知空间注意力) 2. SE Channel Attention(Squeeze-Excitation 通道注意力)
创新点:两种注意力串联使用,空间注意力先聚焦关键区域, 通道注意力再增强重要特征通道
对比评估的 13 种变体包括: - 无注意力(基线) - SE (Squeeze-Excitation) - CBAM (Convolutional Block Attention Module) - ECA (Efficient Channel Attention) - CA (Coordinate Attention) - DA (Dual Attention) - GDAU (本文提出) - 以及以上变体的组合 """
import torch import torch.nn as nn import torch.nn.functional as F
class PositionAwareSpatialAttention(nn.Module): """ 位置感知空间注意力 传统空间注意力只关注"哪里重要", 位置感知还编码"位置先验"—— 在 DMS 中,眼睛/嘴巴的位置有先验分布 """ def __init__(self, kernel_size: int = 7): super().__init__() self.pos_embed = nn.Parameter(torch.randn(1, 2, 1, 1)) self.conv = nn.Conv2d(2, 1, kernel_size, padding=kernel_size//2, bias=False) def forward(self, x: torch.Tensor) -> torch.Tensor: """ Args: x: (B, C, H, W) Returns: x_attended: (B, C, H, W) """ B, C, H, W = x.shape pos = F.interpolate(self.pos_embed, size=(H, W), mode='bilinear', align_corners=False) avg_pool = x.mean(dim=1, keepdim=True) max_pool = x.max(dim=1, keepdim=True)[0] spatial = torch.cat([avg_pool + pos[:, 0:1], max_pool + pos[:, 1:2]], dim=1) attn = torch.sigmoid(self.conv(spatial)) return x * attn
class SEChannelAttention(nn.Module): """ Squeeze-Excitation 通道注意力 压缩空间维度 → 学习通道权重 → 激活重要通道 """ def __init__(self, channels: int, reduction: int = 16): super().__init__() self.fc = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(channels, channels // reduction), nn.ReLU(), nn.Linear(channels // reduction, channels), nn.Sigmoid() ) def forward(self, x: torch.Tensor) -> torch.Tensor: w = self.fc(x).unsqueeze(-1).unsqueeze(-1) return x * w
class GDAU(nn.Module): """ Guided Dual-Attention Unit 顺序:空间注意力 → 通道注意力 "Guided" 指空间注意力引导通道注意力的聚焦方向 """ def __init__(self, channels: int, reduction: int = 16): super().__init__() self.spatial_attn = PositionAwareSpatialAttention() self.channel_attn = SEChannelAttention(channels, reduction) self.norm = nn.BatchNorm2d(channels) def forward(self, x: torch.Tensor) -> torch.Tensor: residual = x x = self.spatial_attn(x) x = self.channel_attn(x) return self.norm(residual + x)
class CompactDrowsinessNet(nn.Module): """ 紧凑疲劳检测网络 基于 MobileNetV2 backbone + GDAU 模块 设计目标: - 精度 ≥95% - 参数量 <2M - INT8 量化后 <2MB - FPGA 可部署 """ def __init__(self, num_classes: int = 2): super().__init__() self.backbone = nn.Sequential( nn.Conv2d(3, 32, 3, stride=2, padding=1, bias=False), nn.BatchNorm2d(32), nn.ReLU6(), self._conv_bn(32, 16, 1), self._conv_dw(16, 32, 1), GDAU(32, reduction=8), self._conv_dw(32, 64, 2), self._conv_bn(64, 32, 1), GDAU(32, reduction=8), self._conv_dw(32, 128, 2), self._conv_bn(128, 64, 1), GDAU(64, reduction=16), self._conv_dw(64, 128, 1), nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Dropout(0.2), nn.Linear(128, num_classes) ) def _conv_bn(self, inp, oup, stride): return nn.Sequential( nn.Conv2d(inp, oup, 3, stride, 1, bias=False), nn.BatchNorm2d(oup), nn.ReLU6() ) def _conv_dw(self, inp, oup, stride): return nn.Sequential( nn.Conv2d(inp, inp, 3, stride, 1, groups=inp, bias=False), nn.BatchNorm2d(inp), nn.ReLU6(), nn.Conv2d(inp, oup, 1, 1, 0, bias=False), nn.BatchNorm2d(oup), nn.ReLU6() ) def forward(self, x): return self.backbone(x)
if __name__ == "__main__": import time model = CompactDrowsinessNet(num_classes=2) model.eval() x = torch.randn(1, 3, 224, 224) start = time.time() with torch.no_grad(): out = model(x) fp32_ms = (time.time() - start) * 1000 total_params = sum(p.numel() for p in model.parameters()) print("=" * 60) print("CompactDrowsinessNet (GDAU) 测试") print("=" * 60) print(f"输入: {x.shape}") print(f"输出: {out.shape}") print(f"参数量: {total_params:,} ({total_params/1e6:.2f}M)") print(f"FP32 推理: {fp32_ms:.2f} ms ({1000/fp32_ms:.1f} FPS)") print(f"\n--- INT8 量化预估 ---") print(f"FP32 模型大小: {total_params * 4 / 1024:.1f} KB") print(f"INT8 模型大小: {total_params / 1024:.1f} KB") print(f"INT8 推理预估: {fp32_ms * 0.4:.2f} ms ({1000/(fp32_ms*0.4):.1f} FPS)") print(f"精度损失预估: 1-3%") gdau_count = sum(1 for m in model.modules() if isinstance(m, GDAU)) print(f"\nGDAU 模块数: {gdau_count}") print(f"GDAU 参数占比: {sum(p.numel() for m in model.modules() if isinstance(m, GDAU) for p in m.parameters()) / total_params * 100:.1f}%")
|