Contextual Embeddings and ELMo

Consider the word "bank" in "I deposited money at the bank" versus "I sat on the river bank." A static word embedding like word2vec or GloVe assigns "bank" exactly one vector, forever. This holds regardless of which sentence it appears in. This is a fundamental mismatch with how language actually works. Word meaning is fluid, and disambiguating it requires looking at surrounding context. Contextual embeddings solve this by computing a word's vector representation as a function of the entire sentence it occurs in. So "bank" gets a different vector in each sentence above. This idea reshaped NLP practice almost overnight. Before contextual embeddings, the standard pipeline was: look up a static vector for each token, feed the sequence into a task-specific recurrent network, and train that network on a labeled dataset. The static vectors carried general distributional knowledge. But all context-sensitivity had to be learned from scratch by the (often small) task-specific model. ELMo (Embeddings from Language Models), introduced in 2018, proposed instead to pretrain a deep bidirectional language model on a huge unlabeled corpus. It then used the model's internal hidden states as rich, contextualized word representations. These representations already encode syntax, semantics, and disambiguation information learned from language modeling itself. This happens before any task-specific training even begins. The big picture is a shift from "one vector per word type" to "one vector per word token in context." It is also a shift from feature engineering to representation transfer. ELMo also introduced a second, equally influential idea. Instead of using only the top layer of a deep network, it learns a task-specific weighted combination of all layers. This works since different layers specialize in different linguistic phenomena. Lower layers capture more syntax, while higher layers capture more semantics. This layer-mixing recipe, along with the pretrain-then-adapt philosophy, became the direct conceptual ancestor of Transformer architectures like BERT and GPT. This article builds ELMo from the ground up. It covers the bidirectional language modeling objective, the character-level convolutional input layer, the stacked bidirectional LSTM, the layer-weighting mechanism, and how the resulting embeddings plug into downstream tasks. We derive the complexity of training and inference. We prove why the bidirectional setup does not "cheat" by seeing the answer. We trace a full worked example. Finally, we place ELMo in its historical context, relative to both static embeddings and the attention-based models that superseded it.

Key takeaways
  • Static embeddings (word2vec, GloVe) give one fixed vector per word type; contextual embeddings give a different vector per token occurrence, computed from the sentence.
  • ELMo pretrains a deep bidirectional LSTM language model (forward LM + backward LM) on unlabeled text, then reuses its internal states as features.
  • Input words are represented via a character-level CNN, avoiding a fixed vocabulary and handling unknown/rare words gracefully.
  • ELMo's key trick: for a downstream task, learn a softmax-weighted linear combination of all LSTM layers, not just the top layer.
  • Complexity is dominated by the LSTM's sequential recurrence, giving O(n)O(n) time per direction but with poor parallelism across the sequence length nn.
  • ELMo's pretrain-then-finetune-with-frozen-features paradigm directly foreshadows the pretraining recipes used by Transformer architectures.
The core insight

A word's vector should depend on its sentence, and the best way to get such vectors is to pretrain a deep bidirectional language model and mix its internal layer activations.

The idea

A static embedding maps each word type ww in a fixed vocabulary to a single vector ew∈Rd\mathbf{e}_w \in \mathbb{R}^d, learned once from a corpus and then frozen. This is the representation produced by word2vec, GloVe, or fastText. Its core limitation is lexical ambiguity collapse: polysemous words (bank, bat, spring), words whose sense shifts with register or domain, and words entangled in idioms all get squashed into one averaged vector that must serve every context simultaneously. A contextual embedding instead defines a function

hi=f(w1,w2,…,wn,i)\mathbf{h}_i = f(w_1, w_2, \ldots, w_n, i)

