Review and Feedback of First Rust Code for a Basic Neural Network

This is a rust port of the code from here.

extern crate rand;
use rand::Rng;

fn main() {

    let mut rng = rand::thread_rng();
    let mut syn_weight: Vec<f64> = Vec::new();
    for x in 0..3 {
        syn_weight.push(rng.gen_range::<f64>(-1.0, 1.0));
    }

    let input_set: Vec<Vec<f64>> = vec![vec![0.0, 0.0, 1.0],
                                        vec![1.0, 1.0, 1.0],
                                        vec![1.0, 0.0, 1.0],
                                        vec![0.0, 1.0, 1.0]];
    let output_set: Vec<Vec<f64>> = vec![vec![0.0], vec![1.0], vec![1.0], vec![0.0]];

    for x in 0..10000000 {
        vec_dot(&input_set, &syn_weight);
        let output = vec_sigmoid(vec_dot(&input_set, &syn_weight));
        let mut error: Vec<f64> = Vec::new();
        for (idx, val) in output_set.iter().enumerate() {
            error.push(val[0] - output[idx]);
        }
        let derivative = vec_sigmoid_derivative(output);
        let mut result: Vec<f64> = Vec::new();
        for (idx, val) in derivative.iter().enumerate() {
            result.push(val * error[idx]);
        }
        syn_weight = vec_add(&(vec_dot(&(vec_transpose(&input_set)), &result)),
                            &syn_weight);
    }
    let mut test_input: Vec<Vec<f64>> = vec![vec![1.0, 0.0, 0.0]];
    println!("Output");
    println!("{:?}", vec_sigmoid(vec_dot(&test_input, &syn_weight)));
}

fn vec_transpose(input: &Vec<Vec<f64>>) -> Vec<Vec<f64>> {
    let mut outer: Vec<Vec<f64>> = Vec::new();
    for z in 0..input[0].len() {
        let mut inner: Vec<f64> = Vec::new();
        for y in 0..input.len() {
            inner.push(input[y][z]);
        }
        outer.push(inner);
    }
    outer
}

fn vec_add(x: &Vec<f64>, y: &Vec<f64>) -> Vec<f64> {
    let mut sum: Vec<f64> = Vec::new();
    for (idx, val) in x.iter().enumerate() {
        sum.push(val + y[idx]);
    }
    sum
}

fn sigmoid(x: &f64) -> f64 {
    1.0 / (1.0 + ((-1.0 * x).exp()))
}

fn vec_sigmoid(x: Vec<f64>) -> Vec<f64> {
    let mut result: Vec<f64> = Vec::new();
    for val in x.iter() {
        result.push(sigmoid(val));
    }
    result
}

fn sigmoid_derivative(x: &f64) -> f64 {
    x * (1.0 - x)
}

fn vec_sigmoid_derivative(x: Vec<f64>) -> Vec<f64> {
    let mut result: Vec<f64> = Vec::new();
    for val in x.iter() {
        result.push(sigmoid_derivative(val));
    }
    result
}

fn vec_dot(x: &Vec<Vec<f64>>, y: &Vec<f64>) -> Vec<f64> {
    let mut result: Vec<f64> = Vec::new();
    for val in x.iter() {
        let mut sum: f64 = 0.0;
        for (idx_inner, value) in y.iter().enumerate() {
            sum += val[idx_inner] * value;
        }
        result.push(sum);
    }
    result
}

Gist Url: gist.github.com/be8b9123da9b0f0246ca83960fc159e8
PlaygroundUrl : Rust Playground

Thanks in advance.
Regards
Delta

1 Like

If you tried ndarray, just ignore me.

2 Likes

@Congee

Thanks for the review, its my first time using Rust, wanted to try stuff without the ndarray library, appreciate you pointing it out.

Edited code based on discussion on reddit

    extern crate rand
    use rand::Rng;
    fn main() {

    let mut rng = rand::thread_rng();
    let mut syn_weight: Vec<f64> = Vec::new();
    for _ in 0..3 {
        syn_weight.push(rng.gen_range::<f64>(-1.0, 1.0));
    }

    let input_set: Vec<Vec<f64>> = vec![vec![0.0, 0.0, 1.0],
                                        vec![1.0, 1.0, 1.0],
                                        vec![1.0, 0.0, 1.0],
                                        vec![0.0, 1.0, 1.0]];
    let output_set: Vec<Vec<f64>> = vec![vec![0.0], vec![1.0], vec![1.0], vec![0.0]];

    for _ in 0..10000000 {
        vec_dot(&input_set, &syn_weight);
        let output = vec_sigmoid(&vec_dot(&input_set, &syn_weight));
        let mut error: Vec<f64> = Vec::new();
        for (idx, val) in output_set.iter().enumerate() {
            error.push(val[0] - output[idx]);
        }
        let derivative = vec_sigmoid_derivative(&output);
        let mut result: Vec<f64> = Vec::new();
        for (idx, val) in derivative.iter().enumerate() {
            result.push(val * error[idx]);
        }
        syn_weight = vec_add(&(vec_dot(&(vec_transpose(&input_set)), &result)),
                            &syn_weight);
    }
    let test_input: Vec<Vec<f64>> = vec![vec![1.0, 0.0, 0.0]];
    println!("Output");
    println!("{:?}", vec_sigmoid(&vec_dot(&test_input, &syn_weight)));
}

fn vec_transpose(input: &Vec<Vec<f64>>) -> Vec<Vec<f64>> {
    let mut outer: Vec<Vec<f64>> = Vec::new();
    for z in 0..input[0].len() {
        let mut inner: Vec<f64> = Vec::new();
        for y in 0..input.len() {
            inner.push(input[y][z]);
        }
        outer.push(inner);
    }
    outer
}

fn vec_add(x: &Vec<f64>, y: &Vec<f64>) -> Vec<f64> {
    let mut sum: Vec<f64> = Vec::new();
    for (idx, val) in x.iter().enumerate() {
        sum.push(val + y[idx]);
    }
    sum
}

fn sigmoid(x: &f64) -> f64 {
    1.0 / (1.0 + ((-1.0 * x).exp()))
}

fn vec_sigmoid(x: &Vec<f64>) -> Vec<f64> {
    x.into_iter().map(sigmoid).collect::<Vec<f64>>()
}

fn sigmoid_derivative(x: &f64) -> f64 {
    x * (1.0 - x)
}

fn vec_sigmoid_derivative(x: &Vec<f64>) -> Vec<f64> {
    x.into_iter().map(sigmoid_derivative).collect::<Vec<f64>>()
}

fn vec_dot(x: &Vec<Vec<f64>>, y: &Vec<f64>) -> Vec<f64> {
    let mut result: Vec<f64> = Vec::new();
    for val in x.iter() {
        let mut sum: f64 = 0.0;
        for (idx_inner, value) in y.iter().enumerate() {
            sum += val[idx_inner] * value;
        }
        result.push(sum);
    }
    result
}