Hidden Markov Models › Parameter Estimation from Data

Michael BrenndoerferUpdated May 7, 202510 min read
Language AI HandbookAll chapters
Part 6 of 67Sequence Labeling
Chapter 40Hidden Markov Models › Parameter Estimation from Data
Reading section 5 of 13 · Parameter Estimation from Data · 10 minSection 5 of 13 · 10 min

Book contents

Section 5 of 13 · 10 min read

Hidden Markov Models › Parameter Estimation from DataOne section · Sections in this chapter5 / 13 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

An HMM is only useful if we can learn its parameters from data. For POS tagging, we have access to annotated corpora where each word is labeled with its correct tag. This is supervised learning, and parameter estimation is straightforward: we count occurrences and normalize.

The intuition behind maximum likelihood estimation is simple: the best model is the one that assigns the highest probability to the observed data. If you saw the bigram "DT NN" a thousand times in training but "DT VB" only ten times, the data is telling you that determiners are followed by nouns far more often than by verbs, and your transition probabilities should reflect that. Maximum likelihood formalized this intuition into a precise estimation procedure.

Maximum Likelihood Estimation

The maximum likelihood estimates for HMM parameters are simple frequency ratios derived from counting occurrences in the training data. The estimates have a satisfying form: every probability is just the count of the event you care about divided by the count of the conditioning context. There are no free hyperparameters, no optimization loops, and no gradient descent. You count, you divide, and you are done.

For initial probabilities, we count how often each state starts a sequence:

π^i=count(s1=i)count(sequences)\hat{\pi}_i = \frac{\text{count}(s_1 = i)}{\text{count}(\text{sequences})}

where:

  • π^i\hat{\pi}_i: the estimated probability of starting in state ii
  • count(s1=i)\text{count}(s_1 = i): the number of sequences that begin with state ii
  • count(sequences)\text{count}(\text{sequences}): the total number of sequences in the training data

For transition probabilities, we count how often state jj follows state ii:

a^ij=count(st−1=i,st=j)count(st−1=i)\hat{a}_{ij} = \frac{\text{count}(s_{t-1} = i, s_t = j)}{\text{count}(s_{t-1} = i)}

where:

  • a^ij\hat{a}_{ij}: the estimated probability of transitioning from state ii to state jj
  • count(st−1=i,st=j)\text{count}(s_{t-1} = i, s_t = j): the number of times state ii is immediately followed by state jj
  • count(st−1=i)\text{count}(s_{t-1} = i): the total number of times state ii appears (except at sequence end)

For emission probabilities, we count how often state ii emits observation oo:

b^i(o)=count(st=i,ot=o)count(st=i)\hat{b}_i(o) = \frac{\text{count}(s_t = i, o_t = o)}{\text{count}(s_t = i)}

where:

  • b^i(o)\hat{b}_i(o): the estimated probability of emitting observation oo from state ii
  • count(st=i,ot=o)\text{count}(s_t = i, o_t = o): the number of times state ii emits observation oo
  • count(st=i)\text{count}(s_t = i): the total number of times state ii appears

In words: the initial probability of state ii is the fraction of sequences that start with ii. The transition probability from ii to jj is the fraction of times ii is followed by jj. The emission probability of oo from ii is the fraction of times state ii emits oo.

Notice that each distribution is estimated independently. Transition probabilities depend only on pairs of consecutive tags; emission probabilities depend only on individual tag-word pairs. This independence makes estimation extremely fast and scales to large corpora without difficulty. The Brown corpus used below contains over 57,000 sentences and more than a million words; the entire parameter estimation completes in seconds.

Let's implement this with NLTK's tagged corpora. The Brown corpus contains over 57,000 sentences with POS tags, giving us enough data to estimate reliable probabilities. We'll use the Universal tagset, which maps the fine-grained Brown tags to 17 coarse categories for cleaner patterns.

In[9]:
Code
# Download required data (run once interactively, then comment out)
# nltk.download("brown", quiet=True)
# nltk.download("universal_tagset", quiet=True)

# Load Brown corpus with Universal POS tags
from nltk.corpus import brown

tagged_sents = brown.tagged_sents(tagset="universal")
In[10]:
Code
from collections import defaultdict


