Source code for mir.ml.decoder
"""Inverse codec: reconstruct a CDR3 sequence from its (compact) embedding.
A **parallel** decoder (irrm-codec style): the compact PCA code is expanded and
refined by 1-D convolutions into length-40 per-position token logits in one shot
(no autoregression). Trained with cross-entropy against the fixed-length-40
tokenization; the reconstructed string drops gap tokens.
"""
from __future__ import annotations
import torch
from torch import nn
from mir.ml.tokenize import AA, FIXED_LEN, N_TOKENS
_GAP_IDX = N_TOKENS - 1
[docs]
class SequenceDecoder(nn.Module):
"""Compact embedding code → ``(B, 40, 21)`` per-position token logits.
Args:
code_dim: Input code width (e.g. #PCs of the compact embedding).
hidden: Width of the expansion MLP.
channels: Conv refinement channels (first is the reshaped depth).
kernel: Conv kernel size.
"""
def __init__(
self,
code_dim: int,
hidden: int = 512,
channels: tuple[int, ...] = (128, 64),
kernel: int = 3,
):
super().__init__()
c0 = channels[0]
self._c0 = c0
self.fc = nn.Sequential(
nn.Linear(code_dim, hidden),
nn.ReLU(),
nn.Linear(hidden, c0 * FIXED_LEN),
nn.ReLU(),
)
layers: list[nn.Module] = []
c_in = c0
for c_out in channels[1:]:
layers += [
nn.Conv1d(c_in, c_out, kernel, padding=kernel // 2),
nn.BatchNorm1d(c_out),
nn.ReLU(),
]
c_in = c_out
layers += [nn.Conv1d(c_in, N_TOKENS, kernel, padding=kernel // 2)]
self.conv = nn.Sequential(*layers)
[docs]
def forward(self, z: torch.Tensor) -> torch.Tensor:
h = self.fc(z).view(z.size(0), self._c0, FIXED_LEN)
return self.conv(h).transpose(1, 2) # (B, FIXED_LEN, N_TOKENS)
[docs]
def tokens_to_seq(idx_row) -> str:
"""Map a length-40 token-index row back to an amino-acid string (gaps dropped)."""
return "".join(AA[i] for i in idx_row if 0 <= int(i) < len(AA))