mathsnap-ai / models /decoder.py
Reriiii
Initial deploy: Mini-CoMER HMER backend
297dd64
Raw History Blame Contribute Delete
3.82 kB
"""
CoMER decoder: Transformer with ARM (Attention Refinement Module).
Based on: https://github.com/Green-Wood/CoMER
Stripped from ICAL: removed SCCM, FusionModule, implicit/fusion heads.
"""
from typing import List
import torch
import torch.nn as nn
from einops import rearrange
from torch import FloatTensor, LongTensor
from .pos_enc import WordPosEnc
from .transformer.arm import AttentionRefinementModule
from .transformer.transformer_decoder import (
TransformerDecoder,
TransformerDecoderLayer,
)
def _build_transformer_decoder(
d_model: int,
nhead: int,
num_decoder_layers: int,
dim_feedforward: int,
dropout: float,
dc: int,
cross_coverage: bool,
self_coverage: bool,
) -> TransformerDecoder:
decoder_layer = TransformerDecoderLayer(
d_model=d_model,
nhead=nhead,
dim_feedforward=dim_feedforward,
dropout=dropout,
)
if cross_coverage or self_coverage:
arm = AttentionRefinementModule(
nhead, dc, cross_coverage, self_coverage)
else:
arm = None
decoder = TransformerDecoder(decoder_layer, num_decoder_layers, arm)
return decoder
class Decoder(nn.Module):
def __init__(
self,
d_model: int,
nhead: int,
num_decoder_layers: int,
dim_feedforward: int,
dropout: float,
dc: int,
cross_coverage: bool,
self_coverage: bool,
vocab_size: int,
pad_idx: int = 0,
):
super().__init__()
self.pad_idx = pad_idx
self.word_embed = nn.Sequential(
nn.Embedding(vocab_size, d_model), nn.LayerNorm(d_model)
)
self.pos_enc = WordPosEnc(d_model=d_model)
self.norm = nn.LayerNorm(d_model)
self.model = _build_transformer_decoder(
d_model=d_model,
nhead=nhead,
num_decoder_layers=num_decoder_layers,
dim_feedforward=dim_feedforward,
dropout=dropout,
dc=dc,
cross_coverage=cross_coverage,
self_coverage=self_coverage,
)
# Single projection head (CoMER)
self.proj = nn.Linear(d_model, vocab_size)
def _build_attention_mask(self, length, device):
mask = torch.full(
(length, length), fill_value=1, dtype=torch.bool, device=device
)
mask.triu_(1)
return mask
def forward(
self, src: FloatTensor, src_mask: LongTensor, tgt: LongTensor
) -> FloatTensor:
"""
Parameters
----------
src : FloatTensor [b, h, w, d]
src_mask: LongTensor [b, h, w]
tgt : LongTensor [b, l]
Returns
-------
FloatTensor [b, l, vocab_size]
"""
_, l = tgt.size()
tgt_mask = self._build_attention_mask(l, tgt.device)
tgt_pad_mask = tgt == self.pad_idx
tgt = self.word_embed(tgt) # [b, l, d]
tgt = self.pos_enc(tgt) # [b, l, d]
tgt = self.norm(tgt)
h = src.shape[1]
src = rearrange(src, "b h w d -> (h w) b d")
src_mask = rearrange(src_mask, "b h w -> b (h w)")
tgt = rearrange(tgt, "b l d -> l b d")
out = self.model(
tgt=tgt,
memory=src,
height=h,
tgt_mask=tgt_mask,
tgt_key_padding_mask=tgt_pad_mask,
memory_key_padding_mask=src_mask,
)
out = rearrange(out, "l b d -> b l d")
out = self.proj(out)
return out
def transform(
self, src: List[FloatTensor], src_mask: List[LongTensor], input_ids: LongTensor
) -> FloatTensor:
"""For beam search: run forward and return logits."""
assert len(src) == 1 and len(src_mask) == 1
return self(src[0], src_mask[0], input_ids)