TAI BUI
Back to curriculum
BuildPython

Entity Linking & Disambiguation

NER found "Paris." Entity linking decides: Paris, France? Paris Hilton? Paris, Texas? Paris (the Trojan prince)? Without linking, your knowledge graph stays ambiguous.

TIME: ~60 minutesPREREQUISITES: Phase 5 · 06 (NER), Phase 5 · 24 (Coreference Resolution)

Learning Objectives

  • Explain what entity linking adds on top of NER: mapping each mention to a unique knowledge-base entry, and why disambiguation is the harder half of the pipeline.
  • Identify the three disambiguation approaches (prior + context, embedding-based, generative) and the two measurements, mention recall and disambiguation accuracy, used to evaluate them.
  • Build an alias-index candidate generator and a context-based disambiguator, and know how to upgrade the disambiguator to sentence-transformer embeddings.
  • Evaluate an entity-linking pipeline on AIDA-CoNLL, and choose a disambiguation approach and NIL-handling strategy for a given knowledge base, language, and latency budget.

The Problem

Entity linking (EL) resolves each mention to a unique entry in a knowledge base: Wikidata, Wikipedia, DBpedia, or your domain KB. It breaks into two subtasks:

  1. Candidate generation. Given a mention's surface form, which KB entries are plausible?
  2. Disambiguation. Given the context, which candidate is the right one?

Here's why the second step is the hard one. A sentence reads: "Jordan beat the press." Your NER tags "Jordan" as PERSON. Good. But which Jordan?

  • Michael Jordan (basketball)?
  • Michael B. Jordan (actor)?
  • Michael I. Jordan (Berkeley ML professor, and yes, this confusion is real in ML papers)?
  • Jordan (the country)?
  • Jordan (Hebrew first name)?

Both steps are learnable. Both are benchmarked. The combined pipeline has been stable for a decade: what changes is the quality of the disambiguator.

Warm-up0/3
1. A named entity recognizer reads 'Jordan beat the press.' What does it output for the word Jordan?Warm-up
2. What does coreference resolution do?Warm-up
3. Two coworkers are both named Chris, and an email says 'Chris approved it'. How does a human reader most reliably decide which Chris is meant?Warm-up
Check: The Problem0/5
1. What does entity linking add on top of NER?Define
2. What are the two main subtasks in entity linking?Define
3. What is a knowledge base (KB) in entity linking?Define
4. What does the disambiguation step do?Define
5. Why is disambiguation the harder half of entity linking?Explain

The Concept

Entity linking pipeline: mention → candidates → disambiguated entity

Candidate generation. Given the mention surface form ("Jordan"), look up candidates in an alias index. Wikipedia alias dictionaries cover most named entities: "JFK" → John F. Kennedy, Jacqueline Kennedy, JFK airport, JFK (movie). Typical index returns 10-30 candidates per mention.

Disambiguation: three approaches.

  1. Prior + context (Milne & Witten, 2008). P(entity | mention) × context-similarity(entity, text). Works well, fast, no training.
  2. Embedding-based (ELQ / REL / Blink). Encode mention + context. Encode each candidate's description. Pick max cosine. The 2020-2024 default.
  3. Generative (GENRE, 2021; LLM-based, 2023+). Decode the entity's canonical name token-by-token. Constrained to a trie of valid entity names so output is guaranteed to be a valid KB id.

End-to-end vs pipeline. Modern models (ELQ, BLINK, ExtEnD, GENRE) run NER + candidate generation + disambiguation in one pass. Pipeline systems still dominate in production because you can swap components.

The two measurements

  • Mention recall (candidate gen). Fraction of gold mentions where the correct KB entry appears in the candidate list. Floor for the whole pipeline.
  • Disambiguation accuracy / F1. Given correct candidates, how often the top-1 is right.

Always report both. A system with 99% disambiguation on 80% candidate recall is an 80% pipeline.

Check: The Concept0/10
1. What is an alias index?Define
2. How does candidate generation work in entity linking?Define
3. Why must mention recall be reported alongside disambiguation accuracy?Explain
4. Which pair of scoring methods names two of the three disambiguation approaches?Explain
5. In the prior + context approach, what is multiplied to score a candidate?Explain
6. How does an embedding-based linker such as BLINK choose among candidates?Explain
7. How does GENRE perform entity linking?Explain
8. Why do pipeline systems still dominate production even though end-to-end models exist?Explain
9. Of 100 gold mentions, the correct entity is in the candidate list for 90, and the top-1 pick is right on 72 of those 90. What are mention recall and disambiguation accuracy?Apply
10. Your team improves the disambiguator by 10 points, but end-to-end accuracy barely moves. Mention recall is 70%. What does this suggest?Analyze