that produces the representation of token wiw_i as a function of the entire input sequence, not just wiw_i in isolation. The same word type can now receive different vectors depending on its neighbors. ELMo instantiates ff as the internal hidden states of a deep bidirectional LSTM language model, so representations come "for free" as a byproduct of a well-understood, well-studied pretraining objective: predicting the next word (and the previous word) in a sentence. Two design choices make ELMo work well in practice. First, the model is bidirectional but not naively so: it trains a genuinely separate forward LSTM (reading left-to-right, predicting wt+1w_{t+1} from w1,…,wtw_1,\ldots,w_t) and a genuinely separate backward LSTM (reading right-to-left, predicting wt−1w_{t-1} from wn,…,wt+1w_n,\ldots,w_{t+1}), then concatenates their states. This avoids the training-time information leak that would occur if a single recurrent pass could see the target word through the other direction (an issue that motivated masked, rather than autoregressive, objectives in later architectures). Second, ELMo does not simply expose the top LSTM layer as "the" embedding. Instead, it exposes all layers — the character-CNN input layer and every biLSTM layer — and lets each downstream task learn its own softmax-normalized weighted sum. This is justified empirically and theoretically: different layers of a deep LSTM language model specialize in different levels of linguistic structure, so a syntax-heavy task (like part-of-speech tagging) and a semantics-heavy task (like word-sense disambiguation or question answering) should draw on different layers with different emphasis.

How it works

Step 1 — Character-level input representation. Rather than a lookup table indexed by word identity (which requires a closed vocabulary and fails on out-of-vocabulary words), ELMo represents each word by running its character sequence through a convolutional layer, followed by a highway network and a linear projection. This produces a context-independent word vector xk\mathbf{x}_k for word wkw_k, built purely from its spelling, before any recurrence happens. This is important: it means unknown words, misspellings, and morphologically rich languages are all handled gracefully, since the model never needs an explicit "UNK" fallback.

Step 2 — Forward and backward language models. Given the sequence of context-independent vectors x1,…,xn\mathbf{x}_1, \ldots, \mathbf{x}_n, a forward LSTM language model computes

p(w1,…,wn)=∏k=1np(wk∣w1,…,wk−1)p(w_1, \ldots, w_n) = \prod_{k=1}^{n} p\big(w_k \mid w_1, \ldots, w_{k-1}\big)

by running an LL-layer stacked LSTM forward through the sequence, producing hidden states h→k,j\overrightarrow{\mathbf{h}}_{k,j} at layer j∈{1,…,L}j \in \{1,\ldots,L\} and position kk. Symmetrically, a backward LSTM language model computes

p(w1,…,wn)=∏k=1np(wk∣wk+1,…,wn)p(w_1, \ldots, w_n) = \prod_{k=1}^{n} p\big(w_k \mid w_{k+1}, \ldots, w_n\big)

producing hidden states h←k,j\overleftarrow{\mathbf{h}}_{k,j}. The two directions share the character-CNN input layer and the output softmax vocabulary projection, but maintain separate LSTM parameters, so the forward LSTM never has access to right-context and vice versa. Training jointly maximizes the sum of the forward and backward log-likelihoods.

Step 3 — Assembling per-layer representations. For each token kk and each layer j=1,…,Lj = 1, \ldots, L, concatenate the two directions:

hk,j=[h→k,j ; h←k,j].\mathbf{h}_{k,j} = \big[\overrightarrow{\mathbf{h}}_{k,j} \, ; \, \overleftarrow{\mathbf{h}}_{k,j}\big].

Together with the layer-0 "token representation" hk,0=[xk;xk]\mathbf{h}_{k,0} = [\mathbf{x}_k ; \mathbf{x}_k] (the character-CNN output, duplicated to match dimensionality), this gives L+1L+1 candidate vectors per token.

Step 4 — Task-specific layer mixing. For a specific downstream task task\textit{task}, ELMo computes

ELMoktask=γtask∑j=0Lsjtask hk,j,\text{ELMo}_k^{\textit{task}} = \gamma^{\textit{task}} \sum_{j=0}^{L} s_j^{\textit{task}} \, \mathbf{h}_{k,j},

