blog.dbrgn.ch

Programming a Perceptron in Rust

written on Friday, May 15, 2015 by

Two years ago, I wrote a blogpost about implementing a perceptron in Python that quite a few people liked. Nowadays I'm getting started with the Rust programming language, so after the big Rust 1.0 release today I thought it would be a good moment to do another post about implementing a simple perceptron, this time in Rust.

If you need any background information about perceptrons, please refer to my last post about this topic. I won't discuss that topic again here.

Creating a new project

First, let's create a new Rust project with Cargo:

$ cargo new perceptron-rs
$ cd perceptron-rs

Then add the rand crate as a dependency in Cargo.toml:

[dependencies]
rand = "0.3"

Helper functions

For our implementation, we need some helper functions:

For this example we won't bother writing generic code, so the implementations are quite straightforward:

/// Heaviside Step Function
fn heaviside(val: f64) -> i8 {
    (val >= 0.0) as i8
}

/// Dot product of input and weights
fn dot(input: (i8, i8, i8), weights: (f64, f64, f64)) -> f64 {
    input.0 as f64 * weights.0
    + input.1 as f64 * weights.1
    + input.2 as f64 * weights.2
}

Structs

Let's define a simple struct to hold the training data and the expected output. As in the original blogpost, we'll train the perceptron to do the boolean OR function.

A TrainingDatum can hold an input value (implemented as 3-tuple) and an expected output value (a simple integer).

struct TrainingDatum {
    input: (i8, i8, i8),
    expected: i8,
}

The first two values of the input field are the two inputs for the OR function. The third value is the bias.

Initialization

First we need to load the rand library and initialize a random number generator instance:

extern crate rand;

use rand::Rng;
use rand::distributions::{Range, IndependentSample};

// ...

let mut rng = rand::thread_rng();

The training data is provided as an array of TrainingDatum instances:

let training_data = [
    TrainingDatum { input: (0, 0, 1), expected: 0 },
    TrainingDatum { input: (0, 1, 1), expected: 1 },
    TrainingDatum { input: (1, 0, 1), expected: 1 },
    TrainingDatum { input: (1, 1, 1), expected: 1 },
];

We then initialize the weight vector with random data between 0 and 1:

let range = Range::new(0.0, 1.0);
let mut w = (
    range.ind_sample(&mut rng),
    range.ind_sample(&mut rng),
    range.ind_sample(&mut rng),
);

The learning rate is set to 0.2 and the iteration count to 100.

// Learning rate
let eta = 0.2;

// Number of iterations
let n = 100;

Now we can start the training process!

// Training
println!("Starting training phase with {} iterations...", n);
for _ in 0..n {

    // Choose a random training sample
    let &TrainingDatum { input: x, expected } = rng.choose(&training_data).unwrap();

    // Calculate the dot product
    let result = dot(x, w);

    // Calculate the error
    let error = expected - heaviside(result);

    // Update the weights
    w.0 += eta * error as f64 * x.0 as f64;
    w.1 += eta * error as f64 * x.1 as f64;
    w.2 += eta * error as f64 * x.2 as f64;
}

After 100 iterations, our perceptron should have learned how to behave like an OR function.

// Show result
for &TrainingDatum { input, .. } in &training_data {
    let result = dot(input, w);
    println!("{} OR {}: {:.*} -> {}", input.0, input.1, 8, result, heaviside(result));
}

Testing

Let's run the resulting program!

$ cargo build
$ target/debug/perceptron
Starting training phase with 100 iterations...
0 OR 0: -0.07467118 -> 0
0 OR 1: 0.69958573 -> 1
1 OR 0: 0.82801196 -> 1
1 OR 1: 1.60226888 -> 1

Yay for Rust!

Feedback

I'm still quite new to Rust and not an expert on AI either. So if you have any feedback, suggestion or improvement, please leave it below or on Hacker News!

The code is on Github. If you have improvements concerning the code itself, you may also create a pull request.

This entry was tagged ai, machine_learning and rust