FOUNDATIONS / NOTEBOOK LESSON

32 · Computation graphs, the chain rule, and gradients

Verify the chain rule, finite differences, gradient accumulation, and step-size effects.

Constructed mechanismRead-only · saved experiment

Saved outputs come from recorded teaching runs; opening this page does not rerun an experiment. The code and original outputs are preserved; original-language data and annotations may remain.

32 · Computation graphs, the chain rule, and gradients

Foundations path: 31 → 32 → 33 → 34, then choose search, ads, or recommendation.

Scope: constructed small-array mechanism experiments on CPU; no real logs, formal model training, or business-impact claims.

For each section: predict → change one variable → run → explain. Execute from the beginning first; after edits, rerun affected downstream cells.

English companion. All instructional Markdown is translated. Experiment code, Chinese language examples, execution counts, and saved outputs remain as in the source edition; historical output text may therefore be Chinese. The initial setup cell anchors the working directory and Python import path for this companion's deeper folder; it does not change the experiment. Saved outputs are historical evidence, not a new run. Run from the top after preparing the required data and models.

PYTHON · 01
# English companions live one directory deeper; keep the original experiment code unchanged.
from pathlib import Path as _TranslationPath
import os as _translation_os
import sys as _translation_sys
_translation_root = next((p for p in [_TranslationPath.cwd(), *_TranslationPath.cwd().parents]
                          if (p / "reclab").is_dir()), None)
if _translation_root is None:
    raise RuntimeError("Open this notebook from inside the Search Recommendation Ads Lab checkout.")
_translation_os.chdir(_translation_root)
if str(_translation_root) not in _translation_sys.path:
    _translation_sys.path.insert(0, str(_translation_root))

Construct a scalar computation graph

z=wx+b, L=(z-y)^2. Calculate dL/dw=2(z-y)x by hand first. All values use float64.

PYTHON · 02
import torch
import matplotlib.pyplot as plt
x, y = 2., 1.
w = torch.tensor(0.5, dtype=torch.float64, requires_grad=True)
b = torch.tensor(0.2, dtype=torch.float64, requires_grad=True)
z = w*x+b
loss = (z-y)**2
loss.backward()
assert torch.allclose(w.grad, 2*(z.detach()-y)*x)
print('z, loss, dw, db:', z.item(), loss.item(), w.grad.item(), b.grad.item())
Saved notebook output · not executed on this page
z, loss, dw, db: 1.2 0.03999999999999998 0.7999999999999998 0.3999999999999999

Verify with finite differences

Change only the difference step. Central differences approximate gradients; excessively small steps can cause cancellation error.

PYTHON · 03
def objective(value):
    return (value*x+b.detach().item()-y)**2
steps = [1e-2, 1e-5, 1e-8, 1e-12]
estimates = [(objective(w.item()+h)-objective(w.item()-h))/(2*h) for h in steps]
assert abs(estimates[1]-w.grad.item()) < 1e-8
print(list(zip(steps, estimates)))
plt.semilogx(steps, [abs(v-w.grad.item()) for v in estimates], 'o-')
plt.xlabel('finite difference step'); plt.ylabel('absolute error'); plt.show()
Saved notebook output · not executed on this page
[(0.01, 0.8000000000000007), (1e-05, 0.7999999999962897), (1e-08, 0.7999999995789153), (1e-12, 0.7999816087345124)]
<Figure size 640x480 with 1 Axes>
Saved notebook figure

Gradient accumulation

Rebuild the same graph and call backward again. Predict whether gradients are automatically cleared.

PYTHON · 04
first = w.grad.clone()
((w*x+b-y)**2).backward()
assert torch.allclose(w.grad, 2*first)
print('累加后的梯度:', w.grad.item())
w.grad = None
b.grad = None
((w*x+b-y)**2).backward()
assert torch.allclose(w.grad, first)
Saved notebook output · not executed on this page
累加后的梯度: 1.5999999999999996

Change only learning rate

Start from the same w and fix current b. A descent direction does not guarantee descent for every step size.

PYTHON · 05
rates = [0.05, 1.0]
new_losses = [objective(w.item()-lr*w.grad.item()) for lr in rates]
assert new_losses[0] < loss.item() < new_losses[1]
print('原损失:', loss.item(), '更新后:', dict(zip(rates, new_losses)))
Saved notebook output · not executed on this page
原损失: 0.03999999999999998 更新后: {0.05: 0.014400000000000026, 1.0: 1.959999999999999}

Explain

Is a gradient a local slope or a final parameter? Why do training batches usually clear gradients? Does a failed large step disprove the chain rule?

In the next cell, record your prediction, actual result, reason for any difference, and applicability limits. Keep failed results.

PYTHON · 06
reflection = {'预测': '', '结果': '', '解释与边界': ''}
reflection
Saved notebook output · not executed on this page
{'预测': '', '结果': '', '解释与边界': ''}

GO TO THE SOURCE

Sources for this lesson

Original teaching guideChinese

Lab · Experiment and Evidence Guide

Predict → change one variable → run → explain

Original prerequisites: tensors, gradients, optimization and evaluation. Not an official translation or certification by an external course.

Completion marks are optional and stored only in this browser. Reading a page does not mark it complete.