where stask=softmax(atask)s^{\textit{task}} = \text{softmax}(\mathbf{a}^{\textit{task}}) are learned, task-specific softmax-normalized layer weights, and γtask\gamma^{\textit{task}} is a learned scalar that rescales the entire vector (useful because the distribution of the biLM's internal states can differ substantially from what the downstream task's optimizer expects). Both atask\mathbf{a}^{\textit{task}} and γtask\gamma^{\textit{task}} are trained on the downstream task while the biLM's own weights are typically kept frozen.

Step 5 — Downstream integration. The resulting ELMok\text{ELMo}_k vector is concatenated with (or substituted for) the static embedding for wkw_k and fed into the downstream task's architecture (e.g., a BiLSTM tagger, a span-extraction QA model), which is trained normally with backpropagation, updating everything except the frozen biLM weights. ```

# ----- Pretraining phase ----- function TRAIN_BILM(corpus, L, vocab): initialize CharCNN, Highway, forward_LSTM (L layers), backward_LSTM (L layers), softmax_out for each sentence (w_1, ..., w_n) in corpus:

# context-independent token vectors for k in 1..n: x_k = Highway(CharCNN(chars(w_k)))

# forward pass: left-to-right, causal fwd_h[0] = zero_state for k in 1..n: fwd_h[k] = forward_LSTM(x_k, fwd_h[k-1]) # all L layers p_fwd_k = softmax_out(fwd_h[k][L]) loss += -log p_fwd_k[w_{k+1}] # predict NEXT word

# backward pass: right-to-left, causal in reverse bwd_h[n+1] = zero_state for k in n..1: bwd_h[k] = backward_LSTM(x_k, bwd_h[k+1]) p_bwd_k = softmax_out(bwd_h[k][L]) loss += -log p_bwd_k[w_{k-1}] # predict PREVIOUS word backpropagate(loss) update(CharCNN, Highway, forward_LSTM, backward_LSTM, softmax_out) return CharCNN, Highway, forward_LSTM, backward_LSTM

# ----- Feature extraction / fine-tuning phase ----- function ELMO_EMBED(sentence, biLM, s_task, gamma_task): x = [Highway(CharCNN(chars(w))) for w in sentence] fwd_h = run_forward(biLM.forward_LSTM, x) # L layers, per position bwd_h = run_backward(biLM.backward_LSTM, x) # L layers, per position for k in 1..len(sentence): h[k][0] = concat(x[k], x[k]) # layer-0 = token repr, doubled for j in 1..L: h[k][j] = concat(fwd_h[k][j], bwd_h[k][j]) elmo = [] for k in 1..len(sentence): weighted_sum = sum(s_task[j] h[k][j] for j in 0..L) elmo.append(gamma_task weighted_sum) return elmo # one contextual vector per token

function TRAIN_DOWNSTREAM(task_data, biLM, task_model): freeze(biLM) # biLM weights fixed initialize s_task (softmax weights), gamma_task, task_model for (sentence, label) in task_data: elmo_vecs = ELMO_EMBED(sentence, biLM, s_task, gamma_task) input_reprs = [concat(static_embed(w), elmo_vecs[k]) for k, w in enumerate(sentence)] prediction = task_model(input_reprs) loss = task_loss(prediction, label) backpropagate(loss) # updates s_task, gamma_task, update(s_task, gamma_task, task_model) # and task_model only ```

Figure
ELMo architecture: character CNN input, stacked biLSTM, and task-specific layer mixing
ELMo: forward + backward LSTMs feed a task-specific weighted sum "bank" (char sequence) Char-CNN + Highway → x_k layer 0 (token repr) forward LSTM w1 w2 bank backward LSTM bank w4 w5 layer 2 hidden states (fwd ⊕ bwd) Σ sⱼ · hⱼ (task weights) × γ_task ELMo_k(bank)

Complexity & correctness

