Tensor Flow 📂 Tensors · 5 of 6 37 min read

Tensor Indexing, Slicing and tf.gather in TensorFlow

Learn to pick parts of a tensor. Use indexing, negative indexes and start:stop:step slices on 1-D and 2-D tensors, plus the ellipsis and tf.newaxis. Then use tf.gather for any list of positions, gather_nd for single cells, and boolean_mask and tf.where to select by a condition. Every tool has a live playground.

Section 01

The Story — Picking Seats in a Cinema

Row 3, Seat 5, Please
A cinema hall is a grid of seats. To find one seat you need two numbers: the row and the seat. That is indexing.

Sometimes you book a block: "rows 2 to 4, seats 1 to 6". That is slicing. The block is always a neat rectangle.

And sometimes you have a list of friends' seats all over the hall: "row 7, row 1, row 7 again". You pick them in that exact order. That is tf.gather.

Getting parts of a tensor is something you will do every day. You will take one image from a batch, the last time step of a sequence, or the rows of a word-embedding table. This lesson covers all the main tools.

📍
Indexing
x[2]
One position. Removes that axis.
✂️
Slicing
x[1:4]
A regular range. Keeps the axis.
🧿
tf.gather
tf.gather(x, [3, 0])
Any list of positions, any order, repeats allowed.
🎭
boolean_mask
x[x > 0]
Keep only values that pass a test.

Section 02

Indexing a 1-D Tensor

Indexing works just like Python lists. The first position is 0. Negative numbers count from the end.

import tensorflow as tf

x = tf.constant([10, 20, 30, 40, 50, 60])

print(x[0])        # first
print(x[-1])       # last
print(x[2].numpy(), x[-2].numpy())
OUTPUT
tf.Tensor(10, shape=(), dtype=int32) tf.Tensor(60, shape=(), dtype=int32) 30 50
Value102030405060
Index012345
Negative index-6-5-4-3-2-1

Section 03

Slicing — start : stop : step

A slice has three parts. Each part is optional.

✂️ x[start : stop : step]
start
Where to begin. Included. Default: the beginning.
stop
Where to end. Not included. Default: the end.
step
How far to jump. Default: 1. A negative step walks backwards.
x = tf.constant([10, 20, 30, 40, 50, 60])

print(x[1:4].numpy())     # positions 1, 2, 3
print(x[:3].numpy())      # first three
print(x[3:].numpy())      # from 3 to the end
print(x[::2].numpy())     # every second value
print(x[::-1].numpy())    # reversed
print(x[-3:].numpy())     # last three
OUTPUT
[20 30 40] [10 20 30] [40 50 60] [10 30 50] [60 50 40 30 20 10] [40 50 60]

Section 04

Indexing Many Dimensions

For a tensor with more axes, separate the index for each axis with a comma: x[row, col].

m = tf.reshape(tf.range(12), [3, 4])
print(m)

print("m[1, 2]  =", m[1, 2].numpy())            # one value
print("m[1]     =", m[1].numpy())               # whole row 1
print("m[:, 2]  =", m[:, 2].numpy())            # whole column 2
print("m[0:2, 1:3] =\n", m[0:2, 1:3].numpy())   # a block
OUTPUT
tf.Tensor( [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]], shape=(3, 4), dtype=int32) m[1, 2] = 6 m[1] = [4 5 6 7] m[:, 2] = [ 2 6 10] m[0:2, 1:3] = [[1 2] [5 6]]
📏
The Rank Rule

An integer index removes that axis. A slice keeps it. So m[1] has shape (4,), but m[1:2] has shape (1, 4). Same numbers, different rank.

The Ellipsis (...) and tf.newaxis

... means "all the axes in between". tf.newaxis adds an axis of size 1.

images = tf.zeros([32, 28, 28, 3])       # batch, height, width, channels

print(images[..., 0].shape)            # red channel of every image
print(images[0, ...].shape)            # first image
print(images[:, :, :, tf.newaxis].shape)
OUTPUT
(32, 28, 28) (28, 28, 3) (32, 28, 28, 1, 3)

Section 05

Try It — The Slice Playground

✂️ Type a Slice, See the Cells Interactive

Write any index or slice for the matrix x. The picked cells light up in the order they are read. Click a chip to try an example.


Section 06

You Cannot Assign Into a Tensor

Tensors are immutable. The line x[0] = 5 fails. You have two options.

x = tf.constant([1, 2, 3, 4])

# Option 1: build a NEW tensor with some values replaced
y = tf.tensor_scatter_nd_update(x, indices=[[0], [2]], updates=[100, 300])
print(y.numpy())

# Option 2: use a Variable, which can change in place
v = tf.Variable([1, 2, 3, 4])
v[1:3].assign([20, 30])
print(v.numpy())
OUTPUT
[100 2 300 4] [ 1 20 30 4]

Section 07

tf.gather — Pick Any Positions

Slices can only pick evenly spaced positions. tf.gather(params, indices, axis=0) picks any list of positions, in any order. You can even repeat a position.

m = tf.constant([[1, 2, 3],
                 [4, 5, 6],
                 [7, 8, 9],
                 [10, 11, 12]])

print(tf.gather(m, [3, 0, 0]))            # rows 3, 0, 0
print(tf.gather(m, [2, 0], axis=1))       # columns 2 and 0
OUTPUT
tf.Tensor( [[10 11 12] [ 1 2 3] [ 1 2 3]], shape=(3, 3), dtype=int32) tf.Tensor( [[ 3 1] [ 6 4] [ 9 7] [12 10]], shape=(4, 2), dtype=int32)
📚
Where You Will Use It: Word Embeddings

An embedding table has one row per word. A sentence is a list of word IDs. tf.gather(table, word_ids) gives one vector per word. This is exactly what a Keras Embedding layer does inside.

vocab_size, dim = 6, 3
table = tf.reshape(tf.range(vocab_size * dim, dtype=tf.float32), [vocab_size, dim])

sentence = tf.constant([4, 1, 4, 2])        # "the cat the dog" as word IDs
vectors = tf.gather(table, sentence)
print(vectors.shape)
print(vectors.numpy())
OUTPUT
(4, 3) [[12. 13. 14.] [ 3. 4. 5.] [12. 13. 14.] [ 6. 7. 8.]]

tf.gather_nd — Pick Single Cells by Full Address

With gather_nd, each index is a full address like [row, col]. You get one value per address.

cells = tf.gather_nd(m, indices=[[0, 0], [1, 2], [3, 1]])
print(cells.numpy())       # m[0,0], m[1,2], m[3,1]
OUTPUT
[ 1 6 11]

Section 08

Try It — The Gather Lab

🧿 Watch tf.gather Collect Values Interactive

Type indices and press Gather. Each picked row (or column, or cell) flies into the result, one by one. For gather_nd, type pairs like [0,1], [3,2].


Section 09

Picking by Condition — boolean_mask and tf.where

scores = tf.constant([[55, 91, 38],
                      [72, 45, 88]])

passed = scores >= 50                     # a bool tensor, same shape
print(passed.numpy())

print(tf.boolean_mask(scores, passed).numpy())   # values that passed (flattened)
print(tf.where(passed).numpy())                  # [row, col] of each True
print(tf.where(passed, scores, 0).numpy())       # keep passed, else 0
OUTPUT
[[ True True False] [ True False True]] [55 91 72 88] [[0 0] [0 1] [1 0] [1 2]] [[55 91 0] [72 0 88]]
🎭 Try It — Mask Explorer Interactive

Move the slider to change the test. Cells that pass turn green. See what each function returns.


Section 10

Which Tool Should I Use?

You want…UseExampleResult rank
One position on an axisInteger indexx[2]rank − 1
An evenly spaced rangeSlicex[1:5:2]same rank
Any list of rows or columnstf.gathertf.gather(x, [4, 0, 4])same rank
Scattered single cellstf.gather_ndtf.gather_nd(x, [[0, 1], [2, 2]])depends on index shape
Values that pass a testtf.boolean_masktf.boolean_mask(x, x > 0)1-D (for a full mask)
Positions that pass a testtf.where(cond)tf.where(x > 0)(count, rank)
⚠️
Out-of-Range Indices

On the CPU, tf.gather raises an error for a bad index. On a GPU, it quietly returns 0 for that position. So always check your indices, for example with tf.debugging.assert_less(idx, x.shape[0]).


Section 11

Golden Rules

📍 Indexing and Gathering — Rules to Remember
1
Counting starts at 0. -1 is the last item.
2
In a slice, stop is not included. x[1:4] gives 3 items.
3
An integer index drops the axis. A slice keeps it. Watch your shapes.
4
Tensors cannot be changed in place. Use tf.tensor_scatter_nd_update or a tf.Variable.
5
Use tf.gather for any list of positions, in any order. It is how embedding lookups work.
6
Use tf.boolean_mask or tf.where to select by a condition.