Skip to content

Instantly share code, notes, and snippets.

@0xm00n
Created October 5, 2023 05:48
Show Gist options
  • Save 0xm00n/38dacd364fac4a656b003530ac5e197a to your computer and use it in GitHub Desktop.
Save 0xm00n/38dacd364fac4a656b003530ac5e197a to your computer and use it in GitHub Desktop.
Multi-layer perceptron with an input layer, 1 hidden layer, and an output layer written from scratch in rust. The sigmoid activation function is used for both the hidden and output layers.
use std::f64;
// sigmoid activation function
fn sigmoid(x: f64) -> f64 {
1.0 / (1.0 + (-x).exp())
}
// derivative of the sigmoid function
fn sigmoid_prime(x: f64) -> f64 {
sigmoid(x) * (1.0 - sigmoid(x))
}
struct NeuralNetwork {
// weights and biases
input_hidden_weights: Vec<Vec<f64>>,
hidden_output_weights: Vec<f64>,
hidden_biases: Vec<f64>,
output_bias: f64,
}
impl NeuralNetwork {
// initialize the neural network with random weights and biases
fn new(input_size: usize, hidden_size: usize) -> Self {
let input_hidden_weights = vec![vec![0.5; input_size]; hidden_size];
let hidden_output_weights = vec![0.5; hidden_size];
let hidden_biases = vec![0.5; hidden_size];
let output_bias = 0.5;
NeuralNetwork {
input_hidden_weights,
hidden_output_weights,
hidden_biases,
output_bias,
}
}
// forward pass
fn forward(&self, input: &Vec<f64>) -> (Vec<f64>, f64) {
let hidden_activations: Vec<f64> = self.input_hidden_weights.iter().zip(&self.hidden_biases)
.map(|(weights, bias)| {
let sum: f64 = weights.iter().zip(input).map(|(w, i)| w * i).sum();
sigmoid(sum + bias)
})
.collect();
let output_sum: f64 = hidden_activations.iter().zip(&self.hidden_output_weights)
.map(|(h, w)| h * w).sum();
let output = sigmoid(output_sum + self.output_bias);
(hidden_activations, output)
}
// training using backpropagation
fn train(&mut self, input: &Vec<f64>, target: f64, learning_rate: f64) {
let (hidden_activations, output) = self.forward(input);
// print the output of the neural network
println!("Output: {}", output);
// calculate the output error
let output_error = target - output;
println!("Error: {}", output_error);
// calculate the gradient for the output layer
let output_gradient = output_error * sigmoid_prime(output);
// update the weights and bias for the hidden-output layer
for (w, h) in self.hidden_output_weights.iter_mut().zip(&hidden_activations) {
*w += learning_rate * output_gradient * h;
}
self.output_bias += learning_rate * output_gradient;
// calculate the hidden layer errors
let hidden_errors: Vec<f64> = self.hidden_output_weights.iter()
.map(|w| output_gradient * w).collect();
// update the weights and biases for the input-hidden layer
for ((weights, bias), error) in self.input_hidden_weights.iter_mut().zip(&mut self.hidden_biases).zip(&hidden_errors) {
let hidden_gradient = error * sigmoid_prime(hidden_activations[0]);
for (w, i) in weights.iter_mut().zip(input) {
*w += learning_rate * hidden_gradient * i;
}
*bias += learning_rate * hidden_gradient;
}
// print the updated weights and biases
println!("Updated input-hidden weights: {:?}", self.input_hidden_weights);
println!("Updated hidden-output weights: {:?}", self.hidden_output_weights);
println!("Updated hidden biases: {:?}", self.hidden_biases);
println!("Updated output bias: {}", self.output_bias);
}
}
fn main() {
// example usage
let mut nn = NeuralNetwork::new(2, 2);
// print initial weights and biases
println!("Initial input-hidden weights: {:?}", nn.input_hidden_weights);
println!("Initial hidden-output weights: {:?}", nn.hidden_output_weights);
println!("Initial hidden biases: {:?}", nn.hidden_biases);
println!("Initial output bias: {}", nn.output_bias);
let input = vec![0.5, 0.25];
let target = 1.0;
// train the neural network
for _ in 0..10000 {
nn.train(&input, target, 0.8);
println!("-----------------------------------");
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment