Skip to main content

README.md

<p align="center">
  <img src="assets/micrograd_ex.svg" width="200" height="200" alt="MicrogradEx logo" />
</p>

<p align="center">
  <a href="https://github.com/nshkrdotcom/micrograd_ex">
    <img alt="GitHub: micrograd" src="https://img.shields.io/badge/GitHub-micrograd_ex-0b0f14?logo=github" />
  </a>
  <a href="https://github.com/nshkrdotcom/micrograd_ex/blob/main/LICENSE">
    <img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-0b0f14.svg" />
  </a>
</p>


# MicrogradEx

MicrogradEx is a small educational scalar autodiff and neural-network library in Elixir, inspired by Andrej Karpathy's [micrograd](https://github.com/karpathy/micrograd).

It implements reverse-mode autodiff over scalar `Value` nodes, a tiny neural-network layer, and a Livebook-first recreation of the classic micrograd two-moons classification demo.

The goal is not performance. The goal is to make the mechanics of backpropagation visible in idiomatic Elixir.

## What is this?

MicrogradEx is a pure-Elixir learning project for reverse-mode automatic differentiation. It keeps the same educational scope as the original Python micrograd: scalar values, explicit computation graphs, neurons, layers, and small MLPs.

The main user experience is `notebooks/micrograd_demo.livemd`, which mirrors the official micrograd workflow in pure Elixir: make a two-moons dataset, train `MLP.new(2, [16, 16, 1])`, plot loss and accuracy, and visualize a decision boundary.

## What this repo includes

* scalar `Value` objects with reverse-mode autodiff;
* immutable `Gradients` tables returned from `Value.backward/1`;
* tiny neural-network modules: `Neuron`, `Layer`, and `MLP`;
* deterministic pure-Elixir dataset generators: moons, spiral, and blobs;
* max-margin classification loss with L2 regularization;
* immutable SGD-style training;
* plot-data helpers for Livebook/Vega-Lite;
* scalar graph inspection and DOT export;
* a flagship Livebook demo;
* ExDoc guides.

## Quick start: Livebook demo

Install livebook if needed:

```bash
mix do local.rebar --force, local.hex --force
mix escript.install hex livebook
```

Path may not be found. If you're using asdf and if needed:

```bash
asdf reshim elixir
```


Clone the repo and open the main notebook:

```bash
git clone <repo-url>
cd micrograd_ex
livebook server
```

Then open:

```text
notebooks/micrograd_demo.livemd
```

Run the notebook from top to bottom. It will:

1. build a two-moons dataset;
2. inspect a scalar autodiff graph;
3. initialize `MLP.new(2, [16, 16, 1])`;
4. confirm the model has `337` parameters;
5. train for 100 steps;
6. plot loss and accuracy;
7. visualize the learned decision boundary.

The notebook uses `Mix.install/2` for Kino and Vega-Lite. Those visualization packages are not runtime dependencies of the core library.

## Extra experiments

After running the main demo, open:

```text
notebooks/micrograd_extras.livemd
```

The extras notebook explores dataset noise, model size, regularization, learning-rate schedules, decision-boundary resolution, and the spiral dataset. It is optional; the main demo remains the official parity path.

## Quick start: IEx

```bash
iex -S mix
```

```elixir
alias MicrogradEx.NN.MLP
alias MicrogradEx.{Datasets, Losses, Trainer}

dataset = Datasets.moons(100, noise: 0.1, seed: {1337, 1337, 1337})
model = MLP.new(2, [16, 16, 1], seed: {1337, 1337, 1337})

initial = Losses.max_margin(model, dataset.xs, dataset.ys)

run =
  Trainer.train(model, dataset,
    steps: 100,
    alpha: 1.0e-4
  )

%{
  initial_loss: initial.total_loss.data,
  final_loss: run.final_loss,
  final_accuracy: run.final_accuracy
}
```

## Official micrograd demo parity

