7 releases
0.1.6 | Nov 15, 2024 |
---|---|
0.1.5 | Feb 11, 2024 |
0.1.3 | Jan 25, 2024 |
0.1.2 | Sep 20, 2023 |
0.1.0 | Sep 19, 2022 |
#32 in Science
129 downloads per month
110KB
1.5K
SLoC
Perestroika
Perestroika
- a library for simulating evolution.
Description
Perestroika
is a perhaps naive library that provides primitives to design simulations that are based on genetic algorithms.
These primitives include Node
s which are agreggated into Layer
s,
which in turn are interconnected with Connection
s.
The whole structure is then considered to be a Genome
,
which is capable of trying to propagate an input to an output.
Genomes are also subjects to mutation events
, which can happen randomly or controllably.
This can be used to drive optimization processes and evolution simulations.
Unlike CNNs, in Perestroika
, the connections can connect nodes from different layers, as long as the relationship between
the source and target is from a shallow layer to a deeper layer.
Usage
Add this to your Cargo.toml
:
[dependencies]
perestroika = "0.1"
Or
cargo add perestroika
Quick start
Since the crate relies heavily on randomness, in many examples a specific seed is used for the sake of reproducibility, this is an intended use case of the crate as well.
Minimal
To generate the smallest useful Genome
with an input and an output Layer
s and a Connection
:
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
use perestroika::Error;
use perestroika::Genome;
use perestroika::GenomeBuilder;
use perestroika::DepthType;
// This genome uses ChaCha8Rng as the random number generator.
// Let's also set a seed for reproducibility:
let rng: ChaCha8Rng = ChaCha8Rng::seed_from_u64(42);
// It is a very simple genome, consisting of a single input node a single output node
// and a connection between them.
let mut genome: Genome = GenomeBuilder::new()
.with_shape(&[1, 1])?
.with_fully_connected_layers(&[[DepthType::Input, DepthType::Output]])?
.build()?;
// Nodes by default use the Identity activation function.
let output = genome.propagate(&[0.42])?;
assert_ne!(output, &[0.0]);
# Ok::<(), Error>(())
More practical
Generating a more complex genome and propagating through it.
use rand::Rng;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
use perestroika::Error;
use perestroika::Genome;
use perestroika::GenomeBuilder;
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1234567);
let mut genome: Genome = GenomeBuilder::new()
.with_shape(&vec![8, 6, 4, 2, 4, 6, 4])?
.with_cnn_structure()?
.build()?;
// Let's create an input vector:
let input_vector: Vec<f64> = vec![0.1, 0.2, 0.3, 0.31428, 0.424242, 0.999, -0.17, 0.5];
// Be sure that the input vector and the InputLayer have matching dimensions.
// Propagate it through the genome to get the output:
let output: Vec<f64> = genome.propagate(&input_vector)?;
// Output: [1.0, 0.20511543379524064, -1.0, 0.15121278824891932].
# Ok::<(), Error>(())
Using different random number generators
It is possible to use other random number generators, as long as they impl
Rng + Clone
.
A possible place to use the generator is in the mutate_randomly
method.
use rand::rngs::StdRng;
use rand::Rng;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
use perestroika::Error;
use perestroika::Genome;
use perestroika::GenomeBuilder;
let mut std_rng = StdRng::seed_from_u64(17);
let mut chacha8_rng = rand_chacha::ChaCha8Rng::seed_from_u64(17);
let mut genome: Genome = GenomeBuilder::new()
.with_shape(&vec![4, 16, 4])?
.with_cnn_structure()?
.build()?;
// Mutate the genome.
genome.mutate_randomly(&mut std_rng)?;
genome.mutate_randomly(&mut chacha8_rng)?;
# Ok::<(), Error>(())
Documentation and examples
See the crate's documentation for the docs.
Check out the examples
directory for examples to get going:
examples/01_simple_node.rs
- Creating a number of simple nodes.examples/02_simlpe_connection.rs
- Creating a couple nodes and a connection between them.examples/03_simple_genome.rs
- Creating a couple of genomes through different methods.examples/04_simple_propagation.rs
- Create a genome and propagate an input to an output.examples/05_simple_mutations.rs
- Create a genome and initiate specific mutations and a random one.examples/06_simple_simulation.rs
- Create a CLI simulation in which creatures with Genomes exist on a grid and their task is to adapt to move to the right side of the grid.
For a more comprehensive simulation example with GUI and tunable parameters, check the kombinat repository.
Notes
Note 1: The current roadmap is to figure an API within the 0.1.* versions and stabilize it. This means that until 0.2, the API and functionality will probably change, break or differ in one way or another between versions.
Note 2: Perestroika
aims to be a minimalist simulation library with very minimal dependencies.
Currently (v0.1.6), it depends only on rand-*
for the generators.
Issues, bugs, suggestions
For issues, bugs, suggestions, comments or anything of the sort feel free to open a new issue on the repository.
Perestroika's Zulip might be a place for more direct communication, although it is pretty empty at the moment.
Motivation
The motivation behind this project is to make a full scale project in Rust, while integrating many more other fields such as game development in Bevy, DevOps and, of course, neural networks.
Uses
Currently being used in an in-development game that I prototype in my spare time.
More projects that employ perestroika will be listed below.
Background
"Perestroika" is the Russian word for "Reconstruction" and serves as a double pun: Perestroika was a late USSR political movement trying to reconstruct the regime; it is also a very fitting description for Genetic recombination (or genetic reshuffling) which is the target of this library.
Bibliography and sources
Efficient Evolution of Neural Networks through Complexification as the paper and background.
The Bibites provide a good overview of an already implemented game that follows similar principles.
Hosting
Dependencies
~1.2–2MB
~35K SLoC