SleepMaMi / models /fusion.py
KeondoPark
Code refactoring WIP
a532d9f
Raw History Blame Contribute Delete
4.81 kB
import torch
import torch.nn as nn
from models.transformer_modules import BlockFlashAttn, Mlp, freq_pos_enc, PatchUpsample
class FusionModule(nn.Module):
def __init__(self, encoder_embed_dim=128,
num_heads=4, mlp_ratio=4, norm_layer=nn.LayerNorm, fusion_depth=2,
fusion_method='concat', pos_enc='sinusoidal'):
super().__init__()
self.fusion_method = fusion_method
self.num_heads = num_heads
if self.fusion_method == 'concat':
self.prenorm = norm_layer(encoder_embed_dim * 2) # prenorm for the weighted sum features
# self.merge = nn.Linear(encoder_embed_dim * 2, encoder_embed_dim)
self.merge = Mlp(in_features=encoder_embed_dim * 2, out_features=encoder_embed_dim)
elif self.fusion_method == 'cross_attn':
self.cross_attention_blocks = nn.ModuleList([
BlockFlashAttn(encoder_embed_dim, num_heads, mlp_ratio, qkv_bias=True, qk_scale=None, norm_layer=norm_layer)
# BlockFlashAttn(encoder_embed_dim, num_heads, mlp_ratio, qkv_bias=True, qk_scale=None, norm_layer=norm_layer)
for i in range(fusion_depth)])
self.post_norm = norm_layer(encoder_embed_dim)
self.pos_enc = pos_enc
if pos_enc == 'rope':
self.rope = freq_pos_enc(dim=encoder_embed_dim // num_heads, max_seq_len=128)
def forward(self, x, y):
if self.fusion_method == 'concat':
x = torch.cat([x, y], dim=-1)
x = self.prenorm(x) # apply prenorm before the forward_features
x = self.merge(x)
elif self.fusion_method == 'cross_attn':
if self.pos_enc == 'rope':
pe = self.rope.unsqueeze(0).to(dtype=x.dtype, device=x.device) # (1, max_seq_len, dim)
pe = pe[:, :x.shape[1], :] # (1, seq_len, dim)
pe = pe.repeat(x.shape[0], 1, 1) # (N, max_seq_len, dim)
pe2 = None
if not x.shape[1] == y.shape[1]:
pe2 = freq_pos_enc(dim=y.shape[2] // self.num_heads, max_seq_len=y.shape[1])
pe2 = pe2.unsqueeze(0).to(dtype=x.dtype, device=x.device) # (1, max_seq_len, dim)
pe2 = pe2.repeat(x.shape[0], 1, 1) # (N, max_seq_len, dim)
else:
pe = None
for blk in self.cross_attention_blocks:
x, _ = blk(x, y, rope=pe, rope2=pe2)
x = self.post_norm(x)
return x
class FusionUpsampleBlock(nn.Module):
def __init__(self, dim_list, fusion_method='cross_attn', fusion_depth=2, fusion_heads=4,
mlp_ratio=4., norm_layer=nn.LayerNorm, pos_enc='sinusoidal',
use_shared=True, use_upsample=True, modalities=["EEG","EOG"]):
super().__init__()
self.fusion_method = fusion_method
self.use_shared = use_shared
self.use_upsample = use_upsample
self.fusion = nn.ModuleDict()
self.upsampler = nn.ModuleDict()
self.dim_list = dim_list
self.modalities = modalities
for m in self.modalities:
if use_shared:
self.fusion[m] = FusionModule(encoder_embed_dim=dim_list[-1],
mlp_ratio=mlp_ratio, norm_layer=norm_layer,
fusion_method=fusion_method, fusion_depth=fusion_depth, num_heads=fusion_heads, pos_enc=pos_enc)
if use_upsample:
upsampler_list = []
for i in range(len(dim_list) - 1):
up = 2 if i > 0 else 5
upsampler_list.append(PatchUpsample(embed_dim=dim_list[1+i], up_dim=dim_list[i], upsample=up, norm_layer=norm_layer))
self.upsampler[m] = nn.ModuleList(upsampler_list)
def forward(self, x_shared, x_specific):
latents_fused = []
upsampled = []
for i, m in enumerate(self.modalities):
if self.use_shared:
x_fused = self.fusion[m](x_specific[-1][i], x_shared[-1][i])
else:
x_fused = x_specific[-1][i]
latents_fused.append(x_fused)
if self.use_upsample:
for j in range(len(self.dim_list)-2, -1, -1):
x_fused = self.upsampler[m][j](x_fused, x_specific[j][i])
upsampled.append(x_fused)
return latents_fused, upsampled