---
title: Question Answering
module: Applications
moduleNumber: 7
lessonNumber: 3
order: 703
summary: >
  A question-answering system takes a natural-language question and returns an
  answer, not a ranked list of documents. Almost every modern system is built on
  one pattern: retrieve then read. We start with the information-retrieval
  machinery that finds candidate text — tf-idf and BM25 term weighting, a worked
  ranking example, the inverted index, and dense embedding retrieval — then build
  the retriever-reader pipeline that extracts an answer span with BERT and trace
  a full retrieve-and-read example end to end.
topics: [Applications]
sources:
  - book: Jurafsky
    ref: "Ch. 23 — Question Answering; §23.1 Information Retrieval"
  - book: Jurafsky
    ref: "§23.2 IR-based Factoid Question Answering (retrieve-and-read, BERT span extraction)"
---

A search engine returns documents; a question-answering system returns an answer.
For the question _"What's the official language of Algeria?"_, the correct output
is the word _Arabic_, not ten Wikipedia pages ranked by relevance. Almost every
modern system produces the answer with one pattern: **retrieve** a small set of
promising passages from a large collection, then **read** or **generate** the
answer from them.[^jm-qa]

Two distinctions organize the field. The first is what
kind of question is asked. A **factoid** question has a short, factual answer that
can be stated in a phrase — a name, a date, a place ("Where is the Louvre?" →
"in Paris"). A **complex** question needs synthesis across several facts or a
paragraph of explanation ("Why did the Roman Republic fall?"). The second is how
the answer is produced. **Extractive** QA returns a literal span copied out of
some source text; **generative** (or abstractive) QA writes the answer in its own
words, possibly combining several sources. Most of this lesson is factoid QA,
because it exposes the machinery most cleanly, but the retrieve-then-generate
skeleton scales straight up to the complex, generative case.

$$
% caption: The two dominant QA architectures share a retriever. IR-based QA reads
% an answer span out of retrieved passages; generative QA feeds them to a language
% model that writes the answer.
\begin{tikzpicture}[>=stealth, font=\footnotesize,
  box/.style={draw, minimum width=22mm, minimum height=11mm, align=center},
  db/.style={draw, cylinder, shape aspect=0.32, minimum width=15mm, minimum height=17mm, align=center, font=\scriptsize, shape border rotate=90}]
  \definecolor{acc}{HTML}{2348F2}
  \node[box] (q) at (0,0) {question $q$};
  \node[db] (coll) at (0,-2.3) {docs};
  \node[box, draw=acc, text=acc, thick] (ret) at (3.4,-1.1) {retriever};
  \node[box] (read) at (7.0,0) {reader\\(span)};
  \node[box] (gen) at (7.0,-2.3) {generator\\(LLM)};
  \node[box, draw=acc, text=acc] (a) at (10.4,-1.1) {answer};
  \draw[->, thick] (q) -- (ret);
  \draw[->, thick] (coll) -- (ret);
  \draw[->, acc, thick] (ret) -- (read) node[midway, above, font=\scriptsize] {passages};
  \draw[->, acc, thick] (ret) -- (gen) node[midway, below, font=\scriptsize] {passages};
  \draw[->, thick] (read) -- (a);
  \draw[->, thick] (gen) -- (a);
\end{tikzpicture}
$$

## Information retrieval

Everything downstream depends on the retriever handing the reader a handful of
passages that actually contain the answer, so we start there. **Information
retrieval** (IR) is the task of returning, from a large document collection, the
documents most relevant to a query.[^jm-ir] We write the query as $q$ and a
document as $d$, and represent both as vectors in a shared space; relevance is
then a geometric quantity — how aligned the query vector is with the document
vector.

### Term weighting and the vector space model

The oldest representation is a **sparse** word-count vector: one dimension per
vocabulary term, most entries zero. Raw counts overweight common words, so each
term $t$ in a document $d$ is weighted by **tf-idf**, the product of a term
frequency and an inverse document frequency,

$$
\text{tf-idf}(t, d) = \text{tf}_{t,d} \cdot \text{idf}_t,
\qquad
\text{tf}_{t,d} = \log_{10}(\text{count}(t,d) + 1),
\qquad
\text{idf}_t = \log_{10} \frac{N}{\text{df}_t},
$$

