Tensor Flow 📂 Variables and Automatic Gradients · 3 of 5 32 min read

Gradients for Many Variables with tf.GradientTape

Compute gradients for many variables in one backward pass. Pass lists or dicts of variables, match gradient shapes to weight shapes, and get gradients for a whole Keras model. Explore a live loss landscape for w and b, build dense layer shapes, and learn about non-scalar targets and unconnected gradients.

Section 01

The Story — The Sound Engineer's Mixing Desk

Many Knobs, One Goal
A sound engineer has a desk with dozens of knobs: bass, treble, volume, echo. The song sounds a little wrong. Which knobs should she turn, which way, and how far?

If she had a magic meter, it would show an arrow on every knob at once: "turn this one up a lot, that one down a little, leave this one alone".

A neural network is a desk with thousands or millions of knobs (its weights). The magic meter is tape.gradient(loss, variables). It returns one gradient for every variable in a single call.

In the last lesson you found the gradient for one variable. Real models have many. The good news: the tape handles any number of variables at once, with one backward pass.


Section 02

Two Variables: A Line y = w·x + b

We want the line to predict y = 7 when x = 2. Our current guess is w = 1, b = 0. The loss is the squared error.

import tensorflow as tf

w = tf.Variable(1.0)
b = tf.Variable(0.0)
x, target = 2.0, 7.0

with tf.GradientTape() as tape:
    pred = w * x + b
    loss = (pred - target) ** 2

dw, db = tape.gradient(loss, [w, b])      # a list in, a list out
print("pred :", pred.numpy(), " loss:", loss.numpy())
print("dL/dw:", dw.numpy())
print("dL/db:", db.numpy())
OUTPUT
pred : 2.0 loss: 25.0 dL/dw: -20.0 dL/db: -10.0
✍️ Check by Hand
error
pred − target = 2 − 7 = −5
dL/dw
2 × error × x = 2 × (−5) × 2 = −20
dL/db
2 × error × 1 = 2 × (−5) = −10
meaning
Both are negative, so increasing w and b will lower the loss. w matters twice as much because x = 2.

Any Structure Works: List, Tuple or Dict

The gradient comes back in the same structure you pass in.

with tf.GradientTape() as tape:
    loss = (w * x + b - target) ** 2

grads = tape.gradient(loss, {"weight": w, "bias": b})
print({k: float(v) for k, v in grads.items()})
OUTPUT
{'weight': -20.0, 'bias': -10.0}
Animated Diagram — One Backward Pass Feeds Every Variable
w x (data) b × + loss dL/d(pred) dL/dw dL/db

The gradient starts at the loss and flows back. At the "+" node it splits: one path goes to b, the other through "×" to w. The data x gets no gradient because we did not ask for it.


Section 03

Try It — Walk on the Loss Landscape

🗺️ Two Knobs, One Loss Interactive

The colour map shows the loss (mean squared error) for every pair of w and b. Dark = low loss. Click or drag on the map to choose w and b. The red arrow is −gradient: the direction that lowers the loss fastest. Edit the data to make your own problem.

loss landscape — w across, b up
data (dots) and line y = w·x + b

Section 04

Gradients Have the Same Shape as Variables

This is a key rule. A weight matrix of shape (3, 2) gets a gradient of shape (3, 2). One number per knob.

tf.random.set_seed(1)
X = tf.random.normal([5, 3])                 # batch of 5, 3 features
y = tf.random.normal([5, 2])                 # 2 targets
W = tf.Variable(tf.random.normal([3, 2]))
b = tf.Variable(tf.zeros([2]))

with tf.GradientTape() as tape:
    pred = X @ W + b
    loss = tf.reduce_mean((pred - y) ** 2)

dW, db = tape.gradient(loss, [W, b])
print("W", W.shape, "-> dW", dW.shape)
print("b", b.shape, "   -> db", db.shape)
OUTPUT
W (3, 2) -> dW (3, 2) b (2,) -> db (2,)

A Whole Keras Model in One Call

model = tf.keras.Sequential([
    tf.keras.Input(shape=(3,)),
    tf.keras.layers.Dense(4, activation="relu"),
    tf.keras.layers.Dense(1),
])

with tf.GradientTape() as tape:
    loss = tf.reduce_mean(model(X) ** 2)

grads = tape.gradient(loss, model.trainable_variables)
for var, g in zip(model.trainable_variables, grads):
    print(f"{var.path:26s} {str(var.shape):8s} grad {g.shape}")
OUTPUT
sequential/dense/kernel (3, 4) grad (3, 4) sequential/dense/bias (4,) grad (4,) sequential/dense_1/kernel (4, 1) grad (4, 1) sequential/dense_1/bias (1,) grad (1,)

Section 05

Try It — Dense Layer Shape Builder

🧱 How Many Gradients Does a Layer Need? Interactive

Set the batch size, the number of input features and the number of units. See every shape in the forward and backward pass.


Section 06

Two Surprises to Know

1. A Non-scalar Target Is Summed First

If the target is a vector, tape.gradient gives the gradient of its sum. For one gradient per output, use tape.jacobian.

x = tf.Variable(2.0)
with tf.GradientTape(persistent=True) as tape:
    ys = tf.stack([x, x ** 2, x ** 3])      # [2, 4, 8]

print("gradient :", tape.gradient(ys, x).numpy(), "  (1 + 4 + 12)")
print("jacobian :", tape.jacobian(ys, x).numpy())
del tape
OUTPUT
gradient : 17.0 (1 + 4 + 12) jacobian : [ 1. 4. 12.]

2. An Unused Variable Gives None

a = tf.Variable(1.0)
unused = tf.Variable(5.0)

with tf.GradientTape() as tape:
    loss = a * 3

print(tape.gradient(loss, [a, unused]))

with tf.GradientTape() as tape:
    loss = a * 3
print(tape.gradient(loss, [a, unused],
      unconnected_gradients=tf.UnconnectedGradients.ZERO))
OUTPUT
[<tf.Tensor: shape=(), dtype=float32, numpy=3.0>, None] [<tf.Tensor: shape=(), dtype=float32, numpy=3.0>, <tf.Tensor: shape=(), dtype=float32, numpy=0.0>]
⚠️
None in a Training Loop

If an optimizer warns "Gradients do not exist for variables …", some layer is not used in the loss. Check your model's forward pass, or pass unconnected_gradients on purpose.


Section 07

Golden Rules

🎛️ Many Variables — Rules to Remember
1
Pass a list, tuple or dict of variables. You get gradients back in the same structure.
2
Each gradient has the same shape as its variable.
3
For a Keras model use tape.gradient(loss, model.trainable_variables).
4
Reduce the loss to a scalar (usually with tf.reduce_mean) before taking gradients.
5
The negative gradient points "downhill" for all variables at once. That is the idea behind training.