4 releases

0.1.13 Jul 20, 2020
0.1.12 Feb 26, 2020
0.1.1 Feb 9, 2020
0.1.0 Jan 31, 2020

#2378 in Algorithms

MIT license

165KB
3K SLoC

Radiate Web

Often trining deep learning alorithms is an expensive CPU/GPU action and with Radiate there is no exception. To aid in this problem, Radiate Web exposes a few data transfer objects to be able to build your learning algorithm remotely, then send it to another machine to train or test. This is a small extension that goes with Radiate.

Population Data Transfer Object (DTO)

Simply build your transfer object to send over to your other machine by defining the parameters of a simple population. This does not allow you to define a few of the parameters, mainly the 'run' function which determines when to stop training and is required to be on the training machine.

Radiate Data Transfer Object

Build a Radiate genetic algorithm with NEAT (Neuroeolution of Augmented Topologies) to send by encapuslating the rest of the training options and they're environment.

Example

This example code can be found here which describes how the client and server are set up using Rocket and Tokio to build a web service and handle the routing.

Client

#![feature(proc_macro_hygiene, decl_macro)]

extern crate radiate;
extern crate radiate_web;
extern crate serde;
extern crate serde_json;
extern crate serde_derive;
extern crate reqwest;

use radiate::prelude::*;
use radiate_web::prelude::*;
use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let data = generate_post_data();                // generate the data to send
    let client = reqwest::Client::new();            // create the client object 
    let mut headers = HeaderMap::new();             // add application/json to the headers because that is how NEAT is seaialized
    headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
    let res = client.post("http://0.0.0.0:42069/")  // listn on local host
        .headers(headers)                           // add the headers then add data and send it
        .body(data)
        .send().await;
    Ok(())
}

#[allow(dead_code)]
fn generate_post_data() -> String {
    
    // create an environment
    let neat_env = NeatEnvironment::new()
        .set_input_size(2)
        .set_output_size(1)
        .set_weight_mutate_rate(0.8)
        .set_edit_weights(0.1)
        .set_weight_perturb(1.75)
        .set_new_node_rate(0.03)
        .set_new_edge_rate(0.04)
        .set_reactivate(0.2)
        .set_activation_functions(vec![
            Activation::LeakyRelu(0.02)
        ]);

    // build the neat network
    let net = Neat::new()
        .input_size(2)
        .batch_size(1)
        .dense_pool(1, Activation::Sigmoid);

    // build the population
    let population = NeatPopulationBuilder::new()
            .num_evolve(100)
            .size(100)
            .dynamic_distance(true)
            .debug_process(true)
            .config(Config {
                inbreed_rate: 0.001,
                crossover_rate: 0.75,
                distance: 0.5,
                species_target: 5
            })
            .stagnation(10)
            .genocide(vec![Genocide::KillWorst(0.9)]);
    
    // put it all together
    let radiate_dto = RadiateDto::new()
            .env(neat_env)              // give the dto and neat enviornment
            .train(100, 0.3)            // if you want to train the algorithm traditionally this is where to define it 
            .neat(net)                  // add the neat object
            .population(population)     // add the population object 
            .to_json();                 // put it all to json and return it
    
    // save to a file for testing via Postman
    radiate_dto
}

Server

Simple example of training a neat network to solve the traditional XOR problem.

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use] extern crate rocket;
#[macro_use] extern crate rocket_contrib;
extern crate radiate;
extern crate radiate_web;
extern crate serde;
extern crate serde_json;
extern crate serde_derive;

use std::fs::File;
use radiate::prelude::*;
use radiate_web::prelude::*;
use rocket::config::{Config as RConfig, Environment as REnv};
use rocket_contrib::json::{Json, JsonValue};

fn main() {
    let r_config = RConfig::build(REnv::Staging)
        .address("0.0.0.0")
        .port(42069)
        .finalize()
        .unwrap();

    rocket::custom(r_config)
        .mount("/", routes![run])
        .launch();
}

