| import torch |
| import torch.nn as nn |
| from torch.autograd import Function |
|
|
|
|
| class GradientReversalFunction(Function): |
| @staticmethod |
| def forward(ctx, input_tensor, lambda_value): |
| ctx.lambda_value = lambda_value |
| return input_tensor.view_as(input_tensor) |
|
|
| @staticmethod |
| def backward(ctx, grad_output): |
| return -ctx.lambda_value * grad_output, None |
|
|
|
|
| class GradientReversalLayer(nn.Module): |
| def __init__(self, lambda_value: float = 1.0): |
| super().__init__() |
| self.lambda_value = lambda_value |
|
|
| def forward(self, input_tensor: torch.Tensor) -> torch.Tensor: |
| return GradientReversalFunction.apply(input_tensor, self.lambda_value) |
|
|
|
|
| class DomainDiscriminator(nn.Module): |
| def __init__(self, input_size: int, hidden_size: int = 128): |
| super().__init__() |
| self.network = nn.Sequential( |
| nn.Linear(input_size, hidden_size), |
| nn.ReLU(), |
| nn.Dropout(0.3), |
| nn.Linear(hidden_size, 2), |
| ) |
|
|
| def forward(self, input_tensor: torch.Tensor) -> torch.Tensor: |
| return self.network(input_tensor) |
|
|
|
|
| class ConvolutionalFeatureExtractor(nn.Module): |
| def __init__(self): |
| super().__init__() |
| self.block_1 = nn.Sequential( |
| nn.Conv2d(1, 32, kernel_size=3, padding=1), |
| nn.BatchNorm2d(32), |
| nn.ReLU(), |
| nn.MaxPool2d(2), |
| ) |
| self.block_2 = nn.Sequential( |
| nn.Conv2d(32, 64, kernel_size=3, padding=1), |
| nn.BatchNorm2d(64), |
| nn.ReLU(), |
| nn.MaxPool2d(2), |
| ) |
| self.block_3 = nn.Sequential( |
| nn.Conv2d(64, 128, kernel_size=3, padding=1), |
| nn.BatchNorm2d(128), |
| nn.ReLU(), |
| nn.MaxPool2d(2), |
| ) |
|
|
| def forward(self, input_tensor: torch.Tensor) -> torch.Tensor: |
| x = self.block_1(input_tensor) |
| x = self.block_2(x) |
| x = self.block_3(x) |
| return x |
|
|
|
|
| class AttentionPooling(nn.Module): |
| def __init__(self, feature_size: int): |
| super().__init__() |
| self.attention_layer = nn.Linear(feature_size, 1) |
|
|
| def forward(self, sequence_tensor: torch.Tensor) -> torch.Tensor: |
| attention_scores = self.attention_layer(sequence_tensor).squeeze(-1) |
| attention_weights = torch.softmax(attention_scores, dim=1).unsqueeze(-1) |
| pooled_output = torch.sum(sequence_tensor * attention_weights, dim=1) |
| return pooled_output |
|
|
|
|
| class TemporalAttentionPooling(nn.Module): |
| def __init__(self, feature_size: int, attention_hidden_size: int = 128): |
| super().__init__() |
| self.score_network = nn.Sequential( |
| nn.Linear(feature_size, attention_hidden_size), |
| nn.Tanh(), |
| nn.Linear(attention_hidden_size, 1), |
| ) |
|
|
| def forward(self, sequence_tensor: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: |
| attention_scores = self.score_network(sequence_tensor).squeeze(-1) |
| attention_weights = torch.softmax(attention_scores, dim=1).unsqueeze(-1) |
| pooled_output = torch.sum(sequence_tensor * attention_weights, dim=1) |
| return pooled_output, attention_weights |
|
|
|
|
| class CnnBiLstmAdversarialModel(nn.Module): |
| def __init__(self, num_emotions: int = 7): |
| super().__init__() |
| self.cnn_feature_extractor = ConvolutionalFeatureExtractor() |
| self.bi_lstm = nn.LSTM( |
| input_size=128 * 16, |
| hidden_size=128, |
| num_layers=1, |
| batch_first=True, |
| bidirectional=True, |
| ) |
| self.attention_pooling = AttentionPooling(feature_size=256) |
| self.shared_projection = nn.Sequential( |
| nn.Linear(256, 256), |
| nn.LayerNorm(256), |
| nn.ReLU(), |
| nn.Dropout(0.3), |
| ) |
| self.emotion_head = nn.Sequential( |
| nn.Linear(256, 128), |
| nn.ReLU(), |
| nn.Dropout(0.3), |
| nn.Linear(128, num_emotions), |
| ) |
| self.impairment_head = nn.Sequential( |
| nn.Linear(256, 128), |
| nn.ReLU(), |
| nn.Dropout(0.3), |
| nn.Linear(128, 2), |
| ) |
| self.gradient_reversal = GradientReversalLayer(lambda_value=1.0) |
| self.domain_discriminator = DomainDiscriminator(256, hidden_size=128) |
|
|
| def forward( |
| self, |
| mel_spectrogram: torch.Tensor, |
| return_domain_output: bool = True, |
| ) -> dict[str, torch.Tensor]: |
| cnn_output = self.cnn_feature_extractor(mel_spectrogram) |
| batch_size, channels, height, width = cnn_output.shape |
| sequence_input = ( |
| cnn_output.permute(0, 3, 1, 2).contiguous().view(batch_size, width, channels * height) |
| ) |
|
|
| lstm_output, _ = self.bi_lstm(sequence_input) |
| shared_representation = self.attention_pooling(lstm_output) |
| shared_representation = self.shared_projection(shared_representation) |
|
|
| emotion_logits = self.emotion_head(shared_representation) |
| impairment_logits = self.impairment_head(shared_representation) |
|
|
| outputs = { |
| "shared_representation": shared_representation, |
| "emotion_logits": emotion_logits, |
| "impairment_logits": impairment_logits, |
| } |
|
|
| if return_domain_output: |
| reversed_features = self.gradient_reversal(shared_representation) |
| outputs["domain_logits"] = self.domain_discriminator(reversed_features) |
|
|
| return outputs |
|
|
|
|
| class CnnAttentionAdversarialModel(nn.Module): |
| def __init__(self, num_emotions: int = 7): |
| super().__init__() |
|
|
| self.cnn_feature_extractor = ConvolutionalFeatureExtractor() |
| self.attention_pooling = TemporalAttentionPooling( |
| feature_size=128 * 16, |
| attention_hidden_size=128, |
| ) |
|
|
| self.shared_projection = nn.Sequential( |
| nn.Linear(128 * 16, 256), |
| nn.LayerNorm(256), |
| nn.ReLU(), |
| nn.Dropout(0.3), |
| ) |
|
|
| self.emotion_head = nn.Sequential( |
| nn.Linear(256, 128), |
| nn.ReLU(), |
| nn.Dropout(0.3), |
| nn.Linear(128, num_emotions), |
| ) |
|
|
| self.impairment_head = nn.Sequential( |
| nn.Linear(256, 128), |
| nn.ReLU(), |
| nn.Dropout(0.3), |
| nn.Linear(128, 2), |
| ) |
|
|
| self.gradient_reversal = GradientReversalLayer(lambda_value=1.0) |
| self.domain_discriminator = DomainDiscriminator(256, hidden_size=128) |
|
|
| def forward( |
| self, |
| mel_spectrogram: torch.Tensor, |
| return_domain_output: bool = True, |
| return_attention: bool = False, |
| ) -> dict[str, torch.Tensor]: |
| cnn_output = self.cnn_feature_extractor(mel_spectrogram) |
| batch_size, channels, height, width = cnn_output.shape |
| sequence_input = ( |
| cnn_output.permute(0, 3, 1, 2).contiguous().view(batch_size, width, channels * height) |
| ) |
|
|
| pooled_output, attention_weights = self.attention_pooling(sequence_input) |
| shared_representation = self.shared_projection(pooled_output) |
|
|
| emotion_logits = self.emotion_head(shared_representation) |
| impairment_logits = self.impairment_head(shared_representation) |
|
|
| outputs = { |
| "shared_representation": shared_representation, |
| "emotion_logits": emotion_logits, |
| "impairment_logits": impairment_logits, |
| } |
|
|
| if return_domain_output: |
| reversed_features = self.gradient_reversal(shared_representation) |
| outputs["domain_logits"] = self.domain_discriminator(reversed_features) |
|
|
| if return_attention: |
| outputs["attention_weights"] = attention_weights |
|
|
| return outputs |
|
|
|
|
| class MADA(nn.Module): |
| """Notebook-aligned Multi-Adversarial Domain Adaptation model.""" |
|
|
| def __init__( |
| self, |
| in_dim: int = 418, |
| hidden: int = 512, |
| latent: int = 256, |
| n_emotions: int = 7, |
| dropout: float = 0.35, |
| ): |
| super().__init__() |
| self.encoder = nn.Sequential( |
| nn.Linear(in_dim, hidden), |
| nn.LayerNorm(hidden), |
| nn.GELU(), |
| nn.Dropout(dropout), |
| nn.Linear(hidden, hidden), |
| nn.LayerNorm(hidden), |
| nn.GELU(), |
| nn.Dropout(dropout), |
| nn.Linear(hidden, latent), |
| nn.LayerNorm(latent), |
| nn.GELU(), |
| ) |
| self.emotion_head = nn.Sequential( |
| nn.Linear(latent, 256), |
| nn.ReLU(), |
| nn.Dropout(dropout), |
| nn.Linear(256, 128), |
| nn.ReLU(), |
| nn.Dropout(dropout / 2), |
| nn.Linear(128, n_emotions), |
| ) |
| self.impairment_head = nn.Sequential( |
| nn.Linear(latent, 64), |
| nn.ReLU(), |
| nn.Dropout(dropout), |
| nn.Linear(64, 2), |
| ) |
| self.grl = GradientReversalLayer(0.0) |
| self.domain_disc = nn.Sequential( |
| nn.Linear(latent, 128), |
| nn.ReLU(), |
| nn.Dropout(0.3), |
| nn.Linear(128, 2), |
| ) |
|
|
| def set_lambda(self, lambda_value: float) -> None: |
| self.grl.lambda_value = lambda_value |
|
|
| def forward( |
| self, |
| input_tensor: torch.Tensor, |
| return_domain_output: bool = True, |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor | None]: |
| latent_representation = self.encoder(input_tensor) |
| emotion_logits = self.emotion_head(latent_representation) |
| impairment_logits = self.impairment_head(latent_representation) |
| if not return_domain_output: |
| return latent_representation, emotion_logits, impairment_logits, None |
| domain_logits = self.domain_disc(self.grl(latent_representation)) |
| return latent_representation, emotion_logits, impairment_logits, domain_logits |
|
|