Chapter 3 · Section 3.5 · 70 min

Sequence to sequence and the bottleneck

Everything so far has predicted a sequence from its own past. Translation is harder. The input is a sentence in one language, the output is a sentence in another, and the two have different lengths, different word orders, and no one-to-one correspondence between words. "Le chat noir dort" has the adjective after the noun, "the black cat sleeps" has it before. German puts verbs at the end of clauses, Japanese puts them at the end of sentences. Before 2014, the best systems were phrase-based statistical pipelines with separately trained translation tables, reordering models, and n-gram language models, years of engineering in each.

Then two groups showed that a pair of recurrent networks could learn the whole mapping end to end. Sutskever, Vinyals, and Le (2014) and Cho et al. (2014) built encoder-decoder models, also called sequence to sequence or seq2seq. One RNN reads the source sentence into a vector, and a second RNN, a language model conditioned on that vector, writes the translation. The idea is simple and general (it was soon applied to summarization, dialogue, parsing, speech recognition, and image captioning), and it has one flaw so glaring that fixing it gave us attention. This section builds the model, explains how it is trained and why training and generation disagree, shows how to decode with beam search and why beam search needs length normalization, and then measures the flaw.

Encoder-decoder RNNs

Write the source sentence as x=(x1,…,xTx)\mathbf x = (x_1, \dots, x_{T_x}) and the target as y=(y1,…,yTy)\mathbf y = (y_1, \dots, y_{T_y}), with yTy=</s>y_{T_y} = \texttt{</s>}. A translation model is a conditional language model. By the chain rule of §3.1, now conditioned on the source,

p(y∣x)=∏i=1Typ(yi∣y1,…,yi−1,x).p(\mathbf y \mid \mathbf x) = \prod_{i=1}^{T_y} p(y_i \mid y_1, \dots, y_{i-1}, \mathbf x).

The encoder is an RNN (in practice an LSTM or GRU) that reads the source and produces states h1,…,hTx\mathbf h_1, \dots, \mathbf h_{T_x}. Its final state becomes a single context vector, c=hTx∈Rdh\mathbf c = \mathbf h_{T_x} \in \R^{d_h}. The decoder is a second RNN with its own weights. Its state si\mathbf s_i starts from the context and is updated with the previous target word,

s0=c,si=fdec ⁣(si−1,Eyi−1tgt),p(yi∣y<i,x)=softmax⁡(Wout si+bout),\mathbf s_0 = \mathbf c, \qquad \mathbf s_i = f_{\text{dec}}\!\left(\mathbf s_{i-1}, \mathbf E^{\text{tgt}}_{y_{i-1}}\right), \qquad p(y_i \mid y_{<i}, \mathbf x) = \operatorname{softmax}(\mathbf W_{\text{out}}\,\mathbf s_i + \mathbf b_{\text{out}}),

where y0=<s>y_0 = \texttt{<s>} and Etgt\mathbf E^{\text{tgt}} is the target-side embedding table. In plain words, the encoder compresses the source into one vector, and the decoder is an ordinary RNN language model of the target language whose memory starts out holding that vector. Cho et al. went a step further and fed c\mathbf c into every decoder step and every output, so the decoder could not forget it, but the information still had to pass through the one vector.

   encoder                                    decoder
   le → chat → noir → dort ═══ c ═══▶ s1 → s2 → s3 → s4 → s5
   h1    h2     h3     h4             ↑    ↑    ↑    ↑    ↑
                                     <s>  the black cat sleeps
                                      ↓    ↓    ↓    ↓    ↓
                                     the black cat sleeps </s>

Training maximizes ∑log⁡p(y∣x)\sum \log p(\mathbf y \mid \mathbf x) over a parallel corpus of sentence pairs. The gradient flows from every target word back through the decoder, through c\mathbf c, and into every step of the encoder.

Teacher forcing and exposure bias

How exactly is the decoder trained? At step ii it needs the previous word yi−1y_{i-1} as input. During training we have the true target sentence, so we feed the true previous word, regardless of what the model would have predicted. This is called teacher forcing (Williams and Zipser 1989). The decoder's inputs are the target shifted right by one position, and the loss is the cross-entropy at every position,

L=−∑i=1Tylog⁡p(yi∣y1,…,yi−1,x),decoder inputs (<s>,y1,…,yTy−1), targets (y1,…,yTy).\mathcal L = -\sum_{i=1}^{T_y} \log p\big(y_i \mid y_1, \dots, y_{i-1}, \mathbf x\big), \qquad \text{decoder inputs } (\texttt{<s>}, y_1, \dots, y_{T_y - 1}),\ \text{targets } (y_1, \dots, y_{T_y}).

Teacher forcing is efficient and stable. Every position's loss can be computed from known inputs, and with a Transformer decoder all positions are computed in parallel, which is precisely how GPT is trained in §6.3. In a minibatch, sentences have different lengths, so they are padded and the padded positions are excluded from the loss.

Core

Teacher-forcing inputs and a padding-masked loss

Implement shift_right(Y, bos, pad), which turns a padded batch of targets Y (B, T) into decoder inputs by prepending bos and dropping the last column, and masked_ce(logits, Y, pad), which returns the mean cross-entropy over the real (non-pad) target tokens only. logits has shape (B, T, V).

Hidden tests, hints, and AI help, from $1.99 a month.

Training and generation see different inputs

Here is the catch. At generation time there is no true target to feed. The decoder must condition on its own previous predictions. During training it only ever saw perfect prefixes, so the first time it makes a mistake, it is in a situation it was never trained on. Ranzato et al. (2016) named this mismatch exposure bias. The model is never exposed to its own errors during training, so it has not learned to recover from them, and one wrong word can derail the rest of the sentence.

A toy calculation shows why this matters for long outputs. Suppose that given a correct prefix the model picks a wrong token with probability ϵ=0.02\epsilon = 0.02, but once its prefix contains an error it is confused and errs with probability ϵ′=0.2\epsilon' = 0.2 at every later step. Under teacher forcing, the measured per-token error is just ϵ\epsilon. At generation time the errors compound.

Python · cell 1

For short outputs the two error rates are close. For a 100-token output, free-running generation makes several times as many errors per token as teacher-forced evaluation suggests, because most sentences contain an early mistake and every later token pays for it. This is why a model can look excellent on teacher-forced loss and still produce bad long generations.

Several remedies have been proposed. Scheduled sampling (Samy Bengio et al. 2015) gradually replaces some true previous tokens with the model's own samples during training, so it learns to continue from imperfect prefixes. Sequence-level training (Ranzato et al. 2016, and minimum risk training) optimizes a score of whole generated sequences, such as BLEU, with reinforcement learning, so training sees exactly the generation process. How much exposure bias hurts large modern models is debated, and teacher forcing remains the default because it is so efficient. But the idea of training on the model's own outputs came back in a big way. It is the core of reinforcement learning from human feedback in §16.4, of RL for reasoning in §18.4, and of on-policy distillation, where a student learns from a teacher's feedback on the student's own samples.

The fixed-vector bottleneck

Now the flaw. Every bit of information about the source sentence must pass through c\mathbf c, one vector of dhd_h numbers, no matter how long the sentence is. The decoder cannot look back at the words. It gets one summary, written by an encoder that did not know which details the decoder would need.

How much fits in one vector? The widget below runs an idealized experiment. Each of nn words, drawn from a 24-word vocabulary, adds a random position-specific code to a single dd-dimensional vector, and then we try to read every word back out by matching codes. This is the best case for a linear encoder, with no training noise at all. Compare it with keeping all nn vectors, which is what attention will do.

How much of a sentence fits in one fixed-size vector?
Each of n words (from a 24-word vocabulary) adds a random position-specific code to a single d-dimensional vector. Then we try to read every word back out of that one vector. Attention keeps all n vectors instead, so it can always read them back.
Loading interactive figure…

The capacity of one vector is real and finite. Interference grows with every word. With d=32d = 32, recovery is already down to about half at n=8n = 8, a quarter of dd, because each stored word adds noise of the same size as the signal of every other word (the matching score of the right code grows like dd, the noise like nd\sqrt{nd}, so reading back is reliable only while nn is well below dd). And a leaky memory loses the beginning of the sentence first, which is exactly what reversing the source compensated for. A trained LSTM encoder is not a random linear code, but the same pressure applies. Cho et al. (2014) studied this directly in On the Properties of Neural Machine Translation and found that the encoder-decoder's translation quality degraded rapidly as sentences got longer. Bahdanau et al. (2015) showed the same curve for their baseline encoder-decoder trained on sentences of up to 50 words, while their attention model stayed flat.

We can watch it happen in a real model. The PyTorch block below trains a GRU encoder-decoder, with teacher forcing, to reverse sequences of 3 to 12 digits, a task that needs every input token. It then measures token accuracy by length, both teacher-forced and in free-running greedy generation.

🔥 In PyTorchfor a machine with PyTorch

Both effects of this section show up in one table. Accuracy falls steadily with length even under teacher forcing, because the context vector cannot hold long inputs, and it falls faster in free-running generation, where every early mistake is fed back in. Exact reversal of a 12-digit sequence essentially never succeeds. Hold on to these numbers, because §3.6 trains the same model with attention.

Decoding means searching

Training gives us p(y∣x)p(\mathbf y \mid \mathbf x). To translate, we want the most probable output,

y^=arg⁡max⁡y log⁡p(y∣x)=arg⁡max⁡y∑i=1∣y∣log⁡p(yi∣y<i,x).\hat{\mathbf y} = \arg\max_{\mathbf y}\, \log p(\mathbf y \mid \mathbf x) = \arg\max_{\mathbf y} \sum_{i=1}^{|\mathbf y|} \log p(y_i \mid y_{<i}, \mathbf x).

Exact search is hopeless. There are VnV^n sequences of length nn, and because each word's probability depends on all earlier words, nothing like dynamic programming applies. We need a heuristic.

Greedy decoding takes the most probable word at every step and never looks back. It is fast, and it is often wrong in a specific way. The most probable first word can lead into a region where every continuation is mediocre, while a slightly less probable first word leads to a far better sentence. Greedy cannot undo a choice.

Beam search keeps several options open. It maintains a beam of the KK best partial hypotheses (the beam width). At each step it extends every hypothesis in the beam by every possible next token, scores each extension by its total log-probability, and keeps the KK best. When an extension ends in </s>, that hypothesis is complete. It leaves the beam and joins a list of finished candidates. Search stops when no live hypotheses remain or a maximum length is reached, and the best finished candidate wins.

In plain words, beam search is greedy decoding that hedges its bets KK ways. With K=1K = 1 it is exactly greedy. With K=VnK = V^n it would be exhaustive search. Each step costs KK forward passes of the decoder (batched together) and a top-KK selection over KVKV candidates, so the total cost is O(nKV)O(nKV) plus KK times the cost of greedy decoding. Sutskever et al. found that their model "performs well even with a beam size of 1, and a beam of size 2 provides most of the benefits of beam search". Typical machine translation systems used beams of 4 to 12.

The widget below runs beam search on a tiny hand-built decoder for "le chat noir dort". Each column is one decoding step and each box is a candidate with its total log-probability. Start with K=1K = 1, then raise it one step at a time, leaving the length normalization slider at 0.

Why does a wider beam sometimes give a worse translation?
A hand-built toy decoder (its probabilities are made up for illustration) translating “le chat noir dort”. Each column is one decoding step, and each box is a candidate with its total log-probability. Solid boxes stay in the beam, faded ones are pruned, and boxes ending in <eos> leave the beam as finished translations.
Loading interactive figure…

Three lessons are in the widget. Greedy decoding chooses "the" because it is the most likely first word (0.40 against 0.34 for "a"), but after "the" every continuation is uncertain, and it ends at a total log-probability of −3.33-3.33. With K=2K = 2 the beam keeps "a" alive, and "a black cat sleeps" wins at −1.93-1.93, a sentence the model likes e1.4≈4e^{1.4} \approx 4 times more. And at K=3K = 3 something strange happens. The empty translation, an immediate </s> with probability 0.18, enters the beam, and with log-probability log⁡0.18=−1.71\log 0.18 = -1.71 it beats every real translation.

Interview

Beam search with length normalization

Implement beam_search(step, eos, K, max_len, alpha=0.0). step(prefix) takes a tuple of token ids generated so far (empty at the start) and returns a NumPy array of next-token log-probabilities over the whole vocabulary. Follow these rules exactly.

  • At each step, extend every live hypothesis by every token and keep the KK extensions with the highest total log-probability (break ties by keeping the earlier one in your candidate order, which the tests avoid relying on).
  • Extensions that end in eos move to a finished list and leave the beam. The others stay live.
  • Stop when there are no live hypotheses or after max_len steps. If nothing has finished by then, the live hypotheses count as finished.
  • Return (tokens, score) for the finished hypothesis with the highest score log⁡p(y)/∣y∣α\log p(\mathbf y) / |\mathbf y|^{\alpha}, where ∣y∣|\mathbf y| counts every token including eos. tokens is a tuple that includes the final eos.

The setup block defines the toy model from the widget, TOY_VOCAB, TOY_EOS, and toy_step.

Hidden tests, hints, and AI help, from $1.99 a month.

Why beam search needs length normalization

The empty translation is not a bug in the toy. It is a property of the objective. Every token multiplies in a probability less than one, so every extra token lowers log⁡p(y∣x)\log p(\mathbf y \mid \mathbf x). Short outputs are favored automatically, and the more thoroughly you search, the more likely you are to find a short output the model scores highly. A wider beam is a better optimizer of a flawed objective.

This is well documented in real systems. Koehn and Knowles (2017) listed it among six challenges for neural translation, finding that beam search "only improves translation quality for narrow beams and deteriorates when exposed to a larger search space". Stahlberg and Byrne (2019) ran exact search for a Transformer translation model on English-German news and found that "for 51.8% of the sentences, NMT assigns the global best model score to the empty translation". Beam search works partly because it fails to find the model's true optimum. The search errors cancel a model error.

The standard fix is to normalize the score by length,

score⁡(y)=log⁡p(y∣x)lp⁡(y),lp⁡(y)=∣y∣αorlp⁡(y)=(5+∣y∣)α(5+1)α.\operatorname{score}(\mathbf y) = \frac{\log p(\mathbf y \mid \mathbf x)}{\operatorname{lp}(\mathbf y)}, \qquad \operatorname{lp}(\mathbf y) = |\mathbf y|^{\alpha} \quad\text{or}\quad \operatorname{lp}(\mathbf y) = \frac{(5 + |\mathbf y|)^{\alpha}}{(5 + 1)^{\alpha}} .

In plain words, compare hypotheses by their (roughly) average log-probability per token instead of their total. The exponent α∈[0,1]\alpha \in [0, 1] interpolates between no normalization (α=0\alpha = 0) and a full per-token average (α=1\alpha = 1). The second form is the one Google's neural machine translation system used (Wu et al. 2016), together with a coverage penalty that rewards hypotheses that attend to every source word. Their final setting was α=0.2\alpha = 0.2 for the length penalty and β=0.2\beta = 0.2 for the coverage penalty, tuned on development data. In the widget, dividing by ∣y∣α|\mathbf y|^\alpha with any α\alpha of 0.1 or more restores "a black cat sleeps" at K=3K = 3, while the gentler GNMT penalty needs α\alpha of about 0.25.

A worked check by hand. "a black cat sleeps </s>" has probability 0.34×0.85×0.9×0.62×0.9≈0.1450.34 \times 0.85 \times 0.9 \times 0.62 \times 0.9 \approx 0.145, so log⁡p=−1.93\log p = -1.93 over 5 tokens, and the empty output has log⁡p=log⁡0.18=−1.71\log p = \log 0.18 = -1.71 over 1 token. With α=0.6\alpha = 0.6 the scores are −1.93/50.6=−1.93/2.63=−0.73-1.93/5^{0.6} = -1.93/2.63 = -0.73 against −1.71/1=−1.71-1.71/1 = -1.71, so the real sentence wins comfortably.

Interview

Greedy, beam, and exhaustive search compared

Separate search errors (the decoder failed to find the model's best output) from model errors (the model's best output is bad). Implement greedy_decode(step, eos, max_len) returning (tokens, logp), and exhaustive_best(step, eos, max_len, alpha=0.0) returning the (tokens, score) with the highest normalized score among all sequences that end in eos within max_len tokens, using a depth-first search. The setup block is the same toy model as before.

Hidden tests, hints, and AI help, from $1.99 a month.

BLEU, briefly

How do we score a translation? The dominant automatic metric for a decade was BLEU (Papineni et al. 2002), which compares n-grams of a candidate with those of one or more reference translations. Its two ingredients are modified n-gram precision and a brevity penalty.

For each nn from 1 to 4, count the candidate's n-grams, but clip each n-gram's count at the maximum number of times it appears in a reference, so that "the the the the" cannot get credit for one "the" four times. The modified precision pnp_n is the total clipped count divided by the total number of candidate n-grams, summed over the whole test corpus. Precision alone rewards very short outputs (a one-word candidate can be perfectly precise), so BLEU multiplies by a penalty for candidates shorter than the references. With cc the total candidate length and rr the total reference length,

BLEU⁡=BP⁡⋅exp⁡ ⁣(∑n=1414log⁡pn),BP⁡={1c>re 1−r/cc≤r.\operatorname{BLEU} = \operatorname{BP}\cdot\exp\!\left(\sum_{n=1}^{4} \tfrac{1}{4}\log p_n\right), \qquad \operatorname{BP} = \begin{cases} 1 & c > r \\ e^{\,1 - r/c} & c \le r. \end{cases}

In plain words, BLEU is the geometric mean of the 1- to 4-gram precisions, discounted when the output is too short. A worked example with one sentence. Reference "the black cat sleeps on the mat", candidate "the cat sleeps on the mat". Unigrams match 6 of 6, bigrams 4 of 5 ("the cat" is not in the reference), trigrams 3 of 4, and 4-grams 2 of 3, so the geometric mean is (1⋅0.8⋅0.75⋅0.667)1/4=0.41/4≈0.795(1 \cdot 0.8 \cdot 0.75 \cdot 0.667)^{1/4} = 0.4^{1/4} \approx 0.795. The candidate has 6 words against 7, so BP⁡=e1−7/6≈0.846\operatorname{BP} = e^{1 - 7/6} \approx 0.846, and BLEU ≈0.673\approx 0.673, usually reported as 67.3.

Core

Corpus BLEU with clipping and a brevity penalty

Implement corpus_bleu(hyps, refs, N=4), where hyps and refs are lists of token lists with one reference per hypothesis. Accumulate clipped n-gram matches and candidate n-gram totals over the whole corpus for each nn, compute the brevity penalty from the total lengths, and return BLEU as a number between 0 and 1 (return 0 if any pnp_n is zero).

Hidden tests, hints, and AI help, from $1.99 a month.

BLEU is cheap, language-independent, and correlates with human judgments at the corpus level, which is why it dominated. It is also crude. It rewards surface overlap, so a correct paraphrase scores badly, it says nothing about meaning or fluency at the sentence level, and the same model can get different BLEU from different tokenization and normalization choices, which is why Post (2018) proposed the standardized SacreBLEU. Character-level metrics like chrF and learned metrics like COMET, which compare embeddings with a trained model, correlate better with human judgment and are now preferred in translation research. The broader lesson, that automatic metrics are proxies with blind spots, returns in every evaluation chapter of this book (§6.6, and the difficulty of evaluating language models in general).

decodingwhat it keepscosttypical usefailure mode
greedy1 hypothesisnn decoder stepsfast drafts, structured outputcannot undo an early choice
beam, width KKKK hypotheses≈K×\approx K\times greedytranslation, speech recognitionfavors short outputs without normalization, bland text
beam with length penaltyKK hypotheses, normalized scoresamestandard translation decodingα\alpha must be tuned
sampling (§6.4)1 random pathnn decoder stepsopen-ended generation, chatcan wander, needs temperature or top-p

The bottleneck, stated plainly

Step back and look at what the encoder-decoder asks of its context vector. The decoder must produce a word-by-word translation, and for each word it needs specific facts about specific source words (which noun this adjective modifies, the tense of that verb, the spelling of a name). All of that must have been packed into c\mathbf c in advance, by an encoder that did not know which facts would be needed in which order. When it writes the tenth target word, the decoder cannot go back and reread the source.

That is not how a human translator works. A human reads the sentence, then while writing each target word looks back at the relevant part of the source. Bahdanau, Cho, and Bengio put it precisely in 2014. The fixed-length vector is a bottleneck, and the decoder should be allowed to search the source for the parts relevant to the word it is about to generate. That search is attention, and it is the next section.

Read more

Watch next

🎬 Sequence-to-Sequence (seq2seq) Encoder-Decoder Neural Networks, Clearly Explained!!! · StatQuest with Josh StarmerThe encoder, the decoder, and how training feeds in the known words.
🎬 Beam search (NLP817 10.5) · Herman KamperBeam search, carefully explained with a worked example.
🎬 Evaluating machine translation with BLEU (NLP817 10.8) · Herman KamperBLEU computed step by step.