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: An implementation of the Heaviside Step Function

A function to calculate the dot product of two 3-tuples 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!