Build It

Step 1: build an alias index from Wikipedia redirects

python
alias_to_entities = {
    "jordan": ["Q41421 (Michael Jordan)", "Q810 (Jordan, country)", "Q254110 (Michael B. Jordan)"],
    "paris":  ["Q90 (Paris, France)", "Q663094 (Paris, Texas)", "Q55411 (Paris Hilton)"],
    "apple":  ["Q312 (Apple Inc.)", "Q89 (apple, fruit)"],
}

Wikipedia alias data: ~18M (alias, entity) pairs. Download from Wikidata dumps. Store as inverted index.

Step 2: context-based disambiguation

python
def disambiguate(mention, context, alias_index, entity_desc):
    candidates = alias_index.get(mention.lower(), [])
    if not candidates:
        return None, 0.0
    context_words = set(tokenize(context))
    best, best_score = None, -1
    for entity_id in candidates:
        desc_words = set(tokenize(entity_desc[entity_id]))
        union = len(context_words | desc_words)
        score = len(context_words & desc_words) / union if union else 0.0
        if score > best_score:
            best, best_score = entity_id, score
    return best, best_score

The Jaccard overlap is a toy. Replace with cosine similarity on embeddings (see code/main.py step-2 for the transformer version).

Step 3: embedding-based (BLINK-style)

python
from sentence_transformers import SentenceTransformer
encoder = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
 
def embed_mention(text, mention_span):
    start, end = mention_span
    marked = f"{text[:start]} [MENTION] {text[start:end]} [/MENTION] {text[end:]}"
    return encoder.encode([marked], normalize_embeddings=True)[0]
 
def embed_entity(entity_id, description):
    return encoder.encode([f"{entity_id}: {description}"], normalize_embeddings=True)[0]

At index time, embed every KB entity once. At query time, embed the mention + context once, dot-product against the candidate pool, pick max.

Step 4: generative entity linking (concept)

GENRE decodes the entity's Wikipedia title character-by-character. Constrained decoding (see lesson 20) ensures only valid titles can be output, tied to a KB-backed trie of legal completions. The modern descendant is REL-GEN and LLM-prompted EL with structured output.

python
prompt = f"""Text: {text}
Mention: {mention}
List the best Wikipedia title for this mention.
Respond with JSON: {{"title": "..."}}"""

Combined with a whitelist (Outlines choice), this is the simplest EL pipeline to ship in 2026.

Step 5: evaluate on AIDA-CoNLL

AIDA-CoNLL is the standard EL benchmark: 1,393 Reuters articles, 34k mentions, Wikipedia entities. Report in-KB accuracy (P@1) and out-of-KB NIL-detection rate.

Check: Build It0/7
1. What is AIDA-CoNLL?Define
2. Why does the embedding-based linker embed every KB entity once at index time?Explain
3. What is a safe way to use an LLM for entity linking?Explain
4. With the Step 1 alias index (keys are lowercase) and Step 2 code, what does disambiguate('Berlin', 'Berlin hosted the summit', alias_index, entity_desc) return?Apply
5. In disambiguate, the context words are {scored, bulls, title, finals} and one candidate's description words are {bulls, title, basketball, player, chicago}. What score does that candidate get?Apply
6. Every KB entity was embedded at index time. A mention has 20 candidates. How many encoder calls does scoring that mention need at query time?Apply
7. With the Step 1 alias index, disambiguate is called with the mention 'PARIS'. How many candidates does its loop score?Apply

Pitfalls

  • NIL handling. Some mentions are not in the KB (emerging entities, obscure people). Systems must predict NIL instead of guessing the wrong entity. Measured separately.
  • Mention boundary errors. Upstream NER misses partial spans ("Bank of America" tagged as just "Bank"). EL recall drops.
  • Popularity bias. Trained systems over-predict frequent entities. A mention of "Michael I. Jordan" on an ML paper often links to basketball Jordan.
  • Cross-lingual EL. Mapping mentions in Chinese text to English Wikipedia entities. Requires a multilingual encoder or a translation step.
  • KB staleness. New companies, events, people are not in last year's Wikipedia dump. Production pipelines need a refresh loop.
Check: Pitfalls0/7
1. What is NIL in entity linking?Define
2. Why does popularity bias hurt entity linking in specialized domains?Explain
3. Why must NER mention boundaries be exact for entity linking to work?Explain
4. What does the cross-lingual EL pitfall describe?Explain
5. What is the 'KB staleness' pitfall in entity linking?Explain
6. A linker attaches every mention of a new startup's CEO to a famous person with the same name, with high confidence. Which missing piece explains this?Analyze
7. Documents about ML research mention 'Michael I. Jordan'. The linker keeps returning the basketball player, though the professor is in the candidate list. Which pitfall is this?Analyze

