TensorFlow
tf.unpack
dynamic dimensions
machine learning
Tensor manipulation

Using tf.unpack when first dimension of Variable is None

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

tf.unpack (renamed to tf.unstack in TensorFlow 1.0+) splits a tensor along a given axis into a list of sub-tensors. It fails when the dimension along the split axis is None (unknown at graph construction time) because TensorFlow cannot determine how many tensors to produce. The fix is to use tf.unstack with an explicit num argument, or switch to tf.split or dynamic indexing with tf.gather which handle dynamic shapes naturally.

The Error

python
1import tensorflow as tf
2
3# Placeholder with unknown batch size (first dimension is None)
4x = tf.placeholder(tf.float32, shape=[None, 10])
5
6# This fails because TensorFlow doesn't know how many tensors to produce
7tensors = tf.unstack(x, axis=0)
8# ValueError: Cannot infer num from shape (?, 10)

tf.unstack needs to know the number of output tensors at graph construction time. When the dimension is None, it cannot determine this.

Fix 1: Specify the num Argument

If you know the maximum number of elements at coding time, pass it explicitly:

python
1# Tell TensorFlow to expect exactly 32 tensors
2x = tf.placeholder(tf.float32, shape=[None, 10])
3tensors = tf.unstack(x, num=32, axis=0)
4# Returns a list of 32 tensors, each with shape [10]
5
6# At runtime, the actual batch size MUST be 32
7sess.run(tensors, feed_dict={x: np.zeros((32, 10))})  # Works
8sess.run(tensors, feed_dict={x: np.zeros((16, 10))})  # Runtime error!

This is rigid. The actual input must match the num value exactly.

Fix 2: Use tf.split with Dynamic Shape

tf.split can work with dynamic shapes when you specify the split sizes:

python
1x = tf.placeholder(tf.float32, shape=[None, 10])
2batch_size = tf.shape(x)[0]
3
4# Split into individual tensors dynamically
5tensors = tf.split(x, num_or_size_splits=batch_size, axis=0)
6# Each tensor has shape [1, 10]

However, tf.split with a dynamic num_or_size_splits still returns a dynamic number of outputs, which limits what you can do statically.

Instead of unstacking, index into the tensor dynamically:

python
1x = tf.placeholder(tf.float32, shape=[None, 10])
2
3# Access individual elements by index
4first = x[0]       # Same as tf.gather(x, 0), shape [10]
5second = x[1]      # shape [10]
6ith = x[i]         # shape [10]
7
8# Process each element with tf.map_fn
9result = tf.map_fn(lambda elem: elem * 2, x)
10# Applies the function to each element along axis 0

Fix 4: Use tf.map_fn for Per-Element Operations

tf.map_fn handles dynamic batch sizes because it processes elements one at a time using a tf.while_loop internally:

python
1x = tf.placeholder(tf.float32, shape=[None, 10])
2
3# Apply a function to each element in the batch
4def process(element):
5    return tf.nn.relu(element) * 2
6
7result = tf.map_fn(process, x)
8# result shape: [None, 10], works with any batch size

Fix 5: Use tf.while_loop for Custom Iteration

For more complex per-element logic with dynamic dimensions:

python
1x = tf.placeholder(tf.float32, shape=[None, 10])
2batch_size = tf.shape(x)[0]
3
4# Accumulate results using a while loop
5def body(i, outputs):
6    element = x[i]
7    processed = tf.nn.softmax(element)
8    outputs = outputs.write(i, processed)
9    return i + 1, outputs
10
11i0 = tf.constant(0)
12outputs0 = tf.TensorArray(dtype=tf.float32, size=batch_size, dynamic_size=False)
13_, final_outputs = tf.while_loop(
14    cond=lambda i, _: i < batch_size,
15    body=body,
16    loop_vars=[i0, outputs0]
17)
18result = final_outputs.stack()  # Shape: [batch_size, 10]

TensorFlow 2.x (Eager Execution)

In TensorFlow 2.x with eager execution, dynamic shapes work naturally:

python
1import tensorflow as tf
2
3# No more placeholders. Use tf.function for graphs
4@tf.function
5def process_batch(x):
6    # tf.unstack works when input shape is known at call time
7    elements = tf.unstack(x, axis=0)
8    return [elem * 2 for elem in elements]
9
10# With eager mode, shapes are known at runtime
11x = tf.constant([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]])
12result = process_batch(x)
13# [array([2., 4.]), array([6., 8.]), array([10., 12.])]

For truly dynamic batch sizes in TF2, use tf.map_fn or vectorized operations:

python
1@tf.function
2def process_batch(x):
3    # Vectorized, works with any batch size
4    return x * 2
5
6# Or per-element processing
7@tf.function
8def process_each(x):
9    return tf.map_fn(lambda elem: tf.nn.relu(elem), x)

Migration: tf.unpack to tf.unstack

python
1# TensorFlow 0.x
2tf.unpack(value, num=None, axis=0)
3
4# TensorFlow 1.x+
5tf.unstack(value, num=None, axis=0)
6
7# They are functionally identical, just a rename

Common Pitfalls

  • Forgetting num argument: tf.unstack without num on a dimension of None always fails. Either provide num or use tf.map_fn/tf.gather instead.
  • num mismatch at runtime: If you set num=32 but feed a batch of 16, TensorFlow raises a runtime shape error. Only use num when the dimension is truly fixed.
  • Using tf.unstack where vectorized ops work: Operations like x * 2, tf.nn.relu(x), and tf.matmul already operate batch-wise. Unstacking, processing, and restacking is slower than vectorized computation.
  • TF1 vs TF2 confusion: In TF1 graph mode, shapes must be known at graph construction. In TF2 eager mode, shapes are resolved at runtime. tf.unstack works in TF2 eager mode without num because the shape is known.
  • TensorArray performance: tf.while_loop with TensorArray adds overhead compared to vectorized operations. Use it only when per-element logic cannot be expressed as batch operations.

Summary

  • tf.unstack (formerly tf.unpack) fails on None dimensions because it cannot determine the output count at graph time
  • Pass num=N to tf.unstack when you know the exact dimension size
  • Use tf.map_fn for per-element operations with dynamic batch sizes
  • Use tf.gather or indexing (x[i]) for accessing specific elements
  • In TensorFlow 2.x with eager execution, dynamic shapes are handled naturally
  • Prefer vectorized operations over unstacking whenever possible for better performance

Course illustration
Course illustration

All Rights Reserved.