diff --git a/flake.nix b/flake.nix index e1bf8d3..0608ab4 100644 --- a/flake.nix +++ b/flake.nix @@ -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 + ''); + }; + } + ); } diff --git a/lib/dune b/lib/dune index fc5bd74..fc47a77 100644 --- a/lib/dune +++ b/lib/dune @@ -1,4 +1,4 @@ (library (name smolgrad) (public_name smolgrad) - (modules variable neuron)) + (modules variable neuron layer)) diff --git a/lib/layer.ml b/lib/layer.ml new file mode 100644 index 0000000..19abf84 --- /dev/null +++ b/lib/layer.ml @@ -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 diff --git a/lib/layer.mli b/lib/layer.mli new file mode 100644 index 0000000..726edf5 --- /dev/null +++ b/lib/layer.mli @@ -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 diff --git a/lib/neuron.ml b/lib/neuron.ml index e4f21aa..dc20a2d 100644 --- a/lib/neuron.ml +++ b/lib/neuron.ml @@ -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 diff --git a/lib/neuron.mli b/lib/neuron.mli index 223be7c..06ddfb2 100644 --- a/lib/neuron.mli +++ b/lib/neuron.mli @@ -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 diff --git a/test/neuron_operations.ml b/test/neuron_operations.ml index bd8ce1c..802d8ae 100644 --- a/test/neuron_operations.ml +++ b/test/neuron_operations.ml @@ -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" @@ -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); +;; diff --git a/test/smolgrad_tests.ml b/test/smolgrad_tests.ml index 38cbabc..4daa937 100644 --- a/test/smolgrad_tests.ml +++ b/test/smolgrad_tests.ml @@ -7,4 +7,5 @@ let () = (* unit tests for Neuron *) Neuron_operations.test_neuron_initialization (); + Neuron_operations.test_neuron_weights_reacting_to_input (); ;;