diff --git a/lib/neuron_system/back_prop/net.ex b/lib/neuron_system/back_prop/net.ex new file mode 100644 index 0000000..29fd371 --- /dev/null +++ b/lib/neuron_system/back_prop/net.ex @@ -0,0 +1,33 @@ +defmodule NeuronSystem.BackProp.Net do + @moduledoc """ + Represents an extension of the Net to support back propagation learning algorithm. + + ## Usage + + Use this module in the origin Net module: + + ```elixir + defmodule NeuronSystem.Net do + ... + use NeuronSystem.BackProp.Net + ... + end + ``` + + After that you can learn your net by calling: + ```elixir + net = ... # Net creation flow + # Add neurons, connections... + NeuronSystem.Net.back_prop!(net, %{y: 1}) + ``` + """ + + defmacro __using__(_opts \\ :empty) do + quote do + @spec back_prop!(NeuronSystem.Models.Net.t, map) :: :ok + def back_prop!(net, valid_output) do + NeuronSystem.BackProp.Utils.BackPropRunner.call(net, valid_output) + end + end + end +end diff --git a/lib/neuron_system/back_prop/neuron/hidden_processor.ex b/lib/neuron_system/back_prop/neuron/hidden_processor.ex new file mode 100644 index 0000000..44f937a --- /dev/null +++ b/lib/neuron_system/back_prop/neuron/hidden_processor.ex @@ -0,0 +1,67 @@ +defmodule NeuronSystem.BackProp.Neuron.HiddenProcessor do + alias NeuronSystem.{Models, Processes} + alias NeuronSystem.BackProp.Utils + + def call(net, from_neuron_id, lapse, {%Models.Neuron{id: neuron_id} = neuron_model, options}) do + new_options = options |> push_new_income_lapse(from_neuron_id, lapse) + neuron_out_connections = NeuronSystem.Net.neuron_out_connections(net, neuron_id) + if received_all?(new_options, neuron_out_connections) do + lapse = calc_overall_lapse(new_options) + income_payloads = options |> Map.get(:income_payloads) + neuron_in_connections = NeuronSystem.Net.neuron_in_connections(net, neuron_id) + neuron_in_connections |> Enum.each(fn(connection) -> + connection_income = fetch_income_for_connection(connection, income_payloads) + delta_w = Utils.Calculations.delta_weight(lapse, connection_income) + new_w = Utils.Calculations.new_weight(connection.weight, delta_w) + connection_lapse = Utils.Calculations.connection_lapse(lapse, connection.weight) + NeuronSystem.Net.set_connection_weight(net, connection, new_w) + if %Models.Connection{} = connection do + send_back_prop_inside(net, neuron_id, connection, connection_lapse) + end + end) + if at_least_one_is_in_connection?(neuron_in_connections) do + send_back_prop_completed(net, neuron_id) + end + end + {neuron_model, new_options} + end + + @spec push_new_income_lapse(map, bitstring, float) :: map + defp push_new_income_lapse(options, from_neuron_id, lapse) do + income_lapse = options |> Map.get(:income_lapse, %{}) + new_income_lapse = income_lapse |> Map.put(from_neuron_id, lapse) + Map.put(options, :income_lapse, new_income_lapse) + end + + defp received_all?(%{income_lapse: income_lapse} = _options, out_connections) do + income_count = income_lapse |> Map.keys |> Enum.count + connections_count = out_connections |> Enum.count + income_count == connections_count + end + + defp fetch_income_for_connection(connection, income_payloads) do + connection_source = NeuronSystem.Connection.source(connection) + income_payloads[connection_source] + end + + defp calc_overall_lapse(%{income_lapse: income_lapse} = _options) do + income_lapse |> Map.values |> Enum.sum + end + + defp send_back_prop_inside(net, neuron_id, connection, lapse) do + connection_source = NeuronSystem.Connection.source(connection) + neuron_process_pid = NeuronSystem.Net.neuron_process_pid(net, connection_source) + Processes.Neuron.back_prop(neuron_process_pid, {:hidden, net, neuron_id, lapse}) + end + + defp at_least_one_is_in_connection?(connections) do + Enum.any?(connections, fn + (%Models.InConnection{} = _conn) -> true + _ -> false + end) + end + + defp send_back_prop_completed(%Models.Net{root_pid: root_pid} = _net, neuron_id) do + send root_pid, {:back_prop_completed, neuron_id} + end +end diff --git a/lib/neuron_system/back_prop/neuron/output_processor.ex b/lib/neuron_system/back_prop/neuron/output_processor.ex new file mode 100644 index 0000000..5e1ee44 --- /dev/null +++ b/lib/neuron_system/back_prop/neuron/output_processor.ex @@ -0,0 +1,50 @@ +defmodule NeuronSystem.BackProp.Neuron.OutputProcessor do + @moduledoc """ + Processor of Back Propagation algorithm messages for an output neuron. + + The algorithm for the output neurons is a little bit different then for the input and hidden ones + and contains the following steps: + + 1. Calculate a lapse value using an needed output value and value after a forward pass. + 2. For each neuron's input connection calculate a delta value. + 3. Calculate a new weight for each ouput connection + 4. Change weights of all input connections in a Connection Manager + 5. Send messages to hidden neurons with needed calculated values: in this particular case it is `lapse`. + """ + + alias NeuronSystem.{Processes, Models} + alias NeuronSystem.BackProp.Utils + + @doc """ + Calls processor for an output neuron. + """ + def call(net, valid_output, {%Models.Neuron{id: neuron_id} = neuron_model, %{income_payloads: income_payloads} = options}) do + lapse = calc_lapse(valid_output, options) + NeuronSystem.Net.neuron_in_connections(net, neuron_id) + |> Enum.each(fn(connection) -> + connection_income = fetch_income_for_connection(connection, income_payloads) + delta_w = Utils.Calculations.delta_weight(lapse, connection_income) + new_w = Utils.Calculations.new_weight(connection.weight, delta_w) + connection_lapse = Utils.Calculations.connection_lapse(lapse, connection.weight) + NeuronSystem.Net.set_connection_weight(net, connection, new_w) + send_back_prop_inside(net, neuron_id, connection, connection_lapse) + end) + end + + defp calc_lapse(valid_output, options) do + out_value = options |> Map.get(:out_value) + d_out_value = options |> Map.get(:d_out_value) + Utils.Calculations.out_lapse(out_value, d_out_value, valid_output) + end + + defp fetch_income_for_connection(connection, income_payloads) do + connection_source = NeuronSystem.Connection.source(connection) + income_payloads[connection_source] + end + + defp send_back_prop_inside(net, neuron_id, connection, lapse) do + connection_source = NeuronSystem.Connection.source(connection) + neuron_process_pid = NeuronSystem.Net.neuron_process_pid(net, connection_source) + Processes.Neuron.back_prop(neuron_process_pid, {:hidden, net, neuron_id, lapse}) + end +end diff --git a/lib/neuron_system/back_prop/processes/neuron.ex b/lib/neuron_system/back_prop/processes/neuron.ex new file mode 100644 index 0000000..fd06d97 --- /dev/null +++ b/lib/neuron_system/back_prop/processes/neuron.ex @@ -0,0 +1,29 @@ +defmodule NeuronSystem.BackProp.Processes.Neuron do + @moduledoc """ + Acts as an extension for an original Neuron process. + + This extension provides additional API specifically for Back Propagation feature. + """ + + defmacro __using__(opts \\ :empty) do + quote do + @spec back_prop(pid, {:output | :hidden, NeuronSystem.Models.Net.t, float}) :: :ok + def back_prop(pid, {:output, net, valid_output}) do + GenServer.cast(pid, {:back_prop, :output, net, valid_output}) + end + def back_prop(pid, {:hidden, net, lapse}) do + GenServer.cast(pid, {:back_prop, :hidden, net, lapse}) + end + + def handle_cast({:back_prop, :output, net, valid_output}, state) do + NeuronSystem.BackProp.Neuron.OutputProcessor.call(net, valid_output, state) + {:noreply, state} + end + + def handle_cast({:back_prop, :hidden, net, from_neuron_id, lapse}, state) do + new_state = NeuronSystem.BackProp.Neuron.HiddenProcessor.call(net, from_neuron_id, lapse, state) + {:noreply, state} + end + end + end +end diff --git a/lib/neuron_system/back_prop/utils/back_prop_runner.ex b/lib/neuron_system/back_prop/utils/back_prop_runner.ex new file mode 100644 index 0000000..f428dbf --- /dev/null +++ b/lib/neuron_system/back_prop/utils/back_prop_runner.ex @@ -0,0 +1,56 @@ +defmodule NeuronSystem.BackProp.Utils.BackPropRunner do + @moduledoc """ + Contains logic to start Back Propagation learning algorithm. + + From very superficial sight an algorithm steps are: + + 1. Detect output connection of a Net + 2. Send to each output neuron a message with a needed value inside + 3. Put a receive to wait for learning end + """ + + alias NeuronSystem.{Models, Processes} + + @doc """ + Starts back propagation learning procedure for a specific Net using a some pair from a learning set. + """ + @spec call(NeuronSystem.Models.Net.t, map) :: :ok + def call(%Models.Net{} = net, valid_output) do + net + |> send_propagation(valid_output) + |> collect_results + end + + defp send_propagation(net, valid_output) do + net + |> NeuronSystem.Net.out_connections + |> Enum.each(&send_event_to_source_neuron(&1, net, valid_output)) + net + end + + def send_event_to_source_neuron(connection, net, valid_output) do + neuron_id = connection.source_neuron + neuron_process_pid = NeuronSystem.Net.neuron_process_pid(net, neuron_id) + needed_output = valid_output[connection.key] + Processes.Neuron.back_prop(neuron_process_pid, {:output, net, needed_output}) + end + + defp collect_results(net) do + net + |> detect_input_neurons + |> Enum.map(&collect_result_for_connection(&1)) + end + + def detect_input_neurons(net) do + net + |> NeuronSystem.Net.in_connections + |> Enum.map(&(&1.target_neuron)) + |> Enum.uniq + end + + defp collect_result_for_connection(neuron_id) do + receive do + {:back_prop_completed, ^neuron_id} -> neuron_id + end + end +end diff --git a/lib/neuron_system/back_prop/utils/calculations.ex b/lib/neuron_system/back_prop/utils/calculations.ex new file mode 100644 index 0000000..4f70f99 --- /dev/null +++ b/lib/neuron_system/back_prop/utils/calculations.ex @@ -0,0 +1,39 @@ +defmodule NeuronSystem.BackProp.Utils.Calculations do + @moduledoc """ + In this module are encapsulated all calculations for Back Prop algorithm. + """ + + @learning_speed 0.4 + + @doc """ + Calculates a lapse for an output neuron. + """ + @spec out_lapse(float, float, float) :: float + def out_lapse(calculated_output, d_calculated_output, needed_output) do + (needed_output - calculated_output) * d_calculated_output + end + + @doc """ + Calculates a delta value for a weight of a connection. + """ + @spec delta_weight(float, float) :: float + def delta_weight(lapse, income) do + @learning_speed * lapse * income + end + + @doc """ + Calculates new weight of a connection by old one and delta value. + """ + @spec new_weight(float, float) :: float + def new_weight(weight, delta_w) do + weight + delta_w + end + + @doc """ + Calculates connection's lapse by output lapse and connection's weight. + """ + @spec connection_lapse(float, float) :: float + def connection_lapse(lapse, weight) do + lapse * weight + end +end diff --git a/lib/neuron_system/models/connection.ex b/lib/neuron_system/models/connection.ex index a6e82af..9688355 100644 --- a/lib/neuron_system/models/connection.ex +++ b/lib/neuron_system/models/connection.ex @@ -6,9 +6,10 @@ defmodule NeuronSystem.Models.Connection do its weight. """ - defstruct [:source_neuron, :target_neuron, :weight] + defstruct [:id, :source_neuron, :target_neuron, :weight] alias NeuronSystem.Models + alias NeuronSystem.Utils.CommonHelper @type t :: %__MODULE__{} @@ -18,6 +19,7 @@ defmodule NeuronSystem.Models.Connection do @spec build(Models.Neuron.t, Models.Neuron.t, float) :: Models.Connection.t def build(%Models.Neuron{id: source_id}, %Models.Neuron{id: target_id}, weight) do %__MODULE__{ + id: CommonHelper.gen_process_id("connection"), source_neuron: source_id, target_neuron: target_id, weight: weight diff --git a/lib/neuron_system/models/in_connection.ex b/lib/neuron_system/models/in_connection.ex index 2a07db5..f816cd6 100644 --- a/lib/neuron_system/models/in_connection.ex +++ b/lib/neuron_system/models/in_connection.ex @@ -3,9 +3,10 @@ defmodule NeuronSystem.Models.InConnection do Represents an input connection for a Net. """ - defstruct [:target_neuron, :weight, :key] + defstruct [:id, :target_neuron, :weight, :key] alias NeuronSystem.Models + alias NeuronSystem.Utils.CommonHelper @type t :: %__MODULE__{} @@ -15,6 +16,7 @@ defmodule NeuronSystem.Models.InConnection do @spec build(Models.Neuron.t, float, atom) :: __MODULE__.t def build(%Models.Neuron{id: neuron_id}, weight, key) do %__MODULE__{ + id: CommonHelper.gen_process_id("connection"), target_neuron: neuron_id, weight: weight, key: key diff --git a/lib/neuron_system/models/net.ex b/lib/neuron_system/models/net.ex index 8b639b7..9b4cc12 100644 --- a/lib/neuron_system/models/net.ex +++ b/lib/neuron_system/models/net.ex @@ -5,5 +5,5 @@ defmodule NeuronSystem.Models.Net do Contains the only one value - the PID of the supervisor process of the Net. """ - defstruct [:pid] + defstruct [:pid, :root_pid] end diff --git a/lib/neuron_system/models/out_connection.ex b/lib/neuron_system/models/out_connection.ex index 2aef76f..c29da98 100644 --- a/lib/neuron_system/models/out_connection.ex +++ b/lib/neuron_system/models/out_connection.ex @@ -3,9 +3,10 @@ defmodule NeuronSystem.Models.OutConnection do Represents an output connection for a Net. """ - defstruct [:source_neuron, :weight, :key] + defstruct [:id, :source_neuron, :weight, :key] alias NeuronSystem.Models + alias NeuronSystem.Utils.CommonHelper @type t :: %__MODULE__{} @@ -15,6 +16,7 @@ defmodule NeuronSystem.Models.OutConnection do @spec build(Models.Neuron.t, float, atom) :: __MODULE__.t def build(%Models.Neuron{id: neuron_id}, weight, key) do %__MODULE__{ + id: CommonHelper.gen_process_id("connection"), source_neuron: neuron_id, weight: weight, key: key diff --git a/lib/neuron_system/net.ex b/lib/neuron_system/net.ex index c9c0db8..d5498dd 100644 --- a/lib/neuron_system/net.ex +++ b/lib/neuron_system/net.ex @@ -21,6 +21,8 @@ defmodule NeuronSystem.Net do ``` """ + use NeuronSystem.BackProp.Net + import NeuronSystem.Utils.SpecHelper alias NeuronSystem.{Models, Processes} @@ -50,7 +52,7 @@ defmodule NeuronSystem.Net do "net_process" ) {:ok, pid} = Supervisor.start_child(NeuronSystem.Supervisor, supervisor_spec) - %Models.Net{pid: pid} + %Models.Net{pid: pid, root_pid: self()} end @doc """ @@ -152,7 +154,7 @@ defmodule NeuronSystem.Net do """ @spec activate!(Models.Net.t, map) :: list def activate!(net, income) do - NeuronSystem.Net.Activator.call(net, income, self()) + NeuronSystem.Net.Activator.call(net, income) end @doc """ @@ -168,6 +170,61 @@ defmodule NeuronSystem.Net do end) end + @doc """ + Detects and returns the whole connections in a Net + """ + @spec get_all_connections(Models.Net.t) :: list + def get_all_connections(%Models.Net{} = net) do + NeuronSystem.Net.connection_manager(net) + |> Processes.ConnectionManager.get_all + end + + @doc """ + Detects and returns net's output connections list + """ + @spec out_connections(Models.Net.t) :: list + def out_connections(%Models.Net{} = net) do + NeuronSystem.Net.connection_manager(net) + |> Processes.ConnectionManager.get_net_out_connections + end + + @doc """ + Detects and returns net's input connections list + """ + @spec in_connections(Models.Net.t) :: list + def in_connections(%Models.Net{} = net) do + NeuronSystem.Net.connection_manager(net) + |> Processes.ConnectionManager.get_net_in_connections + end + + @doc """ + Fetches out connections for the given neuron in the Net + """ + @spec neuron_out_connections(Models.Net.t, bitstring) :: list + def neuron_out_connections(%Models.Net{} = net, neuron_id) do + NeuronSystem.Net.connection_manager(net) + |> Processes.ConnectionManager.get_neuron_out_connections(neuron_id) + end + + @doc """ + Fetches in connections for the given neuron in the Net + """ + @spec neuron_in_connections(Models.Net.t, bitstring) :: list + def neuron_in_connections(%Models.Net{} = net, neuron_id) do + NeuronSystem.Net.connection_manager(net) + |> Processes.ConnectionManager.get_neuron_in_connections(neuron_id) + end + + @doc """ + Changes a weight of a given connection and saves it in a connection manager. + """ + @spec set_connection_weight(Models.Net.t, Models.InConnection.t | Models.OutConnection.t | Models.Connection.t, float) :: :ok + def set_connection_weight(net, connection, new_weight) do + net + |> connection_manager + |> Processes.ConnectionManager.change_weight(connection.id, new_weight) + end + defp detect_child_pid(net_pid, child_module) do worker_spec = Supervisor.which_children(net_pid) |> Enum.find(fn({module, _pid, _type, _opts}) -> diff --git a/lib/neuron_system/net/activator.ex b/lib/neuron_system/net/activator.ex index 4475e84..7426ef5 100644 --- a/lib/neuron_system/net/activator.ex +++ b/lib/neuron_system/net/activator.ex @@ -8,44 +8,34 @@ defmodule NeuronSystem.Net.Activator do 2. Puts a bunch of `receive` blocks to collect outputs """ - alias NeuronSystem.{Models, Processes} + alias NeuronSystem.{Models} @doc """ The main point for an activator. """ - @spec call(Models.Net.t, map, pid) :: list - def call(net, income, root_pid) do - net |> send_in_events(income, root_pid) + @spec call(Models.Net.t, map) :: list + def call(%Models.Net{} = net, income) do + net |> send_in_events(income) net |> collect_net_results end - defp send_in_events(net, income, root_pid) do + defp send_in_events(net, income) do net - |> net_in_connections - |> Enum.each(&send_event(&1, net, income, root_pid)) + |> NeuronSystem.Net.in_connections + |> Enum.each(&send_event(&1, net, income)) end - defp net_in_connections(%Models.Net{} = net) do - NeuronSystem.Net.connection_manager(net) - |> Processes.ConnectionManager.get_net_in_connections - end - - defp send_event(connection, net, income, root_pid) do + defp send_event(connection, net, income) do value = income[connection.key] - NeuronSystem.Utils.SendToNeuronProxy.call(net, connection, value, root_pid) + NeuronSystem.Utils.SendToNeuronProxy.call(net, connection, value) end defp collect_net_results(net) do net - |> net_out_connections + |> NeuronSystem.Net.out_connections |> collect_results end - defp net_out_connections(%Models.Net{} = net) do - NeuronSystem.Net.connection_manager(net) - |> Processes.ConnectionManager.get_net_out_connections - end - defp collect_results(connections) do connections |> Enum.map(&collect_result_for_connection(&1)) end @@ -53,7 +43,7 @@ defmodule NeuronSystem.Net.Activator do defp collect_result_for_connection(connection) do connection_key = connection.key receive do - {:out_result, connection_key, value} -> {connection.key, value} + {:out_result, ^connection_key, value} -> {connection.key, value} end end end diff --git a/lib/neuron_system/processes/connection_manager.ex b/lib/neuron_system/processes/connection_manager.ex index 2407ae6..cfc2f48 100644 --- a/lib/neuron_system/processes/connection_manager.ex +++ b/lib/neuron_system/processes/connection_manager.ex @@ -21,6 +21,14 @@ defmodule NeuronSystem.Processes.ConnectionManager do GenServer.cast(manager_pid, {:add, connection_model}) end + @doc """ + Gets the list of all connections and returns it + """ + @spec get_all(pid) :: list + def get_all(manager_pid) do + GenServer.call(manager_pid, :get_all) + end + @doc """ Gets a list of all input connection for a whole Net """ @@ -53,11 +61,17 @@ defmodule NeuronSystem.Processes.ConnectionManager do GenServer.call(manager_pid, {:get_neuron_in_out, :out, neuron_id}) end - def handle_cast({:add, connection}, state) do - new_state = [connection | state] - {:noreply, new_state} + @doc """ + Changes weight of the specific connection + """ + @spec change_weight(pid, bitstring, float) :: :ok + def change_weight(manager_pid, connection_id, new_weight) do + GenServer.cast(manager_pid, {:change_weight, connection_id, new_weight}) end + def handle_call(:get_all, _from, state) do + {:reply, state, state} + end def handle_call({:get_net_in_out, :in = _type}, _from, state) do connections = state |> filter_by_type(Models.InConnection) {:reply, connections, state} @@ -76,6 +90,17 @@ defmodule NeuronSystem.Processes.ConnectionManager do {:reply, connections, state} end + def handle_cast({:add, connection}, state) do + new_state = [connection | state] + {:noreply, new_state} + end + def handle_cast({:change_weight, connection_id, new_weight}, state) do + connection = state |> Enum.filter(&(&1.id == connection_id)) |> Enum.at(0) + other_connections = state |> Enum.filter(&(&1.id != connection_id)) + new_connection = %{connection | weight: new_weight} + {:noreply, [new_connection | other_connections]} + end + defp filter_by_type(collection, module) do collection |> Enum.filter(fn(x) -> x.__struct__ == module end) diff --git a/lib/neuron_system/processes/neuron.ex b/lib/neuron_system/processes/neuron.ex index 03969bb..003e55a 100644 --- a/lib/neuron_system/processes/neuron.ex +++ b/lib/neuron_system/processes/neuron.ex @@ -6,12 +6,18 @@ defmodule NeuronSystem.Processes.Neuron do """ use GenServer + use NeuronSystem.BackProp.Processes.Neuron alias NeuronSystem.{Models, Processes, Utils} @spec start_link(NeuronSystem.Models.Neuron.t) :: any def start_link(neuron_model) do - GenServer.start_link(__MODULE__, {neuron_model, %{}}) + options = %{ + income_payloads: %{}, + out_value: nil, + d_out_value: nil + } + GenServer.start_link(__MODULE__, {neuron_model, options}) end # Public API @@ -33,19 +39,22 @@ defmodule NeuronSystem.Processes.Neuron do This event means that some neuron from the previous layer was activated and sent its value to a next layer. """ - @spec income_payload(pid, {binary, float, Models.Net.t, pid}) :: :ok - def income_payload(pid, {source_neuron_id, value, net, root_pid}) do - GenServer.cast(pid, {:income_payload, source_neuron_id, value, net, root_pid}) + @spec income_payload(pid, {binary, float, Models.Net.t}) :: :ok + def income_payload(pid, {source_neuron_id, value, net}) do + GenServer.cast(pid, {:income_payload, source_neuron_id, value, net}) end # Callbacks - def handle_cast({:income_payload, source_neuron_id, value, net, root_pid}, state) do - new_state = NeuronSystem.Utils.NeuronIncomePayloadProcessor.call({source_neuron_id, value, net, root_pid}, state) + def handle_cast({:income_payload, source_neuron_id, value, net}, state) do + new_state = NeuronSystem.Utils.NeuronIncomePayloadProcessor.call( + source_neuron_id, value, net, state + ) {:noreply, new_state} end - def handle_cast(:clear_income_payloads, {neuron_model, _income_payloads}) do - {:noreply, {neuron_model, %{}}} + def handle_cast(:clear_income_payloads, {neuron_model, options}) do + new_options = options |> Map.put(:income_payloads, %{}) + {:noreply, {neuron_model, new_options}} end end diff --git a/lib/neuron_system/utils/neuron_income_payload_processor.ex b/lib/neuron_system/utils/neuron_income_payload_processor.ex index db461e9..7410bb3 100644 --- a/lib/neuron_system/utils/neuron_income_payload_processor.ex +++ b/lib/neuron_system/utils/neuron_income_payload_processor.ex @@ -1,19 +1,43 @@ defmodule NeuronSystem.Utils.NeuronIncomePayloadProcessor do + @moduledoc """ + Encapsulates the whole functional for processing income payload to a neuron. + """ + alias NeuronSystem.{Models, Processes, Utils} - def call({source_neuron_id, value, net, root_pid}, {%Models.Neuron{id: neuron_id} = neuron_model, income_payloads}) do - new_income_payloads = Map.put(income_payloads, source_neuron_id, value) + @type neuron_process_state :: {Models.Neuron.t, map} + + @doc """ + Calls income payload processor + """ + @spec call(atom | bitstring, float, Models.Net.t, neuron_process_state) :: neuron_process_state + def call(source_neuron_id, value, net, {%Models.Neuron{id: neuron_id} = neuron_model, %{income_payloads: income_payloads} = options}) do + neuron_connections = get_neuron_connections(net, neuron_id) + new_income_payloads = income_payloads |> Map.put(source_neuron_id, value) payloads_count = new_income_payloads |> Map.keys |> Enum.count + in_connections_count = neuron_connections[:in] |> Enum.count + if in_connections_count == payloads_count do + {out_value, d_out_value} = send_out_messages(net, neuron_model, new_income_payloads, neuron_connections[:out]) + options = options |> Map.put(:out_value, out_value) |> Map.put(:d_out_value, d_out_value) + end + new_options = options |> Map.put(:income_payloads, new_income_payloads) + {neuron_model, new_options} + end + + defp get_neuron_connections(net, neuron_id) do manager_pid = NeuronSystem.Net.connection_manager(net) neuron_in_connections = Processes.ConnectionManager.get_neuron_in_connections(manager_pid, neuron_id) - if Enum.count(neuron_in_connections) == payloads_count do - payloads_sum_value = new_income_payloads |> Map.values |> Enum.sum - neuron_final_value = NeuronSystem.Neuron.activate(neuron_model, payloads_sum_value) - Processes.ConnectionManager.get_neuron_out_connections(manager_pid, neuron_id) - |> Enum.each(fn(out_connection) -> - Utils.SendToNeuronProxy.call(net, out_connection, neuron_final_value, root_pid) - end) - end - {neuron_model, new_income_payloads} + neuron_out_connections = Processes.ConnectionManager.get_neuron_out_connections(manager_pid, neuron_id) + %{in: neuron_in_connections, out: neuron_out_connections} + end + + defp send_out_messages(net, neuron_model, income_payloads, out_connections) do + payloads_sum_value = income_payloads |> Map.values |> Enum.sum + out_value = NeuronSystem.Neuron.activate(neuron_model, payloads_sum_value) + out_connections |> Enum.each(fn(out_connection) -> + Utils.SendToNeuronProxy.call(net, out_connection, out_value) + end) + d_out_value = out_value * (1 - out_value) + {out_value, d_out_value} end end diff --git a/lib/neuron_system/utils/send_to_neuron_proxy.ex b/lib/neuron_system/utils/send_to_neuron_proxy.ex index 8d449db..7e87b01 100644 --- a/lib/neuron_system/utils/send_to_neuron_proxy.ex +++ b/lib/neuron_system/utils/send_to_neuron_proxy.ex @@ -8,15 +8,15 @@ defmodule NeuronSystem.Utils.SendToNeuronProxy do @doc """ Calls sending event processing """ - @spec call(Models.Net.t, Models.InConnection.t | Models.OutConnection.t | Models.Connection.t, float, pid) :: any - def call(_net, %Models.OutConnection{} = connection, value, root_pid) do + @spec call(Models.Net.t, Models.InConnection.t | Models.OutConnection.t | Models.Connection.t, float) :: any + def call(%Models.Net{root_pid: root_pid} = _net, %Models.OutConnection{} = connection, value) do send root_pid, {:out_result, connection.key, value} end - def call(net, connection, value, root_pid) do + def call(%Models.Net{root_pid: root_pid} = net, connection, value) do neuron_source_key = NeuronSystem.Connection.source(connection) neuron_id = connection.target_neuron payload_value = value * connection.weight neuron_process_pid = NeuronSystem.Net.neuron_process_pid(net, neuron_id) - Processes.Neuron.income_payload(neuron_process_pid, {neuron_source_key, payload_value, net, root_pid}) + Processes.Neuron.income_payload(neuron_process_pid, {neuron_source_key, payload_value, net}) end end diff --git a/mix.exs b/mix.exs index 0ab24f5..38d8872 100644 --- a/mix.exs +++ b/mix.exs @@ -29,8 +29,9 @@ defmodule NeuronSystem.Mixfile do # Type "mix help deps" for more examples and options defp deps do [ - {:ex_doc, "~> 0.13", only: :dev}, - { :uuid, "~> 1.1.4" } + { :ex_doc, "~> 0.13", only: :dev }, + { :uuid, "~> 1.1.4" }, + { :mock, github: "jjh42/mock", ref: "7f2251f781f646a08bb65c85c215f107c9627435" } ] end end diff --git a/mix.lock b/mix.lock index 16ce6c5..5e9e229 100644 --- a/mix.lock +++ b/mix.lock @@ -1,3 +1,5 @@ %{"earmark": {:hex, :earmark, "1.0.1", "2c2cd903bfdc3de3f189bd9a8d4569a075b88a8981ded9a0d95672f6e2b63141", [:mix], []}, "ex_doc": {:hex, :ex_doc, "0.13.0", "aa2f8fe4c6136a2f7cfc0a7e06805f82530e91df00e2bff4b4362002b43ada65", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]}, + "meck": {:hex, :meck, "0.8.4", "59ca1cd971372aa223138efcf9b29475bde299e1953046a0c727184790ab1520", [:rebar, :make], []}, + "mock": {:git, "https://github.com/jjh42/mock.git", "7f2251f781f646a08bb65c85c215f107c9627435", [ref: "7f2251f781f646a08bb65c85c215f107c9627435"]}, "uuid": {:hex, :uuid, "1.1.4", "36c7734e4c8e357f2f67ba57fb61799d60c20a7f817b104896cca64b857e3686", [:mix], []}} diff --git a/test/lib/neuron_system/connection_test.exs b/test/lib/neuron_system/connection_test.exs new file mode 100644 index 0000000..df5723f --- /dev/null +++ b/test/lib/neuron_system/connection_test.exs @@ -0,0 +1,25 @@ +defmodule NeuronSystem.ConnectionTest do + use ExUnit.Case, async: true + + alias NeuronSystem.{Models} + + test "source when connection is input" do + connection = %Models.InConnection{key: :x1} + source_value = NeuronSystem.Connection.source(connection) + assert source_value == :x1 + end + + test "source when connection is between neurons" do + source_neuron = "neuron:sadsdasdas" + connection = %Models.Connection{source_neuron: source_neuron} + source_value = NeuronSystem.Connection.source(connection) + assert source_value == source_neuron + end + + test "source when connection is output" do + source_neuron = "neuron:sadsdasdas" + connection = %Models.OutConnection{source_neuron: source_neuron} + source_value = NeuronSystem.Connection.source(connection) + assert source_value == source_neuron + end +end diff --git a/test/lib/neuron_system/models/connection_test.exs b/test/lib/neuron_system/models/connection_test.exs new file mode 100644 index 0000000..7e23a3e --- /dev/null +++ b/test/lib/neuron_system/models/connection_test.exs @@ -0,0 +1,17 @@ +defmodule NeuronSystem.Models.ConnectionTest do + use ExUnit.Case, async: true + + alias NeuronSystem.{Models} + + test "build builds new connection model" do + source_neuron = %Models.Neuron{id: "source_neuron"} + target_neuron = %Models.Neuron{id: "target_neuron"} + connection = Models.Connection.build(source_neuron, target_neuron, 0.5) + assert %Models.Connection{ + id: "connection" <> _random_uuid, + source_neuron: "source_neuron", + target_neuron: "target_neuron", + weight: 0.5 + } = connection + end +end diff --git a/test/lib/neuron_system/models/in_connection_test.exs b/test/lib/neuron_system/models/in_connection_test.exs new file mode 100644 index 0000000..c04887d --- /dev/null +++ b/test/lib/neuron_system/models/in_connection_test.exs @@ -0,0 +1,16 @@ +defmodule NeuronSystem.Models.InConnectionTest do + use ExUnit.Case, async: true + + alias NeuronSystem.{Models} + + test "build builds new model for input connection" do + neuron_model = %Models.Neuron{id: "target_neuron"} + connection = Models.InConnection.build(neuron_model, 0.5, :x1) + assert %Models.InConnection{ + id: "connection" <> _random_uuid, + target_neuron: "target_neuron", + weight: 0.5, + key: :x1 + } = connection + end +end diff --git a/test/lib/neuron_system/models/neuron_test.exs b/test/lib/neuron_system/models/neuron_test.exs new file mode 100644 index 0000000..051644e --- /dev/null +++ b/test/lib/neuron_system/models/neuron_test.exs @@ -0,0 +1,11 @@ +defmodule NeuronSystem.Models.NeuronTest do + use ExUnit.Case, async: true + + alias NeuronSystem.{Models} + + test "build build new model for neuron" do + activation_func = fn(x) -> x * 3 end + neuron = Models.Neuron.build(activation_func) + assert %Models.Neuron{activation_function: _activation_func} = neuron + end +end diff --git a/test/lib/neuron_system/models/out_connection_test.exs b/test/lib/neuron_system/models/out_connection_test.exs new file mode 100644 index 0000000..b34d78c --- /dev/null +++ b/test/lib/neuron_system/models/out_connection_test.exs @@ -0,0 +1,17 @@ +defmodule NeuronSystem.Models.OutConnectionTest do + use ExUnit.Case, async: true + + alias NeuronSystem.{Models} + + test "build builds new model for output connection" do + neuron_model = %Models.Neuron{id: "source_neuron"} + connection = Models.OutConnection.build(neuron_model, 0.5, :y) + assert %Models.OutConnection{ + id: "connection" <> _random_uuid, + source_neuron: "source_neuron", + weight: 0.5, + key: :y + } = connection + end +end + diff --git a/test/lib/neuron_system/net/activator_test.exs b/test/lib/neuron_system/net/activator_test.exs new file mode 100644 index 0000000..4225e6d --- /dev/null +++ b/test/lib/neuron_system/net/activator_test.exs @@ -0,0 +1,23 @@ +defmodule NeuronSystem.Net.ActivatorTest do + use ExUnit.Case, async: true + + import Mock + + test "call sends events to neurons and waits for responses" do + net = NeuronSystem.Net.create + {:ok, neuronA} = NeuronSystem.Net.add_neuron(net, :perceptron, [threshold: 0.5]) + {:ok, inConnection} = NeuronSystem.Net.add_connection(net, :in, neuronA, 0.5, :x1) + NeuronSystem.Net.add_connection(net, :out, neuronA, 0.5, :y1) + + + with_mock NeuronSystem.Utils.SendToNeuronProxy, [call: fn(_net, _connection, _value) -> :ok end] do + send self(), {:out_result, :y1, 2.3} + + results = NeuronSystem.Net.Activator.call(net, %{x1: 2.0}) + + assert [y1: 2.3] = results + + assert called NeuronSystem.Utils.SendToNeuronProxy.call(net, inConnection, 2.0) + end + end +end diff --git a/test/lib/neuron_system/net_test.exs b/test/lib/neuron_system/net_test.exs new file mode 100644 index 0000000..914848e --- /dev/null +++ b/test/lib/neuron_system/net_test.exs @@ -0,0 +1,152 @@ +defmodule NeuronSystem.NetTest do + use ExUnit.Case + + alias NeuronSystem.{Models} + + test "create adds new supervisor for a Net" do + net_model = NeuronSystem.Net.create + assert %Models.Net{} = net_model + process_info = Process.info(net_model.pid) + assert process_info != nil + end + + test "create saves info to the struct and returns it" do + %Models.Net{pid: net_pid, root_pid: root_pid} = NeuronSystem.Net.create + assert net_pid != nil + assert root_pid == self() + end + + test "create adds default list of childrens for a Net" do + net_model = NeuronSystem.Net.create + childrens = Supervisor.which_children(net_model.pid) + assert Enum.count(childrens) == 1 + end + + test "add_neuron adds new neuron with a custom activation function" do + %Models.Net{pid: net_pid} = net = NeuronSystem.Net.create + childrens = Supervisor.which_children(net_pid) + assert Enum.count(childrens) == 1 + {:ok, %Models.Neuron{}} = NeuronSystem.Net.add_neuron(net, fn(x) -> x end) + childrens = Supervisor.which_children(net_pid) + assert Enum.count(childrens) == 2 + end + + test "add_neuron adds perceptron neuron" do + %Models.Net{pid: net_pid} = net = NeuronSystem.Net.create + childrens = Supervisor.which_children(net_pid) + assert Enum.count(childrens) == 1 + {:ok, %Models.Neuron{}} = NeuronSystem.Net.add_neuron(net, :perceptron, [threshold: 0.5]) + childrens = Supervisor.which_children(net_pid) + assert Enum.count(childrens) == 2 + end + + test "add_neuron adds sigmoid neuron" do + %Models.Net{pid: net_pid} = net = NeuronSystem.Net.create + childrens = Supervisor.which_children(net_pid) + assert Enum.count(childrens) == 1 + {:ok, %Models.Neuron{}} = NeuronSystem.Net.add_neuron(net, :sigmoid, [threshold: 0.5]) + childrens = Supervisor.which_children(net_pid) + assert Enum.count(childrens) == 2 + end + + test "neuron_process_pid returns PID of a neuron's process if such one exists" do + net = NeuronSystem.Net.create + {:ok, %Models.Neuron{id: neuron_id}} = NeuronSystem.Net.add_neuron(net, :sigmoid, [threshold: 0.5]) + neuron_pid = NeuronSystem.Net.neuron_process_pid(net, neuron_id) + assert neuron_pid != nil + end + + test "neuron_process_pid returns nil if such one does not exist" do + net = NeuronSystem.Net.create + neuron_pid = NeuronSystem.Net.neuron_process_pid(net, "neuron:ahahahaa") + assert neuron_pid == nil + end + + test "connection_manager returns PID of a connection manager of a Net" do + net = NeuronSystem.Net.create + connection_manager_pid = NeuronSystem.Net.connection_manager(net) + assert connection_manager_pid != nil + end + + test "add_connection adds link between neurons" do + net = NeuronSystem.Net.create + source_neuron_model = %Models.Neuron{} + target_neuron_model = %Models.Neuron{} + connections = NeuronSystem.Net.get_all_connections(net) + assert Enum.count(connections) == 0 + NeuronSystem.Net.add_connection(net, source_neuron_model, target_neuron_model, 0.3) + connections = NeuronSystem.Net.get_all_connections(net) + assert Enum.count(connections) == 1 + end + + test "add_connection adds income connection of a Net" do + net = NeuronSystem.Net.create + target_neuron_model = %Models.Neuron{} + connections = NeuronSystem.Net.get_all_connections(net) + assert Enum.count(connections) == 0 + NeuronSystem.Net.add_connection(net, :in, target_neuron_model, 0.3, :x1) + connections = NeuronSystem.Net.get_all_connections(net) + assert Enum.count(connections) == 1 + end + + test "add_connection adds output connection of a Net" do + net = NeuronSystem.Net.create + target_neuron_model = %Models.Neuron{} + connections = NeuronSystem.Net.get_all_connections(net) + assert Enum.count(connections) == 0 + NeuronSystem.Net.add_connection(net, :out, target_neuron_model, 0.3, :y1) + connections = NeuronSystem.Net.get_all_connections(net) + assert Enum.count(connections) == 1 + end + + test "in_connections returns input connections for a Net" do + net = NeuronSystem.Net.create + target_neuron_model = %Models.Neuron{} + connections = NeuronSystem.Net.in_connections(net) + assert Enum.count(connections) == 0 + NeuronSystem.Net.add_connection(net, :in, target_neuron_model, 0.3, :x1) + connections = NeuronSystem.Net.in_connections(net) + assert Enum.count(connections) == 1 + end + + test "out_connection returns output connections of a Net" do + net = NeuronSystem.Net.create + target_neuron_model = %Models.Neuron{} + connections = NeuronSystem.Net.out_connections(net) + assert Enum.count(connections) == 0 + NeuronSystem.Net.add_connection(net, :out, target_neuron_model, 0.3, :y1) + connections = NeuronSystem.Net.out_connections(net) + assert Enum.count(connections) == 1 + end + + test "get_all_connections returns the list of all connections in a Net" do + net = NeuronSystem.Net.create + source_neuron_model = %Models.Neuron{} + target_neuron_model = %Models.Neuron{} + connections = NeuronSystem.Net.get_all_connections(net) + assert Enum.count(connections) == 0 + NeuronSystem.Net.add_connection(net, source_neuron_model, target_neuron_model, 0.3) + NeuronSystem.Net.add_connection(net, :in, target_neuron_model, 0.3, :x1) + NeuronSystem.Net.add_connection(net, :out, source_neuron_model, 0.3, :y1) + connections = NeuronSystem.Net.get_all_connections(net) + assert Enum.count(connections) == 3 + end + + test "neuron_out_connections returns a list of out connection of a neuron" do + net = NeuronSystem.Net.create + source_neuron_model = %Models.Neuron{id: "source_neuron"} + target_neuron_model = %Models.Neuron{id: "target_neuron"} + NeuronSystem.Net.add_connection(net, source_neuron_model, target_neuron_model, 0.3) + connections = NeuronSystem.Net.neuron_out_connections(net, source_neuron_model.id) + assert Enum.count(connections) == 1 + end + + test "neuron_in_connections returns a list of out connection of a neuron" do + net = NeuronSystem.Net.create + source_neuron_model = %Models.Neuron{id: "source_neuron"} + target_neuron_model = %Models.Neuron{id: "target_neuron"} + NeuronSystem.Net.add_connection(net, source_neuron_model, target_neuron_model, 0.3) + connections = NeuronSystem.Net.neuron_in_connections(net, target_neuron_model.id) + assert Enum.count(connections) == 1 + end +end diff --git a/test/lib/neuron_system/neuron_test.exs b/test/lib/neuron_system/neuron_test.exs new file mode 100644 index 0000000..029c719 --- /dev/null +++ b/test/lib/neuron_system/neuron_test.exs @@ -0,0 +1,12 @@ +defmodule NeuronSystem.NeuronTest do + use ExUnit.Case, async: true + + alias NeuronSystem.{Models} + + test "activate calls activation function with a given net value" do + neuron_model = %Models.Neuron{activation_function: fn(x) -> x * 2 end} + net_value = 3 + activation_result = NeuronSystem.Neuron.activate(neuron_model, net_value) + assert activation_result == 6 + end +end diff --git a/test/lib/neuron_system/utils/common_helper_test.exs b/test/lib/neuron_system/utils/common_helper_test.exs new file mode 100644 index 0000000..a8c3992 --- /dev/null +++ b/test/lib/neuron_system/utils/common_helper_test.exs @@ -0,0 +1,8 @@ +defmodule NeuronSystem.Utils.CommonHelperTest do + use ExUnit.Case, async: true + + test "gen_process_id generates string with a given prefix and random suffix" do + process_id = NeuronSystem.Utils.CommonHelper.gen_process_id("neuron") + assert String.contains?(process_id, "neuron") + end +end diff --git a/test/lib/neuron_system/utils/send_to_neuron_proxy_test.exs b/test/lib/neuron_system/utils/send_to_neuron_proxy_test.exs new file mode 100644 index 0000000..8f68aae --- /dev/null +++ b/test/lib/neuron_system/utils/send_to_neuron_proxy_test.exs @@ -0,0 +1,31 @@ +defmodule NeuronSystem.Utils.SendToNeuronProxyTest do + use ExUnit.Case, async: false + + alias NeuronSystem.{Models, Processes} + + import Mock + + test "call sends message to the root if connection is output" do + net = %Models.Net{root_pid: self()} + connection = %Models.OutConnection{key: :y1} + value = 10 + NeuronSystem.Utils.SendToNeuronProxy.call(net, connection, value) + receive do + {:out_result, :y1, out_value} -> assert value == out_value + after + 1_000 -> assert false + end + end + + test "call sends income payload to a neurons in a next layer" do + with_mocks([ + {Processes.Neuron, [], [income_payload: fn(_pid, {_source_key, _payload_value, _net}) -> :ok end]}, + {NeuronSystem.Net, [], [neuron_process_pid: fn(_net, _neuron_id) -> "pid2" end]} + ]) do + net = %Models.Net{root_pid: self()} + connection = %Models.Connection{weight: 0.5, source_neuron: "source_neuron", target_neuron: "target_neuron"} + NeuronSystem.Utils.SendToNeuronProxy.call(net, connection, 5) + assert called Processes.Neuron.income_payload("pid2", {"source_neuron", 2.5, net}) + end + end +end diff --git a/test/lib/neuron_system/utils/spec_helper_test.exs b/test/lib/neuron_system/utils/spec_helper_test.exs new file mode 100644 index 0000000..1224af0 --- /dev/null +++ b/test/lib/neuron_system/utils/spec_helper_test.exs @@ -0,0 +1,59 @@ +defmodule NeuronSystem.Utils.SpecHelperTest do + use ExUnit.Case, async: true + + alias NeuronSystem.{Models} + + defmodule TestProcess do + end + + test "build_supervisor_spec builds specification for a supervisor process" do + spec = NeuronSystem.Utils.SpecHelper.build_supervisor_spec(TestProcess, [], "test_process") + assert {:ok, + {"test_process:" <> _random_uuid, { + NeuronSystem.Utils.SpecHelperTest.TestProcess, + :start_link, [] + }, + :permanent, + :infinity, + :supervisor, + [NeuronSystem.Utils.SpecHelperTest.TestProcess] + } + } = spec + end + + test "build_worker_spec builds specification for a worker process" do + worker_id = "test_process:a1212" + spec = NeuronSystem.Utils.SpecHelper.build_worker_spec(TestProcess, [], worker_id) + assert { + :ok, + {"test_process:a1212", { + NeuronSystem.Utils.SpecHelperTest.TestProcess, + :start_link, + [] + }, + :permanent, + 5000, + :worker, + [NeuronSystem.Utils.SpecHelperTest.TestProcess] + } + } = spec + end + + test "build_neuron_worker_spec builds specification for a neuron process" do + neuron_model = %Models.Neuron{id: "neuron:1234"} + spec = NeuronSystem.Utils.SpecHelper.build_neuron_worker_spec(neuron_model) + assert { + :ok, { + "neuron:1234", { + NeuronSystem.Processes.Neuron, + :start_link, + [%NeuronSystem.Models.Neuron{activation_function: nil, id: "neuron:1234"}] + }, + :permanent, + 5000, + :worker, + [NeuronSystem.Processes.Neuron] + } + } = spec + end +end diff --git a/test/lib/neuron_system_test.exs b/test/lib/neuron_system_test.exs new file mode 100644 index 0000000..8cc57f2 --- /dev/null +++ b/test/lib/neuron_system_test.exs @@ -0,0 +1,44 @@ +defmodule NeuronSystemTest do + use ExUnit.Case + + test "forward way of a simple Net with 3 layers" do + income = %{x1: 1.0, x2: 0.0} + + # Create network + net = NeuronSystem.Net.create + + # Create needed neurons + {:ok, neuronA1} = NeuronSystem.Net.add_neuron(net, :sigmoid, [threshold: 0.0]) + {:ok, neuronA2} = NeuronSystem.Net.add_neuron(net, :sigmoid, [threshold: 0.0]) + {:ok, neuronA3} = NeuronSystem.Net.add_neuron(net, :sigmoid, [threshold: 0.0]) + {:ok, neuronB1} = NeuronSystem.Net.add_neuron(net, :sigmoid, [threshold: 0.0]) + {:ok, neuronB2} = NeuronSystem.Net.add_neuron(net, :sigmoid, [threshold: 0.0]) + {:ok, neuronC1} = NeuronSystem.Net.add_neuron(net, :sigmoid, [threshold: 0.0]) + + # Add in connection + NeuronSystem.Net.add_connection(net, :in, neuronA1, 0.19592, :x1) + NeuronSystem.Net.add_connection(net, :in, neuronA2, 0.32244, :x1) + NeuronSystem.Net.add_connection(net, :in, neuronA3, 0.16243, :x1) + NeuronSystem.Net.add_connection(net, :in, neuronA1, 0.6, :x2) + NeuronSystem.Net.add_connection(net, :in, neuronA2, -0.5, :x2) + NeuronSystem.Net.add_connection(net, :in, neuronA3, 0.4, :x2) + + # Add out connection + NeuronSystem.Net.add_connection(net, :out, neuronC1, 1.0, :y) + + # Add connection in hidden layers + NeuronSystem.Net.add_connection(net, neuronA1, neuronB1, -0.0855) + NeuronSystem.Net.add_connection(net, neuronA1, neuronB2, -0.398) + NeuronSystem.Net.add_connection(net, neuronA2, neuronB1, 0.715) + NeuronSystem.Net.add_connection(net, neuronA2, neuronB2, 1.10207) + NeuronSystem.Net.add_connection(net, neuronA3, neuronB1, 0.374) + NeuronSystem.Net.add_connection(net, neuronA3, neuronB2, 0.79194) + NeuronSystem.Net.add_connection(net, neuronB1, neuronC1, 0.831) + NeuronSystem.Net.add_connection(net, neuronB2, neuronC1, 0.133) + + # Activate it! + results = NeuronSystem.Net.activate!(net, income) + + assert [y: 0.6511113215704272] = results + end +end diff --git a/test/neuron_system_test.exs b/test/neuron_system_test.exs deleted file mode 100644 index 2fe4d4d..0000000 --- a/test/neuron_system_test.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule NeuronSystemTest do - use ExUnit.Case - doctest NeuronSystem - - test "the truth" do - assert 1 + 1 == 2 - end -end