{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {
    "kernelspec": {
      "name": "aner",
      "display_name": "Aner",
      "language": "aner"
    },
    "language_info": {
      "name": "aner",
      "file_extension": ".aner"
    },
    "aner": {
      "execution_mode": "persistent-session",
      "runtime_mode": "development"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": "# MNIST: simple neural training\n\nThe same MLP, immutable partitions, Xavier initialization, Adam optimizer and mini batch schedule are used at every level of control. Download the [verified preparation script](https://aner.albarena.ai/reference/tools/benchmarks/fetch_mnist.sh), then run `bash fetch_mnist.sh mnist` from this notebook's directory before execution. The data cells use that local `mnist` folder. The official test set is evaluated only after the fixed fifth epoch.\n\nSelect Dev in the Aner editor to see variables, training progress and gradient samples. Select Prod and restart/rerun every cell to measure execution without observations. Native Float64 is supported on CPU and CUDA; MPS requires Float32 or an explicit precision policy. Set the profile in the configuration cell before creating parameters.\n\nUse `%aner prod` in a separate cell and rerun all cells after changing modes.\n"
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "%aner timeout 600"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "%aner dev\n"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": "## Execution profile\n\nUse `cuda` on an available accelerator, or `mps` with `Float32` on a supported Mac. Placement is explicit and inspectable.\n"
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "import aner.nn\n",
        "nn.configure(device: \"cpu\", precision: \"Float64\", policy: \"preserve\")\n"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": "## Dataset protocol\n\n54,000 training rows, 6,000 stratified validation rows, and 10,000 untouched official test rows. No writer IDs are supplied by IDX; the validation split is row stratified and does not claim writer grouping.\n"
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "// MNIST MLP: 784 -> 300 -> 100 -> 10. Fixed five epochs; test used once at the end.\n",
        "// First run tools/benchmarks/fetch_mnist.sh from the repository root.\n",
        "import aner.dataset\n",
        "import aner.ml\n",
        "import aner.dev\n",
        "import aner.viz\n",
        "import aner.metrics\n",
        "\n",
        "let seed = 42\n",
        "let epochs = 5\n",
        "let batch_size = 128\n",
        "let official = dataset.mnist(directory: \"mnist\", partition: \"train\")\n",
        "let testing = dataset.mnist(directory: \"mnist\", partition: \"test\")\n",
        "let partitions = official.partition(validation: 0.1, test: 0.0, seed: 2026, stratify: true, group_aware: false)\n",
        "let training = partitions.train()\n",
        "let validation = partitions.validation()\n",
        "print(partitions.audit())\n",
        "dataset.require_disjoint(training, testing)\n",
        "dataset.require_disjoint(validation, testing)\n",
        "training.require_training()\n",
        "// Placement follows CLI options; preserve is the default precision policy.\n",
        "print(nn.execution())\n",
        "print(nn.backend_details())\n",
        "dev.automatic(false)\n"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": "## Model and fixed training run\n\nKeep this phase in a single cell when measuring its elapsed time. The loss is the row weighted mean of each mini batch objective before its update; validation uses detached parameters after each epoch.\n"
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "nn.reset_memory_peaks()\n",
        "let started = nn.clock()\n",
        "// Exploration: topology, training contract and evaluation are explicit.\n",
        "let network = nn.mlp(inputs: 784, hidden: List(300, 100), outputs: 10, seed: seed)\n",
        "let settings = ml.training(epochs: epochs, batch: batch_size, rate: 0.001, seed: seed)\n",
        "    .validation(validation)\n",
        "let fitted = ml.classifier(network, standardize: false, loss: \"cross_entropy\", optimizer: \"adam\")\n",
        "    .fit(training, settings, progress: true)\n",
        "\n",
        "print(\"BENCHMARK_FIT_SECONDS\")\n",
        "print(nn.clock() - started)\n",
        "print(\"BENCHMARK_TRAIN_LOSS\")\n",
        "print(fitted.training_loss())\n",
        "print(\"BENCHMARK_TRAIN_ACCURACY\")\n",
        "print(fitted.training_accuracy())\n",
        "let test_started = nn.clock()\n",
        "let evaluation = fitted.evaluate(testing)\n",
        "print(\"BENCHMARK_TEST_SECONDS\")\n",
        "print(nn.clock() - test_started)\n",
        "print(\"BENCHMARK_TEST_LOSS\")\n",
        "print(evaluation.loss())\n",
        "print(\"BENCHMARK_TEST_ACCURACY\")\n",
        "print(evaluation.accuracy())\n",
        "print(\"BENCHMARK_MEMORY\")\n",
        "print(nn.memory())\n",
        "print(fitted.summary())\n",
        "dev.capture(\"Final model\", fitted)\n"
      ],
      "execution_count": null,
      "outputs": []
    }
  ]
}
