Inference, dynamics and causality
Calculus, chain rules and credit assignment
The chain rule makes distant learning tractable by factorising a global loss sensitivity into local pullbacks, but the resulting gradient becomes defensible credit only after path, scale and intervention checks.
TLDR
- The chain rule turns a distant effect into products and sums of local sensitivities.
- Reverse-mode differentiation reuses one backward sweep to obtain many parameter gradients from one scalar loss.
- The same multiplication that enables learning can make gradients vanish, explode, cancel or shatter.
- A raw gradient is coordinate-dependent local sensitivity, not causal blame or global importance.
- Before changing a component, verify the derivative and test finite interventions, paths and equivalent parameterisations.
The camera setting that the gradient barely notices
Consider Trellis, a synthetic inspection model that turns a camera signal into a defect probability. One internal gate receives the value z = 8, so its sigmoid output is 0.9997. The desired output for this example is 0.5. With squared error, the loss is about 0.1248.
Ask backpropagation how the loss changes if z moves. It returns a gradient of roughly 0.00017. That looks like an instruction to ignore the gate. Yet resetting z from 8 to 0 makes the output exactly 0.5 and removes the loss.
Nothing is inconsistent. The gradient answered a narrow question: what happens under an infinitesimal move from 8? The reset asked a different question: what happens under a large intervention that crosses a saturated region? A gradient is a local sensitivity in a declared computation, not a universal measure of what caused the failure.
The error that does not travel backwards
“Backpropagating the error” invites a misleading picture. It sounds as if the wrong answer is packaged at the output and carried backwards, layer by layer, until every parameter receives its share of blame. That story is useful for orientation, but it hides the mathematical object doing the work.
The backward quantity is not the error itself. It is the sensitivity of the chosen scalar loss to an intermediate value. At each operation, the system asks: if this input changed by a tiny amount while the rest of the local calculation stayed fixed, how much would the final loss change? The answer is then translated through the next operation, and the next, until it reaches a parameter.
This is why calculus matters. A derivative is a disciplined local comparison. For a scalar function y = f(x), the derivative at x is the limiting ratio between a small output change and the small input change that produced it. It says nothing by itself about large moves, alternative mechanisms, bad labels, missing variables or whether the intervention is physically available.
Make the comparison more explicit. Let a system have two adjustable quantities, θ₁ and θ₂, and let its loss be L(θ₁, θ₂). At one operating point, the gradient might be (4, 1). This does not mean that the first quantity caused four times as much harm. It means that, under the chosen units and while making an infinitesimal move parallel to one coordinate axis, loss changes four times as quickly along θ₁ as along θ₂.
The available action may point elsewhere. If a physical constraint permits only the joint move d = (1, −1), then the relevant first-order change is the directional derivative ∇L · d = 3. If the permitted move is d = (1, −4), the first-order change is zero even though neither coordinate gradient is zero. Credit becomes actionable only after the candidate direction of change is named. A component score without an action model quietly assumes that every coordinate can move independently, by the same unit, at negligible cost.
The watershed picture also exposes a hidden assumption. We often speak as though a component owns a fixed amount of responsibility. In fact, its derivative depends on the current point, the route to the loss and the coordinates used to describe the component. Change any of those, and the number can change even when the forward behaviour does not.
Return to Trellis. At z = 8, the sigmoid is nearly flat. Move z by 0.001 and the output barely changes, so the derivative is correctly tiny. Now allow one different intervention: replace the gate setting with z = 0. That move crosses the plateau and reaches a region with a steep slope. The loss disappears.
Which answer is right? Both, because they answer different counterfactuals. The derivative describes an infinitesimal perturbation within the present regime. The reset describes a finite, feasible action. Local credit becomes actionable only when the intervention scale matches the decision you can actually make.
The same boundary applies to the loss. Suppose Trellis is trained against an incorrect label. Backpropagation can be mathematically flawless and still push every parameter in the wrong practical direction. Suppose the loss omits energy use, false-negative cost or a policy constraint. No chain rule will smuggle those concerns into the gradient. It differentiates the objective provided, not the objective intended.
Consider the one-parameter predictor ŷ = θx with squared loss ½(ŷ − t)². At θ = 0, use two records with the same input x = 1. The first target is t = 2, so its gradient is −2. The second target is t = −2, so its gradient is +2. The mean gradient is exactly zero.
The parameter is not irrelevant to either record. The selected batch objective asks it to satisfy incompatible local demands and then averages them away. A different weighting, subgroup slice or loss would return a different credit signal.
This cancellation is common enough to deserve its own diagnostic. Record the reduction operation, not just the final scalar. A mean over examples, time steps or tasks determines which positive and negative sensitivities meet before the backward pass begins. Per-example gradients can reveal conflict that the aggregate gradient conceals. They still remain local sensitivities, but they expose which parts of the declared objective are supplying them.
flowchart TD A["The error that does not travel<br/>backwards"] --> B["One global question, many<br/>local answers"] B --> C["When the path changes the credit"] C --> D["From sensitivity to a change<br/>you can defend"] D -. "re-test" .-> A classDef first fill:#0a1424,stroke:#B96D00,stroke-width:2px,color:#e6f3ff classDef step fill:#0a1424,stroke:#3f7da0,stroke-width:1px,color:#e6f3ff class A first class B,C,D step
One global question, many local answers
The chain rule earns its importance by making a distant question decomposable. Begin with a calculation small enough to hold in working memory.
Let the input be x = 2, a weight be w = 2, and a bias be b = 1. The model computes a = wx + b = 5, then y = a² = 25. The target is t = 20, and the loss is L = ½(y − t)² = 12.5.
The local derivatives are simple: ∂L/∂y = 5, ∂y/∂a = 10, and ∂a/∂w = 2. Multiply them and ∂L/∂w = 100. A small move Δw = −0.01 therefore predicts ΔL ≈ −1. The actual loss change is about −0.978, close because the move is small.
In scalar notation, if z₀ = θ, z₁ = f₁(z₀), and so on until L = zₖ, then the derivative is the product of local derivatives along the chain.
Vectors change the notation, not the idea. Each operation has a Jacobian, a linear map that approximates how a small input displacement changes the output. Reverse mode starts with sensitivity one at the scalar loss and repeatedly applies transposed Jacobians. In automatic-differentiation language, it computes vector-Jacobian products, or pullbacks, from output cotangents to input cotangents (JAX authors).
z̄ᵢ means ∂L/∂zᵢ. The Jacobian translates output sensitivity into input sensitivity. It is evaluated at the cached forward value.
Read the transpose in that expression as a pullback. Suppose a local operation maps a three-number input into a two-number output. Its Jacobian has two rows and three columns. The backward pass does not need every entry of that matrix as a standalone object. It receives a two-number sensitivity from downstream and multiplies by the transposed Jacobian, producing the three-number sensitivity required upstream. Automatic-differentiation systems call this a vector–Jacobian product or, depending on orientation, a Jacobian-transpose–vector product. The operation answers one targeted question without materialising a potentially enormous Jacobian.
A concrete linear node makes this visible. Let z = Aθ, where θ contains three parameters, z has two outputs and A is a two-by-three matrix. If downstream computation supplies g = ∂L/∂z, then the parameter gradient is Aᵀg. Each column of A tells how one parameter moves both outputs. Multiplication by g weights those movements by what the loss currently cares about. The result is credit for this loss and this downstream state, not a permanent property of the parameter.
During the forward pass, each primitive operation records or makes recoverable the values needed by its local derivative rule. During the reverse pass, it receives an output cotangent, applies its pullback and accumulates the result at its parents. When a value is reused, contributions from every executed use are added. When control flow skips a branch, that unexecuted branch contributes nothing to that run.
This local contract is the source of both efficiency and inspectability. For straight-line arithmetic programs, Baur and Strassen (1983) showed that all first partial derivatives of a scalar result can be obtained with only constant-factor arithmetic overhead relative to evaluating the program. Modern systems complicate the accounting with memory traffic, parallel kernels, control flow and recomputation, but the structural bargain remains: one scalar question can be pulled back to many inputs by reusing local work.
This direction is computationally decisive. A training loss usually has millions or billions of parameter inputs and one scalar output. Forward mode would carry one input perturbation through the graph at a time. Reverse mode shares the forward calculation, then obtains all parameter sensitivities in one reverse traversal. Automatic differentiation is broader than neural-network backpropagation, but backprop is its most visible machine-learning use (Baydin et al., 2018). The efficient reverse accumulation idea also predates its neural-network popularisation (Baur and Strassen, 1983).
Reverse mode works because every local derivative can be reused by every upstream parameter that flows through it. The forward pass records values. The backward pass composes local linear maps in the opposite order. Shared subcomputations are differentiated once, not re-expanded separately for every parameter.
Automatic differentiation is therefore neither symbolic differentiation nor finite differencing. Symbolic differentiation rewrites an expression and can expand it into unwieldy formulae. Finite differencing reruns the program after perturbing an input and suffers from truncation and floating-point error. Automatic differentiation executes derivative rules alongside the original primitives, so its result is exact up to ordinary numerical arithmetic for the graph that actually ran. It can still be wrong for the intended system if the graph omits a path, detaches a state, uses an unsuitable surrogate derivative or encodes the wrong objective.
The computational advantage also explains why scalar losses dominate training interfaces. If a system has many outputs and one wants the complete Jacobian with respect to many parameters, no single reverse sweep contains every possible output question. Each seed at the output selects a different linear combination. A scalar loss compresses those questions before differentiation. That compression is a modelling decision: it determines which errors, examples and desiderata become commensurable enough to share one backward signal.
Real computational graphs branch and merge. At a fork, one value influences several descendants. Its total gradient is the sum of the contributions returning through every child. This sum is as important as the pathwise products.
Let p = ab, q = ac, s = p + q, and L = ½(s − 1)². Choose a = 2, b = 3 and c = −1. Then s = 4 and ∂L/∂s = 3. The path through p contributes 3 × b = 9 to ∂L/∂a. The path through q contributes 3 × c = −3. The total is 6.
Neither branch owns the final answer. Credit is a signed path sum. If b + c = 0, the gradient to a becomes zero even though both branches are active and individually sensitive.
| Method | What it computes | Best fit | Main failure or cost |
|---|---|---|---|
| Symbolic differentiation | Transforms an expression into another expression | Small closed-form formulae | Expressions can expand and duplicate work |
| Finite difference | Approximates change by evaluating nearby points | Black-box checks and small parameter sets | One or more evaluations per direction; step-size and rounding trade-offs |
| Forward mode | Pushes an input perturbation through Jacobian-vector products | Few inputs, many outputs | Repeated work when inputs greatly outnumber outputs |
| Reverse mode | Pulls an output sensitivity through vector-Jacobian products | Many inputs, one scalar loss | Stores or recomputes forward intermediates; inherits graph and differentiability assumptions |
Rumelhart, Hinton and Williams showed how repeated weight adjustments derived this way can make hidden units acquire task-relevant representations (1986). The achievement was not that calculus suddenly discovered hidden layers. It was that a global training error could be translated into local update directions for every weight without separately solving a new optimisation problem for each one.
When the path changes the credit
The chain rule does more than transmit sensitivity. It reshapes it. Products govern a single path, sums govern competing paths, and the graph determines which paths exist. These operations create predictable failure modes.
Imagine a message crossing a corridor of identical rooms. Each room multiplies it by a gain α. After T rooms, both the forward influence and the backward sensitivity contain the factor αᵀ. With α = 0.9 and 60 rooms, the factor is about 0.0018. With α = 1.1, it is about 304.
No individual room is dramatic. The apparent phase change belongs to repeated multiplication. Depth turns modest local bias into exponential attenuation or amplification.
In recurrent networks, the same parameters are applied over time, so credit to an early state contains a product of many transition Jacobians. Pascanu, Mikolov and Bengio analysed vanishing and exploding gradients through analytical, geometric and dynamical lenses, and proposed gradient clipping for explosion (2013). Clipping controls the size of an update after the fact. It does not reconstruct information already erased by a near-zero product.
The scalar corridor hides a more selective effect. In a vector system, every room applies a Jacobian matrix rather than one gain. A matrix can stretch one direction, shrink another and rotate both. Products of such matrices may preserve the overall norm while erasing the particular direction needed to solve the task. Conversely, a large norm may be concentrated in an irrelevant direction. Monitoring only the average gradient magnitude can therefore miss directional loss of credit.
Singular values provide the local lens. For one Jacobian, a singular value above one amplifies its associated direction and a value below one contracts it. Across depth, the singular directions themselves can rotate, so multiplying average gains is only a rough warning. The exact signal depends on the ordered product encountered along the executed trajectory. This is why initialization, normalisation, gating and architecture alter trainability even when they compute families of similar functions.
Residual connections change that product in a specific way. A plain layer hₖ₊₁ = Fₖ(hₖ) contributes the Jacobian Jₖ. A residual layer hₖ₊₁ = hₖ + Fₖ(hₖ) contributes I + Jₖ, where I is the identity map. The identity term supplies a direct route for both forward state and backward sensitivity. It does not guarantee stable training, but it changes the default from repeatedly replacing a signal to repeatedly perturbing it. The empirical success of residual networks and the analysis of shattered gradients support this path-based explanation within the architectures studied by He and colleagues (2016) and Balduzzi and colleagues (2017).
Feed-forward depth has an additional problem. Even when gradient magnitudes remain controlled, gradients for nearby inputs can become poorly correlated and resemble noise. Balduzzi and colleagues called this gradient shattering and showed that skip connections resist it better than plain deep networks (2017). Residual architectures provide identity routes through which signals and gradients can travel more directly, one reason they made much deeper networks easier to optimise (He et al., 2016).
Take the ReLU model y = v ReLU(wx) with x = 1, w = 2, v = 3 and target 5. It outputs 6. The gradients are ∂L/∂w = 3 and ∂L/∂v = 2.
Now replace the parameters with w′ = 20 and v′ = 0.3. Because ReLU is positively homogeneous, the function is unchanged for every input: 0.3 ReLU(20x) = 3 ReLU(2x). Yet the gradients become 0.3 and 20. The apparent ranking flips.
The reparameterisation example reveals a geometric choice hidden inside ordinary gradient descent. A derivative is a covector: it converts a proposed displacement into a first-order loss change. To turn that covector into an update vector, an optimiser needs a rule for measuring distance in parameter space. Plain gradient descent uses the Euclidean geometry of the current coordinates. Rescale those coordinates and the same function receives a different update.
Methods such as path-normalised optimisation try to remove part of this arbitrariness for positively homogeneous networks. Neyshabur, Salakhutdinov and Srebro (2015) designed Path-SGD so that function-preserving node rescalings do not change its geometry in the same way. This is not a universal solution. Different invariances suit different model classes, and function-space change can still interact with data and loss. The useful lesson is narrower: a ranking becomes stronger when it survives transformations that leave the relevant behaviour unchanged.
This is not a numerical curiosity. If an engineer ranks modules by raw gradient magnitude, equivalent parameterisations can suggest opposite priorities. The gradient is a covector: it transforms when coordinates change. A step direction also depends on the metric used to turn that covector into a parameter displacement. Ordinary gradient descent silently chooses Euclidean geometry in the current coordinates.
Build two programs with the same forward calculation y = f(g(x)). In the second, insert a stop-gradient operation after g. Both programs produce exactly the same output for every input. During learning, however, the first sends credit into g and the second sends none.
The forward function does not uniquely determine the learning graph. Freezing a module, detaching a state, using a non-differentiable tool or replacing a value with a cached constant changes who can receive credit without changing the current prediction.
The strongest alternative to exact backpropagation is not that credit can be ignored. It is that useful teaching signals may be approximate. Feedback alignment replaces exact transposed forward weights with fixed random feedback pathways, yet can train several linear and nonlinear networks because the forward weights adapt towards useful alignment (Lillicrap et al., 2016). Direct feedback alignment goes further by sending output error directly to hidden layers (Nøkland, 2016).
These results weaken any claim that the exact chain-rule gradient is the only possible learning signal. They do not show that all approximate signals work, that biological brains implement those algorithms, or that gradient correctness is irrelevant. For a fixed smooth graph and scalar objective, reverse mode still computes the precise local derivative. The alternative algorithms change the learning rule and accept a different guarantee.
Let L(θ) = θ³. At θ = 0, the gradient is zero, yet any small negative move lowers the loss and any small positive move raises it. The tangent is flat because the leading local change is cubic, not because the point is an optimum. In several dimensions, saddles and flat plateaux create richer versions of the same warning.
A finite update asks more than the gradient can answer. For a parameter displacement Δθ, the second-order approximation is ΔL ≈ gᵀΔθ + ½ΔθᵀHΔθ. Here g is the gradient, H is the Hessian and the quadratic term records local curvature along the proposed move. The approximation assumes sufficient smoothness and a displacement small enough for neglected higher-order terms to remain minor. Hessian–vector products can estimate the curvature term without constructing the full matrix, as shown by Pearlmutter (1994).
Curvature improves a step forecast, not the meaning of credit. It can reveal that a large gradient points into a sharply curved wall, or that a modest gradient lies in a broad productive valley. It still inherits the chosen loss, graph, data and coordinates. Second-order optimisation narrows the gradient-to-action gap for local moves; it does not turn optimisation sensitivity into historical causation.
Nonsmooth activations add another boundary. A ReLU has zero derivative throughout its inactive region and no unique classical derivative at zero. Software chooses a convention at the kink. A dead unit can therefore receive no local gradient even when a sufficiently large bias change would reactivate it. Gradient checking near such points must distinguish a coding error from a genuine lack of smoothness.
Gradient-based explanations create a related warning. Input attribution is not the same problem as assigning parameter updates, so the fields should not be equated. Still, implementation invariance and model-randomisation tests show why a visually plausible gradient-derived explanation is not automatically faithful (Sundararajan et al., 2017) (Adebayo et al., 2018). The lesson transfers as a design discipline: test the claimed meaning, rather than only the arithmetic.
stateDiagram-v2 [*] --> Claim state "An autodiff-free<br/>credit-assignment audit" as Claim state "Frame the claim" as Test state "Test the mechanism" as Revision Claim --> Test: expose mechanism Test --> Decision: evidence survives Test --> Revision: rival survives Revision --> Claim: narrow or rebuild Decision --> [*]
From sensitivity to a change you can defend
Return to Trellis as a configured system rather than a bare equation. It contains a sensor setting, a clipping preprocessor, a trainable encoder, a calibration layer, a decision threshold and a labelled dataset. Only some of these objects lie inside the differentiable graph. Only some are allowed to change. Only the declared loss contributes a learning signal.
A large encoder gradient may identify a locally effective parameter update. It does not prove that the encoder created the bad label, that changing it is safer than changing exposure, or that the policy threshold should move. A zero sensor gradient may mean saturation, a detached path, cancellation across examples or a genuinely insensitive operating point. The number needs a typed interpretation.
Optimisation credit is the local derivative of the selected loss in the current coordinates. Intervention credit is the observed or estimated effect of a bounded feasible change. Mechanism credit is the part of that effect that remains stable when alternative paths, equivalent parameterisations and relevant data slices are tested.
The three can agree, but they need not. Treating them as one concept is the gradient-to-action gap.
The practical object should therefore be a credit-assignment assay, not a leaderboard of components. Its manifest records the scalar objective, the data slice and reduction, the candidate variable, its units, the executed path, the allowed intervention, the numerical tolerance and the invariances that matter. Without those fields, two teams can report “the gradient” while answering different questions.
The burden should match the decision. A reversible optimiser step may need only a verified derivative and a line-search check. Freezing a module for a month should also test data slices, alternative paths and nearby operating points. Replacing a sensor, changing a policy threshold or blaming a team requires evidence outside the differentiable model. Do not demand every rung for every update, but do not spend higher-stakes authority on lower-rung evidence.
This distinction suggests a ladder rather than a verdict. Each rung permits a stronger decision while retaining the limits of the previous one.
| Rung | Test | Positive result permits | Negative result means |
|---|---|---|---|
| Graph credit | Trace the exact path from parameter to loss, including detachments, reuse and aggregation | Interpret the derivative within that graph | No gradient claim is available for the severed component |
| Verified local sensitivity | Compare the hand or automatic derivative with a central finite difference away from kinks | Trust the local implementation to numerical tolerance | Suspect a bug, unstable step size or nonsmooth point |
| Feasible intervention | Apply a bounded change that respects the allowed operating range | Use the gradient sign as an update proposal at that scale | The linear approximation is too local, or interactions dominate |
| Path stability | Ablate, hold fixed or separately measure competing routes and data slices | Attribute the effect to a more specific mechanism within the model | Cancellation, redundancy or confounding paths control the result |
| Representation stability | Repeat under function-preserving rescaling or compare function-space change | Prioritise the component with less coordinate arbitrariness | Raw magnitude is a property of parameterisation, not stable importance |
Declare the question. Trellis uses squared calibration loss on a named synthetic batch. The candidate is the pre-sigmoid gate value z, measured in the encoder’s internal units. A small optimiser update is allowed; a direct reset is available only as a diagnostic intervention.
Verify the local calculation. At z = 8, the hand derivative and central finite difference agree near 0.00017. This supports graph credit and numerical correctness at that point. It does not yet support ignoring the gate.
Vary intervention scale. A change of −0.001 produces the tiny loss reduction predicted by the tangent. Changes of −0.1, −1 and the reset to zero increasingly depart from that prediction as they cross the sigmoid’s curved shoulder. The gate therefore has weak local optimisation credit at the saturated point but strong finite intervention credit over an available diagnostic range.
Test the path. Repeat after removing clipping, detaching the calibration layer and evaluating records separately. If the effect disappears only when one route is severed, that route carries model-internal mechanism credit. If subgroup gradients oppose one another, the aggregate score was hiding a trade-off. If the ranking changes under an equivalent rescaling of adjacent ReLU layers, raw parameter magnitude is not representation-stable.
The defensible conclusion is deliberately typed: the gate is locally saturated under the declared loss; finite changes can materially alter that loss; and the strength of any module-level attribution depends on the tested path and representation. Nothing in the assay establishes why the real camera produced a signal, whether the label is correct or whether changing the deployed sensor is safe.
A comparative test of the ladder can be small and decisive. Generate families of two-layer networks that implement the same input-output function through different positive rescalings. Add controlled saturation, redundant paths and mixtures of examples with cancelling gradients. Rank candidate components once by raw gradient magnitude and once by the highest ladder rung passed. Then apply predeclared bounded interventions and measure realised loss change, ranking stability across batches and sensitivity to equivalent parameterisations.
The main prediction is not that higher rungs always produce larger gains. It is that they reduce false confidence: fewer high-ranked components should fail to improve under the permitted intervention, and fewer priorities should reverse under behaviour-preserving re-expression. A null result would be useful. It would show that the additional assay cost does not buy better decisions for that model family, loss and intervention range.
The ladder should predict which gradient-ranked component changes produce stable improvement better than raw gradient magnitude alone. Evidence would strengthen the proposal if rankings that survive finite interventions, path tests and rescaling generalise across batches and nearby operating points. It would weaken it if the extra tests add no predictive value or merely inject noise.
A rival explanation is that any improvement comes from generic robustness testing rather than the specific distinction among optimisation, intervention and mechanism credit. The framework should not be used to infer real-world causality outside the declared system. Its smallest useful test is a scalar or two-layer network with one saturation case, one fork and one equivalent rescaling.
An autodiff-free credit-assignment audit
The lab below exposes the local products that a framework normally hides. The interactive controls show depth amplification and saturation. The Python program computes a two-layer network by hand, checks every gradient with central differences, compares first-order predictions with finite moves, and reproduces the reparameterisation example.
"""Autodiff-free credit-assignment lab for Paper 17.
Uses only Python's standard library. It compares hand-derived gradients with
central finite differences, then exposes saturation, depth and reparameterisation
failure cases.
"""
from __future__ import annotations
from dataclasses import dataclass, replace
import math
from typing import Dict, Tuple
@dataclass(frozen=True)
class Params:
w1: float = 0.8
b1: float = -0.1
w2: float = 1.2
b2: float = 0.05
def forward(p: Params, x: float, target: float) -> Dict[str, float]:
"""Two-layer scalar network: affine -> tanh -> affine -> squared loss."""
a = p.w1 * x + p.b1
h = math.tanh(a)
y = p.w2 * h + p.b2
loss = 0.5 * (y - target) ** 2
return {"a": a, "h": h, "y": y, "loss": loss}
def manual_gradient(p: Params, x: float, target: float) -> Dict[str, float]:
"""Reverse pass written explicitly from local derivatives."""
cache = forward(p, x, target)
dloss_dy = cache["y"] - target
dloss_dw2 = dloss_dy * cache["h"]
dloss_db2 = dloss_dy
dloss_dh = dloss_dy * p.w2
dloss_da = dloss_dh * (1.0 - cache["h"] ** 2)
dloss_dw1 = dloss_da * x
dloss_db1 = dloss_da
return {
"w1": dloss_dw1,
"b1": dloss_db1,
"w2": dloss_dw2,
"b2": dloss_db2,
}
def central_difference(
p: Params, x: float, target: float, name: str, eps: float = 1e-6
) -> float:
"""Black-box local check. It is approximate and sensitive to eps."""
plus = replace(p, **{name: getattr(p, name) + eps})
minus = replace(p, **{name: getattr(p, name) - eps})
return (forward(plus, x, target)["loss"] - forward(minus, x, target)["loss"]) / (
2.0 * eps
)
def bounded_effect(
p: Params, x: float, target: float, name: str, delta: float
) -> Tuple[float, float]:
"""Return actual loss change and first-order predicted loss change."""
changed = replace(p, **{name: getattr(p, name) + delta})
actual = forward(changed, x, target)["loss"] - forward(p, x, target)["loss"]
predicted = manual_gradient(p, x, target)[name] * delta
return actual, predicted
def sigmoid(z: float) -> float:
if z >= 0:
e = math.exp(-z)
return 1.0 / (1.0 + e)
e = math.exp(z)
return e / (1.0 + e)
def saturation_case(z: float = 8.0, target: float = 0.5) -> Dict[str, float]:
p = sigmoid(z)
loss = 0.5 * (p - target) ** 2
local_gradient = (p - target) * p * (1.0 - p)
reset_p = sigmoid(0.0)
reset_loss = 0.5 * (reset_p - target) ** 2
return {
"probability": p,
"loss": loss,
"local_gradient": local_gradient,
"loss_change_if_reset_to_zero": reset_loss - loss,
}
def relu_pair(w: float, v: float, x: float, target: float) -> Dict[str, float]:
pre = w * x
h = max(0.0, pre)
y = v * h
loss = 0.5 * (y - target) ** 2
active = 1.0 if pre > 0.0 else 0.0
return {
"y": y,
"loss": loss,
"dw": (y - target) * v * active * x,
"dv": (y - target) * h,
}
def reparameterisation_case(c: float = 10.0) -> Dict[str, Dict[str, float]]:
"""Positive rescaling preserves y = v ReLU(w x) but changes raw gradients."""
original = relu_pair(w=2.0, v=3.0, x=1.0, target=5.0)
rescaled = relu_pair(w=2.0 * c, v=3.0 / c, x=1.0, target=5.0)
return {"original": original, "rescaled": rescaled}
def depth_case(gain: float, depth: int) -> float:
"""Sensitivity through a scalar chain h_t = gain * h_(t-1)."""
if depth < 0:
raise ValueError("depth must be non-negative")
return gain**depth
def run_audit() -> None:
p = Params()
x, target = 1.7, 0.4
analytic = manual_gradient(p, x, target)
print("Manual gradient versus central finite difference")
for name, value in analytic.items():
numeric = central_difference(p, x, target, name)
scale = max(1.0, abs(value), abs(numeric))
rel_error = abs(value - numeric) / scale
print(f" {name}: manual={value:+.8f} finite={numeric:+.8f} error={rel_error:.2e}")
print("\nBounded intervention on w1")
for delta in (-0.50, -0.05, -0.005):
actual, predicted = bounded_effect(p, x, target, "w1", delta)
print(f" delta={delta:+.3f} actual={actual:+.8f} linear prediction={predicted:+.8f}")
print("\nSaturation case")
for key, value in saturation_case().items():
print(f" {key}: {value:+.8f}")
print("\nFunction-preserving ReLU rescaling")
twins = reparameterisation_case()
for name, values in twins.items():
print(f" {name}: y={values['y']:.2f}, dw={values['dw']:.2f}, dv={values['dv']:.2f}")
print("\nDepth sensitivity")
for gain in (0.9, 1.0, 1.1):
print(f" gain={gain:.1f}, depth=60, product={depth_case(gain, 60):.6g}")
if __name__ == "__main__":
run_audit()
How to run it
- Save the embedded program as
paper17_credit_assignment_lab.py. - Run it with:
python paper17_credit_assignment_lab.py - No package installation is required. The first output block should report tiny relative errors between the manual derivatives and central finite differences.
A positive gradient check permits confidence that the local derivative was implemented correctly at the tested point. Agreement between predicted and actual loss change for small steps permits using the gradient as a local proposal. Divergence at larger steps reveals curvature, saturation or path interactions. The rescaling output demonstrates why raw gradient magnitude is not stable credit.
It does not prove convergence to a global optimum, validate a real dataset, identify the true cause of an observed failure, or authorise a consequential action. It tests mathematical consistency and decision stability inside a synthetic model.
Optional depth: why central differences are a check, not a replacement
A central difference estimates ∂L/∂θ using [L(θ + ε) − L(θ − ε)] / 2ε. Large ε introduces truncation error because curvature matters. Tiny ε subtracts nearly equal floating-point numbers and amplifies rounding error. Directional checks scale better than checking every coordinate, but they still provide approximate evidence at selected points. Reverse-mode differentiation uses the program’s local derivative rules and is exact up to floating-point arithmetic for the executed smooth branch.
Optional depth: second-order information changes the question again
The gradient gives the first-order slope. The Hessian describes how that slope changes with direction. A Hessian-vector product can reveal curvature without materialising the full Hessian, following the method developed by Pearlmutter (1994). Curvature can improve step selection, but it remains conditional on the same loss, graph and coordinates. More derivatives do not automatically produce broader causal meaning.
sequenceDiagram participant Q as Question participant M as Mechanism participant E as Evidence participant D as Decision Q->>M: The error that does not travel backwards M->>E: An autodiff-free credit-assignment audit E-->>M: compare rival explanations E->>D: Use gradients to propose and interventions to authorise D-->>Q: revise scope or act
Use gradients to propose and interventions to authorise
When a model fails, do not ask which parameter or module is “to blame” and then sort by gradient magnitude. Ask what derivative was actually computed. Name the scalar loss, the data aggregation, the executed path, the coordinate system and the intervention scale.
Then separate the decisions. If you are debugging derivative code, a central finite-difference check may be enough. If you are choosing a small optimiser step, a verified local gradient may be enough. If you are deciding which module to redesign, freeze, replace or govern, demand finite intervention evidence, path tests and representation stability.
The chain rule remains one of the most powerful compression devices in machine intelligence. It converts one remote objective into local update information across a composite system. Its limitation is the mirror image of that power: it can only speak through the graph, loss and coordinates it has been given.
This creates a simple release discipline. Store gradients as decision receipts with the objective version, sample identifiers, aggregation rule, model state, graph boundaries and proposed step. For any change that is difficult to reverse, add the finite intervention result and at least one structural challenge. The receipt should state what passed, what failed and which inference remains prohibited. A naked heat map or component ranking is not a receipt.
The same discipline improves scientific claims. Saying that a parameter “matters” should specify whether it has a non-zero derivative, changes behaviour when intervened on, carries a unique path or retains its ranking across equivalent implementations. Those are different observations. Their separation makes negative results interpretable rather than embarrassing.
That decision discipline changes architecture as well as diagnosis. Preserve useful gradient routes with appropriate scaling and skip paths. Expose detachments and nondifferentiable boundaries. Report per-path or per-example cancellation where averages hide it. Compare changes in function space when parameter coordinates are arbitrary. Above all, keep optimisation credit distinct from causal responsibility.
Glossary
- Adjoint or cotangent
- The backward sensitivity of a scalar loss to an intermediate value, often written as a barred variable.
- Automatic differentiation
- Programmatic evaluation of derivatives by composing local derivative rules over executed operations.
- Backpropagation
- Reverse-mode differentiation applied to a learning objective and used to update model parameters.
- Credit assignment
- The problem of connecting a distant outcome to the internal states, actions or parameters that should change.
- Jacobian
- The local linear map from small input changes to small output changes for a vector-valued function.
- Pullback or VJP
- A vector-Jacobian product that maps output sensitivity backwards to input sensitivity.
- Saturation
- A region where an activation changes very little despite changes in its input, producing a small local derivative.
- Stop-gradient
- An operation that preserves a value in the forward pass while blocking its backward derivative path.
References
- Rumelhart, D. E., Hinton, G. E. and Williams, R. J. (1986). Learning representations by back-propagating errors. Nature 323, 533–536. Publisher page.
- Baydin, A. G., Pearlmutter, B. A., Radul, A. A. and Siskind, J. M. (2018). Automatic differentiation in machine learning: a survey. Journal of Machine Learning Research 18(153), 1–43. JMLR.
- Baur, W. and Strassen, V. (1983). The complexity of partial derivatives. Theoretical Computer Science 22, 317–330. DOI.
- JAX authors. The Autodiff Cookbook. Official JAX documentation. Documentation.
- Pascanu, R., Mikolov, T. and Bengio, Y. (2013). On the difficulty of training recurrent neural networks. Proceedings of the 30th International Conference on Machine Learning. PMLR.
- He, K., Zhang, X., Ren, S. and Sun, J. (2016). Deep residual learning for image recognition. CVPR. CVF Open Access.
- Balduzzi, D., Frean, M., Leary, L., Lewis, J. P., Ma, K. W. D. and McWilliams, B. (2017). The shattered gradients problem: if ResNets are the answer, then what is the question? ICML. PMLR.
- Neyshabur, B., Salakhutdinov, R. and Srebro, N. (2015). Path-SGD: path-normalized optimization in deep neural networks. NeurIPS. NeurIPS proceedings.
- Lillicrap, T. P., Cownden, D., Tweed, D. B. and Akerman, C. J. (2016). Random synaptic feedback weights support error backpropagation for deep learning. Nature Communications 7, 13276. Publisher page.
- Nøkland, A. (2016). Direct feedback alignment provides learning in deep neural networks. NeurIPS. NeurIPS proceedings.
- Sundararajan, M., Taly, A. and Yan, Q. (2017). Axiomatic attribution for deep networks. ICML. PMLR.
- Adebayo, J., Gilmer, J., Muelly, M., Goodfellow, I., Hardt, M. and Kim, B. (2018). Sanity checks for saliency maps. NeurIPS. NeurIPS proceedings.
- Pearlmutter, B. A. (1994). Fast exact multiplication by the Hessian. Neural Computation 6(1), 147–160. MIT Press.