Skip to content

Commit

Permalink
Add Layer module; Add neuron activation unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
rounakdatta committed Apr 12, 2024
1 parent adb9fa4 commit bfe93e9
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 59 deletions.
108 changes: 54 additions & 54 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -1,61 +1,61 @@
{
description = "Algorithmic (aka automatic) differentiation implementation in OCaml";
description = "Algorithmic (aka automatic) differentiation implementation in OCaml";

inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};

outputs = { self, nixpkgs, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
isDarwin = pkgs.stdenv.isDarwin;
# thanks to https://github.com/commercialhaskell/stack/issues/1698#issuecomment-178098712 for the idea
darwinFrameworks = pkgs.lib.optionals isDarwin (with pkgs.darwin.apple_sdk.frameworks; [
Cocoa
CoreServices
]);
ocamlEnv = with pkgs.ocamlPackages; [
ocaml
utop
dune_3
findlib
ocaml-lsp
ocamlformat
];
in
{
devShell = pkgs.mkShell {
buildInputs = ocamlEnv ++ [ pkgs.opam ] ++ darwinFrameworks;
shellHook = ''
export IN_NIX_DEVELOP_SHELL=1
outputs = { self, nixpkgs, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
isDarwin = pkgs.stdenv.isDarwin;
# thanks to https://github.com/commercialhaskell/stack/issues/1698#issuecomment-178098712 for the idea
darwinFrameworks = pkgs.lib.optionals isDarwin (with pkgs.darwin.apple_sdk.frameworks; [
Cocoa
CoreServices
]);
ocamlEnv = with pkgs.ocamlPackages; [
ocaml
utop
dune_3
findlib
ocaml-lsp
ocamlformat
];
in
{
devShell = pkgs.mkShell {
buildInputs = ocamlEnv ++ [ pkgs.opam ] ++ darwinFrameworks;
shellHook = ''
export IN_NIX_DEVELOP_SHELL=1
export OPAMROOT=$NIX_BUILD_TOP/.opam
# unsetting the below env var is required for fixing a thorny issue with `num` install
# similar issue & solution thread: https://github.com/ocaml/Zarith/issues/136
unset OCAMLFIND_DESTDIR
export OPAMROOT=$NIX_BUILD_TOP/.opam
# unsetting the below env var is required for fixing a thorny issue with `num` install
# similar issue & solution thread: https://github.com/ocaml/Zarith/issues/136
unset OCAMLFIND_DESTDIR
opam init --bare --disable-sandboxing -y --shell-setup -vv
opam option -global depext=false
OCAML_VERSION=$(ocaml --version | awk '{printf $5}')
opam switch create $OCAML_VERSION
eval $(opam env --switch=$OCAML_VERSION)
opam install . --deps-only -y -v
opam init --bare --disable-sandboxing -y --shell-setup -vv
opam option -global depext=false
OCAML_VERSION=$(ocaml --version | awk '{printf $5}')
opam switch create $OCAML_VERSION
eval $(opam env --switch=$OCAML_VERSION)
opam install . --deps-only -y -v
# figure out what the default shell of this computer is and set it
'' +
(if isDarwin then
''
SHELLY=$(dscl . -read /Users/$USER UserShell | awk '{print $2}')
exec $SHELLY
''
else
''
SHELLY=$(getent passwd $USER | awk -F: '{printf $7}')
exec $SHELLY
'');
};
}
);
# figure out what the default shell of this computer is and set it
'' +
(if isDarwin then
''
SHELLY=$(dscl . -read /Users/$USER UserShell | awk '{print $2}')
exec $SHELLY
''
else
''
SHELLY=$(getent passwd $USER | awk -F: '{printf $7}')
exec $SHELLY
'');
};
}
);
}
2 changes: 1 addition & 1 deletion lib/dune
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(library
(name smolgrad)
(public_name smolgrad)
(modules variable neuron))
(modules variable neuron layer))
12 changes: 12 additions & 0 deletions lib/layer.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Layer = struct
type t = {
neurons : Neuron.Neuron.t list
}