The main Livebook mirrors the original micrograd demo workflow. It does not byte-match sklearn or Matplotlib output; it reproduces the educational workflow in pure Elixir.

| Official Python micrograd | MicrogradEx |
|---|---|
| `sklearn.datasets.make_moons` | `MicrogradEx.Datasets.moons/2` |
| `MLP(2, [16, 16, 1])` | `MicrogradEx.NN.MLP.new(2, [16, 16, 1])` |
| 337 parameters | 337 parameters |
| max-margin loss | `MicrogradEx.Losses.max_margin/4` |
| `model.zero_grad()` | not needed |
| `total_loss.backward()` | `Value.backward(total_loss)` |
| mutate `p.data` | return updated model structs |
| Matplotlib plots | Livebook + Vega-Lite plots |

Expected visuals in the notebook:

* a scatter plot of the two-moons dataset;
* a loss chart showing total, data, and regularization loss;
* an accuracy chart in percent;
* a decision-boundary plot behind the training points.

## Elixir design differences

Python micrograd stores mutable state directly on each `Value`:

* `value.data`;
* `value.grad`.

MicrogradEx keeps `Value` immutable. A backward pass returns a separate `Gradients` table:

```elixir
gradients = MicrogradEx.Value.backward(loss)
dx = MicrogradEx.Value.grad(x, gradients)
```

Model updates are immutable too:

```elixir
next_model = MicrogradEx.NN.apply_gradients(model, gradients, learning_rate)
```

Because gradients are returned fresh from each backward pass, there is no `zero_grad` step.

## Project layout

```text
lib/
  micrograd_ex/
    value.ex          # scalar autodiff values
    gradients.ex      # immutable gradient table
    nn.ex             # Neuron, Layer, MLP
    datasets.ex       # pure-Elixir toy datasets
    losses.ex         # max-margin loss
    trainer.ex        # immutable training loop
    plot_data.ex      # plain rows for plotting
    graph.ex          # graph inspection and DOT export

notebooks/
  micrograd_demo.livemd
  micrograd_extras.livemd

guides/
  getting_started_with_livebook.md
  micrograd_demo_walkthrough.md
  elixir_design_notes.md
  api_reference.md
  troubleshooting.md

test/
```

## Development

```bash
mix deps.get
mix format --check-formatted
mix test
mix credo
mix dialyzer
```

`mix quality` runs the formatter check, Credo, and Dialyzer together.

Validate the Livebook structure:

```bash
bash scripts/validate_livebook.sh
```

Validate documentation:

```bash
bash scripts/validate_docs.sh
```

## Documentation

Generate local docs with:

```bash
mix docs
```

The docs include generated API pages and the guides in `guides/`.

## Guides

* [Getting started with Livebook](guides/getting_started_with_livebook.md)
* [Micrograd demo walkthrough](guides/micrograd_demo_walkthrough.md)
* [Elixir design notes](guides/elixir_design_notes.md)
* [API reference](guides/api_reference.md)
* [Troubleshooting](guides/troubleshooting.md)

## Troubleshooting

If Livebook cannot find the local package, confirm the notebook is still at `notebooks/micrograd_demo.livemd`. Its setup cell uses `Mix.install([{:micrograd_ex, path: ".."}])`.

If training or decision-boundary plotting is slow, remember that this is scalar educational autodiff. Reduce sample count, hidden width, training steps, or use a coarser boundary grid such as `h: 0.35`.

If the parameter count is not `337`, confirm the architecture is exactly:

```elixir
MicrogradEx.NN.MLP.new(2, [16, 16, 1])
```

More setup notes are in [Troubleshooting](guides/troubleshooting.md).

## Attribution

MicrogradEx is inspired by Andrej Karpathy's [micrograd](https://github.com/karpathy/micrograd). The Elixir implementation preserves the scalar educational spirit while adapting state flow to immutable data.

## License

See the LICENSE file.