import math from collections import deque import numpy as np import torch from torch import nn import torch.nn.functional as F from torchvision import models class SelfAttention(nn.Module): """ A vanilla multi-head masked self-attention layer with a projection at the end. """ def __init__(self, n_embd, n_head, attn_pdrop, resid_pdrop): super().__init__() assert n_embd % n_head == 0 # key, query, value projections for all heads self.key = nn.Linear(n_embd, n_embd) self.query = nn.Linear(n_embd, n_embd) self.value = nn.Linear(n_embd, n_embd) # regularization self.attn_drop = nn.Dropout(attn_pdrop) self.resid_drop = nn.Dropout(resid_pdrop) # output projection self.proj = nn.Linear(n_embd, n_embd) self.n_head = n_head def forward(self, x): B, T, C = x.size() # calculate query, key, values for all heads in batch and move head forward to be the batch dim k = self.key(x).view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs) q = self.query(x).view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs) v = self.value(x).view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs) # self-attend: (B, nh, T, hs) x (B, nh, hs, T) -> (B, nh, T, T) att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1))) att = F.softmax(att, dim=-1) att = self.attn_drop(att) y = att @ v # (B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs) y = y.transpose(1, 2).contiguous().view(B, T, C) # re-assemble all head outputs side by side # output projection y = self.resid_drop(self.proj(y)) return y class Block(nn.Module): """ an unassuming Transformer block """ def __init__(self, n_embd, n_head, block_exp, attn_pdrop, resid_pdrop): super().__init__() self.ln1 = nn.LayerNorm(n_embd) self.ln2 = nn.LayerNorm(n_embd) self.attn = SelfAttention(n_embd, n_head, attn_pdrop, resid_pdrop) self.mlp = nn.Sequential( nn.Linear(n_embd, block_exp * n_embd), nn.ReLU(True), # changed from GELU nn.Linear(block_exp * n_embd, n_embd), nn.Dropout(resid_pdrop), ) def forward(self, x): B, T, C = x.size() x = x + self.attn(self.ln1(x)) x = x + self.mlp(self.ln2(x)) return x class TransFuse_layer(nn.Module): """ the full GPT language model, with a context size of block_size """ def __init__(self, n_embd, n_head, block_exp, n_layer, num_anchors, seq_len=1, embd_pdrop=0.1, attn_pdrop=0.1, resid_pdrop=0.1): super().__init__() self.n_embd = n_embd self.seq_len = seq_len self.vert_anchors = num_anchors self.horz_anchors = num_anchors # positional embedding parameter (learnable), image + lidar self.pos_emb = nn.Parameter(torch.zeros(1, 2 * seq_len * self.vert_anchors * self.horz_anchors, n_embd)) self.drop = nn.Dropout(embd_pdrop) # transformer self.blocks = nn.Sequential(*[Block(n_embd, n_head, block_exp, attn_pdrop, resid_pdrop) for layer in range(n_layer)]) # decoder head self.ln_f = nn.LayerNorm(n_embd) self.block_size = seq_len self.apply(self._init_weights) def get_block_size(self): return self.block_size def _init_weights(self, module): if isinstance(module, nn.Linear): module.weight.data.normal_(mean=0.0, std=0.02) if module.bias is not None: module.bias.data.zero_() elif isinstance(module, nn.LayerNorm): module.bias.data.zero_() module.weight.data.fill_(1.0) def forward(self, m1, m2): """ Args: m1 (tensor): B*seq_len, C, H, W m2 (tensor): B*seq_len, C, H, W """ bz = m2.shape[0] // self.seq_len h, w = m2.shape[2:4] # forward the image model for token embeddings m1 = m1.view(bz, self.seq_len, -1, h, w) m2 = m2.view(bz, self.seq_len, -1, h, w) # pad token embeddings along number of tokens dimension token_embeddings = torch.cat([m1, m2], dim=1).permute(0,1,3,4,2).contiguous() token_embeddings = token_embeddings.view(bz, -1, self.n_embd) # (B, an * T, C) # add (learnable) positional embedding for all tokens x = self.drop(self.pos_emb + token_embeddings) # (B, an * T, C) x = self.blocks(x) # (B, an * T, C) x = self.ln_f(x) # (B, an * T, C) x = x.view(bz, 2 * self.seq_len, self.vert_anchors, self.horz_anchors, self.n_embd) x = x.permute(0,1,4,2,3).contiguous() # same as token_embeddings m1_out = x[:, :self.seq_len, :, :, :].contiguous().view(bz * self.seq_len, -1, h, w) m2_out = x[:, self.seq_len:, :, :, :].contiguous().view(bz * self.seq_len, -1, h, w) print("modality1 output:", m1_out.max(), m1_out.min()) print("modality2 output:", m2_out.max(), m2_out.min()) return m1_out, m2_out if __name__ == "__main__": feature1 = torch.randn((4, 512, 20, 20)) feature2 = torch.randn((4, 512, 20, 20)) model = TransFuse_layer(n_embd=512, n_head=4, block_exp=4, n_layer=8, num_anchors=20, seq_len=1) print("TransFuse_layer:", model) feat1, feat2 = model(feature1, feature2) print(feat1.shape, feat2.shape)