Only Brain
A small feed-forward neural network library for Rust, without the learning part.
Only Brain gives you the network: fully connected layers, an activation function per layer, a forward pass, and direct access to every weight and bias. How those weights are found is up to you. Backpropagation, a genetic algorithm, random search or a formula on a napkin all work, because the library never hides the numbers.
- Shape in the type.
NeuralNetwork<IN, OUT>makes a wrongly sized input a compile error. Hidden layers are chosen at runtime. - Plain data at the boundary. Weights and biases go in and out as arrays, slices
and
Vecs. You never touch a matrix type. - Two views of the same network. Read and write it layer by layer, or as one flat list of parameters for black-box search.
- Stable on disk. A small, versioned binary format with no serialization dependency, plus serde for everything else. Old files keep loading.
Installation
Quick start
use NeuralNetwork;
new draws the weights uniformly from [-1, 1) with zero biases. Use
new_with_rng for a seeded, reproducible start.
To run many inputs through the same network, such as a whole dataset,
feed_forward_batch takes a slice of inputs and returns one output per input. It
computes the same values as feed_forward but runs each layer as one matrix product
over every input, which is faster from a few dozen inputs up.
use NeuralNetwork;
let nn = new;
let inputs = ;
let outputs = nn.feed_forward_batch;
assert_eq!;
Training it your way
Layer by layer
For methods that reason about layers, such as backpropagation, every layer's weights and biases can be read and replaced, and single values can be touched directly. Layer 0 is the input layer, so the first layer with weights is layer 1.
use NeuralNetwork;
let mut nn = new;
// Read a layer's weights, adjust them, write them back.
let mut rows = nn.layer_weights;
rows += 0.01;
nn.set_layer_weights;
// Or address one weight or bias: (layer, neuron, input) and (layer, neuron).
nn.set_weight;
nn.set_bias;
As a flat list of parameters
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 NeuralNetwork;
// The genome length for a 10 -> 8 -> 3 network.
let genes = parameter_count_for; // 115
// In a fitness function: a genome becomes a network.
let genome = vec!;
let nn = from_parameters;
// And a network becomes a genome.
assert_eq!;
set_parameters updates an existing network in place and keeps its activation
functions, which are part of the shape rather than the parameters.
NeuralNetwork is Send + Sync, so a population can be scored in parallel. See
examples/xor_evolution.rs
for a complete, seeded neuroevolution run.
Activation functions
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 ;
let mut nn = new;
nn.set_activation_function; // every layer
nn.set_output_activation; // then just the output
nn.set_layer_activation; // or one layer
Saving and loading
Saving and loading a model checks the stored shape against the type you ask for:
use ;
#
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>.
Every failure is a ModelError that says what went wrong: a file that is not a
model, one written by a newer version, a truncated file, or a shape that does not
match the requested type.
serde
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. Deserializing validates the shape too.
use NeuralNetwork;
#
Perceptron
A single neuron over a compile-time-sized vector, for the classics.
use ;
let mut and_gate = new;
and_gate.set_weights;
and_gate.set_bias;
assert_eq!;
assert_eq!;
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.
Development
RUSTDOCFLAGS="-D warnings"
CI runs the same checks on every push and pull request. The README is compiled as doctests, so its examples cannot go stale.
Changelog
See CHANGELOG.md.
License
MIT. See LICENSE.