where $N$ is the number of documents and $\text{df}_t$ the number that contain
$t$. The idf term does the real work here: a word appearing in every document (like _the_)
carries $\text{idf} \approx 0$ and is downweighted to nothing, while a rare,
discriminating word gets a large weight. Documents are then scored against the
query by the cosine of their vectors, which for the usual simplification (query
terms counted once, query normalization constant across documents) reduces to a
sum over the query's terms,

$$
\text{score}(q, d) = \sum_{t \in q} \frac{\text{tf-idf}(t, d)}{|d|},
$$

with $|d|$ the document's vector length; the denominator prevents a long
document from scoring highly on term count alone.

> **Definition (Inverse document frequency).** A weight $\text{idf}_t =
> \log(N / \text{df}_t)$ that measures how discriminating a term is: high for rare
> words tied to a particular topic, near zero for words spread across the whole
> collection. tf-idf multiplies it by the in-document frequency so a term counts
> most when it is frequent _here_ and rare _elsewhere_.

A slightly richer member of the same family is **BM25**, which adds two knobs: a
parameter $k$ balancing term frequency against idf, and $b$ controlling how much
document length is penalized. The BM25 score of $d$ given $q$ is

$$
\sum_{t \in q} \log\!\left(\frac{N}{\text{df}_t}\right)
\cdot
\frac{\text{tf}_{t,d}}{k\!\left(1 - b + b\,\dfrac{|d|}{|d_{\text{avg}}|}\right) + \text{tf}_{t,d}},
$$

where $|d_{\text{avg}}|$ is the average document length. Reasonable defaults are
$k \in [1.2, 2]$ and $b = 0.75$. At $k = 0$ BM25 ignores term frequency entirely
and reduces to a binary idf-weighted match; large $k$ recovers raw term
frequency. BM25 remains a strong, ubiquitous baseline that dense retrievers do
not always beat.

#### Worked example: tf-idf vs. BM25 on two documents

Run both scorers on one query against two toy documents to see the effect of $k$
and $b$. The query is _tragic love_; the collection has $N = 100$ documents, in $10$
of which _love_ occurs and $5$ of which _tragic_ occurs, so the idf weights are
$\text{idf}_{\text{love}} = \log_{10}(100/10) = 1.000$ and $\text{idf}_{\text{tragic}}
= \log_{10}(100/5) = 1.301$. Two candidate documents, with the average document length
$|d_{\text{avg}}| = 120$:

| doc | length | count(_love_) | count(_tragic_) |
| --- | --- | --- | --- |
| $d_1$ | $40$ | $8$ | $2$ |
| $d_2$ | $300$ | $40$ | $2$ |

$d_1$ is short and focused; $d_2$ is long and repeats _love_ forty times. **Plain
tf-idf** (using the log-squashed $\text{tf} = \log_{10}(\text{count}+1)$ and summing
$\text{tf}\cdot\text{idf}$ over the query terms) rewards that repetition:

$$
\text{tf-idf}(d_2) = \underbrace{\log_{10}(41)\cdot 1.000}_{\text{love}} + \underbrace{\log_{10}(3)\cdot 1.301}_{\text{tragic}} = 1.613 + 0.621 = 2.234,
$$

against $\text{tf-idf}(d_1) = \log_{10}(9)\cdot 1.000 + \log_{10}(3)\cdot 1.301 = 0.954 +
0.621 = 1.575$. Plain tf-idf ranks the long, keyword-stuffed $d_2$ **above** $d_1$.

**BM25** at the default $k = 1.2$, $b = 0.75$ (using the _raw_ term count as $\text{tf}$
inside the saturation term, per the formula above) flips the ranking. The _love_ term in
$d_2$, despite forty occurrences, contributes almost the same as _love_ in $d_1$ with
eight, because the tf saturates and the length penalty grows the denominator:

$$
\text{BM25 }d_1(\textit{love}) = 1.000 \cdot \frac{8}{1.2\,(1 - 0.75 + 0.75\cdot\frac{40}{120}) + 8} = \frac{8}{8.60} = 0.930,
$$

$$
\text{BM25 }d_2(\textit{love}) = 1.000 \cdot \frac{40}{1.2\,(1 - 0.75 + 0.75\cdot\frac{300}{120}) + 40} = \frac{40}{42.55} = 0.940.
$$

Forty occurrences in the long document beat eight in the short one by less than a
hundredth of a point. Summing both query terms gives the full scores below, and varying
the knobs isolates what each controls:

