Manipulating BVars
The most important aspect of the usability of this library is allowing you to
seamlessly manipulate BVar s a
s as if they were just a
s, without requiring
you as the user to be able to recognize or acknowledge the difference. Here
are some techniques to that end.
Remember, a BVar s a
is a BVar
containing an a
--- it's an a
that, when
used, keeps track of and propagates your gradient.
Typeclass Interface
BVar
s have Num
, Fractional
, Floating
, Eq
, and Ord
instances. These
instances are basically "lifted" to the BVar
itself, so if you have a BVar s
Double
, you can use (*)
, sqrt
, (>)
, etc. on it exactly as if it were
just a Double
.
Constant Values
If we don't care about a value's gradient, we can use auto
:
auto :: a -> BVar s a
auto x
basically gives you a BVar
that contains just x
alone. Useful for
using with functions that expect BVar
s, but you just have a specific value
you want to use.
Coercible
If a
and b
are Coercible
, then so are BVar s a
and BVar s b
, using
the coerceVar
function. This is useful for "unwrapping" and "wrapping"
BVar
s of newtypes:
newtype MyInt = MyInt Int
getMyInt :: BVar s MyInt -> BVar s Int
getMyInt = coerceVar
Accessing Contents
The following techniques can be used to access values inside BVar
s:
Traversable Containers
One that we saw earlier was sequenceVar
, which we used to turn a BVar
containing a list into a list of BVar
s:
sequenceVar :: (Backprop a, Reifies s W)
=> BVar s [a]
-> [BVar s a]
If you have a BVar
containing a list, you can get a list of BVar
s of all of
that list's elements. (sequenceVar
actually works on all Traversable
instances, not just lists) This is very useful when combined with
-XViewPatterns
, as seen earlier.
Records and Fields
In practice, a lot of usage involves functions involving contents of records or data types containing fields. The previous example, involving a simple ANN, demonstrates this:
data Net = N { _nWeights1 :: L 20 100
, _nBias1 :: R 20
, _nWeights2 :: L 5 20
, _nBias2 :: R 5
}
deriving (Show, Generic)
instance Backprop Net -- can be automatically defined
To compute the result of this network (ran on an R 100
, a 100-vector) and get
the output R 5
, we need do a matrix multiplication by the _nWeights1
field,
add the result to the _nBias1
field...basically, the result is a function of
linear algebra and related operations on the input and all of the contents of
the Net
data type. However, you can't directly use _nWeights
, since it
takes a Net
, not BVar s Net
. And you also can't directly pattern match on
the N
constructor.
There are two main options for this: the lens interface, and the higher-kinded data interface.
Lens Interface
The most straightforward way to do this is the lens-based interface, using
viewVar
or ^^.
.
If we make lenses for Net
using the lens or microlens-th packages:
-- requires -XTemplateHaskell
makeLenses ''Net
or make them manually:
nBias1' :: Functor f => (R 20 -> f (R 20)) -> Net -> f Net
nBias1' f n = (\b -> n { _nBias1 = b }) <$> f (_nBias1 n)
then ^.
from the lens or microlens packages lets you retrieve a field
from a Net
:
(^. nWeights1) :: Net -> L 20 100
(^. nBias1 ) :: Net -> R 20
(^. nWeights2) :: Net -> L 5 20
(^. nBias2 ) :: Net -> R 5
And, ^^.
from backprop (also aliased as viewVar
) lets you do the same
thing from a BVar s Net
(a BVar
containing your Net
):
(^^. nWeights1) :: BVar s Net -> BVar s (L 20 100)
(^^. nBias1 ) :: BVar s Net -> BVar s (R 20)
(^^. nWeights2) :: BVar s Net -> BVar s (L 5 20)
(^^. nBias2 ) :: BVar s Net -> BVar s (R 5)
With our lenses and ^^.
, we can write our network running function. This
time, I'll include the type!
runNet :: Reifies s W
=> BVar s Net
-> BVar s (R 100)
-> BVar s (R 5)
runNet net x = z
where
-- run first layer
y = logistic $ (net ^^. nWeights1) #> x + (net ^^. nBias1)
-- run second layer
z = logistic $ (net ^^. nWeights2) #> y + (net ^^. nBias2)
logistic :: Floating a => a -> a
logistic x = 1 / (1 + exp (-x))
Note that we are using versions of #>
lifted for BVar
s, from the
hmatrix-backprop library:
(#>) :: BVar s (L m n) -> BVar s (R n) -> BVar s (R m)
Higher-Kinded Data Interface
Using the lens based interface, you can't directly pattern match and construct fields. To allow for directly pattern matching, there's another interface option involving the "Higher-Kinded Data" techniques described in this article.
If we had a type-family (that can be re-used for all of your data types):
type family HKD f a where
HKD Identity a = a
HKD f a = f a
We can define Net
instead as:
data Met' f = M { _mWeights1 :: HKD f (L 20 100)
, _mBias1 :: HKD f (R 20)
, _mWeights2 :: HKD f (L 5 20)
, _mBias2 :: HKD f (R 5)
}
deriving Generic
Then our original type is:
type Met = Met' Identity
deriving instance Show Met
instance Backprop Met
Met
is the same as Net
in every way -- it can be pattern matched on to get
the L 20 100
, etc. (the Identity
disappears):
getMetBias1 :: Met -> R 20
getMetBias1 (M _ b _ _) = b
The benefit of this is that we can now directly pattern match on a BVar s Met
to get the internal fields as BVar
s using splitBV
as a view pattern (or the
BV
pattern synonym):
runMet :: Reifies s W
=> BVar s Met
-> BVar s (R 100)
-> BVar s (R 5)
runMet (splitBV -> M w1 b1 w2 b2) x = z
where
-- run first layer
y = logistic $ w1 #> x + b1
-- run second layer
z = logistic $ w2 #> y + b2
runMetPS :: Reifies s W
=> BVar s Met
-> BVar s (R 100)
-> BVar s (R 5)
runMetPS (BV (M w1 b1 w2 b2)) x = z
where
-- run first layer
y = logistic $ w1 #> x + b1
-- run second layer
z = logistic $ w2 #> y + b2
Now, the M w1 b1 w2 b2
pattern can be used to deconstruct both "normal"
Met
s, as well as a BVar s Met
(with splitBV
or BV
).
Note that this HKD access method is potentially less performant than lens access (by about 10-20%).
Potential or Many Fields
Some values "may" or "may not" have values of a given field. An example would
include the nth item in a list or vector, or the Just
of a Maybe
.
For these, the lens-based (prism-based/traversal-based) interface is the main way to access
partial fields. You can use (^^?)
or previewVar
with any Traversal
:
(^?) :: a -> Traversal' a b -> Maybe b
(^^?) :: BVar s a -> Traversal' a b -> Maybe (BVar s b)
If the value in the BVar
"has" that field, then you'll get a Just
with the
BVar
of that field's contents. If it doesn't, you'll get a Nothing
.
You can use this with any prism or traversal, like using _head
to get the
first item in a list if it exists.
If you have a type that might contain many values of a field (like a tree or
list), you can use (^^..)
or toListOfVar
, which works on any Traversal
:
(^..) :: a -> Traversal' a b -> [ b]
(^^..) :: BVar s a -> Traversal' a b -> [BVar s b]
This can be used to implement sequenceVar
, actually:
sequenceVar :: BVar s [a] -> [BVar s a]
sequenceVar xs = xs ^^.. traverse
Tuples
The T2
pattern synonym is provided, which allow you to pattern match on a
BVar s (a, b)
to get a BVar s a
and BVar s b
. The T3
pattern is also
provided, which does the same thing for three-tuples.
Note that T2
and T3
are bidirectional pattern synonyms, and can be used to
construct as well as deconstruct.
Combining BVars
The following techniques can be used to "combine" BVar
s:
Foldable Containers
The "opposite" of sequenceVar
is collectVar
, which takes a foldable
container of BVar
s and returns a BVar
containing that foldable container of
contents:
collectVar :: (Backprop a, Foldable t, Functor t, Reifies s W)
=> t (BVar s a)
-> BVar s (t a)
Constructors
Sometimes you would like to combine a bunch of BVar
s into a BVar
of
specific container or data type.
isoVar
The simplest way to do this is using the isoVar
, isoVar2
, etc. family of
functions:
isoVar2
:: (Backprop a, Backprop b, Backprop c, Reifies s W)
=> (a -> b -> c)
-> (c -> (a, b))
-> BVar s a
-> BVar s b
-> BVar s c
So if we had a type like:
data DoubleInt = DI Double Int
We can combine a Double
and Int
into a DoubleInt
using isoVar2
:
isoVar2 DI (\(DI x y) -> (x,y))
:: Reifies s W
=> BVar s Double
-> BVar s Int
-> BVar s DoubleInt
Higher-Kinded Data Interface
You can also use the "Higher Kinded Data" interface, as well. For our
Met
type above, you can use joinBV
, or the BV
pattern synonym:
makeMet :: Reifies s W
=> BVar s (L 20 100)
-> BVar s (R 20)
-> BVar s (L 5 20)
-> BVar s (R 5)
-> BVar s Met
makeMet w1 b1 w2 b2 = joinBV (M w1 b1 w2 b2)
Modifying fields
If you just want to "set" a specific field, you can use the lens-based
interface with (.~~)
or setVar
. For example, if we wanted to set the
_nWeights2
field of a Net
to a new matrix, we can do:
myNet & nWeights2 .~~ newMatrix
or
setVar nWeights2
:: Reifies s W
=> BVar s (L 20 5)
-> BVar s Net
-> BVar s Net
You can also use (%~~)
or overVar
to apply a function to a specific
inside your value.
Prelude Modules
Finally, the Prelude.Backprop module has a lot of your normal Prelude
functions "lifted" to work on BVar
s of values. For many situations, these
aren't necessary, and normal Prelude functions will work just fine on BVar
s
of values (like (.)
). However, it does have some convenient functions, like
minimum
, foldl'
, fmap
, toList
, fromIntegral
, realToFrac
, etc.
lifted to work on BVar
s. This module is meant to be imported qualified.
Moving On
Now that you know all about BVar
s, you really can just jump into the
haddocks and start writing programs. The next section of this
documentation is more details about the Backprop
typeclass.