def estimate_hmm_parameters(tagged_sentences):
    """Estimate HMM parameters from tagged sentences using MLE."""

    # Counts for estimation
    initial_counts = defaultdict(int)
    transition_counts = defaultdict(lambda: defaultdict(int))
    emission_counts = defaultdict(lambda: defaultdict(int))
    tag_counts = defaultdict(int)

    for sentence in tagged_sentences:
        if len(sentence) == 0:
            continue

        # Count initial state
        first_tag = sentence[0][1]
        initial_counts[first_tag] += 1

        # Count emissions and transitions
        prev_tag = None
        for word, tag in sentence:
            word = word.lower()  # Normalize case

            emission_counts[tag][word] += 1
            tag_counts[tag] += 1

            if prev_tag is not None:
                transition_counts[prev_tag][tag] += 1

            prev_tag = tag

    # Convert counts to probabilities
    num_sentences = len(tagged_sentences)

    # Initial probabilities
    pi = {tag: count / num_sentences for tag, count in initial_counts.items()}

    # Transition probabilities
    A = {}
    for tag1, next_tags in transition_counts.items():
        total = sum(next_tags.values())
        A[tag1] = {tag2: count / total for tag2, count in next_tags.items()}

    # Emission probabilities
    B = {}
    for tag, words in emission_counts.items():
        total = tag_counts[tag]
        B[tag] = {word: count / total for word, count in words.items()}

    return pi, A, B, tag_counts


# Estimate parameters from Brown corpus
pi_est, A_est, B_est, tag_counts = estimate_hmm_parameters(tagged_sents)
Out[11]:
Console
HMM Parameters Estimated from Brown Corpus
==================================================
Training sentences: 57,340
Unique tags: 12

Transition Probabilities (examples):
  P(NOUN | DET)  = 0.6266
  P(VERB | NOUN) = 0.1593
  P(ADV | VERB)  = 0.1033

Emission Probabilities (examples):
  P('the' | DET)   = 0.5106
  P('is' | VERB)   = 0.0553
  P('good' | ADJ)  = 0.0087

The estimated parameters reveal clear patterns. The initial probability distribution shows which tags typically begin sentences in English:

Out[12]:
Console
| Tag | Initial Probability | Common Examples |
|:---:|-------------------:|:----------------|
| DET | 0.2134 | "The dog...", "A cat..." |
| PRON | 0.1597 | "He said...", "It was..." |
| NOUN | 0.1411 | "Dogs are...", "Time flies..." |
| ADP | 0.1228 | "In 1990,...", "For example,..." |
| ADV | 0.0913 | "However,...", "Then..." |
| . | 0.0889 | Sentence fragments |
| CONJ | 0.0491 | "And then...", "But..." |
| VERB | 0.0451 | "Run!", "Consider..." |
| PRT | 0.0367 | Rare as sentence start |
| ADJ | 0.0343 | "Good morning", "Many people..." |
| NUM | 0.0168 | "1984 was...", "Two men..." |
| X | 0.0005 | Rare |