| scorer / setting | $d_1$ | $d_2$ | winner |
| --- | --- | --- | --- |
| plain tf-idf | $1.575$ | $2.234$ | $d_2$ |
| BM25 $k=1.2,\, b=0.75$ (default) | $1.931$ | $1.512$ | $d_1$ |
| BM25 $k=0$ (ignore tf) | $2.301$ | $2.301$ | tie |
| BM25 $k=1.2,\, b=0$ (no length norm) | $1.683$ | $1.784$ | $d_2$ |
| BM25 $k=1.2,\, b=1$ (full length norm) | $2.037$ | $1.451$ | $d_1$ |

Read the last three rows as controlled experiments. At $k = 0$ term frequency drops out
of the formula entirely and both documents score $\text{idf}_{\text{love}} +
\text{idf}_{\text{tragic}} = 2.301$ — a pure binary idf match, since both contain both
query terms. Turning length normalization off ($b = 0$) lets $d_2$'s extra occurrences
win again, exactly as plain tf-idf did; turning it fully on ($b = 1$) penalizes $d_2$'s
length hardest and gives $d_1$ the largest margin. The default $b = 0.75$ sits between,
enough to stop a long document from ranking first on sheer term count. BM25's two
parameters correct the two ways plain tf-idf misranks: unbounded term-frequency
reward and no penalty for length.[^jm-ir]

$$
% caption: Why BM25 flips the ranking. Plain tf-idf (dashed) keeps rewarding extra
% occurrences of a query term, so a long keyword-stuffed document climbs without
% bound; BM25 (solid) saturates the term-frequency contribution toward a ceiling of
% idf, so the fortieth occurrence adds almost nothing over the eighth.
\begin{tikzpicture}[>=stealth, font=\footnotesize]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  \draw[->, black] (0,0) -- (6.6,0) node[right, black, font=\scriptsize] {term count in document};
  \draw[->, black] (0,0) -- (0,3.6) node[above, black, font=\scriptsize] {term score};
  % BM25 saturating curve: tf/(k+tf) scaled, k~1.2 -> flattens quickly
  \draw[acc, very thick, domain=0:6.2, samples=80, variable=\x]
    plot ({\x}, {3.0*\x/(1.2+\x)});
  \node[acc, anchor=west] at (5.0,2.6) {BM25};
  % tf-idf log curve (log-squashed tf), keeps rising
  \draw[red, thick, dashed, domain=0.05:5.9, samples=80, variable=\x]
    plot ({\x}, {1.35*ln(\x+1)/ln(10)+0.55});
  \node[red, anchor=west, font=\scriptsize] at (5.95,1.68) {tf-idf};
  % ceiling line
  \draw[black, dashed] (0,3.0) -- (6.2,3.0);
  \node[font=\scriptsize, anchor=east, text=black] at (6.2,3.15) {idf ceiling};
  % mark the 8 vs 40 occurrences
  \draw[acc!55, dotted] (2.2,0) -- (2.2,{3.0*2.2/(1.2+2.2)});
  \node[font=\scriptsize, anchor=north, text=acc] at (2.2,-0.05) {8};
  \node[font=\scriptsize, anchor=north, text=acc] at (6.0,-0.05) {40};
\end{tikzpicture}
$$

### The inverted index

To score documents we first have to _find_ the ones sharing a term with the query,
and scanning the whole collection per query is far too slow. The **inverted index**
is the data structure that makes it fast.[^jm-index] It maps each term to a **postings
list** — the document IDs that contain it, usually annotated with the term's count
or positions — through a **dictionary** of terms built for quick lookup. Given the
query's terms, a lookup in the dictionary yields exactly the candidate documents
that could score above zero, together with the counts needed to compute tf-idf.

$$
% caption: An inverted index. The dictionary maps each term to its postings list
% of docID:count entries; a query looks up only its own terms and unions the
% resulting candidate documents.
\begin{tikzpicture}[>=stealth, font=\footnotesize,
  term/.style={draw, minimum width=15mm, minimum height=7mm, align=center, font=\ttfamily\footnotesize},
  post/.style={draw, minimum width=13mm, minimum height=7mm, align=center, font=\ttfamily\footnotesize}]
  \definecolor{acc}{HTML}{2348F2}
  \node[font=\footnotesize] at (0,1.1) {dictionary};
  \node[font=\footnotesize] at (5.0,1.1) {postings lists};
  \node[term] (t1) at (0,0.3) {love};
  \node[term] (t2) at (0,-0.7) {nurse};
  \node[term] (t3) at (0,-1.7) {sweet};
  \node[post] (p1a) at (3.0,0.3) {1:1};
  \node[post] (p1b) at (4.7,0.3) {3:1};
  \node[post] (p2a) at (3.0,-0.7) {1:1};
  \node[post] (p2b) at (4.7,-0.7) {4:1};
  \node[post] (p3a) at (3.0,-1.7) {1:2};
  \node[post] (p3b) at (4.7,-1.7) {2:1};
  \node[post] (p3c) at (6.4,-1.7) {3:1};
  \draw[->, acc] (t1) -- (p1a);  \draw[->, acc] (p1a) -- (p1b);
  \draw[->, acc] (t2) -- (p2a);  \draw[->, acc] (p2a) -- (p2b);
  \draw[->, acc] (t3) -- (p3a);  \draw[->, acc] (p3a) -- (p3b); \draw[->, acc] (p3b) -- (p3c);
\end{tikzpicture}
$$

### Dense retrieval

tf-idf and BM25 share a structural weakness: they only match when the query and
document use the _same words_. A user asking about a _tragic love story_ will
never match a passage that says _star-crossed lovers_ — this is the **vocabulary
mismatch** problem.[^jm-dense] To address it, replace sparse word-count vectors with
**dense** ones: a fixed-length embedding, a few hundred real numbers, produced by
an encoder that has learned to place synonymous text nearby. Because the vectors
come from a model trained on meaning rather than surface form, _tragic love story_
and _star-crossed lovers_ land close together even with no shared word.

Modern dense retrieval uses a **bi-encoder**: two encoders, one for the query and
one for the document, each a transformer like [BERT](/natural-language-processing/transformers/large-language-models).
We take the `[CLS]` output of each as the vector and score by dot product,

$$
h_q = \text{BERT}_Q(q)[\texttt{CLS}],
\qquad
h_d = \text{BERT}_D(d)[\texttt{CLS}],
\qquad
\text{score}(d, q) = h_q \cdot h_d.
$$

Encoding both sides _separately_ is what makes this practical at scale: every
document vector $h_d$ is computed once, offline, and stored, so a query only has to
encode $q$ and find its nearest neighbors. That nearest-neighbor search over
millions of dense vectors is itself expensive, so systems use **approximate
nearest neighbor** libraries like Faiss instead of an exact scan.

$$
% caption: Sparse vs. dense retrieval. A sparse tf-idf vector has one mostly-zero
% dimension per vocabulary word and matches on exact terms; a dense bi-encoder maps
% $q$ and $d$ to short real vectors that match on meaning, so paraphrases align.
\begin{tikzpicture}[>=stealth, font=\footnotesize,
  cell/.style={draw, minimum width=6mm, minimum height=6mm, font=\scriptsize},
  dcell/.style={draw, minimum width=8mm, minimum height=6mm, font=\scriptsize}]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  % --- sparse (left) ---
  \node[font=\footnotesize, anchor=south] at (1.5,1.5) {sparse (tf-idf)};
  \foreach \i/\v in {0/0,1/0,2/2,3/0,4/0,5/1,6/0} \node[cell] at (\i*0.62,0.7) {\v};
  \node[font=\scriptsize, anchor=north] at (1.86,0.35) {one dim per word, mostly zero};
  \node[red, font=\scriptsize, anchor=north] at (1.86,-0.35) {matches only exact terms};
  % --- dense (right) ---
  \begin{scope}[xshift=6.5cm]
    \node[font=\footnotesize, anchor=south] at (1.7,1.5) {dense (bi-encoder)};
    \foreach \i/\v in {0/{.3}, 1/{-.8}, 2/{.1}, 3/{.6}} \node[dcell] at (\i*0.9,0.7) {\v};
    \node[font=\scriptsize, anchor=north] at (1.35,0.35) {short real vector};
    \node[acc, font=\scriptsize, anchor=north] at (1.35,-0.35) {paraphrases land nearby};
  \end{scope}
\end{tikzpicture}
$$

### Evaluating retrieval

A ranked retriever is judged with **precision** and **recall**: precision is the
fraction of returned documents that are relevant, recall the fraction of relevant
documents returned. Because both ignore _order_, ranked systems are usually
summarized by **mean average precision** (MAP), which averages the precision
measured at each rank where a relevant document appears, then averages that over a
set of queries. Higher precision across all recall levels is the goal; MAP folds
the whole precision-recall curve into one comparable number.

