Debugging crossbreeding logic
This commit is contained in:
parent
1301d457a9
commit
5670649227
3 changed files with 393 additions and 39 deletions
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
|
@ -10,7 +10,23 @@
|
|||
"name": "Debug",
|
||||
"program": "${workspaceFolder}/gemla/target/debug/gemla.exe",
|
||||
"args": ["./gemla/temp/"],
|
||||
"cwd": "${workspaceFolder}"
|
||||
"cwd": "${workspaceFolder}/gemla"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug Rust Tests",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--manifest-path", "${workspaceFolder}/gemla/Cargo.toml",
|
||||
"--no-run", // Compiles the tests without running them
|
||||
"--package=gemla", // Specify your package name if necessary
|
||||
"--bin=bin"
|
||||
],
|
||||
"filter": { }
|
||||
},
|
||||
"args": [],
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
fn main() {
|
||||
// Replace this with the path to the directory containing `fann.lib`
|
||||
let lib_dir = "F://vandomej/Downloads/vcpkg/packages/fann_x64-windows/lib";
|
||||
let lib_dir = "/opt/homebrew/Cellar/fann/2.2.0/lib";
|
||||
|
||||
println!("cargo:rustc-link-search=native={}", lib_dir);
|
||||
println!("cargo:rustc-link-lib=static=fann");
|
||||
println!("cargo:rustc-link-lib=dylib=fann");
|
||||
// Use `dylib=fann` instead of `static=fann` if you're linking dynamically
|
||||
|
||||
// If there are any additional directories where the compiler can find header files, you can specify them like this:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
extern crate fann;
|
||||
|
||||
use std::{fs::{self, File}, io::{self, BufRead, BufReader}, path::{Path, PathBuf}, sync::Arc};
|
||||
use std::{cmp::min, fs::{self, File}, io::{self, BufRead, BufReader}, path::{Path, PathBuf}, sync::Arc};
|
||||
use fann::{ActivationFunc, Fann};
|
||||
use futures::future::join_all;
|
||||
use gemla::{core::genetic_node::{GeneticNode, GeneticNodeContext}, error::Error};
|
||||
|
@ -24,7 +24,7 @@ const NEURAL_NETWORK_HIDDEN_LAYER_SIZE_MIN: usize = 3;
|
|||
const NEURAL_NETWORK_HIDDEN_LAYER_SIZE_MAX: usize = 35;
|
||||
const NEURAL_NETWORK_INITIAL_WEIGHT_MIN: f32 = -2.0;
|
||||
const NEURAL_NETWORK_INITIAL_WEIGHT_MAX: f32 = 2.0;
|
||||
const NEURAL_NETWORK_CROSSBREED_SEGMENTS_MIN: usize = 1;
|
||||
const NEURAL_NETWORK_CROSSBREED_SEGMENTS_MIN: usize = 2;
|
||||
const NEURAL_NETWORK_CROSSBREED_SEGMENTS_MAX: usize = 20;
|
||||
|
||||
const SIMULATION_ROUNDS: usize = 5;
|
||||
|
@ -70,7 +70,7 @@ impl GeneticNode for FighterNN {
|
|||
fs::create_dir_all(&gen_folder)
|
||||
.with_context(|| format!("Failed to create or access the generation folder: {:?}", gen_folder))?;
|
||||
|
||||
let nn_shapes = HashMap::new();
|
||||
let mut nn_shapes = HashMap::new();
|
||||
|
||||
// Create the first generation in this folder
|
||||
for i in 0..POPULATION {
|
||||
|
@ -95,6 +95,11 @@ impl GeneticNode for FighterNN {
|
|||
fann.save(&nn)
|
||||
.with_context(|| format!("Failed to save nn at {:?}", nn))?;
|
||||
}
|
||||
|
||||
let mut crossbreed_segments = thread_rng().gen_range(NEURAL_NETWORK_CROSSBREED_SEGMENTS_MIN..NEURAL_NETWORK_CROSSBREED_SEGMENTS_MAX);
|
||||
if crossbreed_segments % 2 != 0 {
|
||||
crossbreed_segments += 1;
|
||||
}
|
||||
|
||||
Ok(Box::new(FighterNN {
|
||||
id: context.id,
|
||||
|
@ -103,7 +108,8 @@ impl GeneticNode for FighterNN {
|
|||
generation: 0,
|
||||
scores: vec![HashMap::new()],
|
||||
nn_shapes,
|
||||
crossbreed_segments: thread_rng().gen_range(NEURAL_NETWORK_CROSSBREED_SEGMENTS_MIN..NEURAL_NETWORK_CROSSBREED_SEGMENTS_MAX)
|
||||
// we need crossbreed segments to be even
|
||||
crossbreed_segments,
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -266,45 +272,16 @@ impl GeneticNode for FighterNN {
|
|||
let fann = Fann::from_file(&nn)
|
||||
.with_context(|| format!("Failed to load nn"))?;
|
||||
|
||||
let new_fann = fann.try_clone();
|
||||
|
||||
// Load another nn from the current generation and cross breed it with the current nn
|
||||
let cross_nn = self.folder.join(format!("{}", self.generation)).join(format!("{:06}_fighter_nn_{}.net", self.id, to_keep[thread_rng().gen_range(0..survivor_count)]));
|
||||
let cross_fann = Fann::from_file(&cross_nn)
|
||||
.with_context(|| format!("Failed to load cross nn"))?;
|
||||
|
||||
let mut connections = fann.get_connections(); // Vector of connections
|
||||
let cross_connections = cross_fann.get_connections(); // Vector of connections
|
||||
let segment_count: usize = 3; // For example, choose 3 segments to swap
|
||||
let segment_distribution = Uniform::from(1..connections.len() / segment_count); // Ensure segments are not too small
|
||||
|
||||
let mut start_points = vec![];
|
||||
|
||||
for _ in 0..segment_count {
|
||||
let start_point = segment_distribution.sample(&mut rand::thread_rng());
|
||||
start_points.push(start_point);
|
||||
}
|
||||
start_points.sort_unstable(); // Ensure segments are in order
|
||||
trace!("Crossbreeding Start points: {:?}", start_points);
|
||||
|
||||
for (j, &start) in start_points.iter().enumerate() {
|
||||
let end = if j < segment_count - 1 {
|
||||
start_points[j + 1]
|
||||
} else {
|
||||
connections.len()
|
||||
};
|
||||
|
||||
// Swap segments
|
||||
for k in start..end {
|
||||
connections[k] = cross_connections[k].clone();
|
||||
}
|
||||
}
|
||||
|
||||
fann.set_connections(&connections);
|
||||
let mut new_fann = crossbreed(&fann, &cross_fann, self.crossbreed_segments)?;
|
||||
|
||||
// For each weight in the 5 new nn's there is a 20% chance of a minor mutation (a random number between -0.1 and 0.1 is added to the weight)
|
||||
// And a 5% chance of a major mutation (a random number between -0.3 and 0.3 is added to the weight)
|
||||
let mut connections = fann.get_connections(); // Vector of connections
|
||||
let mut connections = new_fann.get_connections(); // Vector of connections
|
||||
for c in &mut connections {
|
||||
if thread_rng().gen_range(0..100) < 20 {
|
||||
c.weight += thread_rng().gen_range(-0.1..0.1);
|
||||
|
@ -313,7 +290,7 @@ impl GeneticNode for FighterNN {
|
|||
c.weight += thread_rng().gen_range(-0.3..0.3);
|
||||
}
|
||||
}
|
||||
fann.set_connections(&connections);
|
||||
new_fann.set_connections(&connections);
|
||||
|
||||
// Save the new nn's to the new generation folder
|
||||
let new_nn = new_gen_folder.join(format!("{:06}_fighter_nn_{}.net", self.id, i + survivor_count));
|
||||
|
@ -360,10 +337,280 @@ impl GeneticNode for FighterNN {
|
|||
generation: 0,
|
||||
population_size: left.population_size, // Assuming left and right have the same population size.
|
||||
scores: vec![HashMap::new()],
|
||||
crossbreed_segments: left.crossbreed_segments,
|
||||
nn_shapes: HashMap::new(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Crossbreeds two neural networks of different shapes by finding cut points, and swapping neurons between the two networks.
|
||||
/// Algorithm tries to ensure similar functionality is maintained between the two networks.
|
||||
/// It does this by preserving connections between the same neurons from the original to the new network, and if a connection cannot be found
|
||||
/// it will create a new connection with a random weight.
|
||||
fn crossbreed(primary: &Fann, secondary: &Fann, crossbreed_segments: usize) -> Result<Fann, Error> {
|
||||
// First we need to get the shape of the networks and transform this into a format that is easier to work with
|
||||
// We want a list of every neuron id, and the layer it is in
|
||||
let primary_shape = primary.get_layer_sizes();
|
||||
let secondary_shape = secondary.get_layer_sizes();
|
||||
let primary_neurons = generate_neuron_datastructure(&primary_shape);
|
||||
let secondary_neurons = generate_neuron_datastructure(&secondary_shape);
|
||||
|
||||
// Now we need to find the cut points for the crossbreed
|
||||
let start = primary_shape[0] + 1; // Start at the first hidden layer
|
||||
let end = min(primary_shape.iter().sum::<u32>() - primary_shape.last().unwrap(), secondary_shape.iter().sum::<u32>() - secondary_shape.last().unwrap()); // End at the last hidden layer
|
||||
let segment_distribution = Uniform::from(start..end); // Ensure segments are not too small
|
||||
|
||||
let mut cut_points = Vec::new();
|
||||
for _ in 0..crossbreed_segments {
|
||||
let cut_point = segment_distribution.sample(&mut thread_rng());
|
||||
if !cut_points.contains(&cut_point) {
|
||||
cut_points.push(cut_point);
|
||||
}
|
||||
}
|
||||
// Sort the cut points to make it easier to iterate over them
|
||||
cut_points.sort_unstable();
|
||||
|
||||
// We need to transform the cut_points vector to a vector of tuples that contain the start and end of each segment
|
||||
let mut segments = Vec::new();
|
||||
let mut previous = 0;
|
||||
for &cut_point in cut_points.iter() {
|
||||
segments.push((previous, cut_point));
|
||||
previous = cut_point;
|
||||
}
|
||||
|
||||
let new_neurons = crossbreed_neuron_arrays(segments, primary_neurons, secondary_neurons);
|
||||
|
||||
// Now we need to create the new network with the shape we've determined
|
||||
let mut new_shape = vec![];
|
||||
for (_, _, layer, _) in new_neurons.iter() {
|
||||
// Check if new_shape has an entry for layer in it
|
||||
if new_shape.len() <= *layer as usize {
|
||||
new_shape.push(1);
|
||||
}
|
||||
else {
|
||||
new_shape[*layer as usize] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_fann = Fann::new(new_shape.as_slice())
|
||||
.with_context(|| "Failed to create new fann")?;
|
||||
// We need to randomize the weights to a small value
|
||||
new_fann.randomize_weights(-0.1, 0.1);
|
||||
new_fann.set_activation_func_hidden(ActivationFunc::SigmoidSymmetric);
|
||||
new_fann.set_activation_func_output(ActivationFunc::SigmoidSymmetric);
|
||||
|
||||
consolidate_old_connections(primary, secondary, new_shape, new_neurons, &mut new_fann);
|
||||
|
||||
Ok(new_fann)
|
||||
}
|
||||
|
||||
fn consolidate_old_connections(primary: &Fann, secondary: &Fann, new_shape: Vec<u32>, new_neurons: Vec<(u32, bool, usize, u32)>, new_fann: &mut Fann) {
|
||||
// Now we need to copy the connections from the original networks to the new network
|
||||
// We can do this by referencing our connections array, it will contain the original id's of the neurons
|
||||
// and their new id as well as their layer. We can iterate one layer at a time and copy the connections
|
||||
|
||||
// Start by iterating layer by later
|
||||
let primary_connections = primary.get_connections();
|
||||
let secondary_connections = secondary.get_connections();
|
||||
for layer in 1..new_shape.len() {
|
||||
// filter out the connections that are in the current layer and previous layer
|
||||
let current_layer_connections = new_neurons.iter().filter(|(_, _, l, _)| l == &layer).collect::<Vec<_>>();
|
||||
let previous_layer_connections = new_neurons.iter().filter(|(_, _, l, _)| l == &(layer - 1)).collect::<Vec<_>>();
|
||||
|
||||
// Now we need to iterate over the connections in the current layer
|
||||
for (neuron_id, is_primary, _, new_id) in current_layer_connections.iter() {
|
||||
// We need to find the connections from the previous layer to this neuron
|
||||
for (previous_neuron_id, _, _, previous_new_id) in previous_layer_connections.iter() {
|
||||
// First we use primary to and check the correct connections array to see if the connection exists
|
||||
// If it does, we add it to the new network
|
||||
let connection = if *is_primary {
|
||||
primary_connections.iter().find(|connection| &connection.from_neuron == previous_neuron_id && &connection.to_neuron == neuron_id)
|
||||
}
|
||||
else {
|
||||
secondary_connections.iter().find(|connection| &connection.from_neuron == previous_neuron_id && &connection.to_neuron == neuron_id)
|
||||
};
|
||||
|
||||
// If the connection exists, we need to add it to the new network
|
||||
if let Some(connection) = connection {
|
||||
new_fann.set_weight(*previous_new_id, *new_id, connection.weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn crossbreed_neuron_arrays(segments: Vec<(u32, u32)>, primary_neurons: Vec<(u32, usize)>, secondary_neurons: Vec<(u32, usize)>) -> Vec<(u32, bool, usize, u32)> {
|
||||
// We now need to determine the resulting location of the neurons in the new network.
|
||||
// To do this we need a new structure that keeps track of the following information:
|
||||
// - The neuron id from the original network
|
||||
// - Which network it originated from (primary or secondary)
|
||||
// - The layer the neuron is in
|
||||
// - The resulting neuron id in the new network which will be calculated after the fact
|
||||
let mut new_neurons = Vec::new();
|
||||
let mut current_layer = 0;
|
||||
let mut is_primary = true;
|
||||
for (i, &segment) in segments.iter().enumerate() {
|
||||
// If it's the first slice, copy neurons from the primary network up to the cut_point
|
||||
if i == 0 {
|
||||
for (neuron_id, layer) in primary_neurons.iter() {
|
||||
if neuron_id <= &segment.1 {
|
||||
if layer > ¤t_layer {
|
||||
current_layer += 1;
|
||||
}
|
||||
new_neurons.push((*neuron_id, is_primary, current_layer, 0));
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
let target_neurons = if is_primary { &primary_neurons } else { &secondary_neurons };
|
||||
|
||||
for (neuron_id, layer) in target_neurons.iter() {
|
||||
// Iterate until neuron_id equals the cut_point
|
||||
if neuron_id >= &segment.0 && neuron_id <= &segment.1 {
|
||||
// We need to do something different depending on whether the neuron layer is, lower, higher or equal to the target layer
|
||||
|
||||
// Equal
|
||||
if layer == ¤t_layer {
|
||||
new_neurons.push((*neuron_id, is_primary, current_layer, 0));
|
||||
}
|
||||
// Earlier
|
||||
else if layer < ¤t_layer {
|
||||
// If it's in an earlier layer, add it to the earlier layer
|
||||
// Check if there's a lower id from the same individual in that earlier layer
|
||||
// As long as there isn't a neuron from the other individual in between the lower id and current id, add the id values from the same individual
|
||||
let earlier_layer_neurons = new_neurons.iter().filter(|(_, _, l, _)| l == layer).collect::<Vec<_>>();
|
||||
// get max id from that layer
|
||||
let highest_id = earlier_layer_neurons.iter().max_by_key(|(id, _, _, _)| id);
|
||||
if let Some(highest_id) = highest_id {
|
||||
if highest_id.1 == is_primary {
|
||||
let neurons_to_add = target_neurons.iter().filter(|(id, l)| id > &highest_id.0 && id <= neuron_id && l == layer).collect::<Vec<_>>();
|
||||
for (neuron_id, _) in neurons_to_add {
|
||||
new_neurons.push((*neuron_id, is_primary, current_layer, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Later
|
||||
else if layer > ¤t_layer {
|
||||
// If the highest id in the current layer is from the same individual, add anything with a higher id to the current layer before moving to the next layer
|
||||
// First filter new_neurons to look at neurons from the current layer
|
||||
let current_layer_neurons = new_neurons.iter().filter(|(_, _, l, _)| l == ¤t_layer).collect::<Vec<_>>();
|
||||
let highest_id = current_layer_neurons.iter().max_by_key(|(id, _, _, _)| id);
|
||||
if let Some(highest_id) = highest_id {
|
||||
if highest_id.1 == is_primary {
|
||||
let neurons_to_add = target_neurons.iter().filter(|(id, l)| id > &highest_id.0 && *l == layer - 1).collect::<Vec<_>>();
|
||||
for (neuron_id, _) in neurons_to_add {
|
||||
new_neurons.push((*neuron_id, is_primary, current_layer, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If it's in a future layer, move to the next layer
|
||||
current_layer += 1;
|
||||
|
||||
// Add the neuron to the new network
|
||||
// Along with any neurons that have a lower id in the future layer
|
||||
let neurons_to_add = target_neurons.iter().filter(|(id, l)| id <= &neuron_id && l == layer).collect::<Vec<_>>();
|
||||
for (neuron_id, _) in neurons_to_add {
|
||||
new_neurons.push((*neuron_id, is_primary, current_layer, 0));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else if neuron_id >= &segment.1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Switch to the other network
|
||||
is_primary = !is_primary;
|
||||
}
|
||||
|
||||
// For the last segment, copy the remaining neurons
|
||||
let target_neurons = if is_primary { &primary_neurons } else { &secondary_neurons };
|
||||
// Get output layer number
|
||||
let output_layer = target_neurons.iter().max_by_key(|(_, l)| l).unwrap().1;
|
||||
|
||||
// For the last segment, copy the remaining neurons from the target network
|
||||
// But when we reach the output layer, we need to add a new layer to the end of new_neurons regardless of it's length
|
||||
// and copy the output neurons to that layer
|
||||
for (neuron_id, layer) in target_neurons.iter() {
|
||||
if neuron_id > &segments.last().unwrap().1 {
|
||||
if layer == &output_layer {
|
||||
// Calculate which layer the neurons should be in
|
||||
current_layer = new_neurons.iter().max_by_key(|(_, _, l, _)| l).unwrap().2 + 1;
|
||||
for (neuron_id, _) in target_neurons.iter().filter(|(_, l)| l == &output_layer) {
|
||||
new_neurons.push((*neuron_id, is_primary, current_layer, 0));
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if *neuron_id == &segments.last().unwrap().1 + 1 {
|
||||
let earlier_layer_neurons = new_neurons.iter().filter(|(_, _, l, _)| l == layer).collect::<Vec<_>>();
|
||||
// get max id from that layer
|
||||
let highest_id = earlier_layer_neurons.iter().max_by_key(|(id, _, _, _)| id);
|
||||
if let Some(highest_id) = highest_id {
|
||||
if highest_id.1 == is_primary {
|
||||
let neurons_to_add = target_neurons.iter().filter(|(id, l)| id > &highest_id.0 && id <= neuron_id && l == layer).collect::<Vec<_>>();
|
||||
for (neuron_id, _) in neurons_to_add {
|
||||
new_neurons.push((*neuron_id, is_primary, *layer, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
new_neurons.push((*neuron_id, is_primary, *layer, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filtering layers with too few neurons, if necessary
|
||||
let layer_counts = new_neurons.iter().fold(vec![0; current_layer + 1], |mut counts, &(_, _, layer, _)| {
|
||||
counts[layer] += 1;
|
||||
counts
|
||||
});
|
||||
|
||||
// Filter out layers based on the minimum number of neurons per layer
|
||||
new_neurons = new_neurons.into_iter()
|
||||
.filter(|&(_, _, layer, _)| layer_counts[layer] >= NEURAL_NETWORK_HIDDEN_LAYER_SIZE_MIN)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Collect and sort unique layer numbers
|
||||
let mut unique_layers = new_neurons.iter()
|
||||
.map(|(_, _, layer, _)| *layer)
|
||||
.collect::<Vec<_>>();
|
||||
unique_layers.sort();
|
||||
unique_layers.dedup(); // Removes duplicates, keeping only unique layer numbers
|
||||
|
||||
// Create a mapping from old layer numbers to new (gap-less) layer numbers
|
||||
let layer_mapping = unique_layers.iter().enumerate()
|
||||
.map(|(new_layer, &old_layer)| (old_layer, new_layer))
|
||||
.collect::<HashMap<usize, usize>>();
|
||||
|
||||
// Apply the mapping to renumber layers in new_neurons
|
||||
new_neurons.iter_mut().for_each(|(_, _, layer, _)| {
|
||||
*layer = *layer_mapping.get(layer).unwrap_or(layer); // Fallback to original layer if not found, though it should always find a match
|
||||
});
|
||||
|
||||
// Assign new IDs
|
||||
// new_neurons must be sorted by layer, then by neuron ID within the layer
|
||||
new_neurons.sort_unstable_by(|a, b| a.2.cmp(&b.2).then(a.0.cmp(&b.0)));
|
||||
new_neurons.iter_mut().enumerate().for_each(|(new_id, neuron)| {
|
||||
neuron.3 = new_id as u32;
|
||||
});
|
||||
|
||||
new_neurons
|
||||
}
|
||||
|
||||
fn generate_neuron_datastructure(shape: &[u32]) -> Vec<(u32, usize)> {
|
||||
shape.iter().enumerate().flat_map(|(i, &size)| {
|
||||
(0..size).enumerate().map(move |(j, _)| (j as u32, i))
|
||||
}).collect()
|
||||
}
|
||||
|
||||
async fn read_score_from_file(file_path: &Path, nn_id: &str) -> Result<f32, io::Error> {
|
||||
let mut attempts = 0;
|
||||
|
||||
|
@ -399,4 +646,95 @@ async fn read_score_from_file(file_path: &Path, nn_id: &str) -> Result<f32, io::
|
|||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn crossbreed_neuron_arrays_test() {
|
||||
// Assign
|
||||
let segments = vec![(0, 3), (4, 6), (7, 8), (9, 10)];
|
||||
|
||||
let primary_network = vec![
|
||||
// Input layer
|
||||
(0, 0), (1, 0), (2, 0), (3, 0),
|
||||
// Hidden layer 1
|
||||
(4, 1), (5, 1), (6, 1), (7, 1), (8, 1), (9, 1), (10, 1), (11, 1),
|
||||
// Hidden layer 2
|
||||
(12, 2), (13, 2), (14, 2), (15, 2), (16, 2), (17, 2),
|
||||
// Output layer
|
||||
(18, 3), (19, 3), (20, 3), (21, 3),
|
||||
];
|
||||
|
||||
let secondary_network = vec![
|
||||
// Input layer
|
||||
(0, 0), (1, 0), (2, 0), (3, 0),
|
||||
// Hidden layer 1
|
||||
(4, 1), (5, 1), (6, 1),
|
||||
// Hidden layer 2
|
||||
(7, 2), (8, 2), (9, 2),
|
||||
// Hiden Layer 3
|
||||
(10, 3), (11, 3), (12, 3),
|
||||
// Hidden Layer 4
|
||||
(13, 4), (14, 4), (15, 4),
|
||||
// Hiden Layer 5
|
||||
(16, 5), (17, 5), (18, 5),
|
||||
// Output layer
|
||||
(19, 6), (20, 6), (21, 6), (22, 6),
|
||||
];
|
||||
|
||||
// Act
|
||||
let result = crossbreed_neuron_arrays(segments.clone(), primary_network.clone(), secondary_network.clone());
|
||||
|
||||
// Expected Result Set
|
||||
let expected: HashSet<(u32, bool, usize, u32)> = vec![
|
||||
// Input layer: Expect 4
|
||||
(0, true, 0, 0), (1, true, 0, 1), (2, true, 0, 2), (3, true, 0, 3),
|
||||
// Hidden Layer 1: Expect 8
|
||||
(4, false, 1, 4), (5, false, 1, 5), (6, false, 1, 6), (7, true, 1, 7), (8, true, 1, 8), (9, true, 1, 9), (10, true, 1, 10), (11, true, 1, 11),
|
||||
// Hidden Layer 2: Expect 9
|
||||
(7, false, 2, 12), (8, false, 2, 13), (9, false, 2, 14), (12, true, 2, 15), (13, true, 2, 16), (14, true, 2, 17), (15, true, 2, 18), (16, true, 2, 19), (17, true, 2, 20),
|
||||
// Output Layer: Expect 4
|
||||
(18, true, 3, 21), (19, true, 3, 22), (20, true, 3, 23), (21, true, 3, 24),
|
||||
].into_iter().collect();
|
||||
|
||||
// Convert Result to HashSet for Comparison
|
||||
let result_set: HashSet<(u32, bool, usize, u32)> = result.into_iter().collect();
|
||||
|
||||
// Assert
|
||||
assert_eq!(result_set, expected);
|
||||
|
||||
// Now we test the ooposite case
|
||||
// Act
|
||||
let result = crossbreed_neuron_arrays(segments.clone(), secondary_network.clone(), primary_network.clone());
|
||||
|
||||
// Expected Result Set
|
||||
let expected: HashSet<(u32, bool, usize, u32)> = vec![
|
||||
// Input layer: Expect 4
|
||||
(0, true, 0, 0), (1, true, 0, 1), (2, true, 0, 2), (3, true, 0, 3),
|
||||
// Hidden Layer 1: Expect 7
|
||||
(4, false, 1, 4), (5, false, 1, 5), (6, false, 1, 6), (7, false, 1, 7), (8, false, 1, 8), (9, false, 1, 9), (10, false, 1, 10),
|
||||
// Hidden Layer 2: Expect 3
|
||||
(7, true, 2, 11), (8, true, 2, 12), (9, true, 2, 13),
|
||||
// Hidden Layer 3: Expect 3
|
||||
(10, true, 3, 14), (11, true, 3, 15), (12, true, 3, 16),
|
||||
// Hidden Layer 4: Expect 3
|
||||
(13, true, 4, 17), (14, true, 4, 18), (15, true, 4, 19),
|
||||
// Hidden Layer 5: Expect 3
|
||||
(16, true, 5, 20), (17, true, 5, 21), (18, true, 5, 22),
|
||||
// Output Layer: Expect 4
|
||||
(19, true, 6, 23), (20, true, 6, 24), (21, true, 6, 25), (22, true, 6, 26),
|
||||
].into_iter().collect();
|
||||
|
||||
// Convert Result to HashSet for Comparison
|
||||
let result_set: HashSet<(u32, bool, usize, u32)> = result.into_iter().collect();
|
||||
|
||||
// Assert
|
||||
assert_eq!(result_set, expected);
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue