Hidden Markov Models › HMM for POS Tagging
Language AI HandbookAll chapters Hidden Markov Models › HMM for POS TaggingOne section · Sections in this chapter6 / 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
Now let's build a complete HMM POS tagger. Given a sentence, we want to find the most likely tag sequence. This is called the decoding problem because it is analogous to decoding a message in a noisy channel: the "true signal" is the tag sequence, the "noise" is the mapping from tags to words, and your task is to recover the most probable original signal given what you received.
where:
- : the optimal (most likely) state sequence
- : "the value of that maximizes" the following expression
- : the posterior probability of state sequence given observations
- : the joint probability of observations and states
The second equality follows from Bayes' rule: . Since is constant for a given observation sequence (it does not depend on which state sequence we are evaluating), maximizing the posterior is equivalent to maximizing the joint probability.
A naive approach would enumerate all possible tag sequences, compute their joint probabilities, and return the maximum. But with tags and words, there are possible sequences. For 12 tags and a 20-word sentence, that is over 3.8 quadrillion possibilities. Even at a billion evaluations per second, exhaustive search would take over 3 million years.
The Viterbi algorithm solves this efficiently in time using dynamic programming. We will cover Viterbi in detail in the next chapter. For now, let's implement a simplified greedy decoder that selects the locally best tag at each position.
Greedy decoding works as follows: for the first word, we pick the tag that maximizes . For each subsequent word, we pick the tag that maximizes given the previous tag we chose. This is fast but can make mistakes when a locally optimal choice leads to a globally suboptimal sequence.
class SimpleHMMTagger:
"""A simple HMM POS tagger with greedy decoding."""
def __init__(self, pi, A, B, vocab):
self.pi = pi
self.A = A
self.B = B
self.vocab = vocab
self.tags = list(A.keys())
def get_emission_prob(self, tag, word):
"""Get emission probability, handling unknown words."""
word = word.lower()
if word in self.B[tag]:
return self.B[tag][word]
return self.B[tag]["<UNK>"]
def tag_greedy(self, sentence):
"""Tag sentence using greedy (locally optimal) decoding."""
tags = []
for i, word in enumerate(sentence):
if i == 0:
# First word: use initial and emission probabilities
best_tag = None
best_prob = -1
for tag in self.tags:
prob = self.pi.get(tag, 1e-10) * self.get_emission_prob(
tag, word
)
if prob > best_prob:
best_prob = prob
best_tag = tag
else:
# Subsequent words: use transition and emission
prev_tag = tags[-1]
best_tag = None
best_prob = -1
for tag in self.tags:
trans_prob = self.A.get(prev_tag, {}).get(tag, 1e-10)
emit_prob = self.get_emission_prob(tag, word)
prob = trans_prob * emit_prob
if prob > best_prob:
best_prob = prob
best_tag = tag
tags.append(best_tag)
return tags
# Create tagger from estimated parameters
tagger = SimpleHMMTagger(pi_est, A_est, B_smoothed, vocab)# Test on some sentences
test_sentences = [
["The", "dog", "runs", "quickly"],
["She", "reads", "books"],
["The", "old", "man", "the", "boat"], # Classic garden path sentence
]
predictions = []
for sent in test_sentences:
tags = tagger.tag_greedy(sent)
predictions.append(list(zip(sent, tags)))Greedy HMM Tagger Results ================================================== Sentence 1: The dog runs quickly Tags: The/DET dog/NOUN runs/NOUN quickly/ADV Sentence 2: She reads books Tags: She/PRON reads/VERB books/NOUN Sentence 3: The old man the boat Tags: The/DET old/ADJ man/NOUN the/DET boat/NOUN
The greedy tagger works reasonably well for straightforward sentences but can make errors when local decisions conflict with global coherence. The sentence "The old man the boat" is a famous garden path sentence where "man" functions as a verb (meaning "to operate") and "old" is a noun phrase ("the elderly people"). Greedy decoding often misses such non-local dependencies because each decision only considers the immediately preceding tag.
Garden path sentences illustrate a broader point about greedy decoding: a locally optimal choice can lock you into a globally suboptimal path. Once the greedy decoder decides "man" is a noun, it has committed to that interpretation and there is no backtracking. The Viterbi algorithm avoids this by postponing final decisions. Rather than committing to a single path at each step, Viterbi maintains the best partial sequence ending in each state, and only commits to a full sequence once all positions have been processed. This global search within the dynamic programming framework is what separates Viterbi from greedy decoding and why it consistently outperforms greedy by several percentage points on held-out data.