## IR-based factoid QA: retrieve and read

With a retriever in hand, the dominant IR-based architecture is **retrieve and
read**.[^jm-read] Stage one retrieves relevant passages from the collection using
the IR machinery above. Stage two runs a neural **reading-comprehension** model
over each passage: given the question and a passage that may contain the answer,
it extracts the answer **span** — a contiguous run of tokens.

$$
% caption: The retrieve-and-read pipeline. A retriever pulls candidate passages
% from the indexed collection; a reading-comprehension model then extracts the
% answer span from the best passages.
\begin{tikzpicture}[>=stealth, font=\footnotesize,
  box/.style={draw, minimum width=24mm, minimum height=13mm, align=center},
  db/.style={draw, cylinder, shape aspect=0.3, minimum width=15mm, minimum height=18mm, align=center, font=\scriptsize, shape border rotate=90}]
  \definecolor{acc}{HTML}{2348F2}
  \node[align=center, font=\footnotesize] (q) at (0,0) {question $q$:\\when was\\Mozart born?};
  \node[db] (idx) at (0,-2.4) {indexed\\docs};
  \node[box, draw=acc, text=acc, thick] (ret) at (3.6,-1.2) {retriever};
  \node[box, draw=acc, text=acc, thick] (rd) at (8.0,-1.2) {reader\\(BERT span)};
  \node[align=center, font=\footnotesize] (a) at (11.5,-1.2) {answer:\\1756};
  \draw[->, thick] (q) -- (ret);
  \draw[->, thick] (idx) -- (ret);
  \draw[->, acc, thick] (ret) -- (rd) node[midway, above, font=\scriptsize] {passages};
  \draw[->, thick] (rd) -- (a);
\end{tikzpicture}
$$

### Span extraction with BERT

The reading model computes, for a question $q$ of $n$ tokens $q_1, \dots, q_n$ and
a passage $p$ of $m$ tokens $p_1, \dots, p_m$, the probability $P(a \mid q, p)$
that each candidate span $a$ is the answer. It factors that span probability into a
**start** and an **end**: for span from token $a_s$ to token $a_e$,

$$
P(a \mid q, p) = P_{\text{start}}(a_s \mid q, p) \cdot P_{\text{end}}(a_e \mid q, p).
$$

So the model only has to compute, for every passage token $p_i$, the probability
$p_{\text{start}}(i)$ that it begins the answer and $p_{\text{end}}(i)$ that it ends
it. The standard baseline concatenates the question and passage into one BERT
input, separated by a `[SEP]` token and prefixed by `[CLS]`, yielding an
embedding $p'_i$ for every passage token.[^jm-span] This reuses the
[fine-tuning](/natural-language-processing/transformers/fine-tuning-and-prompting)
recipe wholesale — a pretrained encoder plus a small task head trained on labelled data.

The head is two learned vectors, a span-start embedding $S$ and a span-end
embedding $E$. The start probability of token $i$ is a softmax of the dot product
between $S$ and that token's embedding, and the end probability is the same with
$E$,

$$
P_{\text{start}}(i) = \frac{\exp(S \cdot p'_i)}{\sum_j \exp(S \cdot p'_j)},
\qquad
P_{\text{end}}(i) = \frac{\exp(E \cdot p'_i)}{\sum_j \exp(E \cdot p'_j)}.
$$

The score of a candidate span from $i$ to $j$ is $S \cdot p'_i + E \cdot p'_j$, and
the model returns the highest-scoring span with $j \ge i$. Fine-tuning minimizes
the negative log-likelihood of the gold start and end positions,

$$
L = -\log P_{\text{start}}(a_s) - \log P_{\text{end}}(a_e).
$$