Time complexity. Let nn be sentence length, LL the number of stacked LSTM layers, dd the hidden dimension, and ∣V∣|V| the vocabulary size for the softmax output. Character-CNN encoding of a word of length cc costs O(c⋅d)O(c \cdot d) per word (convolution + highway), so O(n⋅c⋅d)O(n \cdot c \cdot d) total, typically negligible compared to the recurrence. Each LSTM layer processes the sequence with O(n)O(n) sequential steps, each step costing O(d2)O(d^2) (matrix multiplications for gates), giving O(n⋅d2)O(n \cdot d^2) per layer and O(L⋅n⋅d2)O(L \cdot n \cdot d^2) for the full stack, doubled for the two directions: O(L⋅n⋅d2)O(L \cdot n \cdot d^2) overall (constant factor 2 absorbed). The softmax output layer costs O(n⋅∣V∣⋅d)O(n \cdot |V| \cdot d) per direction unless a sampled or hierarchical softmax is used, in which case this drops to O(n⋅d⋅log⁡∣V∣)O(n \cdot d \cdot \log|V|) or O(n⋅d⋅k)O(n \cdot d \cdot k) for kk negative samples. Crucially, the LSTM recurrence is inherently sequential: step tt's hidden state depends on step t−1t-1's, so within one direction the nn steps cannot be parallelized across time, even though the two directions (forward/backward) and different sentences in a batch can run in parallel. This is the central efficiency contrast with attention-based architectures, where all positions can be processed simultaneously.

Space complexity. Storing all hidden states across all LL layers and both directions (needed for the downstream layer-mixing step) requires O(L⋅n⋅d)O(L \cdot n \cdot d) memory per sentence. Training additionally requires storing activations for backpropagation through time, which is also O(L⋅n⋅d)O(L \cdot n \cdot d), since BPTT must retain every intermediate gate activation to compute gradients.

Correctness of the bidirectional objective (no information leak). A natural but incorrect design would be to run a single bidirectional LSTM whose forward and backward hidden states are concatenated at every layer and then used to jointly predict the current word wkw_k. This is invalid, because concatenating h→k\overrightarrow{\mathbf{h}}_{k} (which has seen w1,…,wkw_1,\ldots,w_k) with h←k\overleftarrow{\mathbf{h}}_{k} (which has seen wk,…,wnw_k,\ldots,w_n) and then feeding that combined state through further shared layers before predicting wkw_k would let the backward path leak information about wkw_k's left context into a representation used to predict wkw_k itself — worse, if layers are shared and re-processed jointly, the network could shortcut and effectively "copy" wkw_k from the backward path, driving training loss to near-zero without learning anything useful. ELMo avoids this by keeping the forward and backward LSTMs entirely separate stacks of parameters trained on two separate autoregressive objectives:

L=−∑k=1n[log⁡pfwd(wk∣w<k)+log⁡pbwd(wk∣w>k)],\mathcal{L} = -\sum_{k=1}^{n} \Big[ \log p_{\text{fwd}}(w_k \mid w_{<k}) + \log p_{\text{bwd}}(w_k \mid w_{>k}) \Big],

and only after both models are trained are their hidden states concatenated for use as a feature (never fed back into either LM's own prediction). The invariant maintained throughout training is: the forward LSTM's hidden state at position kk is a function only of w1,…,wk−1w_1, \ldots, w_{k-1}, and the backward LSTM's hidden state at position kk is a function only of wk+1,…,wnw_{k+1}, \ldots, w_n. This invariant is preserved by construction (each recurrence only ever consumes strictly-past, resp. strictly-future, tokens) and it is exactly what makes maximizing L\mathcal{L} a valid proxy for learning the true joint distribution of language, rather than a degenerate shortcut.

Why layer-mixing is justified. Each LSTM layer's hidden state is trained (implicitly, via backpropagation from the language-modeling loss) to be useful for predicting the next/previous word using only what the previous layer computed. Layers do not receive an explicit supervision signal telling them "encode syntax" or "encode semantics" — but empirically (via probing classifiers on ELMo's layers, reported in the original work) lower layers achieve stronger performance on tasks that are more local and syntactic (e.g., part-of-speech tagging), while higher layers do better on tasks requiring longer-range semantic disambiguation (e.g., word-sense disambiguation). Because the ideal mixture is task-dependent and not knowable in advance, learning a softmax-normalized convex combination sjtasks_j^{\textit{task}} is a principled way to let each downstream task extract exactly the blend of abstraction levels it needs, while the shared biLM backbone stays frozen and reusable — this is the same idea that later motivates layer-wise probing and layer-selection strategies in Transformer architectures.