: Initial state probabilities estimated from the Brown corpus. Determiners (DET), nouns (NOUN), and pronouns (PRON) dominate sentence beginnings. This reflects common English patterns. {#tbl-initial-probs}

Determiners, nouns, and pronouns dominate sentence beginnings, together accounting for over 75% of sentence starts.

Another revealing view is how emission probabilities vary across tags for ambiguous words. The word "run" can be a noun ("a morning run") or a verb ("I run daily"). Let's see how the learned emission probabilities capture this ambiguity:

Out[13]:
Console
| Word | P(word \| NOUN) | P(word \| VERB) | P(word \| ADJ) | Primary Tag |
|:----:|---------------:|---------------:|---------------:|:-----------:|
| time | 0.005796 | 0.000005 | 0.000000 | NOUN |
| run | 0.000200 | 0.000859 | 0.000000 | VERB |
| set | 0.000323 | 0.001778 | 0.000000 | VERB |

: Emission probabilities for ambiguous words across POS tags. "Time" shows moderate noun vs. verb ambiguity. "Run" is more strongly verbal. "Set" has large probability from both noun and verb tags. This reflects its high ambiguity in English. {#tbl-emission-ambiguity}

The table reveals that "time" has higher emission probability from NOUN than VERB, but both are non-zero. This reflects lexical ambiguity. These patterns form the statistical backbone of HMM-based tagging.

Handling Unknown Words

A critical challenge emerges when we encounter words not seen during training. If a word oo never appeared with tag ii, then b^i(o)=0\hat{b}_i(o) = 0. When we multiply by zero, the entire sequence probability becomes zero, making it impossible to tag sentences containing unknown words.

This is not a minor edge case. In real text, a significant fraction of words at test time will never have appeared in training. Technical documents use specialized vocabulary. Named entities change constantly. New words enter the language every year. An HMM tagger without a strategy for unknown words would completely fail on any non-trivial text.

Smoothing techniques address this problem. The simplest approach is add-one (Laplace) smoothing: add a small count to every word-tag combination before normalizing.

b^i(o)=count(st=i,ot=o)+1count(st=i)+∣V∣\hat{b}_i(o) = \frac{\text{count}(s_t = i, o_t = o) + 1}{\text{count}(s_t = i) + |V|}

where:

  • b^i(o)\hat{b}_i(o): the smoothed emission probability of observation oo from state ii
  • count(st=i,ot=o)\text{count}(s_t = i, o_t = o): the number of times state ii emits observation oo (zero for unseen combinations)
  • count(st=i)\text{count}(s_t = i): the total number of times state ii appears
  • ∣V∣|V|: the vocabulary size (number of unique observations)

The numerator adds 1 to every count. This ensures no probability is ever zero. The denominator adds ∣V∣|V| to compensate, keeping the probabilities properly normalized (summing to 1). This ensures every word has a non-zero probability from every tag, though the probability is very small for unseen combinations.

In[14]:
Code
def estimate_smoothed_emissions(tagged_sentences, alpha=1.0):
    """Estimate emission probabilities with add-alpha smoothing."""

    emission_counts = defaultdict(lambda: defaultdict(int))
    tag_counts = defaultdict(int)
    vocab = set()

    for sentence in tagged_sentences:
        for word, tag in sentence:
            word = word.lower()
            emission_counts[tag][word] += 1
            tag_counts[tag] += 1
            vocab.add(word)

    vocab_size = len(vocab)

    # Smoothed emission probabilities
    B_smoothed = {}
    for tag in tag_counts:
        B_smoothed[tag] = {}
        denominator = tag_counts[tag] + alpha * vocab_size

        for word in vocab:
            count = emission_counts[tag].get(word, 0)
            B_smoothed[tag][word] = (count + alpha) / denominator

        # Store smoothed probability for unknown words
        B_smoothed[tag]["<UNK>"] = alpha / denominator

    return B_smoothed, vocab


B_smoothed, vocab = estimate_smoothed_emissions(tagged_sents)
Out[15]:
Console
Smoothed Emission Probabilities
----------------------------------------
Vocabulary size: 49,815

Known word 'the':
  Unsmoothed P('the' | DET) = 0.510645
  Smoothed   P('the' | DET) = 0.374498

Unknown word '<UNK>':
  Unsmoothed P('<UNK>' | DET) = 0.000000
  Smoothed   P('<UNK>' | DET) = 0.00000535
  Smoothed   P('<UNK>' | NOUN) = 0.00000307

With smoothing, unseen words get a small but non-zero probability from each tag. This allows the HMM to tag sentences containing new words by relying on transition probabilities to disambiguate. The smoother the unknown-word distribution, the more the model falls back on syntax (transitions) and the less it can rely on lexical identity. In practice, Laplace smoothing is often too aggressive: it assigns the same small probability to an unknown word from every tag, which means a rare capitalized word like "Zuckerberg" gets equal emission probability from NOUN, VERB, and ADJ, leaving disambiguation entirely to surrounding context.

More sophisticated approaches use morphological clues. Words ending in "-tion" are almost always nouns; words ending in "-ly" are almost always adverbs; capitalized words in the middle of a sentence are often proper nouns. By clustering unknown words into morphological classes and learning separate emission probabilities for each class, you can handle unknowns much more accurately without abandoning the HMM framework. This is the strategy used by the TNT tagger (Brants, 2000) and similar high-accuracy HMM systems.