pyzag.curvature
Gauss-Newton curvature extraction, shared by everything that needs H.
H = J^T W J for a residual r(theta), formed without ever materializing
the dense Jacobian: each sampled row costs one reverse-mode sweep. Two consumers
build on this, and they want quite different things from it:
pyzag.preconditioning.GaussNewtonCurvature– caching, staleness and Levenberg-Marquardt damping, to precondition a training loop step by step;pyzag.preconditioning.gauss_newton_rescalers()– a single estimate, to build a static reparametrization.
Keeping the extraction here means one implementation of the row sampling, the cotangent grouping and the packing, rather than one per consumer.
- class pyzag.curvature.CurvatureEstimator(parameters, *, mode='diag', nsub=None, sample_indices=None, cotangents=None, weights=None, generator=None, sampling='random')
Form the Gauss-Newton curvature
H = J^T W Jof a residual.The estimation half of Gauss-Newton, with no notion of an optimizer, a training loop, or a cached value that might go stale. It packs a parameter list into a flat vector, takes reverse-mode products against a residual, and turns a chosen set of rows into
H. Two rather different consumers need exactly that and nothing more:GaussNewtonCurvatureadds caching, staleness and damping, to precondition a training loop step by step;gauss_newton_rescalers()callsestimate()exactly once, to build a static reparametrization.
- Parameters:
parameters (iterable of Tensor) – leaf tensors with
requires_grad=True. Their shapes and order are frozen here.- Keyword Arguments:
mode (str) –
"diag"(default) or"full".nsub (int or None) – residual rows to subsample, one reverse sweep each.
None(default) uses every row, which is exact. Subsampling is an opt-in trade: seeestimate()for what it costs you. Ignored ifsample_indicesorcotangentsis given.sample_indices (array-like of int, optional) – explicit rows to sample.
cotangents (callable or sequence, optional) – custom cotangents; see
estimate()for the precondition they carry.weights (Tensor, optional) – diagonal of
W, broadcastable to the residual shape.NonemeansW = I.generator (torch.Generator, optional) – RNG for reproducible subsampling.
sampling (str) –
"random"(default) draws uniformly without replacement;"stratified"draws one row from each ofnsubequal blocks of the flattened residual.Measured on the NEML2 calibration the two are equivalent: both give a usable scale above
nsub ~ 32and both fail below it, because a parameter whose sensitivity is concentrated in a small part of the residual can be missed either way. Stratified is offered because it cannot alias against a(ntime, nbatch)layout the way an explicit stride can, but it is not a substitute for a large enoughnsub.Watch the scale, not H.
1 / sqrt(H)amplifies a near-zero entry without bound, so||H - H_exact||badly understates the damage: atnsub=8that norm is 17% off while the scale it implies is wrong by twelve orders of magnitude.
- estimate(residual)
Return
(H, nsweeps), sampling rows as configured.With a custom
cotangentssequence each cotangent costs one sweep, but the vector it returns is the sum of the rows it selects. That sum is the correct contribution only when those rows have disjoint parameter support – otherwise the estimate picks up cross terms. Valid for block-diagonal Jacobians (one cotangent per time step across apyro.plateof independent specimens); invalid for parameters shared across the grouped rows, which must be handled separately.Custom cotangents also carry their own weighting: put
sqrt(w_k)in entrykrather than 1, since(sum_k sqrt(w_k) J_k)^2equalssum_k w_k J_k^2when the supports are disjoint. They are used as given, with non / nsampledextrapolation – a custom grouping describes its own coverage.
- property nparam
Total number of scalar parameters
p.
- pack(grads)
Flatten a per-parameter gradient tuple to a length-
pvector, substituting zeros forNone(theallow_unusedcase).
- pack_grads()
Flatten the parameters’ current
.gradto a length-pvector.
- theta()
Flat, detached copy of the current parameter values.
- vjp(residual, cotangent)
One reverse-mode sweep: J^T cotangent, packed to a length-p vector.
- weights_flat(residual)
The diagonal of
W, flattened to match the flattened residual.
- write_grads(vec)
Overwrite every parameter’s
.gradwith the matching slice ofvec. Any previously accumulated gradient is discarded.