Tensor Flow 📂 Tensors · 6 of 6 46 min read

Tensor Maths: Element-wise Ops, tf.matmul, Broadcasting and tf.reduce_*

Master the four kinds of maths behind every neural network: element-wise operations, matrix multiplication with tf.matmul, broadcasting, and reductions like reduce_sum, reduce_mean and argmax. Step through a matrix multiply, test any two shapes for broadcasting, then build a dense layer by hand.

Section 01

The Story — A Spreadsheet That Thinks in Blocks

The Shopkeeper's Price List
A shopkeeper has a table of prices: 3 shops × 4 items. Tax day comes.

He adds a fixed tax of ₹5 to every price. That is broadcasting — one number stretched over the whole table.

He multiplies each price by its own quantity sold. That is an element-wise operation — cell by matching cell.

He works out each shop's total bill using a price list and quantity list at once. That is a matrix multiply.

Finally he adds up each shop's column into one total. That is a reduction.

These four kinds of maths are almost everything a neural network does. A dense layer is a matrix multiply, plus a broadcast bias, then an element-wise activation. The loss is a reduction. Learn these four well, and deep learning code becomes easy to read.

➕
Element-wise
a + b, a * b
Match cells one to one.
✖️
Matrix multiply
tf.matmul(a, b)
Rows times columns, then add.
📡
Broadcasting
matrix + vector
Stretch a smaller tensor to fit.
🧻
Reductions
tf.reduce_sum
Collapse an axis into one value.

Section 02

Element-wise Operations

An element-wise op works on each position on its own. Cell [0, 0] of the result uses only cell [0, 0] of the inputs.

import tensorflow as tf

a = tf.constant([[1., 2., 3.],
                 [4., 5., 6.]])
b = tf.constant([[10., 20., 30.],
                 [40., 50., 60.]])

print(a + b)                 # same as tf.add(a, b)
print((a * b).numpy())       # tf.multiply — NOT matrix multiply
print((b / a).numpy())       # tf.divide
print(tf.maximum(a, 3.0).numpy())
OUTPUT
tf.Tensor( [[11. 22. 33.] [44. 55. 66.]], shape=(2, 3), dtype=float32) [[ 10. 40. 90.] [160. 250. 360.]] [[10. 10. 10.] [10. 10. 10.]] [[3. 3. 3.] [4. 5. 6.]]

Common Element-wise Functions

OperatorFunctionExample
+ - * /tf.add, tf.subtract, tf.multiply, tf.dividea * b
**tf.powa ** 2
// %tf.math.floordiv, tf.math.floormoda // 2
tf.square, tf.sqrt, tf.abstf.sqrt(a)
tf.exp, tf.math.logtf.math.log(a)
tf.maximum, tf.minimumtf.maximum(a, 0.) = ReLU
> == <tf.greater, tf.equal, tf.lessreturns bool
x = tf.constant([-2.0, -0.5, 0.0, 1.0, 3.0])

print("square :", tf.square(x).numpy())
print("abs    :", tf.abs(x).numpy())
print("exp    :", tf.exp(x).numpy().round(3))
print("relu   :", tf.nn.relu(x).numpy())
print("sigmoid:", tf.sigmoid(x).numpy().round(3))
OUTPUT
square : [4. 0.25 0. 1. 9. ] abs : [2. 0.5 0. 1. 3. ] exp : [ 0.135 0.607 1. 2.718 20.086] relu : [0. 0. 0. 1. 3.] sigmoid: [0.119 0.378 0.5 0.731 0.953]
⚠️
Two dtype Surprises

(1) Both inputs must have the same dtype. int32 + float32 is an error. Use tf.cast.
(2) Dividing two int32 tensors with / gives float64, not float32. Cast to float32 first to keep your model in float32.

