use std::time::Instant;
use malevich::{Frame, Line, Plot, Points};
use topos::{Mlp, Network, Shape, Tensor, Tensorial, 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 network: Network<Tensor<f32>> = Network::new();
let mlp = Mlp::new(&network, &[1, HIDDEN_LEN, 1], init::xavier(7));
let input = network.input(features);
let expected = network.input(Tensor::new([SAMPLE_LEN, 1], target_values.clone()));
let predicted = mlp.express(&network, input);
let error = predicted - expected;
let loss = (error * error).sum();
let input_symbol = input.symbol();
let predicted_symbol = predicted.symbol();
let loss_symbol = loss.symbol();
let learning_rate = Tensor::new([], [0.001]);
let mut network = network;
let mut losses = Vec::new();
let training = Instant::now();
for step in 0..STEP_COUNT {
let loss_value = network.resolve(loss_symbol);
let run = network.forward();
let batch_loss = run.of(loss_value).to_vec()[0];
losses.push(batch_loss);
if step % (STEP_COUNT / 5) == 0 {
println!("step {step:4}: loss = {batch_loss:.4}");
}
let gradients = run.backward(loss_value);
network = network.update(&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_with([(input_symbol, Tensor::new([SAMPLE_LEN, 1], grid.clone()))]);
let fit = run.of(network.resolve(predicted_symbol)).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())
);
}