SSCD / models /models.py
dashakoryakovskaya's picture
Update models/models.py
2839af8 verified
Raw
History Blame Contribute Delete
20.7 kB
import torch
import torch.nn as nn
import numpy as np
import torch.nn.functional as F
import logging
import copy
from .help_layers import TransformerEncoderLayer, CustomMambaBlock
class EmotionMamba(nn.Module):
def __init__(self, input_dim_emotion=1024, input_dim_personality=1024, hidden_dim=128, out_features=512, mamba_layer_number=2, mamba_d_model=256, per_activation="sigmoid", positional_encoding=True, num_transformer_heads=4, transformer_dropout=0.1, tr_layer_number=1, dropout=0.1, num_emotions=7, num_traits=5, device='cpu'):
super().__init__()
self.hidden_dim = hidden_dim
self.input_dim_emotion = input_dim_emotion
self.emo_proj = nn.Sequential(
nn.Linear(input_dim_emotion, hidden_dim),
nn.LayerNorm(hidden_dim),
nn.Dropout(dropout)
)
self.emotion_encoder = nn.ModuleList([
CustomMambaBlock(hidden_dim, mamba_d_model, dropout=dropout)
for _ in range(mamba_layer_number)
])
self.emotion_fc_out = nn.Sequential(
nn.Linear(hidden_dim, out_features),
nn.LayerNorm(out_features),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(out_features, num_emotions)
)
def forward(self, emotion_input=None, personality_input=None, return_features=False):
print(emotion_input.float().shape)
print(self.input_dim_emotion, self.hidden_dim)
emo = self.emo_proj(emotion_input.float()) # (B, T, hidden_dim)
for layer in self.emotion_encoder:
emo = layer(emo)
out_emo = self.emotion_fc_out(emo.mean(dim=1)) # (B, num_emotions)
if return_features:
return {
'emotion_logits': out_emo,
'last_encoder_features': emo,
}
else:
return {'emotion_logits': out_emo}
class EmotionTransformer(nn.Module):
def __init__(self, input_dim_emotion=512, input_dim_personality=512, hidden_dim=128, out_features=512, mamba_layer_number=2, mamba_d_model=256, per_activation="sigmoid", positional_encoding=True, num_transformer_heads=4, tr_layer_number=1, dropout=0.1, num_emotions=7, num_traits=5, device='cpu'):
super().__init__()
self.hidden_dim = hidden_dim
self.emo_proj = nn.Sequential(
nn.Linear(input_dim_emotion, hidden_dim),
nn.LayerNorm(hidden_dim),
nn.Dropout(dropout)
)
self.emotion_encoder = nn.ModuleList([
TransformerEncoderLayer(
input_dim=hidden_dim,
num_heads=num_transformer_heads,
dropout=dropout,
positional_encoding=positional_encoding
) for _ in range(tr_layer_number)
])
self.emotion_fc_out = nn.Sequential(
nn.Linear(hidden_dim, out_features),
nn.LayerNorm(out_features),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(out_features, num_emotions)
)
def forward(self, emotion_input=None, personality_input=None, return_features=False):
emo = self.emo_proj(emotion_input.float())
for layer in self.emotion_encoder:
emo += layer(emo, emo, emo)
out_emo = self.emotion_fc_out(emo.mean(dim=1))
if return_features:
return {
'emotion_logits': out_emo,
'last_encoder_features': emo,
}
else:
return {'emotion_logits': out_emo}
class PersonalityTransformer(nn.Module):
def __init__(self, input_dim_emotion=512, input_dim_personality=512, hidden_dim=128, out_features=512, mamba_layer_number=2, mamba_d_model=256, per_activation="sigmoid", positional_encoding=True, num_transformer_heads=4, tr_layer_number=1, dropout=0.1, num_emotions=7, num_traits=5, device='cpu'):
super().__init__()
self.hidden_dim = hidden_dim
self.per_proj = nn.Sequential(
nn.Linear(input_dim_personality, hidden_dim),
nn.LayerNorm(hidden_dim),
nn.Dropout(dropout)
)
self.personality_encoder = nn.ModuleList([
TransformerEncoderLayer(
input_dim=hidden_dim,
num_heads=num_transformer_heads,
dropout=dropout,
positional_encoding=positional_encoding
) for _ in range(tr_layer_number)
])
self.personality_fc_out = nn.Sequential(
nn.Linear(hidden_dim, out_features),
nn.LayerNorm(out_features),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(out_features, num_traits)
)
if per_activation == "sigmoid":
self.activation = nn.Sigmoid()
elif per_activation == "relu":
self.activation = nn.ReLU()
def forward(self, emotion_input=None, personality_input=None, return_features=False, activation=True):
per = self.per_proj(personality_input.float())
for layer in self.personality_encoder:
per += layer(per, per, per)
out_per = self.personality_fc_out(per.mean(dim=1))
if return_features:
return {
'personality_scores': self.activation(out_per) if activation else out_per,
'last_encoder_features': per,
}
else:
return {'personality_scores': self.activation(out_per) if activation else out_per}
class PersonalityMamba(nn.Module):
def __init__(self, input_dim_emotion=512, input_dim_personality=512, hidden_dim=128, out_features=512, mamba_layer_number=2, mamba_d_model=256, per_activation="sigmoid", positional_encoding=True, num_transformer_heads=4, tr_layer_number=1, dropout=0.1, num_emotions=7, num_traits=5, device='cpu'):
super().__init__()
self.hidden_dim = hidden_dim
self.per_proj = nn.Sequential(
nn.Linear(input_dim_personality, hidden_dim),
nn.LayerNorm(hidden_dim),
nn.Dropout(dropout)
)
self.personality_encoder = nn.ModuleList([
CustomMambaBlock(hidden_dim, mamba_d_model, dropout=dropout)
for _ in range(mamba_layer_number)
])
self.personality_fc_out = nn.Sequential(
nn.Linear(hidden_dim, out_features),
nn.LayerNorm(out_features),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(out_features, num_traits)
)
if per_activation == "sigmoid":
self.activation = nn.Sigmoid()
elif per_activation == "relu":
self.activation = nn.ReLU()
def forward(self, emotion_input=None, personality_input=None, return_features=False, activation=True):
per = self.per_proj(personality_input.float())
for layer in self.personality_encoder:
per = layer(per)
out_per = self.personality_fc_out(per.mean(dim=1))
if return_features:
return {
'personality_scores': self.activation(out_per) if activation else out_per,
'last_encoder_features': per,
}
else:
return {'personality_scores': self.activation(out_per) if activation else out_per}
class FusionTransformer(nn.Module):
def __init__(self, emo_model, per_model, input_dim_emotion=512, input_dim_personality=512, hidden_dim=128, out_features=512, mamba_layer_number=2, mamba_d_model=256, per_activation="sigmoid", positional_encoding=True, num_transformer_heads=4, tr_layer_number=1, dropout=0.1, num_emotions=7, num_traits=5, device='cpu'):
super().__init__()
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.hidden_dim = hidden_dim
self.emo_model = emo_model
self.per_model = per_model
for param in self.emo_model.parameters():
param.requires_grad = False
for param in self.per_model.parameters():
param.requires_grad = False
self.emo_proj = nn.Sequential(
nn.Linear(self.emo_model.hidden_dim, hidden_dim),
nn.LayerNorm(hidden_dim),
nn.Dropout(dropout)
)
self.per_proj = nn.Sequential(
nn.Linear(self.per_model.hidden_dim, hidden_dim),
nn.LayerNorm(hidden_dim),
nn.Dropout(dropout)
)
self.emotion_to_personality_attn = nn.ModuleList([
TransformerEncoderLayer(
input_dim=hidden_dim,
num_heads=num_transformer_heads,
dropout=dropout,
positional_encoding=positional_encoding
) for _ in range(tr_layer_number)
])
self.personality_to_emotion_attn = nn.ModuleList([
TransformerEncoderLayer(
input_dim=hidden_dim,
num_heads=num_transformer_heads,
dropout=dropout,
positional_encoding=positional_encoding
) for _ in range(tr_layer_number)
])
self.emotion_personality_fc_out = nn.Sequential(
nn.Linear(hidden_dim*2, out_features),
nn.LayerNorm(out_features),
nn.SiLU(),
nn.Dropout(dropout),
nn.Linear(out_features, num_emotions)
)
self.personality_emotion_fc_out = nn.Sequential(
nn.Linear(hidden_dim*2, out_features),
nn.LayerNorm(out_features),
nn.SiLU(),
nn.Dropout(dropout),
nn.Linear(out_features, num_traits)
)
if per_activation == "sigmoid":
self.activation = nn.Sigmoid()
elif per_activation == "relu":
self.activation = nn.ReLU()
def forward(self, emotion_input=None, personality_input=None, return_features=False):
emo_features = self.emo_model(emotion_input=emotion_input.float(), return_features=True)
per_features = self.per_model(personality_input=personality_input.float(), return_features=True)
emo_emd = self.emo_proj(emo_features['last_encoder_features'])
per_emd = self.per_proj(per_features['last_encoder_features'])
# padding
max_len = max(emo_emd.shape[1], per_emd.shape[1])
emo_emd = emo_emd.cpu().detach().numpy()
per_emd = per_emd.cpu().detach().numpy()
emo_emd = np.pad(emo_emd[:, :max_len, :], ((0, 0), (0, max(0, max_len - emo_emd.shape[1])), (0, 0)), "constant")
per_emd = np.pad(per_emd[:, :max_len, :], ((0, 0), (0, max(0, max_len - per_emd.shape[1])), (0, 0)), "constant")
emo_emd = torch.tensor(emo_emd, device=self.device)
per_emd = torch.tensor(per_emd, device=self.device)
for layer in self.emotion_to_personality_attn:
emo_emd += layer(emo_emd, per_emd, per_emd)
for layer in self.personality_to_emotion_attn:
per_emd += layer(per_emd, emo_emd, emo_emd)
fused = torch.cat([emo_emd, per_emd], dim=-1)
emotion_logits = self.emotion_personality_fc_out(fused.mean(dim=1))
personality_scores = self.personality_emotion_fc_out(fused.mean(dim=1))
if return_features:
return {
'emotion_logits': (emotion_logits+emo_features['emotion_logits'])/2,
'personality_scores': (self.activation(personality_scores)+per_features['personality_scores'])/2,
'last_emo_encoder_features': emo_emd,
'last_per_encoder_features': per_emd,
}
else:
return {'emotion_logits': (emotion_logits+emo_features['emotion_logits'])/2,
'personality_scores': (self.activation(personality_scores)+per_features['personality_scores'])/2,}
class ModalityProjector(nn.Module):
def __init__(self, in_dim, out_dim, dropout=0.1):
super().__init__()
self.proj = nn.Sequential(
nn.Linear(in_dim, out_dim),
nn.ReLU(),
nn.Dropout(dropout),
nn.LayerNorm(out_dim),
)
def forward(self, x):
return self.proj(x)
class AdapterFusion(nn.Module):
def __init__(self, hidden_dim, dropout=0.1):
super().__init__()
self.adapter = nn.Sequential(
nn.Linear(hidden_dim, hidden_dim // 2),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(hidden_dim // 2, hidden_dim),
)
self.layernorm = nn.LayerNorm(hidden_dim)
def forward(self, x):
return self.layernorm(x + self.adapter(x))
class GuideBank(nn.Module):
def __init__(self, out_dim, hidden_dim):
super().__init__()
self.embeddings = nn.Parameter(torch.randn(out_dim, hidden_dim))
def forward(self):
return self.embeddings
class GraphAttentionLayer(nn.Module):
"""Standard graph attention layer for multimodal fusion."""
def __init__(self, in_dim, out_dim=None, dropout=0.1, alpha=0.2):
super(GraphAttentionLayer, self).__init__()
out_dim = out_dim or in_dim
self.W = nn.Linear(in_dim, out_dim, bias=False)
self.a = nn.Parameter(torch.empty(size=(2 * out_dim, 1)))
nn.init.xavier_uniform_(self.a.data, gain=1.414)
self.leakyrelu = nn.LeakyReLU(alpha)
self.dropout = nn.Dropout(dropout)
self.out_dim = out_dim
def forward(self, h, adj):
"""
h: [B, N, D]
adj: [B, N, N] binary mask
"""
B, N, D = h.size()
Wh = self.W(h) # [B, N, D']
Wh_i = Wh.unsqueeze(2).expand(-1, -1, N, -1) # [B, N, N, D']
Wh_j = Wh.unsqueeze(1).expand(-1, N, -1, -1) # [B, N, N, D']
a_input = torch.cat([Wh_i, Wh_j], dim=-1) # [B, N, N, 2D']
e = self.leakyrelu(torch.matmul(a_input, self.a).squeeze(-1)) # [B, N, N]
zero_vec = -9e15 * torch.ones_like(e)
attention = torch.where(adj > 0, e, zero_vec) # mask non-neighbors
attention = F.softmax(attention, dim=-1)
attention = self.dropout(attention)
h_prime = torch.matmul(attention, Wh) # [B, N, D']
return h_prime
class FeatureSlice(nn.Module):
"""
Slice concatenated vector [emo‖pkl]:
- mode='both' → return as is
- mode='emo' → take the left half (Emo)
- mode='pkl' → take the right half (PKL)
"""
def __init__(self, mode: str = "both"):
super().__init__()
if mode not in ("both", "emo", "pkl"):
raise ValueError("feature_slice must be 'both' | 'emo' | 'pkl'")
self.mode = mode
def forward(self, x: torch.Tensor) -> torch.Tensor:
if self.mode == "both":
return x
half = x.size(-1) // 2
return x[..., :half] if self.mode == "emo" else x[..., half:]
class MultiModalFusionModelWithAblation(nn.Module):
def __init__(
self,
hidden_dim=512,
num_heads=8,
dropout=0.1,
emo_out_dim=7,
pkl_out_dim=5,
device='cpu',
ablation_config=None,
attention=None
):
super().__init__()
self.hidden_dim = hidden_dim
self.num_heads = num_heads
self.dropout = dropout
self.device = device
self.attention = attention
# Ablation configuration
self.ablation_config = ablation_config or {}
self.disabled_modalities = set(self.ablation_config.get("disabled_modalities", []))
self.disable_graph_attn = self.ablation_config.get("disable_graph_attn", False)
self.disable_cross_attn = self.ablation_config.get("disable_cross_attn", False)
self.disable_emo_logit_proj = self.ablation_config.get("disable_emo_logit_proj", False)
self.disable_pkl_logit_proj = self.ablation_config.get("disable_pkl_logit_proj", False)
self.disable_guide_bank = self.ablation_config.get("disable_guide_bank", False)
self.modalities = {
'video': 1024 * 2,
'text': 128 * 2,
}
self.projectors = nn.ModuleDict({
mod: nn.Sequential(
ModalityProjector(in_dim, hidden_dim, dropout),
AdapterFusion(hidden_dim, dropout),
)
for mod, in_dim in self.modalities.items()
})
if not self.disable_graph_attn:
self.graph_attn = GraphAttentionLayer(hidden_dim, dropout=dropout)
# self.graph_attn = GraphAttentionLayer_v4(hidden_dim, dropout=dropout)
self.emo_query = nn.Parameter(torch.randn(1, 1, hidden_dim))
self.pkl_query = nn.Parameter(torch.randn(1, 1, hidden_dim))
if not self.disable_cross_attn:
self.cross_attn = nn.MultiheadAttention(
embed_dim=hidden_dim,
num_heads=num_heads,
dropout=dropout,
batch_first=True,
)
self.emo_head = nn.Linear(hidden_dim, emo_out_dim)
self.pkl_head = nn.Linear(hidden_dim, pkl_out_dim)
# Optional learned fusers (kept for reference)
# self.emo_fusion = nn.Linear(2, 1)
# self.pkl_fusion = nn.Linear(2, 1)
if not self.disable_guide_bank:
self.guide_bank_emo = GuideBank(emo_out_dim, hidden_dim)
self.guide_bank_pkl = GuideBank(pkl_out_dim, hidden_dim)
if not self.disable_emo_logit_proj:
self.emo_logit_proj = nn.Linear(emo_out_dim, hidden_dim)
if not self.disable_pkl_logit_proj:
self.per_logit_proj = nn.Linear(pkl_out_dim, hidden_dim)
def forward(self, batch):
x_mods = []
valid_modalities = []
for mod, feat in batch['features'].items():
if feat is not None and mod in self.projectors and mod not in self.disabled_modalities:
x_proj = self.projectors[mod](feat.to(self.device)) # [B, D]
x_mods.append(x_proj)
valid_modalities.append(mod)
if not x_mods:
raise ValueError("No valid modality features found")
x_mods = torch.stack(x_mods, dim=1) # [B, N, D]
B, N, D = x_mods.size()
if self.disable_graph_attn:
context = x_mods
else:
adj = torch.ones(B, N, N, device=self.device)
context = self.graph_attn(x_mods, adj) # [B, N, D]
emo_q = self.emo_query.expand(B, 1, -1) # [B, 1, D]
pkl_q = self.pkl_query.expand(B, 1, -1) # [B, 1, D]
if self.disable_cross_attn:
emo_repr = context.mean(dim=1)
pkl_repr = context.mean(dim=1)
else:
emo_repr, _ = self.cross_attn(emo_q, context, context)
pkl_repr, _ = self.cross_attn(pkl_q, context, context)
emo_repr = emo_repr.squeeze(1)
pkl_repr = pkl_repr.squeeze(1)
emo_logit_feats = []
per_logit_feats = []
for mod in valid_modalities:
emo_logit_feats.append(batch['emotion_logits'][mod].to(self.device))
per_logit_feats.append(batch['personality_scores'][mod].to(self.device))
if emo_logit_feats and not self.disable_emo_logit_proj:
emo_repr += self.emo_logit_proj(torch.stack(emo_logit_feats).mean(dim=0))
if per_logit_feats and not self.disable_pkl_logit_proj:
pkl_repr += self.per_logit_proj(torch.stack(per_logit_feats).mean(dim=0))
emo_pred = self.emo_head(emo_repr)
pkl_pred = torch.sigmoid(self.pkl_head(pkl_repr))
if not self.disable_guide_bank:
if not self.ablation_config.get("disable_guide_emo", False):
guides_emo = self.guide_bank_emo() # [emo_out_dim, D]
emo_sim = F.cosine_similarity(emo_repr.unsqueeze(1), guides_emo.unsqueeze(0), dim=-1)
# emo_stack = torch.stack([emo_pred, emo_sim], dim=-1) # [B, C, 2]
# emo_final = self.emo_fusion(emo_stack).squeeze(-1) # [B, C]
emo_final = (emo_pred + emo_sim) / 2
else:
emo_final = emo_pred
if not self.ablation_config.get("disable_guide_pkl", False):
guides_pkl = self.guide_bank_pkl() # [pkl_out_dim, D]
pkl_sim = F.cosine_similarity(pkl_repr.unsqueeze(1), guides_pkl.unsqueeze(0), dim=-1)
# pkl_stack = torch.stack([pkl_pred, torch.sigmoid(pkl_sim)], dim=-1)
# pkl_final = self.pkl_fusion(pkl_stack).squeeze(-1)
pkl_final = (pkl_pred + torch.sigmoid(pkl_sim)) / 2
else:
pkl_final = pkl_pred
else:
emo_final = emo_pred
pkl_final = pkl_pred
return {'emotion_logits': emo_final, "personality_scores": pkl_final}