How BEAM works
BEAM stands for Bidirectional Expert-Anchored Model. This page builds the model up step by step: two experts read every residue, their counts become probabilities through a prior, and a single sweep sums over every possible sequence. None of it is needed to use BEAM, but it explains how its answers are computed over every possible sequence rather than estimated by simulation.
Every residue is read twice
Section titled “Every residue is read twice”Many sequence families are anchored at both ends. A CDR3, the example on this page, is assembled from gene segments: its start is encoded by the V gene and its end by the J gene; the middle is the junction, where nucleotides are added and removed at random. So the start of a CDR3 is best described by counting positions from the front, and the end by counting from the back: the residue three from the end of a CDR3 is constrained by the J gene, whatever the CDR3’s length.
An expert and its window
Section titled “An expert and its window”BEAM reads every sequence with two components, called experts. An expert walks along a sequence one residue at a time, and at each residue it predicts what is there from two things only:
- where it is, counted from its own end of the sequence;
- what it has just read: the residues before it, its context window. The reference model reads .
Its prediction is a probability for each of the 20 amino acids, learned from the training sequences (below). The forward expert walks from the start, so its window holds the residues before; the backward expert walks from the end, reading the sequence backwards, so its window holds the residues after. Both walk the whole sequence, and they cross in the middle:
Two experts walk a sequence
Loading…
Press Walk to move both experts along CASSLGQGYEQYF, one residue per step: the forward expert (teal) from the start, the backward expert (orange) from the end. Each window frames what its expert looks at: the four residues it has just read (shaded) and the residue it predicts (shown as ? until revealed); dashed tiles lie past the end of the sequence. Each card shows its expert's prediction under the reference model, a probability for each amino acid. Once the residues are revealed, the strip under the sequence keeps the probability each expert gave: the forward expert's above the line, the backward expert's below it.
Both experts are sure of the opening CAS and the closing F, giving each of them 0.89 or more.
In the junction between them both are less certain, and they often disagree: at residue 5, L,
the forward expert gives 0.25 and the backward expert 0.10; at residue 9, Y, the forward expert
gives 0.05, while the backward expert, which has just read EQYF, gives 0.71.
Two experts, one factor
Section titled “Two experts, one factor”Every residue therefore gets two probabilities, from the forward expert and from the backward expert. BEAM combines them into the residue’s factor, their weighted geometric mean:
A geometric mean is high only when both experts find the residue plausible; when one of them
objects, the factor falls. So the model knows the conserved start and the conserved end,
without being told which V or J genes exist. Change only the last residue of a typical CDR3 from
F to W, an ending that is rare in TCRβ CDR3s, and both experts object: the backward expert has
rarely seen a sequence end in W, and the forward expert has rarely seen W follow YEQY.
import BEAM
model = BEAM.load_pretrained("human_tcrb_cdr3_aa_foundation")
real = "CASSLGQGYEQYF"swapped = "CASSLGQGYEQYW" # the same, with the final F changed to W
print(f"{real} {model.pseq(real):.3e}")print(f"{swapped} {model.pseq(swapped):.3e}")print(f"ratio: {model.pseq(real) / model.pseq(swapped):,.0f}")CASSLGQGYEQYF 3.296e-05CASSLGQGYEQYW 4.304e-12ratio: 7,656,791Weighing two experts
Loading…
Choose a residue of the sequence: the two experts' windows close in on it, the forward expert's from the left and the backward expert's from the right. The rows are their two predictions for that residue and, below them, the combined factor ψ for every amino acid. Choose a column to read out its factor, for instance W at the last residue, and move the slider to change the weights; at either end of the slider, one expert alone decides. The factors need not add up to one: the normaliser, below, takes care of that.
A model is a table of counts
Section titled “A model is a table of counts”Building a model is counting. Each expert keeps a table with one row for every position, counted from its own end, and every context of the residues before it in its reading direction; each row records how often each residue came next. The animation counts a small training set of six CDR3s with , so that its rows fit on screen:
Counting a training set
Loading…
Press Count to walk both experts through the training sequences together. At each residue, the forward expert counts it in the row of its position from the start and the two residues before it (teal window), and the backward expert in the row of its position from the end and the two residues after it (orange window). Each card shows that row's counts so far.
An expert’s probability of a residue comes from its row: from the counts where the row was seen often, and from a prior where it was seen rarely or never, as the next section explains.
Sequences the model has never seen
Section titled “Sequences the model has never seen”A repertoire contains a vanishing fraction of all possible CDR3s, so a model must say something sensible about sequences it never saw. The longer an expert’s context, the more specific its prediction, but the more rarely that exact context was seen: four given residues at a given position may have been seen thousands of times, a few times, or never. BEAM resolves this with a hierarchical prior: each level’s prediction is a weighted average of what its own context was seen to do and what the next shorter context predicts,
- is how often the context of residues was seen at this position, and how often residue followed it;
- is the weight on those counts. It grows with : a context seen far more often than times speaks for itself, and one never seen passes on the shorter context’s prediction unchanged;
- the chain starts from every residue equally likely and climbs from the position alone () to the full context ().
The prior, level by level
Loading…
Choose a sequence, an expert and a residue; the window marks the context. From the bottom up, each level mixes what its context was seen to do (teal) with what the level below predicts (indigo). Its answer (white) lies between the two, closer to the one with more weight, and is handed up to the next level (dashed line); the top row is the expert's probability, the value explain returns. The model is built from 20,000 cells drawn from the reference model. The slider multiplies every strength α: lower values trust thin counts, higher values lean on short contexts. The lower panel shows how likely 5,000 new cells are at each multiple, relative to the best.
This is the right thing to do for four reasons:
- Trust follows the evidence. A context’s counts get weight in proportion to how often it was seen, so a context seen twice cannot overrule a shorter one seen thousands of times.
- Nothing is ruled out. Every residue keeps some probability from the levels below, so a new sequence is less likely, never impossible.
- It is the Bayesian answer. is the posterior mean of the context’s distribution under a Dirichlet prior centred on the shorter context’s prediction, with strength .
- The data choose the strengths. BEAM sets each to the value under which its own counts are most probable, their evidence, without holding any data out. New sequences are on average 98% as likely under the strengths it chooses as under the best multiple of them, and the strengths adapt to how much data there is: built from 20,000 cells, the model gives four residues of context a strength of 3.1 million, passing the three-residue prediction through almost unchanged, while the reference model, built from far more sequences, gives the same level a strength of 78.
The result: a plausible new CDR3 gets a moderate probability, a junk string gets a vanishingly small one, and every sequence up to the maximum length is possible.
Assembling a probability
Section titled “Assembling a probability”A sequence’s probability is the product of its residues’ factors and the probability of its length, divided by a normaliser. For a sequence of length ,
- is the probability of the length;
- is residue ‘s factor, from the two experts;
- is the normaliser: the same product of factors summed over every sequence of length , so that, given the length, the probabilities add up to one.
explain returns all of these terms in logs (see
Which residues drive a score), and they add
up to the model’s own score:
import BEAM
model = BEAM.load_pretrained("human_tcrb_cdr3_aa_foundation")s = "CASSLGQGYEQYF"e = model.explain(s)
# ln P(s) = ln pi(L) + sum_i ln psi_i - ln Z(L), with ln psi_i = w_F ln q_F + w_B ln q_Bprint(f"length term ln pi(L) = {e['log_length_prior']:9.4f}")print(f"residue terms sum of ln psi_i = {e['combined'].sum():9.4f}")print(f"normaliser ln Z(L) = {e['log_partition']:9.4f}")print(f"ln P(s), assembled = {e['log_length_prior'] + e['combined'].sum() - e['log_partition']:9.4f}")print(f"ln P(s), from log_pseq = {model.pseq(s, log=True):9.4f}")length term ln pi(L) = -1.7650residue terms sum of ln psi_i = -10.3225normaliser ln Z(L) = -1.7671ln P(s), assembled = -10.3203ln P(s), from log_pseq = -10.3203Assembling a probability
Loading…
Press Assemble to build the probability of CASSLGQGYEQYF under the reference model, one factor at a time. The axis is a log scale, so each factor below one moves the running value to the right by its size: first the probability of the length, then each residue's factor ψ (the two experts' windows mark the residue), and last the division by the normaliser Z(13), which moves it back to the left. The end point is the sequence's probability, as log_pseq returns it.
Summing over every sequence
Section titled “Summing over every sequence”The normaliser adds up the product of factors of every sequence of length . There are of them, more than for a typical CDR3 of 13 residues, so listing them is impossible, and BEAM never does. Each factor depends only on a residue and the residues around it, so once the start of a sequence is written, everything still to come depends only on its last residues: its state. All the starts that end in the same state can be added up and carried forward as one number.
BEAM therefore sweeps the positions from start to end, keeping one running total per state. A state’s total is the sum, over the states it can be reached from, of their totals times the factor of the step; after the last position, the totals add up to . This is a dynamic program over a layered graph, one layer per position. The animation sweeps a small model of DNA sequences with , small enough to draw every state:
One sweep through the layered graph
Loading…
Press Sweep to sweep a model built from ten short DNA sequences of 6 to 11 bases, with k = 2, for length 8. Each column is one position, and each dot a state: the last two residues written (N stands for an unread base), grouped by the residue written last. At each step, every state collects the totals of the five states it can be reached from, each times the factor of the step: the forward expert's factor for the new residue and the backward expert's for the residue two back, which the new residue completes. Dot size is the state's share of its column's total, and line width the share each edge brings. Choose a dot to see its sum. The bars compare the sequences summed so far with the edges walked.
The reference model’s graph for length 13 has 1,608,421 states and 28,968,420 edges, and one sweep through it adds up every one of sequences.
The same idea gives every other quantity on this site that sums over all sequences: expected numbers of distinct sequences, overlaps, publicness and distances are computed by sweeps through the graph instead of by listing or sampling sequences.
On a model small enough to list every sequence, the total probability can be checked directly:
import itertoolsimport mathfrom BEAM import BeamModel# A small model over sequences of up to 4 residues, so that every one of them# can be listed: 20 + 20**2 + 20**3 + 20**4 = 168,420 sequences.model = BeamModel(sequences, order=2, max_length=4)
every = ["".join(t) for L in range(1, 5) for t in itertools.product("ACDEFGHIKLMNPQRSTVWY", repeat=L)]total = math.fsum(math.exp(v) for v in model.log_pseq_batch(every) if v > -math.inf)print(f"{len(every):,} sequences, total probability {total:.15f}")168,420 sequences, total probability 0.999999999999994Where the probability lives
Section titled “Where the probability lives”Under the reference model, every string of up to 27 amino acids has some probability, but almost all of the probability sits on a tiny fraction of them. By length, most possible strings are long, while most CDR3s are 13 to 16 residues; and within each length, a small share of the strings carries most of the probability.
import numpy as npimport BEAM
BEAM.set_memory_budget(32 << 30) # the fine probability grid below needs about 20 GBmodel = BEAM.load_pretrained("human_tcrb_cdr3_aa_foundation")
# 1. By length: the model's probability against the share of all possible stringsstrings = {L: 20.0 ** L for L in range(1, 28)}total = sum(strings.values())print("length probability share of possible strings")for L in range(10, 21, 2): print(f"{L:6} {model.length_distribution.get(L, 0):11.3g} {strings[L] / total:25.2e}")print(f"{27:6} {model.length_distribution.get(27, 0):11.3g} {strings[27] / total:25.2e}")
# 2. By probability: how much of the probability sits on how few sequences,# with every sequence grouped onto a fine grid of probabilitiesp, w = model.pseq_analysis().atoms(16384).as_arrays()order = np.argsort(p)[::-1]mass = np.cumsum(p[order] * w[order])support = np.cumsum(w[order]) / w.sum()for cut in (1e-8, 1e-10, 1e-12): k = np.searchsorted(-p[order], -cut, side="right") - 1 print(f"sequences with P >= {cut:.0e}: {mass[k]:.1%} of the probability, {support[k]:.1e} of the strings")length probability share of possible strings 10 0.00483 7.25e-23 12 0.0768 2.90e-20 14 0.225 1.16e-17 16 0.137 4.64e-15 18 0.0289 1.86e-12 20 0.00508 7.42e-10 27 1.04e-05 9.50e-01sequences with P >= 1e-08: 49.9% of the probability, 5.8e-29 of the stringssequences with P >= 1e-10: 78.0% of the probability, 3.6e-27 of the stringssequences with P >= 1e-12: 91.7% of the probability, 1.6e-25 of the stringsStrings of 27 residues are 95% of all possible strings, yet the model almost never produces them. And the sequences with probability above carry half of all the probability while making up about of the possible strings: a vanishingly small, highly structured corner of sequence space.
What the model says about each position
Section titled “What the model says about each position”At a given length, the probability of each residue at each position is computed over every
sequence of that length (positional_marginals), and drawn as a sequence logo it shows the conserved start, the
conserved end and the variable junction between them.
Sequence logos of the reference model
Loading…
Choose a CDR3 length to see the probability of each residue at each position, drawn as a sequence logo: letter height is the residue's share of the information at that position. For lengths 13 to 16, switch between the common sequences (probability above 10⁻⁷) and the rare ones.