Visualize how a Bidirectional layer processes a sequence in both forward and backward directions to capture full context before merging the results.
A forward RNN at position t has seen tokens 1 to t and nothing after. That is a real handicap, because disambiguation frequently depends on what comes next.
Take “The bank was steep and muddy.” At the word bank a forward-only model has seen only “The”, and must commit to a representation before steep and muddy arrives to settle the meaning. A backward pass has that information immediately.
Run one recurrent pass forward and another backward, then concatenate. Every position gets context from both sides - which is a large accuracy win, and rules the model out of any task that generates text.
A bidirectional layer runs two entirely separate recurrent cells with their own weights. The forward cell reads left to right; the backward cell reads the same sequence right to left. At each position their hidden states are concatenated:
ht = [h→t ; h←t]
Two consequences follow immediately. The output at each position is twice as wide, so the next layer’s input size doubles — a frequent shape-mismatch bug. And there are twice the parameters, since nothing is shared between the two directions.
Note the passes are independent: the backward cell does not see the forward cell’s states. They are computed separately and only joined at the end, which also means they can run in parallel.
A forward LSTM at word 5 knows words 1 to 5. A bidirectional layer runs a second, independent LSTM backwards — from the end to the beginning — and concatenates the two hidden states at each position.
hᵗ = [hᵗ→ ; hᵗ←]
So the representation at word 5 now contains what came before it and what came after. The output width doubles: 256 hidden units in each direction gives 512-dimensional outputs.
Why it helps is easiest to see with an ambiguous word:
"The bank was steep and muddy."
At "bank", a forward-only model has seen "the" and nothing else — it cannot yet know which sense is meant. The backward pass has seen "steep and muddy" and resolves it immediately. Neither direction alone is sufficient; together they are.
The same argument applies to named entities ("Washington" as a person or a place depends on what follows), part-of-speech tagging, and coreference.
Bidirectional layers require the entire sequence to be available before processing starts. That rules them out for two important cases:
Generation. Predicting the next word cannot use the next word. A bidirectional language model would have access to the answer, which makes training trivial and inference impossible.
Streaming. Live transcription, real-time translation and any incremental system cannot wait for the end of the input.
So the rule is clear: bidirectional for understanding a complete input, unidirectional for generating or streaming.
| Task | Bidirectional? |
|---|---|
| Sentiment classification | Yes |
| Named entity recognition | Yes |
| Part-of-speech tagging | Yes |
| Text generation | No |
| Live transcription | No |
| Machine translation encoder | Yes |
| Machine translation decoder | No |
That last pair is worth noting: an encoder-decoder model is frequently bidirectional in the encoder and causal in the decoder, because the source sentence is complete and the target is being produced.
lstm = nn.LSTM(300, 256, batch_first=True, bidirectional=True)
out, (h, c) = lstm(x)
out.shape # (batch, seq, 512) -- 256 forward + 256 backward
h.shape # (2, batch, 256) -- one final state per directionTwo details cause most of the confusion.
The output is twice as wide. Any layer after it must expect 2 × hidden_size. Forgetting this is the most common bidirectional bug, and it produces an immediate shape error.
The final states are not simply the last row of out. The forward direction's final state corresponds to the last token; the backward direction's corresponds to the first. For classification, concatenate the two final states, or pool over out — do not take out[:, -1, :], which mixes the forward state at the end with the backward state at the end (which has seen only the last token).
h_fwd, h_bwd = h[0], h[1]
summary = torch.cat([h_fwd, h_bwd], dim=-1) # correct for classification
A bidirectional layer runs two independent RNNs and concatenates them. That doubles the parameters, gives every position full context, and makes the layer unusable for generation -- all for the same reason.
A bidirectional layer requires the entire sequence before it can produce any output, because the backward pass starts at the end. That rules it out whenever the future genuinely is not available:
Where the full sequence is available — classification, tagging, named entity recognition, and the encoder half of a translation model — bidirectionality is close to free accuracy. This is exactly the split between BERT, which is bidirectional and cannot generate, and GPT, which is unidirectional and can.
A bidirectional layer runs independent forward and backward recurrent passes and concatenates them, so every position is represented with context from both sides — at twice the parameters and twice the output width. It is the right default for classification and tagging, where the whole sequence is available, and impossible for generation or streaming, where the backward pass would be reading the future it is meant to predict.
Twice the computation and twice the parameters. Two independent LSTMs, each with its own four weight matrices. For 300-dimensional input and 256 hidden units, about 1.15 million parameters instead of 574,000.
Twice the memory during training, since both passes' activations must be stored.
No latency benefit from parallelism. The two directions are independent of each other and can run concurrently, but each is still strictly sequential internally.
Full input required, which is the real cost rather than the arithmetic.
Whether the accuracy gain justifies it depends on the task. For tagging and span extraction, where the right context is genuinely necessary, the gain is usually substantial. For document-level topic classification, where the signal is distributed and redundant, it is often marginal.
Bidirectionality in transformers is not a separate layer — it is the default, and unidirectionality is what has to be added.
An encoder's self-attention lets every position attend to every other, in both directions, with no extra machinery. That is exactly what BERT is: a bidirectional encoder, and its masked-language-model training objective exists precisely because bidirectional attention makes next-token prediction trivial.
A decoder adds a causal mask that sets attention scores for future positions to negative infinity, so their weights become zero. Generation becomes possible because the model genuinely cannot see ahead.
| Recurrent | Transformer | |
|---|---|---|
| Bidirectional | Two passes, concatenated | Default — unmasked attention |
| Unidirectional | One forward pass | Causal mask applied |
| Cost of bidirectionality | 2× parameters and compute | None |
So "bidirectional layer" is largely a recurrent-era concept. The idea survives; the implementation is a mask rather than a second network.
Can I use a bidirectional layer for language modelling? No — it would see the token it is meant to predict.
Does the backward LSTM share weights with the forward one? No, they are entirely separate parameter sets.
Should I concatenate or sum the two directions? Concatenate, which is the standard and keeps both intact. Summing halves the width and loses information.
Is BERT a bidirectional LSTM? No — it is a bidirectional transformer encoder. The word "bidirectional" in its name refers to unmasked attention.
Does bidirectionality help every task? No. It helps most where right context disambiguates — tagging, entity recognition, span extraction — and least on tasks with redundant global signal.
Can I stack bidirectional layers? Yes, and each layer's doubled output feeds the next. Two is usually the practical limit.
Say the answer out loud before you reveal it — recalling it is what makes it stick, and rereading it is not.
What does this module say about “Why one direction is not enough”?
A forward RNN at position t has seen tokens 1 to t and nothing after. That is a real handicap, because disambiguation frequently depends on what comes next.
What does this module say about “How the two passes combine”?
A bidirectional layer runs two entirely separate recurrent cells with their own weights. The forward cell reads left to right; the backward cell reads the same sequence right to left. At each position their hidden states are concatenated:
What does this module say about “Reading the sequence twice”?
A forward LSTM at word 5 knows words 1 to 5. A bidirectional layer runs a second, independent LSTM backwards — from the end to the beginning — and concatenates the two hidden states at each position.
Visualize how a Bidirectional layer processes a sequence in both forward and backward directions to capture full context before merging the results.