network_evaluator (faber_tweann v2.4.0)

View Source

Synchronous neural network evaluator for inference.

This module provides synchronous (blocking) forward propagation for neural networks. Unlike the process-based cortex/neuron approach used during training, this is designed for fast inference in real-time applications like games.

Usage

Create a network from a genotype: {ok, Network} = network_evaluator:from_genotype(AgentId)

Or create a simple feedforward network: Network = network_evaluator:create_feedforward(42, [16, 8], 6)

Evaluate: Outputs = network_evaluator:evaluate(Network, Inputs)

Summary

Functions

Compile network for NIF acceleration.

Create a CfC feedforward network with random weights and CfC neuron metadata.

Create a feedforward network with random weights.

Create a feedforward network with specified activation.

Create a feedforward network with separate output activation.

Evaluate the network with given inputs.

Evaluate with reward-modulated (three-factor) plasticity.

Evaluate with online Hebbian plasticity (memory-by-learning).

Evaluate the network with stateful CfC processing.

Deserialize a network from binary.

Load a network from a genotype stored in ETS, weights included.

Deserialize a network from a JSON-compatible map.

Get the hidden layer activation function from a network.

Read the CfC internal state (the memory carrier); undefined for a plain net.

Get the layer list from a network.

Get neuron metadata from a CfC network.

Get the output layer activation function from a network.

Get network topology information for visualization.

Get visualization data for rendering the network.

Get all weights from the network as a flat list.

Reset internal state of a CfC network to zeros.

Set neuron metadata on a network.

Set weights from a flat list.

Strip the compiled_ref from a network to release NIF memory.

Serialize a network to binary using Erlang term format.

Serialize a network to a JSON-compatible map.

Types

layer/0

-type layer() :: {Weights :: [[float()]], Biases :: [float()]}.

layer_meta/0

-type layer_meta() :: [neuron_meta()].

network/0

-type network() ::
          #network{layers :: [layer()],
                   activation :: atom(),
                   output_activation :: atom() | undefined,
                   compiled_ref :: reference() | undefined,
                   neuron_meta :: [layer_meta()] | undefined,
                   internal_state :: [[float()]] | undefined}.

neuron_meta/0

-type neuron_meta() :: #{neuron_type := standard | cfc, tau := float(), state_bound := float()}.

Functions

compile_for_nif(Network)

-spec compile_for_nif(network()) -> network().

Compile network for NIF acceleration.

If the NIF is loaded, compiles the network to a flat representation that can be evaluated much faster. Falls back to Erlang evaluation if NIF is not available.

WARNING: Use sparingly! Each compiled network holds a Rust ResourceArc reference that keeps native memory alive. During neuroevolution, do NOT compile networks automatically (especially in create_feedforward or set_weights) as this causes massive memory leaks - one compiled_ref per offspring per generation accumulates unboundedly.

Only call this when you need maximum performance for a specific network that will be evaluated many times (e.g., the final champion network).

create_cfc_feedforward(InputSize, HiddenSizes, OutputSize, Activation, OutputActivation)

-spec create_cfc_feedforward(pos_integer(), [pos_integer()], pos_integer(), atom(), atom() | undefined) ->
                                network().

Create a CfC feedforward network with random weights and CfC neuron metadata.

Same layer structure as create_feedforward but hidden neurons are CfC type. Output neurons remain standard. CfC neurons use evaluate_cfc for temporal adaptation with learnable time constants.

create_feedforward(InputSize, HiddenSizes, OutputSize)

-spec create_feedforward(pos_integer(), [pos_integer()], pos_integer()) -> network().

Create a feedforward network with random weights.

create_feedforward(InputSize, HiddenSizes, OutputSize, Activation)

-spec create_feedforward(pos_integer(), [pos_integer()], pos_integer(), atom()) -> network().

Create a feedforward network with specified activation.

create_feedforward(InputSize, HiddenSizes, OutputSize, Activation, OutputActivation)

-spec create_feedforward(pos_integer(), [pos_integer()], pos_integer(), atom(), atom() | undefined) ->
                            network().

Create a feedforward network with separate output activation.

Hidden layers use Activation, output layer uses OutputActivation. If OutputActivation is undefined, all layers use Activation.

evaluate(Network, Inputs)

-spec evaluate(network(), [float()]) -> [float()].

Evaluate the network with given inputs.

Performs synchronous forward propagation through all layers. Uses NIF acceleration if available and network was compiled.

evaluate_with_activations(Network, Inputs)

evaluate_with_neuromod(Net, Inputs, Rule, M)

-spec evaluate_with_neuromod(network(),
                             [float()],
                             {float(), float(), float(), float(), float()} |
                             {oja, float()} |
                             {pc, [[[{float(), float(), float(), float()}]]], float()},
                             float()) ->
                                {[float()], network()}.

Evaluate with reward-modulated (three-factor) plasticity.

Identical to evaluate_with_plasticity/3, but every weight change is gated by a neuromodulatory signal M (typically a reward): dW = M * Eta * (...). This is the third factor of the classic pre x post x neuromodulator rule -- the piece a fixed Hebbian rule lacks. M scales the rule's learning rate, so M = 0 freezes learning, M above zero reinforces the just-active pathway, M below zero reverses it. Works for the global ABCD, Oja, and per-connection rule shapes alike (M multiplies the whole update).

evaluate_with_plasticity(Network, Inputs, Rule)

-spec evaluate_with_plasticity(network(),
                               [float()],
                               {float(), float(), float(), float(), float()} |
                               {oja, float()} |
                               {pc, [[[{float(), float(), float(), float()}]]], float()}) ->
                                  {[float()], network()}.

Evaluate with online Hebbian plasticity (memory-by-learning).

Forward pass, then update every weight by a Hebbian rule using the pre- and post-synaptic activations of each layer. Three rule shapes are supported: {A,B,C,D,Eta} global ABCD-Hebbian, ONE rule for all synapses: dW = Eta * (A*pre*post + B*pre + C*post + D) {oja, Eta} Oja's self-normalising rule: dW = Eta*post*(pre-post*w) {pc, CoeffLayers, Eta} PER-CONNECTION ABCD: each synapse has its OWN {A,B,C,D} (CoeffLayers mirrors the weight structure), Eta shared. Weights are clamped to [-10, 10]. The evolutionary search tunes the RULE, not the weights, so the network adapts its own weights within an episode. Global ABCD is the special case of per-connection where all synapses share coefficients -- per-connection is strictly more expressive. Biases are not plastic.

evaluate_with_state(Network, Inputs)

-spec evaluate_with_state(network(), [float()]) -> {[float()], network()}.

Evaluate the network with stateful CfC processing.

For standard feedforward networks (no neuron_meta), behaves identically to evaluate/2 but returns {Outputs, Network} tuple.

For CfC networks, each CfC neuron updates its internal state based on the input-dependent time constant, enabling temporal reasoning.

from_binary(Binary)

-spec from_binary(binary()) -> {ok, network()} | {error, term()}.

Deserialize a network from binary.

from_genotype(AgentId)

-spec from_genotype(term()) -> {ok, network()} | {error, term()}.

Load a network from a genotype stored in ETS, weights included.

Faithful or nothing. A genotype whose topology cannot be represented as a stack of dense layers returns {error, {not_layerable, Why}} naming what stopped it, rather than an approximation reported as success.

Until ROADMAP 8a this function counted the neurons, invented a layer shape and filled it with random weights, while its own doc claimed to read the weights from Mnesia. There is no Mnesia and there were no weights. See genotype_to_network for the five conditions that make a genotype unconvertible.

Note this drops per-synapse tuning state (delta, learning rate, parameters), because the evaluator has nowhere to hold it. A converted network is an inference artifact, not a resumable genotype.

from_json(Map)

-spec from_json(map()) -> {ok, network()} | {error, term()}.

Deserialize a network from a JSON-compatible map.

Accepts the format produced by to_json/1.

get_activation(Network)

-spec get_activation(network()) -> atom().

Get the hidden layer activation function from a network.

See get_layers/1 for why callers outside this module should use accessors.

get_internal_state(Network)

-spec get_internal_state(network()) -> [[float()]] | undefined.

Read the CfC internal state (the memory carrier); undefined for a plain net.

get_layers(Network)

-spec get_layers(network()) -> [layer()].

Get the layer list from a network.

Accessor for consumers outside this module. Use this rather than destructuring the network tuple directly, so that adding fields to the network record cannot silently break callers.

get_neuron_meta(Network)

-spec get_neuron_meta(network()) -> [layer_meta()] | undefined.

Get neuron metadata from a CfC network.

Returns undefined for standard feedforward networks.

get_output_activation(Network)

-spec get_output_activation(network()) -> atom() | undefined.

Get the output layer activation function from a network.

Returns undefined when the output layer uses the same activation as the hidden layers.

get_topology(Network)

-spec get_topology(network()) -> map().

Get network topology information for visualization.

Returns a map with layer sizes for rendering the network structure.

get_viz_data(Network, Inputs, InputLabels)

-spec get_viz_data(network(), [float()], [binary()]) -> map().

Get visualization data for rendering the network.

Combines topology, weights, and activations into a format suitable for frontend visualization.

get_weights(Network)

-spec get_weights(network()) -> [float()].

Get all weights from the network as a flat list.

Useful for evolution - can be mutated and set back.

reset_internal_state(Network)

-spec reset_internal_state(network()) -> network().

Reset internal state of a CfC network to zeros.

Call this at the start of each episode to prevent state leakage between independent evaluation sequences (e.g., between game rounds).

set_neuron_meta(Net, Meta)

-spec set_neuron_meta(network(), [layer_meta()] | undefined) -> network().

Set neuron metadata on a network.

Used by mutation operators to update CfC parameters (tau, state_bound).

set_weights(Network, FlatWeights)

-spec set_weights(network(), [float()]) -> network().

Set weights from a flat list.

The list must have the same number of elements as returned by get_weights/1. NOTE: Does NOT compile for NIF - this prevents memory leaks during evolution.

strip_compiled_ref(Network)

-spec strip_compiled_ref(Network :: network() | map() | term()) -> network() | map() | term().

Strip the compiled_ref from a network to release NIF memory.

IMPORTANT: Call this before storing networks long-term (archives, events) to prevent NIF ResourceArc references from accumulating and causing memory leaks. The compiled_ref is a Rust ResourceArc that holds native memory - keeping references alive prevents the memory from being freed.

The network can be recompiled on-demand when needed for evaluation.

to_binary(Network)

-spec to_binary(network()) -> binary().

Serialize a network to binary using Erlang term format.

This is more compact than JSON and preserves exact floating point values. Use this for Erlang-to-Erlang transfer or storage.

to_json(Network)

-spec to_json(network()) -> map().

Serialize a network to a JSON-compatible map.

The output format is suitable for JSON encoding and can be loaded in other runtimes (Python, JavaScript, etc.) for inference.

Format: A map with keys "version", "activation", and "layers". The layers list contains maps with "weights" and "biases" keys.