$$
% caption: BERT span extraction. Question and passage go into one encoder; a
% learned start vector $S$ and end vector $E$ dot with each passage token embedding
% and softmax over the passage gives $p_{\text{start}}$ and $p_{\text{end}}$.
\begin{tikzpicture}[>=stealth, font=\footnotesize,
  tok/.style={draw, minimum width=9mm, minimum height=6.5mm, font=\footnotesize\ttfamily, align=center},
  vec/.style={draw, minimum width=8mm, minimum height=6mm, font=\scriptsize},
  prob/.style={draw, minimum width=9mm, minimum height=6mm, font=\scriptsize}]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  % input tokens
  \node[tok] (cls) at (0,0)    {[CLS]};
  \node[tok] (q1)  at (1.15,0) {q1};
  \node[tok] (qn)  at (2.3,0)  {qn};
  \node[tok] (sep) at (3.45,0) {[SEP]};
  \node[tok] (p1)  at (4.6,0)  {p1};
  \node[tok] (pi)  at (5.75,0) {pi};
  \node[tok] (pm)  at (6.9,0)  {pm};
  \node[font=\scriptsize, anchor=north] at (1.72,-0.45) {question};
  \node[font=\scriptsize, anchor=north] at (5.75,-0.45) {passage};
  % encoder
  \node[draw, draw=acc, text=acc, thick, minimum width=72mm, minimum height=9mm] (enc) at (3.45,1.6) {Encoder (BERT)};
  \foreach \x in {cls,q1,qn,sep,p1,pi,pm} \draw[->, acc] (\x.north) -- (\x.north |- enc.south);
  % start/end vectors dotting with a passage token
  \node[vec, draw=red, text=red] (S) at (4.9,3.3) {S};
  \node[vec, draw=acc, text=acc] (E) at (6.6,3.3) {E};
  \draw[->] (pi.north |- enc.north) ++(0,0) -- (S.south);
  \draw[->] (pi.north |- enc.north) ++(0,0) -- (E.south);
  \node[prob, draw=red, text=red] (ps) at (4.9,4.7) {p-start};
  \node[prob, draw=acc, text=acc] (pe) at (6.6,4.7) {p-end};
  \draw[->, red] (S) -- (ps);
  \draw[->, acc] (E) -- (pe);
\end{tikzpicture}
$$

Unanswerable questions get a simple treatment. Datasets like SQuAD 2.0 include
questions whose answer is _not_ in the passage; the model learns to point both the
start and end at the `[CLS]` token in that case, so `[CLS]` serves as the
no-answer prediction. And because passages are often longer than BERT's token
limit, a long document is chopped into overlapping windows, each treated as its own
passage, and the answer is the highest-scoring span across all of them.

### A worked retrieve-and-read trace

Carry a real question through both stages. Ask _"When was Mozart born?"_ against a
tiny collection of three passages, with a dense bi-encoder retriever and a BERT
reader.

**Stage one — dense retrieval.** The query encoder maps the question to a vector
$h_q$, and each passage was encoded offline to $h_{d}$. Scoring by dot product gives
a ranking; suppose the numbers come out as

| passage | text (excerpt) | $h_q \cdot h_d$ |
| --- | --- | --- |
| $d_2$ | _Wolfgang Amadeus Mozart was born in Salzburg on 27 January 1756 …_ | $18.4$ |
| $d_1$ | _Mozart composed more than 600 works before his death in 1791 …_ | $11.2$ |
| $d_3$ | _Salzburg is a city on the Salzach river in Austria …_ | $6.7$ |

Note that $d_1$ shares the word _Mozart_ and $d_3$ shares _Salzburg_, but the passage
that actually states the birth, $d_2$, wins because the bi-encoder places the meaning
of _"when born"_ near _"was born … on … 1756"_ even though the question's words _when_
and _born_ appear differently in the passage. A sparse BM25 retriever keying on the
word _born_ would also surface $d_2$ here, but would rank a passage using _birth_ or
_native of_ far lower — the vocabulary-mismatch gap dense retrieval closes. The top
$k$ passages (say $k = 2$: $d_2, d_1$) pass to the reader.

**Stage two — span extraction.** The reader concatenates the question with $d_2$ into
one BERT input, `[CLS] when was Mozart born ? [SEP] Wolfgang Amadeus Mozart was born
in Salzburg on 27 January 1756 [SEP]`, and produces an embedding $p'_i$ per token. The
learned start vector $S$ dots highest with the token _27_ and the end vector $E$ dots
highest with _1756_, giving the candidate span _27 January 1756_. Suppose the dot
products land at

$$
S \cdot p'_{27} = 7.9,\quad E \cdot p'_{1756} = 8.3
\;\Rightarrow\;
\text{span score} = S \cdot p'_{i} + E \cdot p'_{j} = 16.2,
$$

and that this beats every span the reader extracts from $d_1$ (whose best span, around
_1791_, scores lower because $d_1$ is about his death, not his birth) and beats the
`[CLS]`-to-`[CLS]` no-answer score. The system returns _27 January 1756_ — or, under a
stricter answer normalization that keeps only the year, _1756_. The retriever narrowed
the collection to two passages; the reader extracted one span from the top passage.