Use It

The 2026 stack:

SituationPick
General-purpose English + WikipediaBLINK or REL
Cross-lingual, KB = WikipediamGENRE
LLM-friendly, few mentions/dayPrompt Claude/GPT-4 with candidate list + constrained JSON
Domain-specific KB (medical, legal)Custom BERT with KB-aware retrieval + fine-tune on domain AIDA-style set
Extremely low-latencyExact-match prior only (Milne-Witten baseline)
Research SOTAGENRE / ExtEnD / generative LLM-EL

Production pattern that ships in 2026: NER → coref → EL on each mention → collapse clusters to one canonical entity per cluster. Output: one KB id per entity in the document, not one per mention.

Check: Use It0/5
1. After NER, coreference and EL have run, how should the document's result be reported?Explain
2. Your team needs entity linking for mentions in Chinese text resolved against English Wikipedia. Which pick does the lesson's 2026 stack table recommend?Design
3. A hospital must link drug and procedure mentions to its own internal KB. Which approach does the lesson's stack table point to?Design
4. A product has an extremely tight latency budget for linking. Which disambiguator does the lesson's stack table recommend?Design
5. A tool links only a few mentions per day to Wikipedia and must never emit an id outside the KB. Which design fits?Design

Ship It

Save as outputs/skill-entity-linker.md:

markdown
---
name: entity-linker
description: Design an entity linking pipeline: KB, candidate generator, disambiguator, evaluation.
version: 1.0.0
phase: 5
lesson: 25
tags: [nlp, entity-linking, knowledge-graph]
---
 
Given a use case (domain KB, language, volume, latency budget), output:
 
1. Knowledge base. Wikidata / Wikipedia / custom KB. Version date. Refresh cadence.
2. Candidate generator. Alias-index, embedding, or hybrid. Target mention recall @ K.
3. Disambiguator. Prior + context, embedding-based, generative, or LLM-prompted.
4. NIL strategy. Threshold on top score, classifier, or explicit NIL candidate.
5. Evaluation. Mention recall @ 30, top-1 accuracy, NIL-detection F1 on held-out set.
 
Refuse any EL pipeline without a mention-recall baseline (you cannot evaluate a disambiguator without knowing candidate gen surfaced the right entity). Refuse any pipeline using LLM-prompted EL without constrained output to valid KB ids. Flag systems where popularity bias affects minority entities (e.g. name-clashes) without domain fine-tuning.
Author's Note

The mistake I see most often on entity linking is treating disambiguation as the interesting problem and candidate generation as a solved one. It's backwards. A disambiguator that beats a Jaccard baseline by ten points is worthless if your alias index only surfaces the correct entity seven times out of ten; you've built a better answer to the wrong shortlist. Before you touch the disambiguator, pull the candidate list for a hundred mentions by hand and check whether the gold entity is even in it. That number is your ceiling, not the disambiguator's F1.

One more thing worth saying directly: NIL prediction is not an afterthought you bolt on later. Ship a linker with no NIL path and it will confidently attach every mention of a person who doesn't have a KB entry yet to whichever existing entity happens to share their name. A wrong answer that looks authoritative is worse than admitting you don't know.

Exercises

  1. Easy. Implement the prior+context disambiguator in code/main.py on 10 ambiguous mentions (Paris, Jordan, Apple). Hand-label the correct entity. Measure accuracy.
  2. Medium. Encode 50 ambiguous mentions with a sentence transformer. Embed each candidate's description. Compare embedding-based disambiguation to Jaccard context overlap.
  3. Hard. Build a 1k-entity domain KB (e.g. employees + products in your company). Implement NER + EL end-to-end. Measure precision and recall on 100 held-out sentences.

Key Terms

TermWhat people sayWhat it actually means
Entity linking (EL)Link to WikipediaMap a mention to a unique KB entry.
Candidate generationWho could it be?Return a shortlist of plausible KB entries for a mention.
DisambiguationPick the right oneScore candidates using context, pick the winner.
Alias indexThe lookup tableMap from surface form → candidate entities.
NILNot in KBExplicit prediction that no KB entry matches.
KBKnowledge baseWikidata, Wikipedia, DBpedia, or your domain KB.
AIDA-CoNLLThe benchmark1,393 Reuters articles with gold entity links.

Further Reading

Code

Ships With