Yashdesai07 commited on
Commit
3d55bc9
·
verified ·
1 Parent(s): dea27d3

Upload fusion_model.py

Browse files
Files changed (1) hide show
  1. scripts/fusion_model.py +82 -0
scripts/fusion_model.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ The trainable part of this project: a fusion head that combines pretrained
3
+ audio / text / face embeddings into a mental-health risk prediction.
4
+
5
+ Two fusion strategies are included:
6
+ - ConcatFusion: simple, strong baseline (concatenate + MLP)
7
+ - AttentionFusion: each modality attends to the others before pooling
8
+ (use this as your "novel" contribution / for the ablation table)
9
+ """
10
+ import torch
11
+ import torch.nn as nn
12
+
13
+ EMBED_DIM = 256
14
+ NUM_CLASSES = 3 # low / moderate / high risk -- change to 1 for PHQ regression
15
+
16
+
17
+ class ConcatFusion(nn.Module):
18
+ def __init__(self, embed_dim=EMBED_DIM, num_classes=NUM_CLASSES, hidden=256, dropout=0.3):
19
+ super().__init__()
20
+ self.net = nn.Sequential(
21
+ nn.Linear(embed_dim * 3, hidden),
22
+ nn.ReLU(),
23
+ nn.Dropout(dropout),
24
+ nn.Linear(hidden, hidden // 2),
25
+ nn.ReLU(),
26
+ nn.Dropout(dropout),
27
+ nn.Linear(hidden // 2, num_classes),
28
+ )
29
+
30
+ def forward(self, audio, text, face):
31
+ x = torch.cat([audio, text, face], dim=-1)
32
+ return self.net(x)
33
+
34
+
35
+ class AttentionFusion(nn.Module):
36
+ """
37
+ Treats the 3 modality embeddings as a sequence of 3 tokens and runs them
38
+ through a small multi-head self-attention block so each modality can be
39
+ re-weighted based on the others (e.g. down-weight a noisy face signal if
40
+ audio+text strongly agree) before pooling and classifying.
41
+ """
42
+
43
+ def __init__(self, embed_dim=EMBED_DIM, num_classes=NUM_CLASSES, num_heads=4, dropout=0.3):
44
+ super().__init__()
45
+ self.modality_embed = nn.Parameter(torch.randn(3, embed_dim) * 0.02)
46
+ self.attn = nn.MultiheadAttention(embed_dim, num_heads, batch_first=True, dropout=dropout)
47
+ self.norm1 = nn.LayerNorm(embed_dim)
48
+ self.ffn = nn.Sequential(
49
+ nn.Linear(embed_dim, embed_dim * 2),
50
+ nn.ReLU(),
51
+ nn.Linear(embed_dim * 2, embed_dim),
52
+ )
53
+ self.norm2 = nn.LayerNorm(embed_dim)
54
+ self.pool_weights = nn.Linear(embed_dim, 1) # learned attention pooling
55
+ self.classifier = nn.Sequential(
56
+ nn.Linear(embed_dim, embed_dim // 2),
57
+ nn.ReLU(),
58
+ nn.Dropout(dropout),
59
+ nn.Linear(embed_dim // 2, num_classes),
60
+ )
61
+
62
+ def forward(self, audio, text, face, return_weights=False):
63
+ # [B, 3, D]
64
+ tokens = torch.stack([audio, text, face], dim=1) + self.modality_embed.unsqueeze(0)
65
+ attn_out, attn_weights = self.attn(tokens, tokens, tokens)
66
+ x = self.norm1(tokens + attn_out)
67
+ x = self.norm2(x + self.ffn(x))
68
+
69
+ # learned weighted pooling across the 3 modality tokens
70
+ pool_scores = torch.softmax(self.pool_weights(x).squeeze(-1), dim=-1) # [B, 3]
71
+ pooled = torch.bmm(pool_scores.unsqueeze(1), x).squeeze(1) # [B, D]
72
+
73
+ logits = self.classifier(pooled)
74
+ if return_weights:
75
+ return logits, pool_scores # pool_scores = per-modality contribution
76
+ return logits
77
+
78
+
79
+ def build_model(kind: str = "attention", **kwargs):
80
+ if kind == "concat":
81
+ return ConcatFusion(**kwargs)
82
+ return AttentionFusion(**kwargs)