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
| import torch import torch.nn as nn import torchvision.models as models
class BiFuseNet(nn.Module): """ BiFuseNet: 双流 3D 时空多模态网络 用于从 RGB 面部视频估计酒精损伤等级 架构: - 空间流:2D CNN 提取每帧面部特征 - 时序流:3D CNN 捕获动态变化 - 融合:双流特征拼接后分类 论文核心思路: 酒精损伤在面部的表现包括: 1. 面部潮红(血管扩张)→ 空间特征 2. 眼睑下垂、微表情异常 → 时空特征 3. 头部晃动加剧 → 时序运动特征 4. 眨眼频率变化 → 时序特征 """ def __init__(self, num_classes: int = 3, backbone: str = "resnet18"): super().__init__() self.spatial_backbone = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) self.spatial_backbone.fc = nn.Identity() spatial_feat_dim = 512 self.temporal_backbone = nn.Sequential( nn.Conv3d(3, 32, kernel_size=(3, 3, 3), padding=(1, 1, 1)), nn.BatchNorm3d(32), nn.ReLU(), nn.MaxPool3d((1, 2, 2)), nn.Conv3d(32, 64, kernel_size=(3, 3, 3), padding=(1, 1, 1)), nn.BatchNorm3d(64), nn.ReLU(), nn.MaxPool3d((1, 2, 2)), nn.Conv3d(64, 128, kernel_size=(3, 3, 3), padding=(1, 1, 1)), nn.BatchNorm3d(128), nn.ReLU(), nn.AdaptiveAvgPool3d((1, 1, 1)) ) temporal_feat_dim = 128 fused_dim = spatial_feat_dim + temporal_feat_dim self.classifier = nn.Sequential( nn.Linear(fused_dim, 256), nn.ReLU(), nn.Dropout(0.5), nn.Linear(256, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, num_classes) ) def forward(self, x: torch.Tensor) -> torch.Tensor: """ Args: x: 面部视频片段, shape=(B, C, T, H, W) B: batch size C: 3 (RGB) T: 帧数(论文使用 16 帧片段) H, W: 224x224 Returns: logits: (B, 3) 三分类 logits [sober, low_AII, severe_AII] """ B, C, T, H, W = x.shape x_2d = x.permute(0, 2, 1, 3, 4) x_2d = x_2d.reshape(B * T, C, H, W) spatial_feat = self.spatial_backbone(x_2d) spatial_feat = spatial_feat.reshape(B, T, -1) spatial_feat = spatial_feat.mean(dim=1) temporal_feat = self.temporal_backbone(x) temporal_feat = temporal_feat.flatten(1) fused = torch.cat([spatial_feat, temporal_feat], dim=-1) logits = self.classifier(fused) return logits
if __name__ == "__main__": model = BiFuseNet(num_classes=3) x = torch.randn(4, 3, 16, 224, 224) with torch.no_grad(): logits = model(x) print("=== BiFuseNet 测试 ===") print(f"输入: {x.shape}") print(f"输出: {logits.shape}") classes = ["sober", "low_AII", "severe_AII"] probs = torch.softmax(logits, dim=-1) for i, cls in enumerate(classes): print(f" {cls}: {probs[0, i]:.4f}") total_params = sum(p.numel() for p in model.parameters()) print(f"\n参数量: {total_params:,}") print(f"模型大小: {total_params * 4 / 1024 / 1024:.1f} MB")
|