Basic usage¶
This section showcases the main components of the library by detailing the contents of examples/basics.py.
1import os
2
3from abrain import Genome, Point3D as Pt, ANN3D
4from examples.common import example_path
We start by importing the essential components, aliased directly under the main package:
Genomeabstracts the evolvable part of the libraryANN3Dis the callable object representing an Artificial Neural Network of emergent topology in 3 dimensionsPoint3Ddescribes a coordinate in the substrate (“the brain”)
Note
While we illustrate here the use of a 3D ANN, everything (but the 3D rendering)
works identically for abrain.ANN2D
11data = Genome.Data.create_for_eshn_cppn(dimension=3, seed=seed)
12g = Genome.random(data)
13for _ in range(mutations):
14 g.mutate(data)
To create a Genome we first need a Data
object to store all the shared parameters (labels, random number generator,
id managers, …).
This is done either via create_for_generic_cppn(), when
using the CPPN directly, or with
create_for_eshn_cppn(), when using this genome to create
ANN3D through ES-HyperNEAT.
We can then generate a random Genome by providing this structure to
random().
To simulate an evolutionary process, we subject this Genome g to a number of
undirected mutations (see Mutations).
Again, the shared Data must be provided.
17inputs = [Pt(-1, -1, -1), Pt(-1, -1, 1), Pt(1, -1, -1), Pt(1, -1, 1)]
18outputs = [Pt(0, 1, -1), Pt(0, 1, 0), Pt(0, 1, 1)]
Before instantiating the ANN, we define the coordinates of the neural inputs (sensors) and outputs (effectors). Thanks to the ES-HyperNEAT algorithms the topology (hidden neurons & connections) will be automatically determined. As much as possible, the provided coordinates should respect the geometrical relationships (i.e. bilateral symmetry, front-back …).
Warning
It is essential that neurons are placed at unique coordinates including hidden ones. Safe coordinates for inputs/outputs are of the form
with \(1 \leq i_j \leq\) maxDepth
In particular, this means that all outside planes (e.g. \(y=\pm 1\)) can never contain hidden neurons and are thus safe for user-defined inputs/outputs
21ann = ANN3D.build(inputs, outputs, g)
22print(f"empty ANN: {ann.empty()}")
23print(f"maximal depth: {ann.stats().depth}")
Creating the ANN is then as trivial as calling the static
build() function with the set of inputs/outputs and the
evolved genome.
Various statistics can be queried on the resulting object including whether the
build procedure resulted in a functional network.
26ann.render3D().write_html(example_path("./sample_ann.html"))
Optionally, one can produce a 3D rendering of the network through the utility
function render3D().
29inputs, outputs = ann.buffers()
30inputs[0] = 1
31inputs[1:3] = [data.rng.uniform(-1, 1) for _ in range(2)]
32inputs[3] = -1
Actually using the ANN requires defining the neural inputs at a given time step, which can be done by direct assignment (line 29) or through slices (line 30). At the same time we also retrieve the output buffer which will store the neural responses computed in the next step.
Note
The default activation function for every hidden and output neurons maps 0 to 0. By contrast input neurons expose the exact same value as that provided. This means that providing constant, small values might result in the whole network staying in a quiescent state.
35n = 5
36ann(inputs, outputs, substeps=n)
Following that, we can query the outbound activity by invoking the ANN with both buffers. An optional parameter substeps can be provided if more than a single activation step is desired, e.g. deep networks with a low update rate.
39print("Outputs:", outputs[0], outputs[1:3])
As with the input buffer, the results can be queried individually or in bulk to set the robot’s outputs (motors…).