use std::time::Instant;
use rayon::prelude::*;
use malevich::{Frame, Line, Plot};
use topos::model::{Detach, Tape, Tensor};
fn main() {
let (network, (w, b, loss, sample_losses)) = Tape::record(|tape| {
let w = tape.parameter(0.0_f64);
let b = tape.parameter(0.0);
let samples = [(1.0, 3.0), (2.0, 5.0), (3.0, 7.0)];
let mut sample_losses = Vec::new();
for (x, y) in samples {
let x = tape.leaf(x);
let y = tape.leaf(y);
let error = w * x + b - y;
sample_losses.push(error * error);
}
let loss = sample_losses
.iter()
.copied()
.reduce(|total, squared| total + squared)
.expect("at least one sample");
(w, b, loss, sample_losses).detach()
});
let parameters = network.parameters();
let run = network.forward(¶meters, []);
let per_sample: Vec<f64> = sample_losses
.par_iter()
.map(|&sample_loss| {
let gradients = run.backward(sample_loss);
gradients.of(w).scalar()
})
.collect();
let total_gradient = run.backward(loss).of(w).scalar();
println!("per-sample d/dw, computed on separate threads: {per_sample:?}");
println!(
"their sum {} equals the total-loss d/dw {} by linearity",
per_sample.iter().sum::<f64>(),
total_gradient
);
let learning_rates = [0.005, 0.02, 0.05];
let training = Instant::now();
let runs: Vec<(f64, Vec<f64>, f64, f64)> = learning_rates
.par_iter()
.map(|&learning_rate| {
let mut parameters = parameters.clone();
let mut losses = Vec::with_capacity(501);
for _ in 0..500 {
let run = network.forward(¶meters, []);
losses.push(run.of(loss).scalar());
let gradients = run.backward(loss).parameters(¶meters);
parameters = parameters.step(&gradients, |parameter, gradient| {
parameter.clone() - gradient.clone() * Tensor::from(learning_rate)
});
}
let run = network.forward(¶meters, []);
losses.push(run.of(loss).scalar());
(
learning_rate,
losses,
parameters.of(w).scalar(),
parameters.of(b).scalar(),
)
})
.collect();
println!(
"trained {} states of 500 steps in {:.3}s",
learning_rates.len(),
training.elapsed().as_secs_f64()
);
println!("parallel training on cloned states (target: w = 2, b = 1):");
for (learning_rate, losses, w, b) in &runs {
let final_loss = losses.last().expect("every run records its final loss");
println!(" lr = {learning_rate:5.3}: loss = {final_loss:.6}, w = {w:.3}, b = {b:.3}");
}
let mut plot = Plot::new()
.title("gradient descent per learning rate")
.x_label("step")
.y_label("loss")
.log_y();
for (learning_rate, losses, ..) in &runs {
plot = plot.layer(Line::y(&losses[..]).label(format!("lr = {learning_rate}")));
}
println!("{}", plot.render_best(&Frame::detect()));
}