Tradeoffs. ELMo's sequential recurrence caps training throughput and cannot exploit the full parallelism available in modern GPU/TPU hardware the way self-attention can, which is one reason Transformer-based models (relying on the Attention Mechanism in NLP rather than recurrence) eventually replaced biLSTM language models as the default pretraining architecture. ELMo also freezes the pretrained biLM and only adapts a lightweight mixing layer, which is cheap and avoids catastrophic forgetting, but it sacrifices the deeper task-specific adaptation that full fine-tuning (as in BERT) can achieve.

Worked example

Take the sentence: "The bank raised interest rates." and contrast it with "They sat by the river bank." We trace how ELMo would represent the token "bank" differently in each.

StepSentence A: "The bank raised interest rates."Sentence B: "They sat by the river bank."
Char-CNN input xbank\mathbf{x}_{\text{bank}}Identical in both — built purely from the characters b-a-n-kIdentical in both — same spelling, same x\mathbf{x}
Forward LSTM context seen at "bank""The" (position 1)"They sat by the river" (positions 1–5)
Backward LSTM context seen at "bank""raised interest rates" (right context)"" (end of sentence, minimal right context)
Layer-1 hidden state hk,1\mathbf{h}_{k,1}Reflects a short left context + strong financial right-context cue from "raised interest rates"Reflects long left context "sat by the river," priming spatial/geographic sense
Layer-2 hidden state hk,2\mathbf{h}_{k,2}Further sharpens toward financial-institution sense; probing studies show later layers disambiguate word sense stronglyFurther sharpens toward riverbank/geographic sense
Task weights stasks^{\textit{task}} (e.g. for a WSD task)Learned weights favor higher layers (semantic)Same fixed weights, but different input hidden states → different output
Final ELMobank\text{ELMo}_{\text{bank}}Vector A: near "loan," "financial-institution," "account" in the induced contextual spaceVector B: near "shore," "river," "embankment" in the induced contextual space
Cosine similarity cos⁡(Vector A,Vector B)\cos(\text{Vector A}, \text{Vector B})Substantially lower than cos⁡(ebank,ebank)=1\cos(\mathbf{e}_{\text{bank}}, \mathbf{e}_{\text{bank}})=1 that a static embedding would give(same comparison)

