# OCAML-NN A fully functional monadic implementation of a Fully-Connected Neural Network (FCNNs) in **OCaml** ## Details Train and run a neural network on any dataset. We implement a fully-connected multi-layered neural network which learns network parameters using back propogation implemented as gradient descent algorithm. The data structure we use is OCaml Arrays. We further provide the following hyper-parameter customizabillity: - Optimization Functions: `Vanilla GD | GD w/Momentum | RMS Prop`
- Activation Functions: `ReLU | TanH | Sigmoid `
- Gradient Descent Type: `Stochastic GD | Mini-Batch GD | Vanilla GD`
- Learning Rate: ``
- Epochs: ``
- Beta1 and Beta2: ``
- Number of Hidden Units: ``
- Epsilon: ``
## Usage There are two functions that can be called along with some utility functions that allows you to read datasets. An example training process is written in `train.ml` `Neuralnet.fit` - Returns a trained model as a monadic state. This state contains four vectors which represent the final gradients of the model. To get the weights and biases pass the gradients through the `run` function of the monad. The function has the following arguments: - `train_x:` input data as a `float array array`. - `train_y:` input data labels as a `int array`. - `lr:` learning rate for the model - `iter:` number of epochs for training - `gd_type:` gradient descent type choose from `SGD | MBGD | GD` - `optimizer:` optimizier type choose from `VGD | GDM | RMSProp` - `activation:` activation function choose from `TanH | ReLU | Sigmoid` - `beta1:` regularization parameter used in GD with Momentum and RMSProp - `beta2:` (1 - `beta1`) used for the same purpose - `hidden_units:` number of hidden units per hidden layer in the model - `output_units:` number of classes in the target label of the dataset - `epsilon:` small value used in RMSProp to avoid divide by zero errors `Neuralnet.inference` - Peforms predictions on the test data provided. The function has the following arguments: - `test_x:` test data as a `float array array` - `test_y:` test data labels as `int array` used for computing `Accuracy` - `activation:` activation function, should be the same as the one above - `w1:` weights of the first layer, from `Neuralnet.fit` - `b1:` biases of the first layer, from `Neuralnet.fit` - `w2:` weights of the second layer, from `Neuralnet.fit` - `b2:` biases of the second layer, from `Neuralnet.fit` The entire OCaml code base is parameterized. One can always change them to customize it. ## Example run Run the code in `train.ml` to train the NeuralNet model on a sample 100 rows MNIST-10 dataset. This is provided in the `data/` folder. To run do the following, from inside the folder: ``` dune build dune exec ./train.exe ``` Run `dune clean`, to clean the `_build/` directory. Notice the usage of `(include_subdirs unqualified)` in the dune file which makes sure that all the sub-directories of the project are in the same directory. ## Tests We run some sample tests on 4000 rows of the MNIST-10 dataset and test on 20 rows. The results are as follows: | Optimization | Accuracy | Time (sec) | |---------------|----------|------------| | Vanilla GD | 0.842 | 65 | | GD w/Momentum | 0.894 | 55 | | RMS Prop | 0.910 | 60 | Here we needed to use `TanH` activation for `RMSProp` so that the gradients are updated. We also compare the efficiency of using a better data structure to represent the vectors. Here we compare representing the data as OCaml `List` vs `Arrays`: | Data Structure | Accuracy | Time (sec) | |-----------------|----------|------------| | Lists | 0.842 | 260 | | Arrays | 0.894 | 60 | We can see a 400% improvement in time just by changing the data structure. The original times were recorded in NB format, which gave us 180 and 780 seconds respectively.