All examples
Neural networks

MNIST with model classes

Organize Dense parameters and objectives in classes while retaining audited sources and explicit training and evaluation loops.

Download .aner source
mnist_oop.aner
// MNIST MLP: 784 -> 300 -> 100 -> 10. Fixed five epochs; test used once at the end.
// First run tools/benchmarks/fetch_mnist.sh from the repository root.
import aner.dataset
import aner.nn
import aner.ml
import aner.dev
import aner.viz
import aner.metrics

let seed = 42
let epochs = 5
let batch_size = 128
let official = dataset.mnist(directory: "build/benchmarks/mnist/data", partition: "train")
let testing = dataset.mnist(directory: "build/benchmarks/mnist/data", partition: "test")
let partitions = official.partition(validation: 0.1, test: 0.0, seed: 2026, stratify: true, group_aware: false)
let training = partitions.train()
let validation = partitions.validation()
print(partitions.audit())
dataset.require_disjoint(training, testing)
dataset.require_disjoint(validation, testing)
training.require_training()
// Placement follows CLI options; preserve is the default precision policy.
print(nn.execution())
print(nn.backend_details())
dev.automatic(false)
nn.reset_memory_peaks()
let started = nn.clock()
// Research control: parameters, forward, objective, batches and Adam updates.
class MnistNetwork {
    var w1: Tensor
    var b1: Tensor
    var w2: Tensor
    var b2: Tensor
    var w3: Tensor
    var b3: Tensor

    fn forward(input: Tensor) {
        let hidden1 = nn.relu(nn.add_bias(nn.matmul(input, w1), b1))
        let hidden2 = nn.relu(nn.add_bias(nn.matmul(hidden1, w2), b2))
        return nn.add_bias(nn.matmul(hidden2, w3), b3)
    }
    // Detached parameter leaves avoid constructing a training graph during evaluation.
    fn infer(input: Tensor) {
        let hidden1 = nn.relu(nn.add_bias(nn.matmul(input, nn.detach(w1)), nn.detach(b1)))
        let hidden2 = nn.relu(nn.add_bias(nn.matmul(hidden1, nn.detach(w2)), nn.detach(b2)))
        return nn.add_bias(nn.matmul(hidden2, nn.detach(w3)), nn.detach(b3))
    }
    fn update(state: Optimizer) {
        w1 = state.update(w1, key: "w1")
        b1 = state.update(b1, key: "b1")
        w2 = state.update(w2, key: "w2")
        b2 = state.update(b2, key: "b2")
        w3 = state.update(w3, key: "w3")
        b3 = state.update(b3, key: "b3")
    }
}
fn objective(logits: Tensor, target: Tensor) {
    return nn.cross_entropy(logits, target)
}
class Scores {
    public let loss: Float64
    public let accuracy: Float64
}
fn evaluate_network(network: MnistNetwork, samples: Dataset, size: Int64) {
    var loss_sum = 0.0
    var correct_sum = 0.0
    for batch in samples.batches(size: size, seed: 0, shuffle: false) {
        let input = nn.place(batch.features())
        let target = nn.place(batch.one_hot())
        let output = network.infer(input)
        let count = Float64(batch.rows())
        loss_sum = loss_sum + nn.item(nn.cross_entropy(output, target)) * count
        correct_sum = correct_sum + metrics.accuracy(nn.argmax(output), batch.targets()) * count
    }
    return Scores(loss: loss_sum / Float64(samples.rows()), accuracy: correct_sum / Float64(samples.rows()))
}
let network = MnistNetwork(
    w1: nn.parameter(nn.place(nn.xavier(rows: 784, cols: 300, seed: seed))),
    b1: nn.parameter(nn.place(nn.zeros(1, 300))),
    w2: nn.parameter(nn.place(nn.xavier(rows: 300, cols: 100, seed: seed + 1))),
    b2: nn.parameter(nn.place(nn.zeros(1, 100))),
    w3: nn.parameter(nn.place(nn.xavier(rows: 100, cols: 10, seed: seed + 2))),
    b3: nn.parameter(nn.place(nn.zeros(1, 10))))
let state = nn.optimizer(name: "adam", rate: 0.001)
fn train_network(network: MnistNetwork, state: Optimizer, training: Dataset, validation: Dataset,
                 epochs: Int64, batch_size: Int64, seed: Int64, started: Float64) {
    let initial = evaluate_network(network, training, batch_size)
    print("BENCHMARK_INITIAL_LOSS")
    print(initial.loss)
    var epoch = 0
    while epoch < epochs {
        var weighted_loss = 0.0
        var batch_index = 0
        let schedule = training.batches(size: batch_size, seed: seed + epoch, shuffle: true)
        for batch in schedule {
            let input = nn.place(batch.features())
            let target = nn.place(batch.one_hot())
            let loss = objective(network.forward(input), target)
            weighted_loss = weighted_loss + nn.item(loss) * Float64(batch.rows())
            nn.backward(loss)
            // One gradient/topology sample per epoch; no per-batch inspection overhead.
            if batch_index == schedule.count() - 1 { viz.capture(epoch + 1, loss) }
            network.update(state)
            batch_index = batch_index + 1
        }
        epoch = epoch + 1
        let held_out = evaluate_network(network, validation, batch_size)
        let online_loss = weighted_loss / Float64(training.rows())
        dev.progress(epoch: epoch, epochs: epochs, loss: online_loss, accuracy: held_out.accuracy, seconds: nn.clock() - started, validation_loss: held_out.loss)
        print("BENCHMARK_EPOCH")
        print(epoch)
        print(online_loss)
        print(held_out.loss)
        print(held_out.accuracy)
    }
    return evaluate_network(network, training, batch_size)
}
let final_training = train_network(network, state, training, validation, epochs, batch_size, seed, started)
print("BENCHMARK_FIT_SECONDS")
print(nn.clock() - started)
print("BENCHMARK_TRAIN_LOSS")
print(final_training.loss)
print("BENCHMARK_TRAIN_ACCURACY")
print(final_training.accuracy)
let test_started = nn.clock()
let evaluation = evaluate_network(network, testing, batch_size)
print("BENCHMARK_TEST_SECONDS")
print(nn.clock() - test_started)
print("BENCHMARK_TEST_LOSS")
print(evaluation.loss)
print("BENCHMARK_TEST_ACCURACY")
print(evaluation.accuracy)
print("BENCHMARK_MEMORY")
print(nn.memory())
dev.capture("Final model", network)
print(state.summary())

Make it your experiment.

With Aner installed, save this program as examples/mnist_oop.aner inside a folder for your experiment. Open a terminal in that folder, then check and run the program.

Terminal
aner check examples/mnist_oop.aner
aner run examples/mnist_oop.aner

The aner command must be on your PATH. Follow the installation guide if your terminal cannot find it.

Use the current development Aner interpreter with audited Dataset/mini-batch support and VS Code extension 0.1.31. Older published 0.1.0-dev packages may predate these APIs. Place all four canonical decompressed MNIST IDX files in build/benchmarks/mnist/data beneath the working directory; the loader verifies fixed SHA256, shapes, and counts. The official test source remains held out. See the MNIST Dense benchmark for source attribution, file preparation, measured results, notebook timeouts, actual precision, and reproducibility.

Iris and Wine are teaching datasets with their own attribution. Example outcomes are not comparative benchmarks or evidence of clinical validity.

Dataset sources & attribution
Install Aner