| import torch |
| import torch.nn as nn |
| from typing import Any |
|
|
| class CrossAttentionFusion(nn.Module): |
| """Fuses waveform and spectral features using Multi-Head Cross-Attention.""" |
| def __init__(self, config_attn: Any, wave_channels: int, spec_channels: int): |
| super().__init__() |
| self.dim = config_attn.dim |
| self.num_heads = config_attn.num_heads |
| |
| |
| self.proj_wave = nn.Conv1d(wave_channels, self.dim, kernel_size=1) |
| self.proj_spec = nn.Conv1d(spec_channels, self.dim, kernel_size=1) |
| |
| |
| self.cross_attn = nn.MultiheadAttention( |
| embed_dim=self.dim, |
| num_heads=self.num_heads, |
| batch_first=True |
| ) |
| |
| self.norm = nn.LayerNorm(self.dim) |
| |
| |
| self.out_channels = self.dim |
|
|
| def forward(self, feat_wave: torch.Tensor, feat_spec: torch.Tensor) -> torch.Tensor: |
| """ |
| Args: |
| feat_wave: Waveform feature tensor of shape (batch, wave_channels, time_steps). |
| feat_spec: Spectral feature tensor of shape (batch, spec_channels, time_steps_spec). |
| |
| Returns: |
| Fused latent tensor of shape (batch, out_channels, time_steps). |
| """ |
| |
| if feat_spec.shape[-1] != feat_wave.shape[-1]: |
| feat_spec = nn.functional.interpolate( |
| feat_spec, size=feat_wave.shape[-1], mode='linear', align_corners=False |
| ) |
| |
| |
| q = self.proj_wave(feat_wave) |
| kv = self.proj_spec(feat_spec) |
| |
| |
| q_t = q.transpose(1, 2) |
| kv_t = kv.transpose(1, 2) |
| |
| |
| |
| attn_out, _ = self.cross_attn(query=q_t, key=kv_t, value=kv_t) |
| |
| |
| fused = self.norm(q_t + attn_out) |
| |
| |
| return fused.transpose(1, 2) |
|
|