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 spot t has seen tokens 1 to t and nothing after. That is a real handicap, because disambiguation often 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 form before steep and muddy arrives to settle the meaning. A backward pass has that data at once.
The fix is to run two passes — one forward, one backward — and concatenate their outputs at each spot. The forward pass gives a hidden state that encodes all of it to the left; the backward pass gives a hidden state that encodes all of it to the right. Stacking the two gives each spot a form that sees the entire sequence.
This is the idea behind the bidirectional layer. It doubles the hidden dimension, because it concatenates two free states. And it doubles the parameter count, because the forward and backward cells learn separate weights. The precision gain is large for tasks like named-entity recognition, part-of-speech tagging, sentiment classification and question answering. Any task where the label at spot t depends on tokens that come after t. The cost is that the full sequence must be available before the layer can run. This rules out autoregressive generation: you cannot predict the next word with a backward pass that needs words you have not made yet. That constraint is why GPT-style models use only a forward pass and BERT-style models use bidirectional layers.
Visualize how a Bidirectional layer processes a sequence in both forward and backward directions to capture full context before merging the results.
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 forward RNN at spot t has seen tokens 1 to t and nothing after. That is a real handicap, because disambiguation often 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 form before steep and muddy arrives to settle the meaning. A backward pass has that data at once.
The fix is to run two passes — one forward, one backward — and concatenate their outputs at each spot. The forward pass gives a hidden state that encodes all of it to the left; the backward pass gives a hidden state that encodes all of it to the right. Stacking the two gives each spot a form that sees the entire sequence.
This is the idea behind the bidirectional layer. It doubles the hidden dimension, because it concatenates two free states. And it doubles the parameter count, because the forward and backward cells learn separate weights. The precision gain is large for tasks like named-entity recognition, part-of-speech tagging, sentiment classification and question answering. Any task where the label at spot t depends on tokens that come after t. The cost is that the full sequence must be available before the layer can run. This rules out autoregressive generation: you cannot predict the next word with a backward pass that needs words you have not made yet. That constraint is why GPT-style models use only a forward pass and BERT-style models use bidirectional layers.
A bidirectional layer runs two fully 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 spot their hidden states are concatenated:
ht = [h→t . H←t]
Two results follow at once. The output at each spot 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 free: the backward cell does not see the forward cell’s states. They are computed apart and only joined at the end. This also means they can run in parallel.
A forward LSTM at word 5 knows words 1 to 5. A bidirectional layer runs a second, free LSTM backwards. From the end to the beginning. And concatenates the two hidden states at each spot.
hᵗ = [hᵗ→ . Hᵗ←]
So the form at word 5 now holds 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 at once. Neither direction alone is enough; 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 need the entire sequence to be available before handling starts. That rules them out for two key cases:
Generation. Predicting the next word cannot use the next word. A bidirectional language model would have access to the answer. This makes training trivial and inference not doable.
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 grasp a complete input, unidirectional for making 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 often bidirectional in the encoder and causal in the decoder,. This is because the source sentence is complete and the target is being gave.
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 gives 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, :]. This 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 free RNNs and concatenates them. That doubles the parameters, gives each spot full context, and makes the layer unusable for generation. All for the same reason.
Building on the same setup, the next block shows the left cases.
The below block covers the left steps.
A bidirectional layer needs the entire sequence before it can give any output, because the backward pass starts at the end. That rules it out when the future truly 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 precision. This is just the split between BERT, which is bidirectional and cannot generate, and GPT. This is unidirectional and can.
A bidirectional layer runs free forward and backward recurrent passes and concatenates them. So each spot is shown 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 not doable for generation or streaming, where the backward pass would be reading the future it is meant to predict.
Twice the work and twice the parameters. Two free 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' triggers must be stored.
No latency gain from parallelism. The two directions are free of each other and can run concurrently. But each is still strictly sequential internally.
Full input needed, which is the real cost rather than the arithmetic.
Whether the precision gain justifies it depends on the task. For tagging and span extraction, where the right context is truly needed, the gain is often large. For document-level topic classification, where the sign 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 each spot attend to each other, in both directions, with no extra machinery. That is just what BERT is: a bidirectional encoder. And its masked-language-model training objective exists just because bidirectional attention makes next-token prediction trivial.
A decoder adds a causal mask that sets attention scores for future spots to negative infinity. So their weights become zero. Generation becomes doable because the model truly 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 fully 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 data.
Is BERT a bidirectional LSTM? No — it is a bidirectional transformer encoder. The word "bidirectional" in its name means unmasked attention.
Does bidirectionality help each task? No. It helps most where right context disambiguates. Tagging, entity recognition, span extraction. And least on tasks with redundant global sign.
Can I stack bidirectional layers? Yes, and each layer's doubled output feeds the next. Two is often 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”?
What does this module say about “How the two passes combine”?
What does this module say about “Reading the sequence twice”?
Run one RNN forward and another backward, concatenate their outputs, and give every position context from both sides of the sequence.