\`RNN\`
TensorFlow
Keras
tf.nn.dynamic_rnn
machine learning

\`RNN\` in Tensorflow vs Keras, depreciation of tf.nn.dynamic_rnn

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

Questions about tf.nn.dynamic_rnn usually come from code written for TensorFlow 1, where graph-building APIs and session execution were the normal style. In current TensorFlow, the preferred path is to build recurrent models with tf.keras.layers.LSTM, GRU, SimpleRNN, or a custom Keras layer.

So the issue is not really TensorFlow versus Keras as competing choices. Keras is the primary high-level API inside TensorFlow, and dynamic_rnn is the legacy TF1-era way of expressing a recurrent network.

What tf.nn.dynamic_rnn Did Well

In TensorFlow 1, dynamic_rnn handled variable-length sequence unrolling and recurrent state propagation without forcing you to manually unroll the loop yourself. A typical pattern looked like this:

python
1import tensorflow as tf
2
3tf.compat.v1.disable_eager_execution()
4
5inputs = tf.compat.v1.placeholder(tf.float32, shape=[None, None, 8])
6cell = tf.compat.v1.nn.rnn_cell.LSTMCell(num_units=16)
7outputs, state = tf.compat.v1.nn.dynamic_rnn(cell, inputs, dtype=tf.float32)

That code is valid for maintaining old TF1 graphs, but it is not how new TensorFlow projects should usually be written.

The Modern Keras Replacement

The direct conceptual replacement is a Keras recurrent layer inside a model. You declare the input shape, choose the recurrent layer, and let Keras manage execution, masking, training, saving, and inference.

python
1import numpy as np
2import tensorflow as tf
3
4x = np.random.rand(32, 10, 8).astype("float32")
5y = np.random.randint(0, 2, size=(32, 1)).astype("float32")
6
7model = tf.keras.Sequential([
8    tf.keras.layers.Input(shape=(10, 8)),
9    tf.keras.layers.LSTM(16),
10    tf.keras.layers.Dense(1, activation="sigmoid"),
11])
12
13model.compile(optimizer="adam", loss="binary_crossentropy")
14model.fit(x, y, epochs=2, verbose=0)

This style integrates cleanly with model.fit, eager execution, and SavedModel export. That is why it replaced most direct dynamic_rnn usage.

Mapping Old Behavior to New Options

When people migrate TF1 recurrent code, the missing detail is often not the layer itself but the output shape and state behavior.

Use these mappings:

  • set return_sequences=True if you need an output for every time step
  • set return_state=True if you need the final hidden state, and for LSTM also the cell state
  • use Masking or an embedding mask when sequences have padding
python
1import tensorflow as tf
2
3inputs = tf.keras.Input(shape=(None, 8))
4masked = tf.keras.layers.Masking(mask_value=0.0)(inputs)
5outputs, state_h, state_c = tf.keras.layers.LSTM(
6    16,
7    return_sequences=True,
8    return_state=True,
9)(masked)
10
11model = tf.keras.Model(inputs=inputs, outputs=[outputs, state_h, state_c])

That covers most TF1 use cases that previously relied on sequence_length and manual graph wiring.

When You Still Need Lower-Level Control

There are valid cases for dropping below the simplest Keras API, such as a custom recurrent cell, unusual state transitions, or a training loop that needs step-level control. Even then, the better modern route is usually one of these:

  • subclass tf.keras.layers.Layer
  • subclass tf.keras.Model
  • use a custom training loop with tf.GradientTape

That keeps the code inside the TensorFlow 2 execution model instead of reviving TF1 session-era patterns.

Migration Advice for Existing Code

If you maintain a TF1 project, do not rewrite everything blindly. First identify whether the code only needs to keep running or whether it needs to become idiomatic TensorFlow 2. Those are different jobs.

If you only need compatibility, tf.compat.v1.nn.dynamic_rnn may be enough. If you need long-term maintainability, migrate to Keras recurrent layers and update the training entry points at the same time.

Common Pitfalls

  • Treating Keras as separate from TensorFlow in modern codebases.
  • Porting TF1 tutorials line for line into TensorFlow 2 without changing the execution model.
  • Forgetting return_sequences or return_state when matching old dynamic_rnn behavior.
  • Migrating the recurrent layer but leaving the rest of the training code tied to placeholders and sessions.
  • Assuming lower-level APIs are better just because they are more explicit.

Summary

  • 'tf.nn.dynamic_rnn is a TensorFlow 1 API for recurrent graphs.'
  • In modern TensorFlow, Keras recurrent layers are the normal replacement.
  • 'LSTM, GRU, and SimpleRNN cover most of the same model shapes with cleaner integration.'
  • Use Keras options such as return_sequences, return_state, and masking to match old behavior.
  • Keep TF1 compatibility only when you need it; prefer migration for long-term maintenance.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.