use std::time::Instant;
use malevich::{Frame, Line, Plot, Points};
use topos::{Activation, Mlp, Module, Shape, Tape, Tensor, init};
const SAMPLE_LEN: usize = 96;
const DOMAIN: f64 = 3.0;
const NOISE_DEVIATION: f64 = 0.1;
const HIDDEN_LEN: usize = 16;
const STEP_COUNT: usize = 3000;
fn main() {
let features: Tensor<f32> = init::uniform(3, DOMAIN)(&Shape::new([SAMPLE_LEN, 1]));
let noise: Tensor<f32> = init::normal(5, NOISE_DEVIATION)(&Shape::new([SAMPLE_LEN, 1]));
let feature_values = features.to_vec();
let target_values: Vec<f32> = feature_values
.iter()
.zip(noise.to_vec())
.map(|(&x, noise)| x.sin() + noise)
.collect();
let tape: Tape<f32> = Tape::new();
let mlp = Mlp::new(
&tape,
&[1, HIDDEN_LEN, 1],
Activation::Tanh,
init::xavier(7),
);
let input = tape.input(features);
let expected = tape.input(Tensor::new([SAMPLE_LEN, 1], target_values.clone()));
let predicted = mlp.express(input);
let error = predicted - expected;
let loss = (error * error).sum();
let (input, predicted, loss) = (input.symbol(), predicted.symbol(), loss.symbol());
let network = tape.into_network();
let mut parameters = network.parameters();
let learning_rate = Tensor::new([], [0.001]);
let mut losses = Vec::new();
let training = Instant::now();
for step in 0..STEP_COUNT {
let run = network.forward(¶meters, []);
let batch_loss = run.of(loss).scalar();
losses.push(batch_loss);
if step % (STEP_COUNT / 5) == 0 {
println!("step {step:4}: loss = {batch_loss:.4}");
}
let gradients = run.backward(loss).parameters(¶meters);
parameters = parameters.step(&gradients, |parameter, gradient| {
parameter.clone() - gradient.clone() * learning_rate.broadcast_like(gradient)
});
}
println!(
"trained {} steps in {:.3}s",
losses.len(),
training.elapsed().as_secs_f64()
);
println!(
"{}",
Plot::new()
.layer(Line::y(&losses[..]).label("full batch"))
.title("regression training")
.x_label("step")
.y_label("sum of squared errors")
.render_best(&Frame::detect())
);
let grid: Vec<f32> = (0..SAMPLE_LEN)
.map(|index| (((index as f64 / (SAMPLE_LEN - 1) as f64) * 2.0 - 1.0) * DOMAIN) as f32)
.collect();
let run = network.forward(
¶meters,
[(input, Tensor::new([SAMPLE_LEN, 1], grid.clone()))],
);
let fit = run.of(predicted).to_vec();
println!(
"{}",
Plot::new()
.layer(Points::xy(&feature_values[..], &target_values[..]).label("samples"))
.layer(Line::function(-DOMAIN..DOMAIN, f64::sin).label("sin x"))
.layer(Line::xy(&grid[..], &fit[..]).label("fit"))
.title("a tanh mlp fit to noisy sin x")
.x_label("x")
.render_best(&Frame::detect())
);
}