Deep Learning in Haskell
Go to file
2016-12-13 01:11:18 +11:00
bench Add stupidly fast col2im 2016-12-13 01:11:18 +11:00
cbits Add stupidly fast col2im 2016-12-13 01:11:18 +11:00
lib Update hmatrix 2016-12-08 10:10:13 +11:00
main Use ExceptT in mnist 2016-12-08 10:10:04 +11:00
src Add stupidly fast col2im 2016-12-13 01:11:18 +11:00
test Add stupidly fast col2im 2016-12-13 01:11:18 +11:00
.gitignore Add .gitignore 2016-06-29 20:24:06 +10:00
.gitmodules Add P 2016-12-02 15:24:12 +11:00
grenade.cabal Add stupidly fast im2col in c 2016-12-12 23:35:00 +11:00
LICENSE Rename NOTICE.txt to LICENSE 2016-06-28 10:25:58 +08:00
mafia Builds warning free with both ghc 7.10 and 8.0 now 2016-07-01 19:29:50 +10:00
README.md Update README 2016-12-09 22:48:41 +11:00

Grenade

First shalt thou take out the Holy Pin, then shalt thou count to three, no more, no less.
Three shall be the number thou shalt count, and the number of the counting shall be three.
Four shalt thou not count, neither count thou two, excepting that thou then proceed to three.
Five is right out.

💣 Machine learning which might blow up in your face 💣

Grenade is a dependently typed, practical, and pretty quick neural network library for concise and precise specifications of complex networks in Haskell.

As an example, a network which can achieve less than 1% error on MNIST can be specified and initialised with random weights in a few lines of code with

randomMnist :: MonadRandom m
            => m (Network '[ Convolution 1 10 5 5 1 1, Pooling 2 2 2 2, Relu, Convolution 10 16 5 5 1 1, Pooling 2 2 2 2, FlattenLayer, Relu, FullyConnected 256 80, Logit, FullyConnected 80 10, Logit]
                          '[ 'D2 28 28, 'D3 24 24 10, 'D3 12 12 10, 'D3 12 12 10, 'D3 8 8 16, 'D3 4 4 16, 'D1 256, 'D1 256, 'D1 80, 'D1 80, 'D1 10, 'D1 10])
randomMnist = randomNetwork

And that's it. Because the types contain all the information we need, there's no specific term level code required; although it is of course possible and easy to build one oneself.

The network can be thought of as a heterogeneous list of layers, where its type includes not only the layers of the network, but also the shapes of data that are passed between the layers.

data Network :: [*] -> [Shape] -> * where
    O     :: Layer x i o => !x -> Network '[x] '[i, o]
    (:~>) :: Layer x i h => !x -> !(Network xs (h ': hs)) -> Network (x ': xs) (i ': h ': hs)

In the above example, the input layer can be seen to be a two dimensional (D2), image with 28 by 28 pixels. When the first Convolution layer runs, it outputs a three dimensional (D3) 24x24x10 image. The last item in the list is one dimensional (D1) with 10 values, representing the categories of the MNIST data.

Usage

To perform back propagation, one can call the eponymous function

backPropagate :: forall input target shapes layers. (Head shapes ~ input, Last shapes ~ target)
              => Network layers shapes -> S' input -> S' target -> Gradients layers

which takes a network, appropriate input and target data, and returns the back propagated gradients for the network. The shapes of the gradients are appropriate for each layer, and may be trivial for layers like Rulu which have no learnable parameters.

The gradients however can always be applied, yielding a new (hopefully better) layer with

applyUpdate :: LearningParameters -> Network ls ss -> Gradients ls -> Network ls ss

Layers in Grenade are represented as Haskell classes, so creating one's own is easy in downstream code. If the shapes of a network are not specified correctly and a layer can not sensibly perform the operation between two shapes, then it will result in a compile time error.

Build Instructions

Grenade currently only builds with the mafia script that is located in the repository. You will also need the lapack and blas libraries and development tools. Once you have all that, Grenade can be build using:

./mafia build

and the tests run using:

./mafia test

Grenade is currently known to build with ghc 7.10 and 8.0.

Thanks

Writing a library like this has been on my mind for a while now, but a big shout out must go to Justin Le, whose dependently typed fully connected network inspired me to get cracking, gave many ideas for the type level tools I needed, and was a great starting point for writing this library.

Performance

Grenade is backed by hmatrix and blas, and uses a pretty clever convolution trick popularised by Caffe, which is surprisingly effective and fast. So for many small scale problems it should be sufficient.

Being purely functional, it's probably pretty easy to parallelise and batch up. My current examples however are single threaded.

Training 15 generations over Kaggle's 42000 sample MNIST training set took under an hour, achieving 0.5% error rate on a 1000 sample holdout set.

Contributing

Contributions are welcome.