SimpleP2G / src /simpleP2G_neigh.py
king159's picture
Release 10 SimpleP2G single-patch checkpoints (fold 0, seed 42)
30e5a24 verified
Raw History Blame Contribute Delete
4.69 kB
"""SimpleP2G-cross: target tokens and spatially positioned neighbor tokens."""
import torch
from torch import nn
from torch.nn import functional as F
from torch.utils.checkpoint import checkpoint
from src.simpleP2G import SimpleP2GDecoderLayer, SimpleP2GRMSNorm
class SimpleP2GNeigh(nn.Module):
"""The target-plus-neighbors model used in the main comparison."""
def __init__(self, config):
super().__init__()
self.config = config
self.decoder_depth = config.decoder_depth
self.num_genes = config.num_genes
self.hidden_size = config.hidden_size
self.cond_size = config.cond_size
self.num_cond_tokens = getattr(config, "num_cond_tokens", 4)
self.num_neighbor_tokens = int(getattr(config, "num_neighbor_tokens", 24))
if str(getattr(config, "neighbor_position_mode", "relative")) != "relative":
raise ValueError("SimpleP2G-cross requires relative neighbor positions")
for key in ("neighbor_cond_size", "global_cond_size"):
if int(getattr(config, key, self.cond_size)) != self.cond_size:
raise ValueError("All image embeddings must use the same encoder dimension")
if (
not getattr(config, "use_target_context", True)
or not getattr(config, "use_neighbor_context", True)
or getattr(config, "use_slide_context", False)
or getattr(config, "use_local_summary", False)
or getattr(config, "context_source_balance", 0.0) != 0
):
raise ValueError(
"This model requires target + neighbor context; use the separate experiment entry point for other variants"
)
self.gene_queries = nn.Embedding(self.num_genes, self.hidden_size)
self.label_embed = nn.Sequential(
nn.Linear(self.cond_size, self.cond_size),
nn.SiLU(),
nn.Linear(self.cond_size, self.hidden_size * self.num_cond_tokens),
)
self.neighbor_proj = nn.Sequential(
nn.Linear(self.cond_size, self.hidden_size),
nn.SiLU(),
nn.Linear(self.hidden_size, self.hidden_size),
)
self.neighbor_pos_proj = nn.Sequential(
nn.Linear(2, self.hidden_size),
nn.SiLU(),
nn.Linear(self.hidden_size, self.hidden_size),
)
self.decoder_blocks = nn.ModuleList([SimpleP2GDecoderLayer(config) for _ in range(self.decoder_depth)])
self.final_norm = SimpleP2GRMSNorm(self.hidden_size)
self.mean_head = nn.Linear(self.hidden_size, 1)
if config.loss_name in ("gaussian_nll", "beta_nll"):
self.log_var_head = nn.Linear(self.hidden_size, 1)
self.gradient_checkpointing = False
self.emb_dropout = nn.Dropout(getattr(config, "dropout", 0.0))
def forward(self, c, neighbor_emb=None, neighbor_mask=None, neighbor_pos=None, global_emb=None):
"""Predict from target (B,D), neighbors (B,K,D) and relative positions.
global_emb is accepted for the shared slide-dataset interface; the
main-comparison model does not consume a slide token.
"""
if neighbor_emb is None or neighbor_pos is None:
raise ValueError("neighbor_emb and neighbor_pos are required")
batch_size = c.shape[0]
output = self.emb_dropout(self.gene_queries.weight.unsqueeze(0).expand(batch_size, -1, -1))
target = self.label_embed(c).view(batch_size, self.num_cond_tokens, self.hidden_size)
neighbors = self.neighbor_proj(neighbor_emb)
neighbors = neighbors + self.neighbor_pos_proj(neighbor_pos.to(dtype=neighbors.dtype))
condition = torch.cat((target, neighbors), dim=1)
if neighbor_mask is None:
neighbor_mask = torch.ones(neighbor_emb.shape[:2], device=c.device, dtype=torch.bool)
target_mask = torch.ones(batch_size, self.num_cond_tokens, device=c.device, dtype=torch.bool)
mask = torch.cat((target_mask, neighbor_mask.bool()), dim=1).unsqueeze(1).unsqueeze(1)
for block in self.decoder_blocks:
if self.gradient_checkpointing and self.training:
output = checkpoint(block, output, condition, None, mask, use_reentrant=False)
else:
output = block(output, encoder_hidden_states=condition, cross_attention_mask=mask)
output = self.final_norm(output)
mean = F.softplus(self.mean_head(output).squeeze(-1))
log_var = (
self.log_var_head(output).squeeze(-1)
if self.config.loss_name in ("gaussian_nll", "beta_nll")
else torch.zeros_like(mean)
)
return mean, log_var