only-brain 0.3.1

A simple Neural Network library, without the learning part.
Documentation

Only Brain

A very simple Neural Network library built in Rust with the objective to allow the user to create, manipulate and train a neural network directly. The user has direct access to weights and biases of the network, allowing them to manipulate the NN as wanted: "without the learning part", so you can bring your own.

Usage

The input and output widths are part of the network's type, so feeding a wrongly sized input is a compile error. The hidden layers are given at construction time. Weights and biases are plain arrays, slices or Vecs, so you don't need nalgebra to use the network.

use only_brain::NeuralNetwork;

fn main() {
    // A 2 -> 2 -> 1 network.
    let mut nn = NeuralNetwork::<2, 1>::new(&[2]);

    // One row per neuron, one weight per neuron of the previous layer.
    nn.set_layer_weights(1, &[[0.1, 0.2],
                              [0.3, 0.4]]);
    nn.set_layer_biases(1, &[0.1, 0.2]);

    nn.set_layer_weights(2, &[[0.9, 0.8]]);
    nn.set_layer_biases(2, &[0.1]);

    let output = nn.feed_forward(&[0.5, 0.2]);

    println!("{:?}", output);
}

Activation per layer

Every layer has its own activation function, so hidden layers and the output layer can differ. The available ones are Sigmoid (the default), Tanh, ReLU, BinaryStep and Identity.

use only_brain::{ActivationFunction, NeuralNetwork};

let mut nn = NeuralNetwork::<10, 3>::new(&[8]);
nn.set_activation_function(ActivationFunction::ReLU); // every layer
nn.set_output_activation(ActivationFunction::Tanh);   // then just the output

Evolving networks: the flat parameter view

Genetic algorithms and other black-box searches work on a flat list of numbers. A network converts to and from one in a documented, stable order: layer by layer, each layer's weights row by row (one row per neuron), then that layer's biases.

use only_brain::NeuralNetwork;

// The genome length for a 10 -> 8 -> 3 network.
let genes = NeuralNetwork::<10, 3>::parameter_count_for(&[8]); // 115

// In a fitness function: a genome becomes a network.
let genome = vec![0.0; genes];
let nn = NeuralNetwork::<10, 3>::from_parameters(&[8], &genome);

// And a network becomes a genome.
assert_eq!(nn.parameters(), genome);

set_parameters updates an existing network in place, keeping its activation functions. NeuralNetwork is Send + Sync, so fitness can be scored in parallel. See examples/xor_evolution.rs for a complete, seeded neuroevolution run.

Saving and loading

Saving and loading a model checks the stored shape against the type you ask for:

use only_brain::{dump_model, load_model, NeuralNetwork};

# fn main() -> Result<(), Box<dyn std::error::Error>> {
let nn = NeuralNetwork::<2, 1>::new(&[2]);
dump_model(&nn, "model.bin")?;

let loaded: NeuralNetwork<2, 1> = load_model("model.bin")?;
# Ok(())
# }

Models are stored in a small, versioned binary format (documented in src/io.rs) that does not depend on any serialization library, so saved models keep loading as the library evolves. Files written by 0.1 and 0.2 still load. write_model and read_model do the same with any Write or Read, such as a Vec<u8>.

NeuralNetwork also implements serde's Serialize and Deserialize through a plain form ({"layers": [{"activation", "weights", "biases"}]}), so it can be embedded in your own types and formats, such as JSON. Deserializing validates the shape too.

Examples

Example Shows
neural_network Building a network by hand and running it.
dump_load Saving and loading a model.
xor_evolution Evolving a network with a genetic algorithm through the flat parameter view.
perceptron A single perceptron.
perceptron_iris Training a perceptron on the Iris dataset with the perceptron rule.

Run one with cargo run --example xor_evolution.

TODO

  • Abstract better the DVector and DMatrix from the user's perspective;
    • Perceptron
    • Neural Network
  • Make the types know the size of the NeuralNetwork/inputs and outputs neurons;
    • Input and output widths are compile-time checked; hidden layers stay dynamic.
  • Fix and improve model dump and load;
    • Typed errors, buffered IO, and shape validation on load
    • Versioned format header, without bincode
  • Flat parameter view, for genetic algorithms
  • Activation function per layer