Emission probability 2026-10-20
An emission probability in a Hidden Markov model is the probability of an observation conditional on its hidden state. For a finite observation alphabet, each state's emission probabilities sum to one. A match state can therefore emit several different characters, each with its own probability.
For each match state, count the nongap amino acids and divide by the number of nongap observations in that column. A gap is not one of the twenty emitted amino acids; including it in the denominator would make the emission probabilities sum to less than one:
def emission_counts(column):
    counts = Counter(str(x) for x in column if x != ".")
    n = sum(counts.values())
    if n == 0:
        raise ValueError("an all-gap column has no empirical emission distribution")
    return counts, n

match_emissions = {}
for state, j in enumerate(match_columns, 1):
    counts, n = emission_counts(A[:, j])
    match_emissions[state] = {aa: count/n for aa, count in counts.items()}
    print(state, j+1, match_emissions[state])
The table gives an exact compact representation of every emission probability: divide each displayed count by the denominator in its row; all unlisted amino acids have probability zero. The state number follows the order of match states, while the column refers to the original multiple sequence alignment.
StateAlignment columnNongap counts
119I:1, K:1, R:1, S:2, V:3, Y:1
229D:1, E:2, G:1, I:1, L:1, Q:2, S:1
339E:2, I:1, K:1, Q:1, R:3, T:1
449L:8, M:1
559E:3, G:1, L:2, N:1, Q:1, S:1
669A:1, D:2, G:1, K:1, N:1, S:2, T:1
779A:1, E:3, I:2, L:1, M:1, V:1
889I:2, L:7
999D:3, E:1, K:1, L:2, N:1, Q:1
10109D:1, E:3, G:1, N:1, P:1, S:2
11119A:5, D:1, G:1, P:1, T:1
12129I:1, L:7, Y:1
13139E:1, F:1, G:1, H:2, M:1, Q:2, T:1
14149F:9
15159F:1, L:8
16169E:1, I:1, L:1, N:3, Q:1, R:1, Y:1
17179D:6, E:2, S:1
18189C:7, F:1, S:1
19199K:1, N:6, R:2
20209E:1, F:1, I:5, L:1, V:1
21219K:3, P:1, Q:1, S:4
22297K:4, P:1, R:1, S:1
23307E:2, F:1, G:1, I:1, K:1, V:1
24317D:3, E:1, G:1, K:1, S:1
25328D:1, E:1, G:1, K:1, N:1, R:3
26338D:7, N:1
27348A:1, P:6, S:1
28359C:1, H:1, I:1, K:1, M:1, R:1, T:3
29369E:1, F:1, G:1, L:1, Q:3, W:1, Y:1
30379I:5, L:1, T:2, Y:1
31389A:1, G:1, P:3, S:4
32399I:1, K:6, L:1, R:1
33409F:1, N:1, Q:4, T:2, V:1
34419A:1, F:1, I:1, V:6
35429I:1, L:7, V:1
36439A:1, C:1, E:1, N:1, R:1, S:3, V:1
37449D:3, E:5, Q:1
38459A:1, C:7, I:1
39469K:2, L:3, R:4
40479A:3, E:1, L:1, N:3, T:1
41489I:4, L:1, T:1, V:3
42499L:9
43509D:1, E:1, H:1, Q:1, S:1, T:1, V:1, Y:2
44519E:1, K:1, Q:2, R:1, V:4
45529L:9
46539G:9
47549P:8, T:1
48559W:9
49569A:2, C:7
50579A:6, C:1, L:1, T:1
51589A:1, D:3, F:1, S:2, V:1, Y:1
52599K:4, R:2, S:3
53609A:2, F:1, I:2, L:2, V:2
54619A:7, G:1, Y:1
55629E:3, G:3, Q:3
56639I:3, M:3, T:1, Y:2
57649F:3, L:2, M:3, N:1
58659H:1, I:3, L:1, V:4
59669K:1, M:1, P:1, R:4, S:1, T:1
60679D:1, E:3, Q:4, Y:1
61689I:1, L:7, M:1
62699D:1, E:4, Q:4
63709K:7, R:2
64719A:1, I:2, M:1, N:1, R:1, Y:3
65729A:1, C:1, D:1, E:2, I:2, V:2
66739K:9
67749H:4, L:1, Q:2, S:1, V:1
68759E:8, Q:1
69769Q:3, R:1, S:2, T:3
70779C:2, E:3, G:1, R:1, S:1, V:1
71789A:1, D:2, E:3, I:1, K:1, Y:1
72799I:2, L:3, M:1, N:1, Q:1, V:1
These are unregularized maximum-likelihood estimates of the emission probabilities. No additional artificial observations are added. Although only emissions are requested here, a full profile hidden Markov model would also require transitions and deletion mechanisms to represent the gaps that remain in some match columns.
Columns 22–28 form one run between alignment columns 21 and 29, corresponding to match states and . A self-repeating insertion state can emit a variable number of residues, so these seven alignment columns require one populated insertion state, not seven successive mandatory states.
Pool all nongap residues in this run to estimate its emission probabilities:
insert_columns = np.flatnonzero(~match_mask)
runs = np.split(insert_columns, np.flatnonzero(np.diff(insert_columns) > 1) + 1)
insert_emissions = []
for run in runs:
    if len(run):
        counts, n = emission_counts(A[:, run].ravel())
        insert_emissions.append({aa: count/n for aa, count in counts.items()})
        print(run+1, n, insert_emissions[-1])
There are 24 emitted residues, giving
All other amino acids have probability zero in this empirical emission probabilities. A conventional full architecture may provide an optional insertion state at every boundary, including the two ends, giving possible insertion positions; only one has an observed insertion here and is required by this alignment's populated insertion region.
The sequence has five characters, while the four match states account for four compulsory emissions. Consequently exactly one insertion state emission is needed. An insertion at would have to emit T, which has probability zero there. The only nonzero hidden path is therefore
Multiply its transition probabilities and emission probabilities, including the probability of exiting immediately and bypassing :
Normally the forward algorithm sums over all compatible paths. Here all other paths have probability zero, so this single path gives the full sequence probability.
For the same uniform background and base-two log-likelihood ratio, replace every emission probability by . The background specifies independent letters, not a competing hidden-state process. Its single emitting state has continuation factor one when scoring a fixed-length word, so each model transition contributes ; dividing every transition by would incorrectly count the background twice.
The complete transition-score diagram is
Every omitted transition has score . Silent Start and End supply no emission score. The complete table of emission scores is
For the unique nonzero path identified in (b), add the five emission scores and all transition scores:
This equals (c). For a sequence with several nonzero hidden paths, the full log-likelihood ratio is the log-sum-exp of their path scores; selecting only the largest score would instead compute a best-path approximation.