i = tf.constant([1, 2, 3])
print((i / 2).dtype)                    # float64!
print((tf.cast(i, tf.float32) / 2).dtype)
print((i // 2).numpy())                 # floor division keeps int32
OUTPUT
<dtype: 'float64'> <dtype: 'float32'> [0 1 1]

Section 03

Try It — Element-wise Calculator

➕ Cell by Cell Interactive

Click any cell in A or B to change its number. Pick an operation and press Compute. Each result cell is built from the matching cells only.


Section 04

Matrix Multiplication — tf.matmul

Matrix multiply is different from element-wise *. Each result cell is a dot product: take one row of A and one column of B, multiply the pairs, and add them up.

Shape Rule
(m, k) @ (k, n) → (m, n)
The inner sizes (k) must match. The outer sizes (m and n) give the result shape.
One Cell
C[i, j] = Σ A[i, t] · B[t, j]
Row i of A "meets" column j of B. k multiplications, then one sum.
A = tf.constant([[1, 2, 3],
                 [4, 5, 6]])          # (2, 3)
B = tf.constant([[7,  8],
                 [9, 10],
                 [11, 12]])           # (3, 2)

C = tf.matmul(A, B)                   # or: A @ B
print(C)

try:
    tf.matmul(A, A)                   # (2,3) @ (2,3): inner sizes 3 != 2
except tf.errors.InvalidArgumentError as e:
    print("Error:", e.message.split("} ")[-1].split(" [Op")[0])
OUTPUT
tf.Tensor( [[ 58 64] [139 154]], shape=(2, 2), dtype=int32) Error: Matrix size-incompatible: In[0]: [2,3], In[1]: [2,3]
A * B — element-wise
Shapes must match (or broadcast)
C[i, j] = A[i, j] × B[i, j]
Used for masks, scaling, gates
A @ B — matrix multiply
Inner sizes must match
C[i, j] = row i · column j
Used in every dense layer

Useful matmul Options

X = tf.random.normal([4, 3])
W = tf.random.normal([5, 3])

print(tf.matmul(X, W, transpose_b=True).shape)     # X @ W.T -> (4, 5)

batch_a = tf.ones([10, 2, 3])                        # a batch of 10 matrices
batch_b = tf.ones([10, 3, 4])
print(tf.matmul(batch_a, batch_b).shape)             # batched matmul -> (10, 2, 4)
OUTPUT
(4, 5) (10, 2, 4)

Section 05

Try It — Matrix Multiply, Step by Step

✖️ Row Meets Column Interactive

Set the sizes of A and B, then press Step to compute one cell, or Play to watch them all. Click cells to edit values. Try making the inner sizes different to see the error.


Section 06

Broadcasting — Stretching to Fit

What if the shapes do not match? TensorFlow tries to broadcast. It stretches the smaller tensor, without copying memory, so the shapes match. This is how one bias vector gets added to every row of a batch.

📡 The Broadcasting Rules
Rule 1
Line up the shapes from the right. If one has fewer axes, pad it with 1s on the left.
Rule 2
Compare each pair of sizes. They fit if they are equal, or if one of them is 1.
Rule 3
A size of 1 is stretched to match the other. The result takes the bigger size on each axis.
prices = tf.constant([[100., 200., 300.],
                      [150., 250., 350.]])     # (2, 3)

print(prices + 5)                               # scalar -> every cell
print(prices * tf.constant([1., 0.5, 2.]))      # (3,) -> every row
print(prices - tf.constant([[10.], [20.]]))     # (2, 1) -> every column

col = tf.constant([[1], [2], [3]])              # (3, 1)
row = tf.constant([10, 20, 30, 40])             # (4,)
print((col + row).shape)                        # (3, 4): both stretch!
OUTPUT
tf.Tensor( [[105. 205. 305.] [155. 255. 355.]], shape=(2, 3), dtype=float32) tf.Tensor( [[100. 100. 600.] [150. 125. 700.]], shape=(2, 3), dtype=float32) tf.Tensor( [[ 90. 190. 290.] [130. 230. 330.]], shape=(2, 3), dtype=float32) (3, 4)
Animated Diagram — A (3, 1) Column Plus a (4,) Row
1 2 3 (3, 1) + 10 20 30 40 (4,) stretch result (3, 4)

The column is copied across 4 times. The row is copied down 3 times. Then the matching cells are added.


Section 07

Try It — Broadcasting Checker

📡 Will These Shapes Broadcast? Interactive

Type two shapes. Use an empty box for a scalar. The table lines them up from the right and checks each axis. Small 2-D cases are also drawn.


Section 08

Reductions — tf.reduce_*

A reduction collapses an axis into a single value. The axis argument says which axis disappears. With no axis, the whole tensor becomes one number.

sales = tf.constant([[3., 5., 2., 8.],
                     [1., 4., 6., 2.],
                     [7., 2., 3., 5.]])       # 3 shops x 4 days

print("total     :", tf.reduce_sum(sales).numpy())
print("per day   :", tf.reduce_sum(sales, axis=0).numpy())   # collapse shops
print("per shop  :", tf.reduce_sum(sales, axis=1).numpy())   # collapse days
print("mean      :", tf.reduce_mean(sales, axis=1).numpy())
print("best day  :", tf.argmax(sales, axis=1).numpy())       # index of max
print("keepdims  :", tf.reduce_sum(sales, axis=1, keepdims=True).shape)
OUTPUT
total : 48.0 per day : [11. 11. 11. 15.] per shop : [18. 13. 17.] mean : [4.5 3.25 4.25] best day : [3 2 0] keepdims : (3, 1)
FunctionReturnsDeep learning use
tf.reduce_sumSumTotal loss, counting
tf.reduce_meanAverageMean loss over a batch
tf.reduce_max / minLargest / smallestMax pooling, stable softmax
tf.reduce_prodProductCounting elements in a shape
tf.reduce_all / anyAND / OR of boolsChecks and assertions
tf.argmax / argminIndex of largest / smallestPredicted class from scores
⚠️
reduce_mean on Integers Rounds Down

tf.reduce_mean(tf.constant([1, 2])) gives 1, not 1.5. The result keeps the int dtype. Cast to float32 before taking a mean.


Section 09

Try It — Reduction Explorer

🧻 Which Axis Collapses? Interactive

Pick a function and an axis. Cells with the same colour are combined into one result value. Click cells to change the numbers.


Section 10

Putting It Together — A Dense Layer by Hand

Now combine all four ideas. This is exactly what a Keras Dense layer computes.

tf.random.set_seed(0)

x = tf.random.normal([4, 3])          # batch of 4 samples, 3 features
W = tf.random.normal([3, 2])          # 3 inputs -> 2 units
b = tf.constant([0.1, -0.2])          # one bias per unit

z = tf.matmul(x, W) + b               # matmul + broadcast   -> (4, 2)
y = tf.nn.relu(z)                     # element-wise         -> (4, 2)
loss = tf.reduce_mean(tf.square(y))   # element-wise + reduce -> ()

print("z shape:", z.shape, " y shape:", y.shape)
print("loss   :", round(float(loss), 4))
OUTPUT
z shape: (4, 2) y shape: (4, 2) loss : 0.5239
01
tf.matmul(x, W)
(4, 3) @ (3, 2) → (4, 2). Each sample becomes 2 weighted sums.
02
+ b
(4, 2) + (2,) → broadcast. The same bias is added to every sample.
03
tf.nn.relu
Element-wise. Negative values become 0.
04
tf.reduce_mean
All values collapse into one loss number.

Section 11

Golden Rules

🧮 Tensor Maths — Rules to Remember
1
* is element-wise. @ or tf.matmul is matrix multiply. Do not mix them up.
2
For matmul, the inner sizes must match: (m, k) @ (k, n) → (m, n).
3
Broadcasting compares shapes from the right. Sizes fit if equal or if one is 1.
4
Both inputs need the same dtype. Cast ints to float32 before dividing or averaging.
5
In a reduction, axis is the axis that disappears. Use keepdims=True to keep it as size 1 for later broadcasting.
6
A dense layer is just activation(x @ W + b). You now know every piece of it.
You have completed Tensors. View all sections →