Source code for mir.ml.encoder
"""Forward codec: a CNN that maps a CDR3 sequence to its TCREMP embedding.
The GPU-friendly approximation of the prototype mapping (irrm-codec forward model).
Trained with **free supervision** — targets are TCREMP embeddings *computed* by
:class:`mir.embedding.tcremp.TCREmp`, so labelled data is never the bottleneck.
"""
from __future__ import annotations
import torch
from torch import nn
from mir.ml.tokenize import FIXED_LEN, N_TOKENS
[docs]
class SequenceEncoder(nn.Module):
"""1-D CNN over the length-40 one-hot CDR3 → a fixed embedding vector.
Args:
embed_dim: Output dimensionality (the target embedding width).
channels: Conv channel sizes.
kernel: Conv kernel size.
hidden: Width of the dense head.
dropout: Dropout on the dense head.
"""
def __init__(
self,
embed_dim: int,
channels: tuple[int, ...] = (64, 128),
kernel: int = 3,
hidden: int = 512,
dropout: float = 0.1,
):
super().__init__()
layers: list[nn.Module] = []
c_in = N_TOKENS
for c_out in channels:
layers += [
nn.Conv1d(c_in, c_out, kernel, padding=kernel // 2),
nn.BatchNorm1d(c_out),
nn.ReLU(),
]
c_in = c_out
self.conv = nn.Sequential(*layers)
self.head = nn.Sequential(
nn.Flatten(),
nn.Linear(c_in * FIXED_LEN, hidden),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(hidden, embed_dim),
)
[docs]
def forward(self, x: torch.Tensor) -> torch.Tensor:
# x: (B, FIXED_LEN, N_TOKENS) -> conv wants (B, N_TOKENS, FIXED_LEN)
return self.head(self.conv(x.transpose(1, 2)))