The Story — Twelve Eggs, Many Cartons
The eggs never change. Only the box changes. In TensorFlow, the box is the shape. Changing the box is called reshape.
And the kind of egg (hen, duck, quail) is like the dtype. You cannot mix kinds in one carton.
Every tensor has three key facts: its shape (size of each dimension), its rank (how many dimensions), and its dtype (the type of each value). Most TensorFlow errors you will ever see are shape or dtype errors. So this lesson is worth learning well.
Try It — Climb the Rank Ladder
Move the slider. Each step adds one more axis. See what real data has that rank.
| Rank | Name | Example shape | Real data |
|---|---|---|---|
| 0 | Scalar | () | A loss value, a temperature |
| 1 | Vector | (4,) | One row of features, an audio clip |
| 2 | Matrix | (3, 4) | A table, a grayscale image, a weight matrix |
| 3 | 3-D tensor | (28, 28, 3) | A colour image (height, width, channels) |
| 4 | 4-D tensor | (32, 28, 28, 3) | A batch of 32 colour images |
Reading Shape, Rank and Size in Code
import tensorflow as tf
x = tf.zeros([2, 3, 4])
print("shape :", x.shape) # TensorShape, known in Python
print("ndim :", x.ndim) # rank as a Python int
print("rank :", tf.rank(x)) # rank as a tensor
print("size :", tf.size(x)) # total number of values
print("dims :", x.shape[0], x.shape[-1])
print("list :", x.shape.as_list())
x.shape vs tf.shape(x)
Python TensorShape |
| Known when the tensor is created |
Can contain None in a tf.function |
An int32 tensor |
| Always has the true size at run time |
| Use it inside graphs when batch size is unknown |
@tf.function(input_signature=[tf.TensorSpec(shape=[None, 3])])
def batch_info(x):
print("static :", x.shape) # printed once at trace time
tf.print("dynamic :", tf.shape(x)) # printed at run time
return x
batch_info(tf.zeros([5, 3]))
Axes and Negative Indexes
Axes are numbered from the outside in. For a tensor of shape (2, 3, 4), axis 0 has size 2 and axis 2 has size 4. Negative numbers count from the end. So axis=-1 always means the last axis.
| Axis | 0 | 1 | 2 |
|---|---|---|---|
| Negative axis | -3 | -2 | -1 |
| Size | 2 | 3 | 4 |
| Meaning (image) | height | width | channels |
dtype — The Type of Every Value
tf.constant(5) gives int32.x > 0.TensorFlow Does Not Mix dtypes for You
NumPy quietly converts types. TensorFlow does not. It raises an error instead. This protects you from silent bugs.
a = tf.constant([1, 2, 3]) # int32
b = tf.constant([0.5, 0.5, 0.5]) # float32
try:
a + b
except tf.errors.InvalidArgumentError as e:
print("Error:", e.message.split(":")[0])
print(tf.cast(a, tf.float32) + b) # fix: cast first
tf.cast — Change the dtype
prices = tf.constant([2.7, -2.7, 9.99])
print(tf.cast(prices, tf.int32)) # cuts toward zero, no rounding!
print(tf.cast(prices, tf.bool)) # non-zero -> True
pixels = tf.constant([0, 128, 255], dtype=tf.uint8)
print(tf.cast(pixels, tf.float32) / 255.0) # classic image scaling
Float to int cuts off the decimal part. It does not round. Use tf.round first if you want rounding. Values too big for the new type wrap around. For example 300 becomes 44 in uint8.
Try It — The dtype Caster
Type some numbers, separated by commas. Pick a target dtype. Numbers with a decimal point start as float32; whole numbers start as int32.
| Input | Output | What happened |
|---|
tf.reshape — Same Values, New Box
tf.reshape(x, new_shape) keeps all the values in the same order. It only changes how they are grouped. There is one rule: the total number of values must stay the same.
x = tf.range(12)
print(tf.reshape(x, [3, 4]))
print(tf.reshape(x, [2, -1]).shape) # -1 becomes 6
print(tf.reshape(x, [2, 2, 3]).shape)
print(tf.reshape(x, [-1]).shape) # flatten to 1-D
try:
tf.reshape(x, [5, 3])
except tf.errors.InvalidArgumentError as e:
print("Error:", e.message.split(" [Op")[0])
Row-Major Order
TensorFlow reads values in row-major order. It walks along the last axis first, like reading a book: left to right, then the next line. Reshape pours values into the new shape in that same order.
Try It — The Reshape Animator
Each value shows its position number. Enter a start shape and a new shape (you may use one -1). Press Reshape and watch each value slide to its new place. Then try Transpose and compare.
Start with shape 2,3. Reshape to 3,2: the numbers stay in reading order 0, 1, 2, 3, 4, 5. Now press Transpose: rows become columns, so the order changes to 0, 3, 1, 4, 2, 5. Same final shape, different data. Mixing these up is a very common bug.
Adding and Removing Axes of Size 1
Many layers need an extra axis. For example, a model trained on batches cannot take one single image. You add a batch axis of size 1.
img = tf.zeros([28, 28]) # one grayscale image
batch = tf.expand_dims(img, axis=0) # add batch axis at the front
print(batch.shape)
with_channel = img[..., tf.newaxis] # add channel axis at the end
print(with_channel.shape)
print(tf.squeeze(tf.zeros([1, 28, 28, 1])).shape) # drop ALL size-1 axes
print(tf.squeeze(tf.zeros([1, 28, 28, 1]), axis=0).shape)
| Function | What it does | Example |
|---|---|---|
tf.reshape | Any new shape with the same size | (2, 6) → (3, 4) |
tf.expand_dims | Insert one axis of size 1 | (28, 28) → (1, 28, 28) |
tf.squeeze | Remove axes of size 1 | (1, 28, 28, 1) → (28, 28) |
tf.transpose | Reorder the axes (moves data) | (2, 3) → (3, 2) |
tf.cast | Change the dtype, not the shape | int32 → float32 |
Shapes You Will Meet in Deep Learning
Golden Rules
axis=-1 always means the last axis.tf.cast to match them.-1 to let TensorFlow fill in a size.tf.transpose, never tf.reshape.