$$
% caption: A worked retrieve-and-read trace for "When was Mozart born?". Dense
% retrieval scores three passages by query-passage dot product and ranks the
% birth passage d2 top; the BERT reader then places the start vector S on "27" and
% the end vector E on "1756", extracting the span "27 January 1756".
\begin{tikzpicture}[>=stealth, font=\footnotesize,
  pass/.style={draw, minimum width=44mm, minimum height=7mm, align=left, font=\scriptsize},
  win/.style={draw=acc, text=acc, minimum width=44mm, minimum height=7mm, align=left, font=\scriptsize},
  tokn/.style={draw, minimum width=13mm, minimum height=6mm, font=\scriptsize, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  \node[anchor=west, font=\scriptsize, text=black] at (-0.2,1.5) {retriever scores (dot product)};
  \node[win]  (d2) at (2.4,0.7)  {d2: born ... 1756};
  \node[anchor=west, font=\scriptsize, text=acc] at (4.9,0.7) {18.4  (top)};
  \node[pass] (d1) at (2.4,-0.1) {d1: died 1791};
  \node[anchor=west, font=\scriptsize, text=black] at (4.9,-0.1) {11.2};
  \node[pass] (d3) at (2.4,-0.9) {d3: Salzburg city};
  \node[anchor=west, font=\scriptsize, text=black] at (4.9,-0.9) {6.7};
  % reader on d2
  \node[anchor=west, font=\scriptsize, text=black] at (-0.2,-2.1) {reader span on d2};
  \node[tokn] (t1) at (0.7,-2.9) {born};
  \node[tokn] (t2) at (2.1,-2.9) {in};
  \node[tokn] (t3) at (3.5,-2.9) {Salzburg};
  \node[tokn, draw=red, text=red] (t4) at (4.9,-2.9) {27 Jan};
  \node[tokn, draw=acc, text=acc] (t5) at (6.3,-2.9) {1756};
  \node[anchor=south, font=\scriptsize, text=red] at (4.9,-2.45) {S};
  \node[anchor=south, font=\scriptsize, text=acc] at (6.3,-2.45) {E};
  \draw[->, acc] (d2.south) to[out=-90,in=90] (2.1,-2.5);
\end{tikzpicture}
$$

### Datasets

Reading-comprehension datasets are built as _(passage, question, answer-span)_
triples. **SQuAD** takes Wikipedia passages and has annotators write questions
whose answers are spans of the passage; **HotpotQA** forces reasoning across
multiple documents; **Natural Questions** uses real Google queries paired with a
Wikipedia page, annotating a long and a short answer or _null_. At training time
the reader sees the gold passage; at inference in the full two-stage system the
passage is withheld and the retriever must find it — which is why _open-domain_ QA
is harder than reading comprehension alone.
The retriever-plus-reader pattern handles any answer that appears verbatim in some
passage. Two cases fall outside it: answers that live in a structured knowledge base
rather than prose, and answers a large language model can generate from its own
weights. This continues in [Question Answering: Knowledge Bases and Language Models](/natural-language-processing/applications/question-answering-knowledge-and-llms).

[^jm-qa]: **Jurafsky & Martin**, _Speech and Language Processing_ (3rd ed.), Ch. 23 — Question Answering: factoid vs. complex questions, extractive vs. abstractive answers, and the retrieve-and-read paradigm shared across IR-based systems.
[^jm-ir]: **Jurafsky & Martin**, §23.1 — Information Retrieval: the vector space model, tf-idf term weighting, cosine document scoring, and BM25.
[^jm-index]: **Jurafsky & Martin**, §23.1.3 — Inverted Index: dictionary and postings lists for efficiently finding documents that contain query terms.
[^jm-dense]: **Jurafsky & Martin**, §23.1.5 — IR with Dense Vectors: the vocabulary-mismatch problem, the BERT bi-encoder, and approximate nearest-neighbor search with Faiss.
[^jm-read]: **Jurafsky & Martin**, §23.2 — IR-based Factoid Question Answering: the retrieve-and-read model and the reading-comprehension task, with SQuAD, HotpotQA, and Natural Questions.
[^jm-span]: **Jurafsky & Martin**, §23.2.2 — Reader (Answer Span Extraction): the BERT span model with learned start/end vectors, the `[CLS]`-as-no-answer trick, and the span-position log-likelihood loss.