let create number_of_inputs number_of_neurons is_non_linear =
let neurons = List.init number_of_neurons (fun _ -> Neuron.Neuron.create number_of_inputs is_non_linear) in
{ neurons = neurons }

let propagate_input (layer: t) input_vector =
List.map (fun neuron -> Neuron.Neuron.weigh_input neuron input_vector) layer.neurons
end
11 changes: 11 additions & 0 deletions lib/layer.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
(* Layer is the aggregator of many neurons parallelly
captures essence of an input by adjusting it's weights and biases *)
module Layer : sig
type t

(* Constructor; constructs a layer of neurons *)
val create : int -> int -> bool -> t

(* Propagates the input across all the neurons in the layer *)
val propagate_input : t -> Variable.Variable.t list -> Variable.Variable.t list
end
3 changes: 2 additions & 1 deletion lib/neuron.ml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ module Neuron = struct
is_non_linear = is_non_linear;
}

let weigh_inputs (neuron: t) input_vector =
(* TODO: Check for dimension-mismatch *)
let weigh_input (neuron: t) input_vector =
(* one-to-one multiplication of inputs to their corresponding weights *)
let weighted_sum = List.fold_left2 (fun accumulator weight_i input_i -> Variable.Variable.(accumulator + weight_i * input_i))
(Variable.Variable.create 0.0) neuron.weights input_vector in
Expand Down
2 changes: 1 addition & 1 deletion lib/neuron.mli
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ module Neuron : sig
val create : int -> bool -> t

(* Passes the training input and lets the neuron weigh the input values against its weights and biases *)
val weigh_inputs : t -> Variable.Variable.t list -> Variable.Variable.t
val weigh_input : t -> Variable.Variable.t list -> Variable.Variable.t
end
19 changes: 17 additions & 2 deletions test/neuron_operations.ml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
open Smolgrad.Neuron
open Smolgrad.Variable

let test_neuron_initialization () =
let n = Neuron.create 5 true in
let n_weights = (Neuron.parameters n).weights in
assert (Smolgrad.Variable.Variable.data (Neuron.parameters n).bias = 0.0);

Alcotest.(check int)
"Neuron: Right number of parameters are set"
Expand All @@ -19,7 +19,22 @@ let test_neuron_initialization () =
List.for_all (fun x -> (Smolgrad.Variable.Variable.data x) >= -1.0 && (Smolgrad.Variable.Variable.data x) <= 1.0) weights in

Alcotest.(check bool)
"Neuron: All weights are in range [-1, 1]"
"Neuron: All weights are in range [-1.0, 1.0]"
true
(are_weights_in_range n_weights);
;;

let test_neuron_weights_reacting_to_input () =
let n = Neuron.create 3 false in

let neuron_activation_for_input_1 = Neuron.weigh_input n [Variable.create 3.0; Variable.create 2.0; Variable.create 1.0] in
let neuron_activation_for_input_2 = Neuron.weigh_input n [Variable.create 8.0; Variable.create 13.0; Variable.create (-4.0)] in

let n_weight_values = Array.of_list (List.map (fun x -> Variable.data x) (Neuron.parameters n).weights) in
let remainder = (3.0 -. 8.0) *. n_weight_values.(0) +. (2.0 -. 13.0) *. n_weight_values.(1) +. (1.0 -. (-4.0)) *. n_weight_values.(2) in

Alcotest.(check (float 0.01))
"Neuron: Neuron activation upon input happened correctly"
remainder
(Smolgrad.Variable.Variable.data neuron_activation_for_input_1 -. Smolgrad.Variable.Variable.data neuron_activation_for_input_2);
;;
1 change: 1 addition & 0 deletions test/smolgrad_tests.ml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ let () =

(* unit tests for Neuron *)
Neuron_operations.test_neuron_initialization ();
Neuron_operations.test_neuron_weights_reacting_to_input ();
;;

0 comments on commit bfe93e9

Please sign in to comment.