| Safe Haskell | None |
|---|---|
| Language | GHC2024 |
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
- newtype GradInputs a = GradInputs {
- unGradInputs :: Array a
- variable :: Additive a => Int -> Int -> Diff' (GradInputs a) a
- variables :: Additive a => [a] -> [Diff' (GradInputs a) a]
- data DiffProcess s a b = DiffProcess {}
- data StdState a = StdState {}
- diffFold :: (Additive s, Additive b) => DiffProcess s a b -> [a] -> (b, b -> [a])
- diffScan :: Additive s => DiffProcess s a b -> [a] -> ([b], [b] -> [a])
- data DiffSystem s i o = DiffSystem {}
- diffSystemAsDiffProcess :: DiffSystem s i o -> s -> DiffProcess s o i
- diffProcessAsDiffSystem :: DiffProcess s a b -> DiffSystem s b a
- diffSystemScan :: Additive s => DiffSystem s i o -> s -> [o] -> ([i], [i] -> [o])
- diffSystemFold :: (Additive s, Additive i) => DiffSystem s i o -> s -> [o] -> (i, i -> [o])
- gradFold :: Additive a => Process (Diff' (GradInputs a) a) (Diff' (GradInputs a) b) -> [a] -> (b, b -> [a])
- gradScan :: Additive a => Process (Diff' (GradInputs a) a) (Diff' (GradInputs a) b) -> [a] -> ([b], [b] -> [a])
- constant :: forall {k} p a (tag :: k). Additive p => a -> Diff tag p a
- netFold :: forall {k} p (tag :: k) a b. Additive p => Process (Diff tag p a) (Diff tag p b) -> p -> [a] -> (b, b -> p)
- netScan :: forall {k} p (tag :: k) a b. Additive p => Process (Diff tag p a) (Diff tag p b) -> p -> [a] -> ([b], [b] -> p)
- 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)
- 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)
- 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)
- stdDiff :: forall {k} p b (tag :: k). (Subtractive p, ExpField b) => Diff tag p b -> Process (Diff tag p b) (Diff tag p b)
- onlineDiffProcess :: (Subtractive b, Divisive b) => Diff' a b -> Diff' b b -> DiffProcess (Averager b b) a b
- maDiffProcess :: (Subtractive b, Divisive b) => b -> DiffProcess (Averager b b) b b
- sqmaDiffProcess :: (Subtractive b, Divisive b) => b -> DiffProcess (Averager b b) b b
- stdDiffProcess :: (Eq b, ExpField b) => b -> DiffProcess (StdState b) b b
- delay1Diff :: Additive a => a -> DiffProcess (DelayState a) a a
- diffDiff :: (Additive a, Additive b) => a -> Diff' (a, a) b -> DiffProcess (DiffState a b) a b
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
| Eq a => Eq (GradInputs a) Source # | |
Defined in Circuit.Stats.Diff | |
| Show a => Show (GradInputs a) Source # | |
Defined in Circuit.Stats.Diff Methods showsPrec :: Int -> GradInputs a -> ShowS # show :: GradInputs a -> String # showList :: [GradInputs a] -> ShowS # | |
| Additive a => Additive (GradInputs a) Source # | |
Defined in Circuit.Stats.Diff | |
| Subtractive a => Subtractive (GradInputs a) Source # | |
Defined in Circuit.Stats.Diff Methods negate :: GradInputs a -> GradInputs a # (-) :: GradInputs a -> GradInputs a -> GradInputs a # | |
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).
State for a differentiable standard deviation: a moving average together with a squared moving average.
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 | |
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 #
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])