circuits-stats
Safe HaskellNone
LanguageGHC2024

Circuit.Stats.Diff

Description

Reverse-mode AD through a Process scan.

The idea is to run the mealy with Diff as the carrier. Each input element becomes a variable over the whole input list, so the final output (or every scan output) carries a pullback wrt every input.

The high-level gradient runners gradScan and gradFold are the canonical entry points. They already use Diff directly, which is the same primitive arrow that circuits-diff builds on. The lower-level DiffProcess / DiffSystem reverse-step machinery is kept because its explicit-state API (capture states and step pullbacks during the forward pass, then replay them backward) does not have a direct, API-preserving translation to circuits-diff's linearizeAt / backprop. Where a direct circuits-diff replacement is possible in the future, the oracles in test/Oracle.hs provide regression guards.

Synopsis

Gradient inputs

newtype GradInputs a Source #

A length-indexed array used as the AD parameter for a scan.

The array stores one cotangent slot per input element. Addition and subtraction are pointwise over the underlying Array.

Constructors

GradInputs 

Fields

Instances

Instances details
Eq a => Eq (GradInputs a) Source # 
Instance details

Defined in Circuit.Stats.Diff

Methods

(==) :: GradInputs a -> GradInputs a -> Bool #

(/=) :: GradInputs a -> GradInputs a -> Bool #

Show a => Show (GradInputs a) Source # 
Instance details

Defined in Circuit.Stats.Diff

Additive a => Additive (GradInputs a) Source # 
Instance details

Defined in Circuit.Stats.Diff

Methods

(+) :: GradInputs a -> GradInputs a -> GradInputs a #

zero :: GradInputs a #

Subtractive a => Subtractive (GradInputs a) Source # 
Instance details

Defined in Circuit.Stats.Diff

variable :: Additive a => Int -> Int -> Diff' (GradInputs a) a Source #

A single input variable: the i-th element of an n-element input.