The essential contrast: a static embedding gives ebankA=ebankB\mathbf{e}_{\text{bank}}^{A} = \mathbf{e}_{\text{bank}}^{B} by definition (cosine similarity exactly 1, since it's the same lookup vector), while ELMo gives two vectors whose cosine similarity is noticeably lower, reflecting genuinely different senses — this is the direct, measurable payoff of context-dependence, and it is exactly what downstream disambiguation-sensitive tasks (word-sense disambiguation, coreference resolution, semantic role labeling) benefit from.

Common pitfalls & traps

PitfallWhy it's wrongFix
Treating ELMo's biLSTM as one interleaved network reading both directions simultaneously in the same layer stack used for predictionLeaks the target word's identity through the opposite direction, producing a training loss that trivially collapsesKeep forward and backward LSTMs as fully separate parameter stacks, concatenate their states only after both are computed, never inside a shared recurrent step used for LM prediction
Using only the top LSTM layer as "the" embeddingDiscards useful syntactic information concentrated in lower layers; task performance is measurably worse than with mixingLearn a softmax-weighted combination of all layers per task, as ELMo specifies
Fine-tuning the entire biLM end-to-end on a small downstream datasetRisks catastrophic forgetting of the broad language-modeling knowledge, and a small dataset cannot re-estimate millions of biLM parameters reliablyFreeze the biLM; only train the lightweight layer-mixing weights and task-specific head
Assuming ELMo vectors are as parallelizable as attention-based embeddingsThe LSTM recurrence is inherently sequential in time, unlike self-attention; naive benchmarking comparisons mislead about training speedAccount for O(n)O(n) sequential depth when estimating wall-clock training/inference cost
Forgetting the character-CNN's role for rare/unknown wordsFalling back to a static "UNK" vector reintroduces the exact problem contextual embeddings were meant to solveAlways route unknown words through the character-level encoder, never through a fixed UNK lookup

Pioneers & history

Origins · 1 minMatthew E. Peters and how this was discovered
ME
Matthew E. Peters
active 2010s–present · Allen Institute for AI, lead author of the ELMo paper (2018), building on prior LSTM language-modeling and multi-task representation work

History
1997
— Hochreiter and Schmidhuber introduce the LSTM, providing the recurrent building block later stacked in ELMo.
2013
— Mikolov et al. release word2vec, cementing static embeddings as the dominant word representation.
2014
— Pennington, Socher, and Manning release GloVe, a matrix-factorization alternative to word2vec.
2018
— Peters et al. publish ELMo ("Deep contextualized word representations"), demonstrating pretrained bidirectional LSTM language models as a source of transferable contextual embeddings.
2018
— Devlin et al. publish BERT, replacing the biLSTM backbone with a Transformer encoder and masked-language-model objective, rapidly superseding ELMo as the standard pretraining recipe.

Where this matters

ELMo's release in 2018 triggered an immediate, measurable jump in state-of-the-art results across a wide range of NLP benchmarks — question answering, textual entailment, semantic role labeling, coreference resolution, named-entity recognition, and sentiment analysis — simply by swapping static embeddings for ELMo features in existing task architectures, with no other architectural change. This demonstrated conclusively that large-scale unsupervised pretraining, transferred via contextual representations, was a more powerful lever than continued hand-engineering of task-specific architectures. That lesson directly shaped the design of Transformer architectures such as BERT, GPT, and their successors, all of which retain ELMo's core recipe — pretrain broadly on unlabeled text, transfer to downstream tasks — while replacing the recurrent backbone with the parallelizable Attention Mechanism in NLP and, in BERT's case, replacing autoregressive language modeling with a masked-token objective that lets a single stack see both directions safely. Contextual embeddings from ELMo and its successors are used throughout industrial NLP systems: search engines rely on contextual query and document representations for relevance ranking, virtual assistants use them for intent classification and slot filling, machine translation systems use contextual encoders, and content-moderation and sentiment-analysis pipelines rely on them to disambiguate sarcasm, negation, and domain-specific word senses that static embeddings cannot capture. Understanding ELMo also clarifies, by contrast, exactly what problem attention-based pretraining was designed to solve — sequential bottlenecks and one-directional shortcuts — making it a natural conceptual stepping stone toward modern large language models.

Ask the tutor: How does ELMo's forward/backward separation compare to BERT's masked-language-model approach to achieving safe bidirectionality?

How exactly does ELMo differ from just averaging word2vec vectors over a sentence?

Averaging static vectors still starts from context-independent per-word representations and combines them with a simple, fixed, order-insensitive operation; the word "bank" contributes the identical vector to the average regardless of its neighbors. ELMo instead computes each word's representation using a deep bidirectional LSTM language model that has processed the specific sequential order and content of the whole sentence, so the representation itself is different per occurrence, not just the sentence-level pooling. ELMo also captures nonlinear, long-range dependencies through its recurrent gates, something a simple average cannot express. The two approaches also differ in training objective: averaging reuses embeddings trained via co-occurrence-based objectives (skip-gram, CBOW), while ELMo's vectors emerge from an autoregressive language-modeling objective across full sentences.

Is ELMo still used in practice, or has it been fully replaced?

In modern production systems, Transformer-based pretrained models (BERT, RoBERTa, GPT-family, and their descendants) have almost entirely replaced ELMo, because self-attention parallelizes better on modern hardware, models can be made much deeper and larger, and masked-language-model or autoregressive Transformer objectives scale more effectively with data and compute. ELMo remains valuable pedagogically and historically: it was the first widely adopted demonstration that deep contextual pretraining beats static embeddings across many tasks, and its layer-mixing insight (that different depths encode different linguistic abstractions) persists as a design principle, informing layer-wise probing studies and layer-selection heuristics used with modern Transformer encoders.

Why does ELMo use a character-level CNN instead of a word-lookup table for input?

A word-lookup table requires a fixed, closed vocabulary decided in advance; any word not seen during training (a typo, a rare proper noun, a novel compound) must be mapped to a generic "unknown" token, discarding useful information. A character-level convolutional network instead builds a vector from the sequence of characters in the word, so morphologically related or novel words with recognizable substrings ("un-", "-ing", "-tion") still receive meaningful representations. This also dramatically shrinks the number of parameters needed for the input layer, since the model needs only enough parameters to recognize character n-gram patterns, not one embedding per word type in a huge vocabulary.

What would go wrong if you fine-tuned the entire ELMo biLM on a small downstream dataset instead of freezing it?

The biLM has typically been trained on hundreds of millions of words to learn broad distributional and syntactic regularities; a downstream dataset for, say, named-entity recognition might contain only tens of thousands of sentences. Fully fine-tuning all biLM parameters on such a small dataset risks catastrophic forgetting, where the model's weights shift to fit the narrow downstream signal and lose the general language competence that made the pretrained representations valuable in the first place. The standard ELMo recipe freezes the biLM and only trains the lightweight layer-mixing weights stasks^{\textit{task}}, the scalar γtask\gamma^{\textit{task}}, and the task-specific head, which is both cheaper and empirically more robust for small-data regimes.

How does the invariant about forward/backward hidden states relate to correctness of the language-modeling objective?

The correctness argument rests on a strict separation invariant: the forward LSTM's hidden state at position kk must be a deterministic function only of tokens w1,…,wk−1w_1, \ldots, w_{k-1}, and the backward LSTM's hidden state at position kk only of wk+1,…,wnw_{k+1}, \ldots, w_n. As long as this invariant holds throughout training, maximizing the forward and backward log-likelihoods is a valid maximum-likelihood estimation of two genuinely different conditional distributions, both consistent with the true underlying language distribution. If the invariant were violated — for instance, by sharing recurrent state across directions before prediction — the objective would no longer correspond to a meaningful density estimation problem, and the trained hidden states would not reliably transfer to downstream tasks.

Why does layer-mixing help more than using just the final LSTM layer?

In a deep stacked LSTM language model, each layer builds on the representations of the layer below it, and empirically (through probing classifier experiments) lower layers tend to encode more local, syntactic regularities (part-of-speech identity, chunk boundaries) while higher layers encode more global, semantic regularities (word sense, coreference-relevant properties). Different downstream tasks weight these needs differently: a shallow syntactic tagging task benefits more from lower-layer information, while a task requiring deep semantic disambiguation benefits more from higher layers. By learning a softmax-weighted combination per task, ELMo lets each downstream model automatically discover and exploit the mixture of abstraction levels most useful for its objective, rather than being restricted to whatever the final layer happens to encode.

Does ELMo handle long-range dependencies as well as attention-based models?

Not as reliably. An LSTM must propagate information about a distant token through many sequential hidden-state updates, and even with gating mechanisms designed to mitigate vanishing gradients, very long-range dependencies can still degrade over long sequences. Self-attention, used in Transformer architectures, instead lets every position directly attend to every other position in a single computation step, giving a more direct path for long-range information flow and better empirical performance on tasks requiring long-distance reasoning. This is one of the concrete architectural motivations for the transition from recurrent contextual encoders like ELMo to attention-based pretrained encoders.

Before this