#[post("/", format = "json", data = "<radiate>")]
fn run(radiate: Json<RadiateDto>) -> Option<JsonValue> {
    
    // unpack the variables
    let env = radiate.0.env?;
    let net = radiate.0.neat?;
    let pop = radiate.0.population?;
    let train = radiate.0.train?;

    // take out the training variables
    let num_evolve = pop.num_evolve?;
    let num_train = train.epochs;
    let learning_rate = train.learning_rate;

    // create a new problem variable
    let xor = XOR::new();

    // set up the population now that it has been recieved
    let (mut solution, _) = Population::<Neat, NeatEnvironment, XOR>::new()
        .constrain(env)
        .populate_clone(net)
        .debug(pop.debug_process?)
        .dynamic_distance(pop.dynamic_distance?)
        .configure(pop.config?)
        .stagnation(pop.stagnation?, pop.genocide?)
        .parental_criteria(pop.parental_criteria?)
        .survivor_criteria(pop.survivor_criteria?)
        .run(|_, fit, num| {
            println!("epoch: {} score: {}", num, fit);
            let diff = 4.0 - fit;
            (diff > 0.0 && diff < 0.01) || num == num_evolve
        }).unwrap();

    // manually train the neural net
    solution.train(&xor.inputs, &xor.answers, learning_rate, Loss::Diff, |iter, _| {
        iter == num_train as usize
    }).unwrap();
    
    // show it
    xor.show(&mut solution);
    Some(json!({"it": "works"}))
}

#[derive(Debug)]
pub struct XOR {
    inputs: Vec<Vec<f32>>,
    answers: Vec<Vec<f32>>
}

impl XOR {
    pub fn new() -> Self {
        XOR {
            inputs: vec![
                vec![0.0, 0.0],
                vec![1.0, 1.0],
                vec![1.0, 0.0],
                vec![0.0, 1.0],
            ],
            answers: vec![
                vec![0.0],
                vec![0.0],
                vec![1.0],
                vec![1.0],
            ]
        }
    }


    fn show(&self, model: &mut Neat) {
        println!("\n");
        for (i, o) in self.inputs.iter().zip(self.answers.iter()) {
            let guess = model.forward(&i).unwrap();
            println!("Guess: {:.2?} Answer: {:.2}", guess, o[0]);
        }
    }
}

impl Problem<Neat> for XOR {

    fn empty() -> Self { XOR::new() }

    fn solve(&self, model: &mut Neat) -> f32 {
        let mut total = 0.0;
        for (ins, outs) in self.inputs.iter().zip(self.answers.iter()) {
            match model.forward(&ins) {
                Some(guess) => total += (guess[0] - outs[0]).powf(2.0),
                None => panic!("Error in training NEAT")
            }
        }
        4.0 - total
    }
}

#[allow(dead_code)]
fn generate_post_data() {
    
    // create an environment
    let neat_env = NeatEnvironment::new()
        .set_input_size(2)
        .set_output_size(1)
        .set_weight_mutate_rate(0.8)
        .set_edit_weights(0.1)
        .set_weight_perturb(1.75)
        .set_new_node_rate(0.03)
        .set_new_edge_rate(0.04)
        .set_reactivate(0.2)
        .set_activation_functions(vec![
            Activation::LeakyRelu(0.02)
        ]);

    // build the neat network
    let net = Neat::new()
        .input_size(2)
        .batch_size(1)
        .dense_pool(1, Activation::Sigmoid);

    // build the population
    let population = NeatPopulationBuilder::new()
            .num_evolve(100)
            .size(100)
            .dynamic_distance(true)
            .debug_process(true)
            .config(Config {
                inbreed_rate: 0.001,
                crossover_rate: 0.75,
                distance: 0.5,
                species_target: 5
            })
            .stagnation(10)
            .genocide(vec![Genocide::KillWorst(0.9)]);
    
    // put it all together
    let radiate_dto = RadiateDto::new()
            .env(neat_env)
            .train(100, 0.3)        // this has it's own DTO too (TrainDto), but it's small
            .neat(net)
            .population(population)
            .to_json();
    
    // save to a file for testing via Postman
    serde_json::to_writer_pretty(&File::create("post_test.json").unwrap(), &radiate_dto).unwrap();
}

Dependencies

~3.5–5MB
~94K SLoC