Conditional Random Fields › Inference Complexity and Practical Considerations
Language AI HandbookAll chapters Conditional Random Fields › Inference Complexity and Practical ConsiderationsOne section · Sections in this chapter9 / 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
CRF inference has specific computational characteristics worth understanding for practical applications.
Time Complexity
The key operations scale as follows:
- Forward/Backward Algorithm: where is sequence length and is number of labels
- Viterbi Decoding:
- Gradient Computation: per training example
The quadratic dependence on means that CRFs become expensive with many labels. For tasks with hundreds of possible labels (like fine-grained NER with many entity types), this can be a bottleneck.


Beam Search Alternative
When exact Viterbi decoding is too slow, beam search provides an approximate alternative. Instead of tracking all labels at each position, beam search keeps only the top partial sequences:
def beam_search_decode(emissions, transitions, beam_width=3, num_tags=5):
"""
Approximate decoding using beam search.
Keeps only top-k partial sequences at each step.
"""
seq_len = emissions.shape[0]
# Each beam entry: (score, path)
beams = [(0.0, [])]
for t in range(seq_len):
candidates = []
for score, path in beams:
prev_tag = path[-1] if path else 0 # 0 for START
for next_tag in range(num_tags):
if t == 0:
trans_score = 0.0
else:
trans_score = transitions[prev_tag, next_tag]
emit_score = emissions[t, next_tag]
new_score = score + trans_score + emit_score
candidates.append((new_score, path + [next_tag]))
# Keep top beam_width
candidates.sort(reverse=True, key=lambda x: x[0])
beams = candidates[:beam_width]
return beams[0] # Best sequence
# Compare beam search with different widths
emissions_np = emissions.detach().numpy()
transitions_np = crf_layer.transitions.detach().numpy()Beam Search vs. Viterbi: ================================================== Viterbi (exact): path=[3, 3, 4, 3, 3, 3], score=-19989.8594 Beam width 1: path=[1, 3, 4, 3, 3, 3], score=8.0399 ✗ Beam width 2: path=[3, 3, 4, 3, 3, 3], score=9.3341 ✓ Beam width 3: path=[3, 3, 4, 3, 3, 3], score=9.3341 ✓ Beam width 5: path=[3, 3, 4, 3, 3, 3], score=9.3341 ✓
Beam search with width 1 is greedy decoding. As beam width increases, accuracy approaches Viterbi but with reduced computational cost for very long sequences or many labels.