PhyloGPN demo#

Load PhyloGPN through explicit AutoClass registration, inspect its F81 rate parameters and embeddings, convert the rates to nucleotide probabilities, and compute the existing C-to-T substitution score.

Setup#

from importlib.metadata import version

from gpn import register_auto_classes
import torch
from transformers import AutoModel, AutoTokenizer

register_auto_classes("phylo")
MODEL_ID = "songlab/PhyloGPN"
MODEL_REVISION = "3556db4c469e67d25f0f7a0a6653b48be3eebf51"
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")

tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, revision=MODEL_REVISION)
model = AutoModel.from_pretrained(MODEL_ID, revision=MODEL_REVISION).to(DEVICE).eval()
print(
    f"gpn={version('gpn')}, transformers={version('transformers')}, "
    f"torch={version('torch')}, device={DEVICE}, "
    f"model dtype={next(model.parameters()).dtype}; "
    f"{MODEL_ID} @ {MODEL_REVISION}"
)
gpn=0.9.0a1, transformers=5.15.0, torch=2.13.0+cpu, device=cpu, model dtype=torch.float32; songlab/PhyloGPN @ 3556db4c469e67d25f0f7a0a6653b48be3eebf51

Sequence batch#

sequences = ['TATAAA', 'GGCCAATCT', 'CACGTG', 'AGGTCACGT', 'GCCAGCC', 'GGGGATTTCC']
receptive_field = 481
pad_size = receptive_field // 2
pad_token = tokenizer.pad_token
padded_sequences = [
    pad_token * pad_size + sequence + pad_token * pad_size
    for sequence in sequences
]
input_ids = tokenizer(
    padded_sequences, return_tensors="pt", padding=True
)["input_ids"].to(DEVICE)

with torch.inference_mode():
    padded_embeddings = model.get_embeddings(input_ids)
    padded_logits = model(input_ids)

embeddings = []
logits = []
for row, sequence in enumerate(sequences):
    length = len(sequence)
    embeddings.append(padded_embeddings[row, :length].cpu())
    logits.append(
        {base: padded_logits[base][row, :length].cpu() for base in "ACGT"}
    )

Position embeddings#

embeddings[0]
tensor([[ 0.2391, -0.4661, -0.0159,  ...,  0.4051, -0.7045, -0.5292],
        [-0.3234, -0.5837,  0.5243,  ..., -0.0163, -0.4431, -0.5394],
        [ 0.0789, -0.3639, -0.1462,  ...,  0.3260, -0.6638, -0.3064],
        [-0.7663, -0.1948,  0.2535,  ...,  0.5135, -0.1959, -0.3657],
        [ 0.0413, -0.2288,  0.2194,  ..., -0.1099, -0.0648, -0.6248],
        [-0.1851, -0.1795,  0.3790,  ...,  0.0529,  0.0603,  0.1869]])

F81 log-rate parameters#

logits[0]
{'A': tensor([1.6577, 4.0178, 1.0112, 3.9506, 3.7107, 3.6846]),
 'C': tensor([1.1716, 0.4637, 0.9406, 0.4236, 0.8937, 0.7400]),
 'G': tensor([-0.1792,  1.1369, -0.0490,  0.8730,  0.3609,  0.7772]),
 'T': tensor([4.0944, 1.2592, 3.8543, 1.4166, 1.8473, 1.4589])}

Nucleotide probabilities#

probability_batches = []
for logit_dictionary in logits:
    logit_tensor = torch.stack(
        [logit_dictionary[base] for base in "ACGT"], dim=-1
    )
    probability_tensor = torch.softmax(logit_tensor, dim=-1)
    probability_batches.append(
        {
            base: probability_tensor[:, nucleotide_index]
            for nucleotide_index, base in enumerate("ACGT")
        }
    )

probability_batches[0]
{'A': tensor([0.0757, 0.8710, 0.0514, 0.8659, 0.8000, 0.8229]),
 'C': tensor([0.0466, 0.0249, 0.0479, 0.0255, 0.0478, 0.0433]),
 'G': tensor([0.0121, 0.0488, 0.0178, 0.0399, 0.0281, 0.0449]),
 'T': tensor([0.8657, 0.0552, 0.8829, 0.0687, 0.1241, 0.0889])}

Zero-shot substitution score#

At zero-based position 1 of the first sequence, the model’s hypothetical T-vs-C log-rate difference is shown below. The observed base there is A, so this is a conditional preference comparison, not a reference-allele-checked VEP call.

logits[0]["T"][1] - logits[0]["C"][1]
tensor(0.7955)