W04: Geometry, gradients, and automatic differentiation
On this page
- Prerequisites and observable goals
- The central question: how does a composed computation change?
- One residual connects a prediction to its gradient
- Three methods with different meanings
- Deriving forward mode with a pair of numbers
- Why reverse mode is attractive for one scalar loss
- Verification and nondifferentiable boundaries
- Independent practice
- Complete solutions: W04
- Retention and readiness
Prerequisites and observable goals#
M10, M36–M37, A01–A02, and basic programming: chain rule, gradients, matrices, and linear transformations.
Derive gradients with correct shapes; implement forward-mode differentiation from an algebraic rule; distinguish exact symbolic structure, floating-point evaluation, finite differences, and automatic differentiation.
The central question: how does a composed computation change?#
For a scalar function , the derivative is a scalar local slope. For , the gradient is a vector of partial derivatives. For , the Jacobian is an matrix whose entry is . These are related objects, not interchangeable notations. The linear approximation is
An -component input perturbation is transformed into an -component output perturbation. Check this shape before manipulating a formula. A transpose placed casually can make a matrix product impossible or compute a different map.
The gradient also has a geometric meaning: the differential of a scalar function is under the usual Euclidean inner product. Changing coordinates or physical units changes the components. A gradient in pixels is not numerically the same as a gradient in meters, even for the same modeled phenomenon. Optimization must account for representation and scaling.
One residual connects a prediction to its gradient#
Let , target , residual , and loss . Write the execution graph as named intermediate objects:
The local derivatives are , , and , , . Multiplying along each dependency path gives
At , prediction is 5, residual 2, loss 2, and gradients with respect to are . The gradient with respect to the input is not an instruction to train the input; it is a sensitivity that may be useful for analysis. Which variables an optimizer updates is an implementation and modeling choice.
For a batch with design matrix and parameter vector , the analogous objective is . Its gradient is . The residual is a vector in observation space; multiplying by maps its correlations with the columns back into parameter space. At a least-squares optimum, the residual is orthogonal to the column space, not necessarily zero. The geometry explains the formula instead of merely demanding that it be memorized.
Three methods with different meanings#
Symbolic differentiation manipulates expressions into derivative expressions, such as converting into . It may simplify and reason about a formula, with domains still needing attention.
Finite differences compare nearby evaluated outputs. A central difference is an approximation whose truncation and roundoff depend on step size, smoothness, and precision. A tiny step can subtract nearly equal rounded values and be worse than a moderate one.
Automatic differentiation (AD) augments the computation with the chain rule for each executed operation. It does not choose a finite-difference step or return a universal symbolic formula. With floating-point operations, its derivative evaluations still have roundoff; with branches it follows an execution path and requires care at discontinuities or nondifferentiable points. “Exact derivative up to floating-point error” refers to the modeled smooth arithmetic expression evaluated by the rules, not a proof that every arbitrary program defines a differentiable real function.
Deriving forward mode with a pair of numbers#
Represent a value and a directional derivative by , where the formal symbol satisfies . This algebraic rule is not the assertion that a nonzero real number has square zero. It packages first-order information:
The vanished term gives the product rule. For , division has tangent . A smooth unary function gives
in this first-order algebra. For example, sine propagates ; exponential propagates ; logarithm requires and propagates .
Seed input with tangent 1 to get its derivative. For , computing gives pair ; then sine gives . Seed a vector with tangent direction to obtain the Jacobian-vector product , without constructing all entries of . Recovering every partial separately by forward mode generally requires multiple seed directions.
Why reverse mode is attractive for one scalar loss#
When there are many parameters and one scalar loss, a reverse pass accumulates each intermediate’s influence on the loss. Start with derivative of the loss with respect to itself equal to 1. Each operation sends its incoming sensitivity backward, multiplied by its local derivative; contributions along multiple paths add. The residual example did exactly this. Reverse mode can compute all parameter sensitivities with work proportional to the executed graph’s size, but usually needs stored or recomputed forward intermediates. G13 develops an explicit neural-network backward pass; it is not a call to an opaque framework substituted for understanding.
Verification and nondifferentiable boundaries#
Check a new AD rule against an independently derived analytic derivative and a range of finite-difference steps away from singularities. Agreement at a single symmetric input may conceal a missing term. For , no derivative exists at zero even if an implementation selects a branch and returns a number. For division and logarithm, maintain the domain restrictions. For iterative solvers, differentiating a finite executed iteration count may differ from differentiating the exact solution of the mathematical fixed-point equation. Name the differentiated object.
Independent practice#
F foundation C standard course work T transfer S optional stretch L runnable lab or project. Attempt the complete set before opening its solutions.
Let . Derive its Jacobian at , apply it to direction , and compare the linear prediction with the exact change. Explain the size and origin of the discrepancy.
For with fixed at , compute prediction, residual, loss, both gradients, and one simultaneous gradient step of size . Check that the loss changes in the expected direction.
Derive the dual-number tangent for from elementary operations. Explain the seed, the propagated intermediates, and why evaluating it with floating-point arithmetic is not symbolic proof or a finite-difference quotient.
For with real and , derive gradient and Hessian and prove uniqueness of the minimizer. Explain what changes at when columns are dependent.
Implement the Dual class and sine, exponential, and logarithm rules in integration_code/starters/dual.py. Support scalar arithmetic and reversed operations. Reject invalid/nonfinite values and invalid real logarithm/division inputs. Test product, quotient, and composed derivatives analytically; document branch and precision limitations.
Optional small hints
For .01, multiply the Jacobian by a column direction. For .02, cache the old residual before either parameter update. For .04, examine for nonzero .
Complete solutions: W04#
Compare the first step where your work differs. A corrected answer is not yet an independent success.
W04.01Show solution
The Jacobian maps the input displacement
The Jacobian is , hence . Multiplication by gives . The old output is ; the exact new output at is , so exact change is . The missed terms are and . They are second-order terms excluded by the linearization, not an arithmetic error in the Jacobian. Shrinking both displacements by the same factor scales these residual terms quadratically.
Back to W04.01W04.02Show solution
A shared forward state feeds both updates
Prediction is , residual 1, loss . Gradients are . The simultaneous step gives . New prediction is , residual zero, loss zero. That exact result depends on this one example and chosen step; it is not a general one-step convergence rule. Updating and recomputing a different residual for would implement a different update schedule.
Back to W04.02W04.03Show solution
The chain rule is carried with each value
Seed . Squaring yields . Applying sine yields . Applying exponential yields tangent
The expression is the analytic derivative of the composition on the real line. The AD implementation evaluates this rule numerically through intermediate pairs; it does not perform algebraic simplification or compare two neighboring input values. An implementation bug in a local rule can still propagate a wrong derivative, and floating-point evaluation still rounds. Check several nonsymmetric inputs and compare independently derived values.
Back to W04.03W04.04Show solution
Regularization changes curvature in every direction
The gradient is and Hessian . For any nonzero ,
Thus is positive definite, the quadratic is strictly convex and coercive, and the unique minimizer solves . Positive definiteness also makes that system invertible. At zero regularization with dependent columns, there exists nonzero with ; adding a multiple of does not change predictions or objective, so minimizers are not unique. Ridge changes the optimization problem; it is not merely a numerically clever way to solve the identical unregularized problem.
Back to W04.04W04.05Show solution
Reference and independent derivative checks
The pair class keeps primal and tangent values explicit and propagates the local rules. Reversed subtraction/division are separate cases: swapping operands is not harmless for noncommutative operations. Real logarithm rejects nonpositive inputs. Nonfinite results are rejected rather than silently advertised as useful derivatives.
"""W04: forward-mode automatic differentiation; each operation carries its tangent."""
from dataclasses import dataclass
import math
@dataclass(frozen=True)
class Dual:
value: float
tangent: float=0.
def __post_init__(self):
if not all(type(x) in (int,float) and math.isfinite(x)
for x in (self.value,self.tangent)):
raise ValueError("finite real value and tangent required")
@staticmethod
def wrap(other):
return other if isinstance(other,Dual) else Dual(other)
def __add__(self,other):
other=self.wrap(other)
return Dual(self.value+other.value,self.tangent+other.tangent)
__radd__=__add__
def __neg__(self):return Dual(-self.value,-self.tangent)
def __sub__(self,other):return self+(-self.wrap(other))
def __rsub__(self,other):return self.wrap(other)+(-self)
def __mul__(self,other):
other=self.wrap(other)
return Dual(self.value*other.value,self.tangent*other.value+self.value*other.tangent)
__rmul__=__mul__
def __truediv__(self,other):
other=self.wrap(other)
if other.value==0:raise ZeroDivisionError("zero primal denominator")
value=self.value/other.value
tangent=(self.tangent-value*other.tangent)/other.value
return Dual(value,tangent)
def __rtruediv__(self,other):return self.wrap(other)/self
def sin(x):
x=Dual.wrap(x)
return Dual(math.sin(x.value),math.cos(x.value)*x.tangent)
def exp(x):
x=Dual.wrap(x);value=math.exp(x.value)
return Dual(value,value*x.tangent)
def log(x):
x=Dual.wrap(x)
if x.value<=0:raise ValueError("log requires positive primal input")
return Dual(math.log(x.value),x.tangent/x.value)
if __name__=='__main__':
w=Dual(2,1);x=2;b=1;y=3
loss=(w*x+b-y)*(w*x+b-y)/2
print(loss) # value 2; derivative with respect to w is 4The tests compare the composed derivative above, known polynomial cases, and invalid domains. They test a directional derivative, not every entry of every possible Jacobian. To extend the class, derive a local rule, specify its domain, implement it, and add both analytic and boundary checks. Handling nondifferentiable functions requires an explicit policy and cannot establish a derivative where none exists.
Back to W04.05Retention and readiness#
State the input/output dimensions before writing a derivative. Explain how a residual in observation space becomes a gradient in parameter space. Compare forward and reverse mode using the number of inputs, outputs, and stored intermediates rather than calling one universally superior.