variables :: Additive a => [a] -> [Diff' (GradInputs a) a] Source #

Turn a list of inputs into a list of differentiable variables.

Each variable selects one array element in the forward pass and scatters a cotangent back to that same position in the backward pass.

Reverse-step differentiable Process

data DiffProcess s a b Source #

A Process machine with explicit state and differentiable inject step extract functions.

The pullbacks are captured during the forward pass and then replayed by a single backward walk, so the cost of a scan gradient is linear in the input length (no closure-chain blow-up).

Constructors

DiffProcess 

Fields

data StdState a Source #

State for a differentiable standard deviation: a moving average together with a squared moving average.

Constructors

StdState 

Fields

Instances

Instances details
Eq a => Eq (StdState a) Source # 
Instance details

Defined in Circuit.Stats.Diff

Methods

(==) :: StdState a -> StdState a -> Bool #

(/=) :: StdState a -> StdState a -> Bool #

Show a => Show (StdState a) Source # 
Instance details

Defined in Circuit.Stats.Diff

Methods

showsPrec :: Int -> StdState a -> ShowS #

show :: StdState a -> String #

showList :: [StdState a] -> ShowS #

Additive a => Additive (StdState a) Source # 
Instance details

Defined in Circuit.Stats.Diff

Methods

(+) :: StdState a -> StdState a -> StdState a #

zero :: StdState a #

Subtractive a => Subtractive (StdState a) Source # 
Instance details

Defined in Circuit.Stats.Diff

Methods

negate :: StdState a -> StdState a #

(-) :: StdState a -> StdState a -> StdState a #

diffFold :: (Additive s, Additive b) => DiffProcess s a b -> [a] -> (b, b -> [a]) Source #

Fold a list through a DiffProcess and return the final output together with a pullback through the entire fold.

>>> let (y, g) = diffFold (maDiffProcess 0) [1,2,3] in (y, g 1)
(3.0,[0.0,0.0,1.0])

diffScan :: Additive s => DiffProcess s a b -> [a] -> ([b], [b] -> [a]) Source #

Scan a list through a DiffProcess and return the per-step outputs together with a pullback that maps output cotangents to input cotangents.

>>> let (ys, g) = diffScan (maDiffProcess 0) [1,2,3] in (ys, g [1,1,1])
([1.0,2.0,3.0],[1.0,1.0,1.0])

Reverse-step differentiable System (no inject; state external)

data DiffSystem s i o Source #

A System-packaged differentiable Moore machine: state is external.

Same dynamics as DiffProcess, without inject. Matches System s (Mono i o) packaging (i = output, o = input):

dsExtract :: Diff' s i     -- output from state
dsStep    :: Diff' (s, o) s  -- next state from state and input

Pair with an initial state via diffSystemAsDiffProcess to reuse diffScan / diffFold. Agents that are Systems sit here for Diff without pretending to be Process first.

Constructors

DiffSystem 

Fields

diffSystemAsDiffProcess :: DiffSystem s i o -> s -> DiffProcess s o i Source #

Bake in s0: inject is one step from that state (same as systemAsProcess).

diffSystemAsDiffProcess sys s0 :: DiffProcess s o i

diffProcessAsDiffSystem :: DiffProcess s a b -> DiffSystem s b a Source #

Drop inject: keep extract and step. Initial state must be supplied again when scanning (via diffSystemAsDiffProcess or diffSystemScan).

diffSystemScan :: Additive s => DiffSystem s i o -> s -> [o] -> ([i], [i] -> [o]) Source #

Scan a DiffSystem from external s0 (via diffSystemAsDiffProcess).

>>> let sys = diffProcessAsDiffSystem (maDiffProcess 0)
>>> let (ys, g) = diffSystemScan sys (PS.A 0 0) [1,2,3] in (ys, g [1,1,1])
([1.0,2.0,3.0],[1.0,1.0,1.0])

diffSystemFold :: (Additive s, Additive i) => DiffSystem s i o -> s -> [o] -> (i, i -> [o]) Source #

Fold a DiffSystem from external s0.

Gradient-aware runners

gradFold :: Additive a => Process (Diff' (GradInputs a) a) (Diff' (GradInputs a) b) -> [a] -> (b, b -> [a]) Source #

Fold a list through a differentiable mealy and return the final output together with a pullback through the entire fold.

>>> let m = PS.asum :: PS.Process (Diff' (GradInputs Double) Double) (Diff' (GradInputs Double) Double); (y, g) = gradFold m [1,2,3] in (y, g 1)
(6.0,[1.0,1.0,1.0])

gradScan :: Additive a => Process (Diff' (GradInputs a) a) (Diff' (GradInputs a) b) -> [a] -> ([b], [b] -> [a]) Source #

Scan a list through a differentiable mealy and return the per-step outputs together with a pullback that maps a list of output cotangents (one per step) to input cotangents.

>>> let m = PS.asum :: PS.Process (Diff' (GradInputs Double) Double) (Diff' (GradInputs Double) Double) in snd (gradScan m [1,2,3]) [1,1,1]
[3.0,2.0,1.0]

Net Process runners

constant :: forall {k} p a (tag :: k). Additive p => a -> Diff tag p a Source #

Lift a plain input value into the parameter space of a network mealy.

The value is treated as a constant: its pullback is always zero.

netFold :: forall {k} p (tag :: k) a b. Additive p => Process (Diff tag p a) (Diff tag p b) -> p -> [a] -> (b, b -> p) Source #

Fold a network mealy and return the final output together with a pullback through the parameters.

netScan :: forall {k} p (tag :: k) a b. Additive p => Process (Diff tag p a) (Diff tag p b) -> p -> [a] -> ([b], [b] -> p) Source #

Scan a network mealy and return the per-step outputs together with a pullback that maps output cotangents to parameter cotangents.

Differentiable online statistics

onlineDiff :: forall {k} p b (tag :: k) a. (Additive p, Subtractive b, Divisive b) => (Diff tag p a -> Diff tag p b) -> (Diff tag p b -> Diff tag p b) -> Process (Diff tag p a) (Diff tag p b) Source #

online with a Diff carrier.

The inject and step functions now operate on differentiable values, so the resulting mealy can be placed inside netFold / netScan and the parameters inside the carrier are learned by ordinary reverse-mode AD.

maDiff :: forall {k} p b (tag :: k). (Additive p, Subtractive b, Divisive b) => Diff tag p b -> Process (Diff tag p b) (Diff tag p b) Source #

Differentiable moving average with a learnable decay parameter.

sqmaDiff :: forall {k} p b (tag :: k). (Additive p, Subtractive b, Divisive b) => Diff tag p b -> Process (Diff tag p b) (Diff tag p b) Source #

Differentiable squared moving average with a learnable decay parameter.

stdDiff :: forall {k} p b (tag :: k). (Subtractive p, ExpField b) => Diff tag p b -> Process (Diff tag p b) (Diff tag p b) Source #

Differentiable standard deviation with a learnable decay parameter.

Reverse-step online statistics

onlineDiffProcess :: (Subtractive b, Divisive b) => Diff' a b -> Diff' b b -> DiffProcess (Averager b b) a b Source #

online as a reverse-step DiffProcess.

maDiffProcess :: (Subtractive b, Divisive b) => b -> DiffProcess (Averager b b) b b Source #

Differentiable moving average as a reverse-step DiffProcess.

sqmaDiffProcess :: (Subtractive b, Divisive b) => b -> DiffProcess (Averager b b) b b Source #

Differentiable squared moving average as a reverse-step DiffProcess.

>>> let (ys, g) = diffScan (sqmaDiffProcess 0) [1,2,3] in (ys, g [1,1,1])
([1.0,4.0,9.0],[2.0,4.0,6.0])

stdDiffProcess :: (Eq b, ExpField b) => b -> DiffProcess (StdState b) b b Source #

Differentiable standard deviation as a reverse-step DiffProcess.

The gradient is taken to be zero when the standard deviation is itself zero (for example, after a single sample), because the usual sqrt derivative is undefined at zero.

>>> let (ys, g) = diffScan (stdDiffProcess 0) [1,2,3] in (ys, g [1,1,1])
([0.0,0.0,0.0],[0.0,0.0,0.0])

Reverse-step delay / diff

delay1Diff :: Additive a => a -> DiffProcess (DelayState a) a a Source #

A one-step delay as a reverse-step DiffProcess.

The initial output is a0; after that the machine emits the previous input.

>>> let (ys, g) = diffScan (delay1Diff 0) [1,2,3] in (ys, g [1,1,1])
([0,1,2],[1,1,0])

diffDiff :: (Additive a, Additive b) => a -> Diff' (a, a) b -> DiffProcess (DiffState a b) a b Source #

diff as a reverse-step DiffProcess.

The first output uses the supplied initial previous value a0 instead of undefined, which makes the machine differentiable. This is exactly the log-return pattern used in Anal.Returns.ret.

>>> let f = Diff $ \(p, p') -> (log (p' / p), \dy -> (negate (dy / p), dy / p'))
>>> let (ys, g) = diffScan (diffDiff 1 f) [1,2,4]
>>> (ys, g [1,1,1])
([0.0,0.6931471805599453,0.6931471805599453],[0.0,0.0,0.25])