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
| import torch import torch.nn as nn
class MultimodalSentimentRecognition(nn.Module): """ 三模态情感识别 模态1: 面部表情 → CNN (ResNet50-based) 模态2: HRV信号 → BiLSTM 模态3: 语音情感 → ECAPA-TDNN 融合: Late Fusion (加权平均 + 注意力) """ def __init__(self, num_classes=7, hidden_dim=256): super().__init__() self.face_cnn = nn.Sequential( nn.Conv2d(3, 64, 7, stride=2, padding=3), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(3, stride=2, padding=1), self._make_res_block(64, 128, stride=2), self._make_res_block(128, 256, stride=2), self._make_res_block(256, 512, stride=2), nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(), nn.Linear(512, hidden_dim), ) self.hrv_bilstm = nn.LSTM( input_size=5, hidden_size=hidden_dim, num_layers=2, batch_first=True, bidirectional=True, ) self.hrv_fc = nn.Linear(hidden_dim * 2, hidden_dim) self.speech_tdnn = ECAPA_TDNN( input_dim=80, hidden_dim=hidden_dim, num_blocks=3, ) self.attention = nn.MultiheadAttention( embed_dim=hidden_dim, num_heads=8 ) self.classifier = nn.Sequential( nn.Linear(hidden_dim * 3, hidden_dim), nn.ReLU(), nn.Dropout(0.3), nn.Linear(hidden_dim, num_classes), ) def _make_res_block(self, in_ch, out_ch, stride=1): return nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, stride=stride, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(), nn.Conv2d(out_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), ) def forward(self, face, hrv, speech): face_feat = self.face_cnn(face) hrv_out, _ = self.hrv_bilstm(hrv) hrv_feat = self.hrv_fc(hrv_out[:, -1, :]) speech_feat = self.speech_tdnn(speech) combined = torch.cat([face_feat, hrv_feat, speech_feat], dim=1) output = self.classifier(combined) return output
class ECAPA_TDNN(nn.Module): """ECAPA-TDNN 语音特征提取器""" def __init__(self, input_dim=80, hidden_dim=256, num_blocks=3): super().__init__() self.blocks = nn.ModuleList([ nn.Sequential( nn.Conv1d(input_dim if i == 0 else hidden_dim, hidden_dim, 5, padding=2), nn.BatchNorm1d(hidden_dim), nn.ReLU(), nn.SEBlock(hidden_dim), ) for i in range(num_blocks) ]) self.att_pool = nn.MultiheadAttention(hidden_dim, 8) def forward(self, x): for block in self.blocks: x = block(x) return x.mean(dim=2)
class SEBlock(nn.Module): """Squeeze-and-Excitation Block""" def __init__(self, channels, reduction=16): super().__init__() self.fc = nn.Sequential( nn.Linear(channels, channels // reduction), nn.ReLU(), nn.Linear(channels // reduction, channels), nn.Sigmoid(), ) def forward(self, x): b, c, _ = x.shape w = x.mean(dim=2) w = self.fc(w).unsqueeze(2) return x * w
|