diff --git a/.gitignore b/.gitignore index 87620ac7..b9dfc1c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ .ipynb_checkpoints/ +__pycache__/ +build/ +*.egg-info/ diff --git a/README.md b/README.md index 931f4d77..a97a5b0a 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,166 @@ # micrograd +A tiny autograd engine whose only dependency is NumPy the linear algebra library. Micrograd implements backpropagation (automatic differentiation) over a graph of mathemtical operations. -![awww](puppy.jpg) +* 20 kilobytes [core code](micrograd/engine.py), 10,000+ times smaller +* as portable as Python and NumPy +* comparable performance as industrial contenders +* code can be timed with Python's native profiler -A tiny Autograd engine (with a bite! :)). Implements backpropagation (reverse-mode autodiff) over a dynamically built DAG and a small neural networks library on top of it with a PyTorch-like API. Both are tiny, with about 100 and 50 lines of code respectively. The DAG only operates over scalar values, so e.g. we chop up each neuron into all of its individual tiny adds and multiplies. However, this is enough to build up entire deep neural nets doing binary classification, as the demo notebook shows. Potentially useful for educational purposes. +This version works with vectors, including matrices (2-dimensional), or higher-dimensional tensors. For @karpathy's original scalar-based version, switch to the code with tag `scalar`. -### Installation +TensorFlow, Apple's MLX and our micrograd, [https://www.brief-ds.com/2025/09/25/tensorflow-mlx.html](https://www.brief-ds.com/2025/09/25/tensorflow-mlx.html) -```bash -pip install micrograd +## Get Started +In any working directory, create a virtual environment, + +```sh +python3 -m venv venv +. venv/bin/activate +cd # if not already in the micrograd's directory +pip3 install . +cd # if different from micrograd +pip3 install jupyter # for running demos in demos/ +pip3 install torch # to run tests/test_vs_torch.py +``` + +Below is a Python snippet. `c` is the matrix-vector product of `a` and `b`. After calling `c.backward()`, the mathematical derivatives of `c` with respect to any variable it depends on are evaluated, e.g `a.grad` is `dc/da`, `b.grad` is `dc/db`. `c.grad` is always all one as `dc/dc=1`. + +```python +from micrograd import Value +from numpy import array + +a = Value(array([[2, 3], [5, 4]])) +b = Value(array([1, -1])) +c = (a @ b).relu() +print(c) # Value(data=[0 1], grad=None) +c.backward() +print(c) # Value(data=[0 1], grad=[1. 1.]) +print(a) # Value(data=..., grad=[[0. 0.], [1. -1.]]) +print(b) # Value(data=..., grad=[5. 4.]) +``` + +PyTorch can only mathematically derive an expression that produces a scalar value. micrograd relaxes it: if the expression produces an array, the sum of the array will be derived. + +For full examples, go to [`demos/`](demos). The scalar-version [demos/demo_scalar.ipynb](demos/demo_scalar.ipynb) takes minutes to run, but the vector-version training [demos/demo_vector.ipynb](demos/demo_vector.ipynb) is instant. + +## Lazy evaluation +When defining a tensor, one may just indicate `shape` and `name`, and later on provide the value corresponding to the `name`. + +```python +from micrograd import Value +from numpy import array + +a = Value(shape=(2, 2), name='var1') +b = Value(shape=(2,), name='var2') +c = (a @ b).relu() +c.forward(var1=array([[2, 3], [5, 4]]), + var2=array([1, -1])) +c.backward() +``` + +By default, a variable awaiting value takes `nan`, if not fed any value in `forward()`. The final result will be `nan`, signalling missing values somewhere. If a mathematical expression contains no variable awaiting value, the `forward()` call is not necessary. Once defined, its value will be stored in `.data`. + +## Data type +As one example, with `f=ab`, `df/da=b`. `a.grad` would inherit the data type of `b`. For this inter-dependence, we design a uniform `DTYPE` for one program, to be passed from the environment. By default `DTYPE=float64`, identical as the Python float type. For example, + +```sh +DTYPE=float32 python3 +``` + +micrograd's `__init__.py` reads `DTYPE` from the environment. In Python, _before_ importing micrograd, one may manipulate the `DTYPE` by + +```python +from os import environ +environ['DTYPE'] = ... + +from micrograd import Value +``` + +One may get the `DTYPE` that micrograd read, + +```python +from micrograd import DTYPE ``` -### Example usage +## Efficient dependency graph computation +The dependency graph of mathematical operations in a mathematical expression is calculated only once then cached, **assuming** this expression is *static*, although the values of its variables may change. + +## Back propogation (automatic differentiation) +If a mathematical expression `x` contains variables awaiting value, call `forward()` once to evaluate it. + +Call `backward()` for mathematical differentiation of `x` with respect to the dependent variables. The `backward()` manages all initialisations of gradients: unlike PyTorch, no `zero_grad()` is necessary before `backward()`. + +```python +x.forward(var1=value1, var2=value2, ...) +x.backward() +``` + +## Supported operators +* `__pow__` +* `__matmul__` +* `tensordot` for tensor contraction: unlike numpy tensordot, the last axis (indexed by -1) of the left tensor contracts with the first axis of the right tensor; the next to last axis (indexed by -2) of the left tensor with the 2nd axis of the right tensor; so on and so forth. +* `relu` +* `exp` +* `log` +* `log1p` +* `tanh` +* `arctanh` +* `T` for transpose +* `sum` +* `mean` + +## Optimise by Stochastic Gradient Descent +We can minimise a mathematical expression by moving the values of its dependent variables. For example, if `x` is defined from `a` and `b`, + +```python +# call x.forward() if necessary +x.backward() +a -= learning_rate * a.grad +b -= learning_rate * b.grad +``` -Below is a slightly contrived example showing a number of possible supported operations: +The [`micrograd.optim.SGD`](micrograd/optim.py) wraps up the above ```python -from micrograd.engine import Value - -a = Value(-4.0) -b = Value(2.0) -c = a + b -d = a * b + b**3 -c += c + 1 -c += 1 + c + (-a) -d += d * 2 + (b + a).relu() -d += 3 * d + (b - a).relu() -e = c - d -f = e**2 -g = f / 2.0 -g += 10.0 / f -print(f'{g.data:.4f}') # prints 24.7041, the outcome of this forward pass -g.backward() -print(f'{a.grad:.4f}') # prints 138.8338, i.e. the numerical value of dg/da -print(f'{b.grad:.4f}') # prints 645.5773, i.e. the numerical value of dg/db +SGD(wrt=[], # list of variables with respect to which + # to perform minimisation + learning_rate=None, + # a non-negative number or a generator of them + momentum=None) ``` -### Training a neural net +The `learning_rate` can accept a generator implementing a schedule of varying learning rates. Typical usage is as below, -The notebook `demo.ipynb` provides a full demo of training an 2-layer neural network (MLP) binary classifier. This is achieved by initializing a neural net from `micrograd.nn` module, implementing a simple svm "max-margin" binary classification loss and using SGD for optimization. As shown in the notebook, using a 2-layer neural net with two 16-node hidden layers we achieve the following decision boundary on the moon dataset: +```python +optimiser = SGD(...) + +for k in range(n_steps): + + # batch_iterator yields a dict + # for the minibatch, e.g. + # + # batch_data = {'X': .., + # 'y': ..} + # + batch_data = next(batch_iterator) -![2d neuron](moon_mlp.png) + loss.forward(**batch_data) + loss.backward() -### Tracing / visualization + optimiser.step() + + # validation + validation_metric.forward() + +``` +## The Demos +The notebooks under `demos/` provide a full demo of training an 2-layer neural network (MLP) binary classifier. This is achieved by initializing a neural net from `micrograd.nn` module, implementing a simple svm "max-margin" binary classification loss and using SGD for optimization. As shown in the notebook, using a 2-layer neural net with two 16-node hidden layers we achieve the following decision boundary on the moon dataset: + +![2d neuron](assets/moon_mlp.png) + +## Tracing / visualization For added convenience, the notebook `trace_graph.ipynb` produces graphviz visualizations. E.g. this one below is of a simple 2D neuron, arrived at by calling `draw_dot` on the code below, and it shows both the data (left number in each node) and the gradient (right number in each node). ```python @@ -54,16 +171,23 @@ y = n(x) dot = draw_dot(y) ``` -![2d neuron](gout.svg) - -### Running tests +![2d neuron](assets/gout.svg) -To run the unit tests you will have to install [PyTorch](https://pytorch.org/), which the tests use as a reference for verifying the correctness of the calculated gradients. Then simply: +## Running tests +If PyTorch requires NumPy lower than version 2, create a new virtual environment `torch`, and install downgraded NumPy there for the tests. -```bash -python -m pytest +```sh +python3 -m venv torch +. torch/bin/activate +pip3 install "numpy<2" # put numpy<2 inside quotation marks + # quotation marks here are important ``` -### License +Run the unit tests: + +```sh +python -m unittest tests/*.py +``` +## License MIT diff --git a/gout.svg b/assets/gout.svg similarity index 100% rename from gout.svg rename to assets/gout.svg diff --git a/moon_mlp.png b/assets/moon_mlp.png similarity index 100% rename from moon_mlp.png rename to assets/moon_mlp.png diff --git a/puppy.jpg b/assets/puppy.jpg similarity index 100% rename from puppy.jpg rename to assets/puppy.jpg diff --git a/demo.ipynb b/demo.ipynb deleted file mode 100644 index b8c12531..00000000 --- a/demo.ipynb +++ /dev/null @@ -1,353 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### MicroGrad demo" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import random\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "%matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from micrograd.engine import Value\n", - "from micrograd.nn import Neuron, Layer, MLP" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "np.random.seed(1337)\n", - "random.seed(1337)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# make up a dataset\n", - "\n", - "from sklearn.datasets import make_moons, make_blobs\n", - "X, y = make_moons(n_samples=100, noise=0.1)\n", - "\n", - "y = y*2 - 1 # make y be -1 or 1\n", - "# visualize in 2D\n", - "plt.figure(figsize=(5,5))\n", - "plt.scatter(X[:,0], X[:,1], c=y, s=20, cmap='jet')" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "MLP of [Layer of [ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2)], Layer of [ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16)], Layer of [LinearNeuron(16)]]\n", - "number of parameters 337\n" - ] - } - ], - "source": [ - "# initialize a model \n", - "model = MLP(2, [16, 16, 1]) # 2-layer neural network\n", - "print(model)\n", - "print(\"number of parameters\", len(model.parameters()))" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Value(data=0.8958441028683222, grad=0) 0.5\n" - ] - } - ], - "source": [ - "# loss function\n", - "def loss(batch_size=None):\n", - " \n", - " # inline DataLoader :)\n", - " if batch_size is None:\n", - " Xb, yb = X, y\n", - " else:\n", - " ri = np.random.permutation(X.shape[0])[:batch_size]\n", - " Xb, yb = X[ri], y[ri]\n", - " inputs = [list(map(Value, xrow)) for xrow in Xb]\n", - " \n", - " # forward the model to get scores\n", - " scores = list(map(model, inputs))\n", - " \n", - " # svm \"max-margin\" loss\n", - " losses = [(1 + -yi*scorei).relu() for yi, scorei in zip(yb, scores)]\n", - " data_loss = sum(losses) * (1.0 / len(losses))\n", - " # L2 regularization\n", - " alpha = 1e-4\n", - " reg_loss = alpha * sum((p*p for p in model.parameters()))\n", - " total_loss = data_loss + reg_loss\n", - " \n", - " # also get accuracy\n", - " accuracy = [(yi > 0) == (scorei.data > 0) for yi, scorei in zip(yb, scores)]\n", - " return total_loss, sum(accuracy) / len(accuracy)\n", - "\n", - "total_loss, acc = loss()\n", - "print(total_loss, acc)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "step 0 loss 0.8958441028683222, accuracy 50.0%\n", - "step 1 loss 1.7235905336972022, accuracy 81.0%\n", - "step 2 loss 0.7429006313851131, accuracy 77.0%\n", - "step 3 loss 0.7705641260584198, accuracy 82.0%\n", - "step 4 loss 0.3692793385976538, accuracy 84.0%\n", - "step 5 loss 0.313545481918522, accuracy 86.0%\n", - "step 6 loss 0.2814234349772435, accuracy 89.0%\n", - "step 7 loss 0.26888733313983904, accuracy 91.0%\n", - "step 8 loss 0.2567147286057417, accuracy 91.0%\n", - "step 9 loss 0.2704862551637922, accuracy 91.0%\n", - "step 10 loss 0.24507023853658053, accuracy 91.0%\n", - "step 11 loss 0.2509905529791503, accuracy 92.0%\n", - "step 12 loss 0.21560951851922952, accuracy 91.0%\n", - "step 13 loss 0.23090378446402726, accuracy 93.0%\n", - "step 14 loss 0.20152151227899445, accuracy 92.0%\n", - "step 15 loss 0.22574506279282217, accuracy 93.0%\n", - "step 16 loss 0.19447987596204114, accuracy 92.0%\n", - "step 17 loss 0.21089496199246363, accuracy 93.0%\n", - "step 18 loss 0.159830773563036, accuracy 94.0%\n", - "step 19 loss 0.1845374874688392, accuracy 93.0%\n", - "step 20 loss 0.18977522856087634, accuracy 91.0%\n", - "step 21 loss 0.19072704042579647, accuracy 93.0%\n", - "step 22 loss 0.11733695088756485, accuracy 97.0%\n", - "step 23 loss 0.12173524408232454, accuracy 95.0%\n", - "step 24 loss 0.1261571261277045, accuracy 95.0%\n", - "step 25 loss 0.16049097780801674, accuracy 95.0%\n", - "step 26 loss 0.18747197705245805, accuracy 92.0%\n", - "step 27 loss 0.16741837891059408, accuracy 95.0%\n", - "step 28 loss 0.09586583491455399, accuracy 97.0%\n", - "step 29 loss 0.0877878370742091, accuracy 96.0%\n", - "step 30 loss 0.11731297569011848, accuracy 95.0%\n", - "step 31 loss 0.09340146460619836, accuracy 97.0%\n", - "step 32 loss 0.12454454903103446, accuracy 95.0%\n", - "step 33 loss 0.07984002652777272, accuracy 97.0%\n", - "step 34 loss 0.07727519232921673, accuracy 97.0%\n", - "step 35 loss 0.07661250143094483, accuracy 98.0%\n", - "step 36 loss 0.10610492379198365, accuracy 96.0%\n", - "step 37 loss 0.09062808429265976, accuracy 99.0%\n", - "step 38 loss 0.10671887043036932, accuracy 95.0%\n", - "step 39 loss 0.05225659921975849, accuracy 98.0%\n", - "step 40 loss 0.06016009895234464, accuracy 100.0%\n", - "step 41 loss 0.08596724533333942, accuracy 96.0%\n", - "step 42 loss 0.051121079431796, accuracy 99.0%\n", - "step 43 loss 0.052401424016428284, accuracy 97.0%\n", - "step 44 loss 0.045306841790015734, accuracy 100.0%\n", - "step 45 loss 0.07211073370655095, accuracy 97.0%\n", - "step 46 loss 0.03334238651310234, accuracy 99.0%\n", - "step 47 loss 0.03143222795751122, accuracy 100.0%\n", - "step 48 loss 0.03658536747111507, accuracy 99.0%\n", - "step 49 loss 0.04829139382390309, accuracy 99.0%\n", - "step 50 loss 0.09875114765619622, accuracy 96.0%\n", - "step 51 loss 0.05449063965875453, accuracy 99.0%\n", - "step 52 loss 0.03392679435708309, accuracy 100.0%\n", - "step 53 loss 0.05261517263568441, accuracy 97.0%\n", - "step 54 loss 0.03250295251424923, accuracy 99.0%\n", - "step 55 loss 0.02888327387207822, accuracy 100.0%\n", - "step 56 loss 0.04139151104027239, accuracy 98.0%\n", - "step 57 loss 0.018987407426128502, accuracy 100.0%\n", - "step 58 loss 0.0252383352388374, accuracy 100.0%\n", - "step 59 loss 0.02079656521341895, accuracy 100.0%\n", - "step 60 loss 0.0325971115781023, accuracy 99.0%\n", - "step 61 loss 0.017863351693480307, accuracy 100.0%\n", - "step 62 loss 0.023008717832211683, accuracy 100.0%\n", - "step 63 loss 0.022079325463581503, accuracy 100.0%\n", - "step 64 loss 0.029432917853529684, accuracy 99.0%\n", - "step 65 loss 0.01625151464409193, accuracy 100.0%\n", - "step 66 loss 0.02846853448326446, accuracy 99.0%\n", - "step 67 loss 0.013994365546208731, accuracy 100.0%\n", - "step 68 loss 0.015552344843651405, accuracy 100.0%\n", - "step 69 loss 0.0338911994616017, accuracy 99.0%\n", - "step 70 loss 0.014229870065926908, accuracy 100.0%\n", - "step 71 loss 0.013255281583285504, accuracy 100.0%\n", - "step 72 loss 0.012300277590022063, accuracy 100.0%\n", - "step 73 loss 0.012676052498355976, accuracy 100.0%\n", - "step 74 loss 0.020593811955954763, accuracy 100.0%\n", - "step 75 loss 0.011845398205364453, accuracy 100.0%\n", - "step 76 loss 0.016012697472883086, accuracy 100.0%\n", - "step 77 loss 0.025458360239222128, accuracy 100.0%\n", - "step 78 loss 0.014382930289661911, accuracy 100.0%\n", - "step 79 loss 0.011698962425817985, accuracy 100.0%\n", - "step 80 loss 0.012318500800515763, accuracy 100.0%\n", - "step 81 loss 0.014121117031464233, accuracy 100.0%\n", - "step 82 loss 0.011664591962446225, accuracy 100.0%\n", - "step 83 loss 0.011589314549188726, accuracy 100.0%\n", - "step 84 loss 0.010990299347735226, accuracy 100.0%\n", - "step 85 loss 0.01098922672069161, accuracy 100.0%\n", - "step 86 loss 0.010988193757655071, accuracy 100.0%\n", - "step 87 loss 0.010987200447388707, accuracy 100.0%\n", - "step 88 loss 0.010986246779084925, accuracy 100.0%\n", - "step 89 loss 0.010985332742365272, accuracy 100.0%\n", - "step 90 loss 0.010984458327280174, accuracy 100.0%\n", - "step 91 loss 0.010983623524308862, accuracy 100.0%\n", - "step 92 loss 0.010982828324359073, accuracy 100.0%\n", - "step 93 loss 0.010982072718767003, accuracy 100.0%\n", - "step 94 loss 0.010981356699297042, accuracy 100.0%\n", - "step 95 loss 0.010980680258141723, accuracy 100.0%\n", - "step 96 loss 0.010980043387921506, accuracy 100.0%\n", - "step 97 loss 0.010979446081684675, accuracy 100.0%\n", - "step 98 loss 0.010978888332907229, accuracy 100.0%\n", - "step 99 loss 0.010978370135492717, accuracy 100.0%\n" - ] - } - ], - "source": [ - "# optimization\n", - "for k in range(100):\n", - " \n", - " # forward\n", - " total_loss, acc = loss()\n", - " \n", - " # backward\n", - " model.zero_grad()\n", - " total_loss.backward()\n", - " \n", - " # update (sgd)\n", - " learning_rate = 1.0 - 0.9*k/100\n", - " for p in model.parameters():\n", - " p.data -= learning_rate * p.grad\n", - " \n", - " if k % 1 == 0:\n", - " print(f\"step {k} loss {total_loss.data}, accuracy {acc*100}%\")\n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(-1.548639298268643, 1.951360701731357)" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# visualize decision boundary\n", - "\n", - "h = 0.25\n", - "x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1\n", - "y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1\n", - "xx, yy = np.meshgrid(np.arange(x_min, x_max, h),\n", - " np.arange(y_min, y_max, h))\n", - "Xmesh = np.c_[xx.ravel(), yy.ravel()]\n", - "inputs = [list(map(Value, xrow)) for xrow in Xmesh]\n", - "scores = list(map(model, inputs))\n", - "Z = np.array([s.data > 0 for s in scores])\n", - "Z = Z.reshape(xx.shape)\n", - "\n", - "fig = plt.figure()\n", - "plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral, alpha=0.8)\n", - "plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)\n", - "plt.xlim(xx.min(), xx.max())\n", - "plt.ylim(yy.min(), yy.max())\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/demos/demo_scalar.ipynb b/demos/demo_scalar.ipynb new file mode 100644 index 00000000..884787c8 --- /dev/null +++ b/demos/demo_scalar.ipynb @@ -0,0 +1,364 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### MicroGrad demo" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is the @karpathy's scalar-based demo." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from micrograd.engine import Value\n", + "from micrograd.nn import Neuron, Layer, MLP" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "np.random.seed(1337)\n", + "random.seed(1337)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# make up a dataset\n", + "\n", + "from sklearn.datasets import make_moons, make_blobs\n", + "X, y = make_moons(n_samples=100, noise=0.1)\n", + "\n", + "y = y*2 - 1 # make y be -1 or 1\n", + "# visualize in 2D\n", + "plt.figure(figsize=(5,5))\n", + "plt.scatter(X[:,0], X[:,1], c=y, s=20, cmap='jet')" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MLP of [Layer of [ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2), ReLUNeuron(2)], Layer of [ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16), ReLUNeuron(16)], Layer of [LinearNeuron(16)]]\n", + "number of parameters 337\n" + ] + } + ], + "source": [ + "# initialize a model \n", + "model = MLP(2, [16, 16, 1]) # 2-layer neural network\n", + "print(model)\n", + "print(\"number of parameters\", len(model.parameters()))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.581063 0.48\n" + ] + } + ], + "source": [ + "# loss function\n", + "def loss(batch_size=None):\n", + " \n", + " # inline DataLoader :)\n", + " if batch_size is None:\n", + " Xb, yb = X, y\n", + " else:\n", + " ri = np.random.permutation(X.shape[0])[:batch_size]\n", + " Xb, yb = X[ri], y[ri]\n", + " inputs = [list(map(Value, xrow)) for xrow in Xb]\n", + " \n", + " # forward the model to get scores\n", + " scores = list(map(model, inputs))\n", + " \n", + " # svm \"max-margin\" loss\n", + " losses = [(1 + -yi*scorei).relu() for yi, scorei in zip(yb, scores)]\n", + " data_loss = sum(losses) * (1.0 / len(losses))\n", + " # L2 regularization\n", + " alpha = 1e-4\n", + " reg_loss = alpha * sum((p*p for p in model.parameters()))\n", + " total_loss = data_loss + reg_loss\n", + " \n", + " # also get accuracy\n", + " accuracy = [(yi > 0) == (scorei.data > 0) for yi, scorei in zip(yb, scores)]\n", + " return total_loss, sum(accuracy) / len(accuracy)\n", + "\n", + "total_loss, acc = loss()\n", + "print(f'{total_loss.data:.6f}', acc)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 0 loss 1.581063, accuracy 48.0%\n", + "step 1 loss 1.384858, accuracy 68.0%\n", + "step 2 loss 0.934716, accuracy 74.0%\n", + "step 3 loss 0.727044, accuracy 78.0%\n", + "step 4 loss 0.384791, accuracy 84.0%\n", + "step 5 loss 0.348114, accuracy 85.0%\n", + "step 6 loss 0.269266, accuracy 88.0%\n", + "step 7 loss 0.223788, accuracy 91.0%\n", + "step 8 loss 0.183176, accuracy 92.0%\n", + "step 9 loss 0.183144, accuracy 94.0%\n", + "step 10 loss 0.234310, accuracy 91.0%\n", + "step 11 loss 0.250955, accuracy 90.0%\n", + "step 12 loss 0.189704, accuracy 93.0%\n", + "step 13 loss 0.139192, accuracy 95.0%\n", + "step 14 loss 0.141982, accuracy 94.0%\n", + "step 15 loss 0.159697, accuracy 98.0%\n", + "step 16 loss 0.447638, accuracy 84.0%\n", + "step 17 loss 0.196442, accuracy 92.0%\n", + "step 18 loss 0.124547, accuracy 96.0%\n", + "step 19 loss 0.139352, accuracy 92.0%\n", + "step 20 loss 0.108038, accuracy 99.0%\n", + "step 21 loss 0.139528, accuracy 95.0%\n", + "step 22 loss 0.077637, accuracy 97.0%\n", + "step 23 loss 0.074556, accuracy 98.0%\n", + "step 24 loss 0.054618, accuracy 98.0%\n", + "step 25 loss 0.059493, accuracy 100.0%\n", + "step 26 loss 0.095297, accuracy 96.0%\n", + "step 27 loss 0.075605, accuracy 99.0%\n", + "step 28 loss 0.066633, accuracy 97.0%\n", + "step 29 loss 0.038468, accuracy 100.0%\n", + "step 30 loss 0.069943, accuracy 97.0%\n", + "step 31 loss 0.033451, accuracy 100.0%\n", + "step 32 loss 0.060605, accuracy 98.0%\n", + "step 33 loss 0.051639, accuracy 99.0%\n", + "step 34 loss 0.026208, accuracy 100.0%\n", + "step 35 loss 0.050012, accuracy 98.0%\n", + "step 36 loss 0.034082, accuracy 100.0%\n", + "step 37 loss 0.078973, accuracy 96.0%\n", + "step 38 loss 0.021444, accuracy 100.0%\n", + "step 39 loss 0.022194, accuracy 100.0%\n", + "step 40 loss 0.026996, accuracy 100.0%\n", + "step 41 loss 0.018086, accuracy 100.0%\n", + "step 42 loss 0.026232, accuracy 100.0%\n", + "step 43 loss 0.016310, accuracy 100.0%\n", + "step 44 loss 0.015634, accuracy 100.0%\n", + "step 45 loss 0.020722, accuracy 100.0%\n", + "step 46 loss 0.014856, accuracy 100.0%\n", + "step 47 loss 0.014340, accuracy 100.0%\n", + "step 48 loss 0.017394, accuracy 100.0%\n", + "step 49 loss 0.029708, accuracy 100.0%\n", + "step 50 loss 0.019136, accuracy 100.0%\n", + "step 51 loss 0.026721, accuracy 100.0%\n", + "step 52 loss 0.013168, accuracy 100.0%\n", + "step 53 loss 0.012894, accuracy 100.0%\n", + "step 54 loss 0.012130, accuracy 100.0%\n", + "step 55 loss 0.011983, accuracy 100.0%\n", + "step 56 loss 0.011980, accuracy 100.0%\n", + "step 57 loss 0.011978, accuracy 100.0%\n", + "step 58 loss 0.011976, accuracy 100.0%\n", + "step 59 loss 0.011973, accuracy 100.0%\n", + "step 60 loss 0.011971, accuracy 100.0%\n", + "step 61 loss 0.011969, accuracy 100.0%\n", + "step 62 loss 0.011967, accuracy 100.0%\n", + "step 63 loss 0.011965, accuracy 100.0%\n", + "step 64 loss 0.011962, accuracy 100.0%\n", + "step 65 loss 0.011961, accuracy 100.0%\n", + "step 66 loss 0.012471, accuracy 100.0%\n", + "step 67 loss 0.012995, accuracy 100.0%\n", + "step 68 loss 0.015891, accuracy 100.0%\n", + "step 69 loss 0.012300, accuracy 100.0%\n", + "step 70 loss 0.011972, accuracy 100.0%\n", + "step 71 loss 0.011970, accuracy 100.0%\n", + "step 72 loss 0.011968, accuracy 100.0%\n", + "step 73 loss 0.011967, accuracy 100.0%\n", + "step 74 loss 0.011965, accuracy 100.0%\n", + "step 75 loss 0.011964, accuracy 100.0%\n", + "step 76 loss 0.011962, accuracy 100.0%\n", + "step 77 loss 0.011960, accuracy 100.0%\n", + "step 78 loss 0.011959, accuracy 100.0%\n", + "step 79 loss 0.011958, accuracy 100.0%\n", + "step 80 loss 0.011956, accuracy 100.0%\n", + "step 81 loss 0.011955, accuracy 100.0%\n", + "step 82 loss 0.011954, accuracy 100.0%\n", + "step 83 loss 0.011952, accuracy 100.0%\n", + "step 84 loss 0.011951, accuracy 100.0%\n", + "step 85 loss 0.011950, accuracy 100.0%\n", + "step 86 loss 0.011949, accuracy 100.0%\n", + "step 87 loss 0.011948, accuracy 100.0%\n", + "step 88 loss 0.011947, accuracy 100.0%\n", + "step 89 loss 0.011946, accuracy 100.0%\n", + "step 90 loss 0.011945, accuracy 100.0%\n", + "step 91 loss 0.011944, accuracy 100.0%\n", + "step 92 loss 0.011943, accuracy 100.0%\n", + "step 93 loss 0.011942, accuracy 100.0%\n", + "step 94 loss 0.011941, accuracy 100.0%\n", + "step 95 loss 0.011941, accuracy 100.0%\n", + "step 96 loss 0.011940, accuracy 100.0%\n", + "step 97 loss 0.011939, accuracy 100.0%\n", + "step 98 loss 0.011939, accuracy 100.0%\n", + "step 99 loss 0.011938, accuracy 100.0%\n" + ] + } + ], + "source": [ + "# optimization\n", + "#for layer in model.layers:\n", + "# print(layer.L2_norm())\n", + " \n", + "for k in range(100):\n", + " \n", + " # forward\n", + " total_loss, acc = loss()\n", + " \n", + " # backward\n", + " total_loss.backward()\n", + " \n", + " # update (sgd)\n", + " learning_rate = 1.0 - 0.9*k/100\n", + " for p in model.parameters():\n", + " p.data -= learning_rate * p.grad\n", + " \n", + " if k % 1 == 0:\n", + " print(f\"step {k} loss {total_loss.data:.6f}, accuracy {acc*100}%\")\n", + " #for layer in model.layers:\n", + " # print(layer.L2_norm(), layer.grad_L2_norm())" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(-1.548639298268643, 1.951360701731357)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# visualize decision boundary\n", + "\n", + "h = 0.25\n", + "x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1\n", + "y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1\n", + "xx, yy = np.meshgrid(np.arange(x_min, x_max, h),\n", + " np.arange(y_min, y_max, h))\n", + "Xmesh = np.c_[xx.ravel(), yy.ravel()]\n", + "inputs = [list(map(Value, xrow)) for xrow in Xmesh]\n", + "scores = list(map(model, inputs))\n", + "Z = np.array([s.data > 0 for s in scores])\n", + "Z = Z.reshape(xx.shape)\n", + "\n", + "fig = plt.figure()\n", + "plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral, alpha=0.8)\n", + "plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)\n", + "plt.xlim(xx.min(), xx.max())\n", + "plt.ylim(yy.min(), yy.max())\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.16" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/demos/demo_vector.ipynb b/demos/demo_vector.ipynb new file mode 100644 index 00000000..b3e50de5 --- /dev/null +++ b/demos/demo_vector.ipynb @@ -0,0 +1,374 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### MicroGrad demo" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is the vector-based version, using the `micrograd.optim.SGD` class as optimiser." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from micrograd.engine import Value\n", + "from micrograd.nn import Neuron, Layer, MLP\n", + "from micrograd.optim import SGD" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "np.random.seed(1337)\n", + "random.seed(1337)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# make up a dataset\n", + "\n", + "from sklearn.datasets import make_moons, make_blobs\n", + "X, y = make_moons(n_samples=100, noise=0.1)\n", + "\n", + "y = y*2 - 1 # make y be -1 or 1\n", + "# visualize in 2D\n", + "plt.figure(figsize=(5,5))\n", + "plt.scatter(X[:,0], X[:,1], c=y, s=20, cmap='jet')" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# the model\n", + "import numpy.random\n", + "from numpy import zeros\n", + "\n", + "batch_size = 100\n", + "nn_x = Value(shape=(batch_size, 2), name='x')\n", + "nn_y = Value(shape=(batch_size,), name='y')\n", + "\n", + "m1 = Value(numpy.random.uniform(-1, 1, (2, 16)))\n", + "b1 = Value(zeros(16))\n", + "m2 = Value(numpy.random.uniform(-1, 1, (16, 16)))\n", + "b2 = Value(zeros(16))\n", + "m3 = Value(numpy.random.uniform(-1, 1, (16,)))\n", + "b3 = Value(0)\n", + "\n", + "def model(x):\n", + " # (batch_size, 16)\n", + " res1 = (x @ m1 + b1).relu()\n", + " # (batch_size, 16)\n", + " res2 = (res1 @ m2 + b2).relu()\n", + " # (batch_size,)\n", + " return res2 @ m3 + b3\n", + "\n", + "y_pred = model(nn_x)\n", + "\n", + "# SVM \"max-margin\" loss\n", + "loss = (1 - nn_y * y_pred).relu().mean()\n", + "\n", + "# L2 regularisation\n", + "alpha = 1e-4\n", + "reg_loss = alpha * ((m1 ** 2).sum() + (b1 ** 2).sum()\n", + " + (m2 ** 2).sum() + (b2 ** 2).sum()\n", + " + (m3 ** 2).sum() + (b3 ** 2).sum())\n", + "\n", + "# quantity to minimise\n", + "total_loss = loss + reg_loss" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Value(data=1.5810633866346033, grad=None)\n" + ] + } + ], + "source": [ + "total_loss.forward(x=X, y=y)\n", + "print(total_loss)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step 0 loss 1.581063, accuracy 48.0%\n", + "step 1 loss 1.384858, accuracy 68.0%\n", + "step 2 loss 0.934716, accuracy 74.0%\n", + "step 3 loss 0.727044, accuracy 78.0%\n", + "step 4 loss 0.384791, accuracy 84.0%\n", + "step 5 loss 0.348114, accuracy 85.0%\n", + "step 6 loss 0.269266, accuracy 88.0%\n", + "step 7 loss 0.223788, accuracy 91.0%\n", + "step 8 loss 0.183176, accuracy 92.0%\n", + "step 9 loss 0.183144, accuracy 94.0%\n", + "step 10 loss 0.234310, accuracy 91.0%\n", + "step 11 loss 0.250955, accuracy 90.0%\n", + "step 12 loss 0.189704, accuracy 93.0%\n", + "step 13 loss 0.139192, accuracy 95.0%\n", + "step 14 loss 0.141982, accuracy 94.0%\n", + "step 15 loss 0.159697, accuracy 98.0%\n", + "step 16 loss 0.447638, accuracy 84.0%\n", + "step 17 loss 0.196442, accuracy 92.0%\n", + "step 18 loss 0.124547, accuracy 96.0%\n", + "step 19 loss 0.139352, accuracy 92.0%\n", + "step 20 loss 0.108038, accuracy 99.0%\n", + "step 21 loss 0.139528, accuracy 95.0%\n", + "step 22 loss 0.077637, accuracy 97.0%\n", + "step 23 loss 0.074556, accuracy 98.0%\n", + "step 24 loss 0.054618, accuracy 98.0%\n", + "step 25 loss 0.059493, accuracy 100.0%\n", + "step 26 loss 0.095297, accuracy 96.0%\n", + "step 27 loss 0.075605, accuracy 99.0%\n", + "step 28 loss 0.066633, accuracy 97.0%\n", + "step 29 loss 0.038468, accuracy 100.0%\n", + "step 30 loss 0.069943, accuracy 97.0%\n", + "step 31 loss 0.033451, accuracy 100.0%\n", + "step 32 loss 0.060605, accuracy 98.0%\n", + "step 33 loss 0.051639, accuracy 99.0%\n", + "step 34 loss 0.026208, accuracy 100.0%\n", + "step 35 loss 0.050012, accuracy 98.0%\n", + "step 36 loss 0.034082, accuracy 100.0%\n", + "step 37 loss 0.078973, accuracy 96.0%\n", + "step 38 loss 0.021444, accuracy 100.0%\n", + "step 39 loss 0.022194, accuracy 100.0%\n", + "step 40 loss 0.026996, accuracy 100.0%\n", + "step 41 loss 0.018086, accuracy 100.0%\n", + "step 42 loss 0.026232, accuracy 100.0%\n", + "step 43 loss 0.016310, accuracy 100.0%\n", + "step 44 loss 0.015634, accuracy 100.0%\n", + "step 45 loss 0.020722, accuracy 100.0%\n", + "step 46 loss 0.014856, accuracy 100.0%\n", + "step 47 loss 0.014340, accuracy 100.0%\n", + "step 48 loss 0.017394, accuracy 100.0%\n", + "step 49 loss 0.029708, accuracy 100.0%\n", + "step 50 loss 0.019136, accuracy 100.0%\n", + "step 51 loss 0.026721, accuracy 100.0%\n", + "step 52 loss 0.013168, accuracy 100.0%\n", + "step 53 loss 0.012894, accuracy 100.0%\n", + "step 54 loss 0.012130, accuracy 100.0%\n", + "step 55 loss 0.011983, accuracy 100.0%\n", + "step 56 loss 0.011980, accuracy 100.0%\n", + "step 57 loss 0.011978, accuracy 100.0%\n", + "step 58 loss 0.011976, accuracy 100.0%\n", + "step 59 loss 0.011973, accuracy 100.0%\n", + "step 60 loss 0.011971, accuracy 100.0%\n", + "step 61 loss 0.011969, accuracy 100.0%\n", + "step 62 loss 0.011967, accuracy 100.0%\n", + "step 63 loss 0.011965, accuracy 100.0%\n", + "step 64 loss 0.011962, accuracy 100.0%\n", + "step 65 loss 0.011961, accuracy 100.0%\n", + "step 66 loss 0.012471, accuracy 100.0%\n", + "step 67 loss 0.012995, accuracy 100.0%\n", + "step 68 loss 0.015891, accuracy 100.0%\n", + "step 69 loss 0.012300, accuracy 100.0%\n", + "step 70 loss 0.011972, accuracy 100.0%\n", + "step 71 loss 0.011970, accuracy 100.0%\n", + "step 72 loss 0.011968, accuracy 100.0%\n", + "step 73 loss 0.011967, accuracy 100.0%\n", + "step 74 loss 0.011965, accuracy 100.0%\n", + "step 75 loss 0.011964, accuracy 100.0%\n", + "step 76 loss 0.011962, accuracy 100.0%\n", + "step 77 loss 0.011960, accuracy 100.0%\n", + "step 78 loss 0.011959, accuracy 100.0%\n", + "step 79 loss 0.011958, accuracy 100.0%\n", + "step 80 loss 0.011956, accuracy 100.0%\n", + "step 81 loss 0.011955, accuracy 100.0%\n", + "step 82 loss 0.011954, accuracy 100.0%\n", + "step 83 loss 0.011952, accuracy 100.0%\n", + "step 84 loss 0.011951, accuracy 100.0%\n", + "step 85 loss 0.011950, accuracy 100.0%\n", + "step 86 loss 0.011949, accuracy 100.0%\n", + "step 87 loss 0.011948, accuracy 100.0%\n", + "step 88 loss 0.011947, accuracy 100.0%\n", + "step 89 loss 0.011946, accuracy 100.0%\n", + "step 90 loss 0.011945, accuracy 100.0%\n", + "step 91 loss 0.011944, accuracy 100.0%\n", + "step 92 loss 0.011943, accuracy 100.0%\n", + "step 93 loss 0.011942, accuracy 100.0%\n", + "step 94 loss 0.011941, accuracy 100.0%\n", + "step 95 loss 0.011941, accuracy 100.0%\n", + "step 96 loss 0.011940, accuracy 100.0%\n", + "step 97 loss 0.011939, accuracy 100.0%\n", + "step 98 loss 0.011939, accuracy 100.0%\n", + "step 99 loss 0.011938, accuracy 100.0%\n" + ] + } + ], + "source": [ + "# optimization\n", + "def learning_rate_schedule(start, decrement):\n", + " assert start >= 0\n", + " assert decrement >= 0\n", + " \n", + " r = start\n", + " while r >= 0:\n", + " yield r\n", + " r -= decrement\n", + " \n", + "optimiser = SGD(wrt=[m1, b1, m2, b2, m3, b3],\n", + " learning_rate=learning_rate_schedule(\n", + " start=1, decrement=.9 / 100))\n", + "\n", + "\n", + "#print((m1.data ** 2).sum() + (b1.data ** 2).sum())\n", + "#print((m2.data ** 2).sum() + (b2.data ** 2).sum())\n", + "#print((m3.data ** 2).sum() + b3.data ** 2)\n", + "\n", + "for k in range(100):\n", + " total_loss.forward(x=X, y=y)\n", + " total_loss.backward()\n", + " optimiser.step()\n", + " acc = ((y_pred.data > 0) == (nn_y.data > 0)).astype(int).mean()\n", + " print(f\"step {k} loss {total_loss.data:.6f}, accuracy {acc*100}%\")\n", + "\n", + " #print((m1.data ** 2).sum() + (b1.data ** 2).sum(),\n", + " # (m1.grad ** 2).sum() + (b1.grad ** 2).sum())\n", + " #print((m2.data ** 2).sum() + (b2.data ** 2).sum(),\n", + " # (m2.grad ** 2).sum() + (b2.grad ** 2).sum())\n", + " #print((m3.data ** 2).sum() + (b3.data ** 2),\n", + " # (m3.grad ** 2).sum() + (b3.grad ** 2))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(-1.548639298268643, 1.951360701731357)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# visualize decision boundary\n", + "\n", + "h = 0.25\n", + "x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1\n", + "y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1\n", + "xx, yy = np.meshgrid(np.arange(x_min, x_max, h),\n", + " np.arange(y_min, y_max, h))\n", + "Xmesh = np.c_[xx.ravel(), yy.ravel()]\n", + "#inputs = [list(map(Value, xrow)) for xrow in Xmesh]\n", + "#scores = list(map(model, inputs))\n", + "scores = model(Value(Xmesh)) \n", + "Z = scores.data > 0\n", + "Z = Z.reshape(xx.shape)\n", + "\n", + "fig = plt.figure()\n", + "plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral, alpha=0.8)\n", + "plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)\n", + "plt.xlim(xx.min(), xx.max())\n", + "plt.ylim(yy.min(), yy.max())\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/trace_graph.ipynb b/demos/trace_graph.ipynb similarity index 97% rename from trace_graph.ipynb rename to demos/trace_graph.ipynb index 055cf341..4de80a51 100644 --- a/trace_graph.ipynb +++ b/demos/trace_graph.ipynb @@ -103,7 +103,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -117,9 +117,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.10.16" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/micrograd/__init__.py b/micrograd/__init__.py index e69de29b..4517fb80 100644 --- a/micrograd/__init__.py +++ b/micrograd/__init__.py @@ -0,0 +1,6 @@ + +from os import environ +DTYPE = environ.get('DTYPE', 'float64') +assert DTYPE in ('float16', 'float32', 'float64', 'float128') + +from .engine import Value, tensordot diff --git a/micrograd/engine.py b/micrograd/engine.py index afd82cc5..ccdc89db 100644 --- a/micrograd/engine.py +++ b/micrograd/engine.py @@ -1,72 +1,314 @@ +from . import DTYPE + +from numpy import (array, ndarray, nan, + ones, zeros, full, + shape as np_shape, where, + maximum, take, prod, + exp, log, log1p, tanh, + arctanh, arcsin, + transpose, sum as np_sum, + tensordot as np_tensordot, + broadcast_to, expand_dims, + isnan, all as np_all) +from numbers import Number +from warnings import warn + class Value: """ stores a single scalar value and its gradient """ - def __init__(self, data, _children=(), _op=''): - self.data = data - self.grad = 0 + def __init__(self, data=None, _children=(), _op='', + shape=None, name=None): + if data is not None: + assert isinstance(data, (ndarray, Number)) + assert name is None + assert shape is None + self.name = None + self.shape = np_shape(data) + # dtype must be enforced on non-scalar data + self.data = data.astype(DTYPE) if self.shape else data + else: + assert name + assert isinstance(shape, tuple) + self.name = name + self.shape = shape + self.data = full(shape, nan, dtype=DTYPE) + self.grad = None # internal variables used for autograd graph construction self._backward = lambda: None self._prev = set(_children) self._op = _op # the op that produced this node, for graphviz / debugging / etc + def _forward(**kwds): + if self.name: + if self.name in kwds: + _value = kwds[self.name] + assert isinstance(_value, (ndarray, Number)) + assert np_shape(_value) == self.shape + # dtype must be enforced on non-scalar + self.data = (_value.astype(DTYPE) if self.shape + else _value) + else: + warn(f'{self.name} not in input data') + self.data = full(self.shape, nan, dtype=DTYPE) + self._forward = _forward + def __add__(self, other): - other = other if isinstance(other, Value) else Value(other) + other = (other if isinstance(other, Value) + else Value(other, _op='c')) out = Value(self.data + other.data, (self, other), '+') + def _forward(**kwds): + out.data = self.data + other.data + out._forward = _forward + def _backward(): - self.grad += out.grad - other.grad += out.grad + # in some cases, the shape of one operand + # would have been broadcast to higher dimensions + if self.ndim < out.ndim: + self.grad += (out.grad + .sum(axis=tuple(range(out.ndim - self.ndim)))) + else: + self.grad += out.grad + if other.ndim < out.ndim: + other.grad += (out.grad + .sum(axis=tuple(range(out.ndim - other.ndim)))) + else: + other.grad += out.grad out._backward = _backward return out def __mul__(self, other): - other = other if isinstance(other, Value) else Value(other) + other = (other if isinstance(other, Value) + else Value(other, _op='c')) out = Value(self.data * other.data, (self, other), '*') + def _forward(**kwds): + out.data = self.data * other.data + out._forward = _forward + def _backward(): - self.grad += other.data * out.grad - other.grad += self.data * out.grad + # in some cases, the shape of one operand + # would have been broadcast to higher dimensions + if self.ndim < out.ndim: + self.grad += ((other.data * out.grad) + .sum(axis=tuple(range(out.ndim - self.ndim)))) + else: + self.grad += other.data * out.grad + if other.ndim < out.ndim: + other.grad += ((self.data * out.grad) + .sum(axis=tuple(range(out.ndim - other.ndim)))) + else: + other.grad += self.data * out.grad out._backward = _backward return out def __pow__(self, other): - assert isinstance(other, (int, float)), "only supporting int/float powers for now" - out = Value(self.data**other, (self,), f'**{other}') + # TOODO: array(3) ** -1 won't do. array(3).astype(float) if excepted + assert isinstance(other, (int, float)), ("only supporting" + " int/float powers for now") + out = Value(self.data ** other, (self,), f'**{other}') + + def _forward(**kwds): + out.data = self.data ** other + out._forward = _forward def _backward(): - self.grad += (other * self.data**(other-1)) * out.grad + self.grad += (other * self.data ** (other - 1)) * out.grad out._backward = _backward return out + @property + def T(self): + out = Value(transpose(self.data), (self,), 'T') + + def _forward(**kwds): + out.data = transpose(self.data) + out._forward = _forward + + def _backward(): + self.grad += transpose(out.grad) + out._backward = _backward + + return out + + @property + def ndim(self): + return len(self.shape) + def relu(self): - out = Value(0 if self.data < 0 else self.data, (self,), 'ReLU') + out = Value(maximum(self.data, 0), (self,), 'ReLU') + + def _forward(**kwds): + out.data = maximum(self.data, 0) + out._forward = _forward def _backward(): - self.grad += (out.data > 0) * out.grad + self.grad += where(out.data > 0, out.grad, 0) out._backward = _backward return out - def backward(self): + def exp(self): + out = Value(exp(self.data), (self,), 'exp') + + def _forward(**kwds): + out.data = exp(self.data) + out._forward = _forward + + def _backward(): + self.grad += out.data * out.grad + out._backward = _backward + + return out + + def log(self): + out = Value(log(self.data), (self,), 'log') + + def _forward(**kwds): + out.data = log(self.data) + out._forward = _forward + + def _backward(): + valid_data = where(self.data >= 0, self.data, nan) + self.grad += 1 / valid_data * out.grad + out._backward = _backward + + return out + + def log1p(self): + out = Value(log1p(self.data), (self,), 'log1p') + + def _forward(**kwds): + out.data = log1p(self.data) + out._forward = _forward + + def _backward(): + valid_data = where(self.data >= -1, self.data, nan) + self.grad += 1 / (1 + valid_data) * out.grad + out._backward = _backward + + return out + + def tanh(self): + out = Value(tanh(self.data), (self,), 'tanh') + + def _forward(**kwds): + out.data = tanh(self.data) + out._forward = _forward + + def _backward(): + self.grad += (1 - tanh(self.data) ** 2) * out.grad + out._backward = _backward + + return out + + def arctanh(self): + out = Value(arctanh(self.data), (self,), 'arctanh') + + def _forward(**kwds): + out.data = arctanh(self.data) + out._forward = _forward + def _backward(): + valid_data = where((-1 <= self.data) & (self.data <= 1), + self.data, nan) + self.grad += 1 / (1 - valid_data ** 2) * out.grad + out._backward = _backward + + return out + + def arcsin(self): + out = Value(arcsin(self.data), (self,), 'arcsin') + + def _forward(**kwds): + out.data = arcsin(self.data) + out._forward = _forward + + def _backward(): + valid_data = where((-1 <= self.data) & (self.data <= 1), + self.data, nan) + self.grad += 1 / (1 - valid_data ** 2) ** .5 * out.grad + out._backward = _backward + + return out + + def sum(self, axis=None): + # map any negative dimension index to non-negative one + de_neg = lambda x: self.ndim + x if x < 0 else x + + if axis is None: + _axis = tuple(range(self.ndim)) + elif isinstance(axis, int): + _axis = de_neg(axis) + else: + _axis = tuple(map(de_neg, axis)) + + out = Value(np_sum(self.data, axis=axis), (self,), 'sum') + + def _forward(**kwds): + out.data = np_sum(self.data, axis=axis) + out._forward = _forward + + def _backward(): + # expand out.grad to same number of dimensions + # as self.data, self.grad + _out_grad = expand_dims(out.grad, _axis) + + # ... expand further to same shape as self.data + self.grad += broadcast_to(_out_grad, self.shape) + out._backward = _backward + + return out + + def mean(self, axis=None): + if axis is None: + denom = prod(self.shape) + else: + denom = prod(take(self.shape, axis)) + return self.sum(axis) * (1 / denom) + + def build_topology(self): # topological order all of the children in the graph - topo = [] - visited = set() - def build_topo(v): - if v not in visited: - visited.add(v) - for child in v._prev: - build_topo(child) - topo.append(v) - build_topo(self) - - # go one variable at a time and apply the chain rule to get its gradient - self.grad = 1 - for v in reversed(topo): + if not hasattr(self, 'topo'): + self.topo = [] + visited = set() + + def build_topo(v): + if v not in visited: + visited.add(v) + for child in v._prev: + build_topo(child) + self.topo.append(v) + + build_topo(self) + + def forward(self, **kwds): + + self.build_topology() + + for v in self.topo: + v._forward(**kwds) + + def backward(self): + + if np_all(isnan(self.data)): + warn('run forward() before backward()') + + self.build_topology() + + # go one variable at a time and + # apply the chain rule to get its gradient + for v in self.topo: + if v.grad is None: # array not allocated yet + v.grad = (ones(v.shape, dtype=DTYPE) if v == self + else zeros(v.shape, dtype=DTYPE)) + else: # array has been allocated + v.grad.fill(1 if v == self else 0) + for v in reversed(self.topo): v._backward() def __neg__(self): # -self @@ -90,5 +332,52 @@ def __truediv__(self, other): # self / other def __rtruediv__(self, other): # other / self return other * self**-1 + def __matmul__(self, other): + return tensordot(self, other, 1) + def __repr__(self): return f"Value(data={self.data}, grad={self.grad})" + + +def tensordot(left, right, axes): + ''' Tensor contraction, only accepting int axes + + Example use: + + tensordot(left, right, axes=2) + + Unlike numpy tensordot, the last axis (indexed by -1) of the left + tensor contracts with the first axis of the right tensor; the + next to last axis (indexed by -2) of the left tensor with the 2nd + axis of the right tensor; so on and so forth. + ''' + assert axes >= 0 # only int axes + assert axes <= left.ndim + assert axes <= right.ndim + + # axes for various numpy tensordot ops later + axes1 = ([-1 - j for j in range(axes)], list(range(axes))) + axes2 = ([-1 - j for j in range(left.ndim - axes)], + list(range(left.ndim - axes))) + axes3 = ([-1 - j for j in range(right.ndim - axes)], + list(range(right.ndim - axes))) + + left = (left if isinstance(left, Value) + else Value(left, _op='c')) + right = (right if isinstance(right, Value) + else Value(right, _op='c')) + out = Value(np_tensordot(left.data, right.data, axes=axes1), + (left, right), '@') + + def _forward(**kwds): + out.data = np_tensordot(left.data, right.data, axes=axes1) + out._forward = _forward + + def _backward(): + left.grad += np_tensordot(out.grad, transpose(right.data), + axes=axes3) + right.grad += np_tensordot(transpose(left.data), out.grad, + axes=axes2) + out._backward = _backward + + return out diff --git a/micrograd/nn.py b/micrograd/nn.py index 30d5d777..702fa039 100644 --- a/micrograd/nn.py +++ b/micrograd/nn.py @@ -1,19 +1,15 @@ -import random +import numpy.random from micrograd.engine import Value class Module: - def zero_grad(self): - for p in self.parameters(): - p.grad = 0 - def parameters(self): return [] class Neuron(Module): - def __init__(self, nin, nonlin=True): - self.w = [Value(random.uniform(-1,1)) for _ in range(nin)] + def __init__(self, nin, nonlin=True, values=None): + self.w = [Value(_) for _ in values] self.b = Value(0) self.nonlin = nonlin @@ -30,7 +26,8 @@ def __repr__(self): class Layer(Module): def __init__(self, nin, nout, **kwargs): - self.neurons = [Neuron(nin, **kwargs) for _ in range(nout)] + values = numpy.random.uniform(-1, 1, (nin, nout)) + self.neurons = [Neuron(nin, values=v, **kwargs) for v in values.T] def __call__(self, x): out = [n(x) for n in self.neurons] @@ -42,6 +39,13 @@ def parameters(self): def __repr__(self): return f"Layer of [{', '.join(str(n) for n in self.neurons)}]" + def L2_norm(self): + return sum([p.data ** 2 for p in self.parameters()]) + + def grad_L2_norm(self): + return sum([p.grad ** 2 for p in self.parameters() + if p.grad is not None]) + class MLP(Module): def __init__(self, nin, nouts): diff --git a/micrograd/optim.py b/micrograd/optim.py new file mode 100644 index 00000000..c458fb08 --- /dev/null +++ b/micrograd/optim.py @@ -0,0 +1,57 @@ + +from . import DTYPE + +from .engine import Value +from numbers import Number +from numpy import zeros + + +class SGD: + ''' Sochastic gradient descent optimiser. + + velocity = momentum * velocity + g + w = w - learning_rate * velocity + + https://pytorch.org/docs/stable/generated/torch.optim.SGD.html + https://github.com/keras-team/keras/blob/master/keras/src/optimizers/sgd.py + ''' + + def __init__(self, wrt=[], learning_rate=None, momentum=None): + ''' + wrt: a list of Values, with respect to which to minimise + the target quantity. + learning_rate: a non-negative number or a generator of them. + momentum: None or a non-negative number. + ''' + assert isinstance(wrt, (list, tuple)) + assert all([isinstance(_, Value) for _ in wrt]) + if isinstance(learning_rate, Number): + assert learning_rate >= 0 + else: + assert hasattr(learning_rate, '__next__') + assert momentum is None or momentum >= 0 + + self.wrt = wrt + self.learning_rate = learning_rate + self.learning_rate_is_number = isinstance(learning_rate, Number) + self.momentum = momentum + if momentum is not None: + self.velocity = [zeros(_.shape, dtype=DTYPE) + for _ in wrt] + + def step(self): + ''' One step of stochastic gradient descent ''' + if self.learning_rate_is_number: + lr = self.learning_rate + else: + lr = next(self.learning_rate) + assert lr >= 0 + + if self.momentum is None: + for v in self.wrt: + v.data -= lr * v.grad + else: + for j, v in enumerate(self.wrt): + self.velocity[j] *= self.momentum + self.velocity[j] += v.grad + v.data -= lr * self.velocity[j] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..dbf16e18 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ + +[build-system] +requires = ['setuptools'] diff --git a/setup.py b/setup.py index fce0cac1..60bb186c 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ version="0.1.0", author="Andrej Karpathy", author_email="andrej.karpathy@gmail.com", - description="A tiny scalar-valued autograd engine with a small PyTorch-like neural network library on top.", + description="A tiny autograd engine", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/karpathy/micrograd", @@ -18,5 +18,6 @@ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], + install_requires=['numpy'], python_requires='>=3.6', ) diff --git a/test/test_engine.py b/test/test_engine.py deleted file mode 100644 index 9a483acf..00000000 --- a/test/test_engine.py +++ /dev/null @@ -1,67 +0,0 @@ -import torch -from micrograd.engine import Value - -def test_sanity_check(): - - x = Value(-4.0) - z = 2 * x + 2 + x - q = z.relu() + z * x - h = (z * z).relu() - y = h + q + q * x - y.backward() - xmg, ymg = x, y - - x = torch.Tensor([-4.0]).double() - x.requires_grad = True - z = 2 * x + 2 + x - q = z.relu() + z * x - h = (z * z).relu() - y = h + q + q * x - y.backward() - xpt, ypt = x, y - - # forward pass went well - assert ymg.data == ypt.data.item() - # backward pass went well - assert xmg.grad == xpt.grad.item() - -def test_more_ops(): - - a = Value(-4.0) - b = Value(2.0) - c = a + b - d = a * b + b**3 - c += c + 1 - c += 1 + c + (-a) - d += d * 2 + (b + a).relu() - d += 3 * d + (b - a).relu() - e = c - d - f = e**2 - g = f / 2.0 - g += 10.0 / f - g.backward() - amg, bmg, gmg = a, b, g - - a = torch.Tensor([-4.0]).double() - b = torch.Tensor([2.0]).double() - a.requires_grad = True - b.requires_grad = True - c = a + b - d = a * b + b**3 - c = c + c + 1 - c = c + 1 + c + (-a) - d = d + d * 2 + (b + a).relu() - d = d + 3 * d + (b - a).relu() - e = c - d - f = e**2 - g = f / 2.0 - g = g + 10.0 / f - g.backward() - apt, bpt, gpt = a, b, g - - tol = 1e-6 - # forward pass went well - assert abs(gmg.data - gpt.data.item()) < tol - # backward pass went well - assert abs(amg.grad - apt.grad.item()) < tol - assert abs(bmg.grad - bpt.grad.item()) < tol diff --git a/tests/test_engine.py b/tests/test_engine.py new file mode 100644 index 00000000..5615eba4 --- /dev/null +++ b/tests/test_engine.py @@ -0,0 +1,372 @@ +from unittest import TestCase +from micrograd import Value, tensordot +from numpy import array, isclose, allclose, nan, inf, empty, pi + +class AutodiffTest(TestCase): + + def test_sanity_check(self): + + a = Value(shape=(2,), name='a') + a.forward(a=array([2, 3])) + a.backward() + self.assertTrue(allclose(a.grad, [1, 1])) + + def test_unary_ops(self): + # test arithmetic, relu + a = Value(shape=(2,), name='a') + b = Value(shape=(2,), name='b') + c = (a + 2).relu() * b ** 2 + c.forward(a=array([-3, 1]), b=array([2, 3])) + c.backward() + self.assertTrue(allclose(c.data, [0, 27])) + self.assertTrue(allclose(a.grad, [0, 9])) + self.assertTrue(allclose(b.grad, [0, 18])) + self.assertTrue(allclose(c.grad, [1, 1])) + + # test exp + d = (a * 2).exp() + d.forward(a=array([1, 2])) + d.backward() + self.assertTrue(allclose(d.data, [7.38905, 54.59815])) + self.assertTrue(allclose(a.grad, [14.77811, 109.1963])) + + # test log + d = a.log() + d.forward(a=array([2, 3])) + d.backward() + self.assertTrue(allclose(d.data, [0.69314718, 1.09861229])) + self.assertTrue(allclose(a.grad, [.5, 1 / 3])) + + # test log1p + d = a.log1p() + d.forward(a=array([2, 3])) + d.backward() + self.assertTrue(allclose(d.data, [1.09861229, 1.38629436])) + self.assertTrue(allclose(a.grad, [0.33333333, 0.25])) + + d.forward(a=array([-2, 3])) + d.backward() + self.assertTrue(allclose(d.data, [nan, 1.38629436], equal_nan=True)) + self.assertTrue(allclose(a.grad, [nan, 0.25], equal_nan=True)) + + # test transpose + f = Value(shape=(2, 1), name='f') + g = f.T ** 2 + g.forward(f=array([[2], [-1]])) + g.backward() + self.assertTrue(allclose(f.grad, [[4], [-2]])) + + # test tanh + k = a.tanh() + k.forward(a=array([0, 2])) + k.backward() + self.assertTrue(allclose(k.data, [0., 0.96402758])) + self.assertTrue(allclose(a.grad, [1., 0.07065082])) + + # test arctanh + h = Value(shape=(5,), name='h') + k = (h * 2).arctanh() + k.forward(h=array([-1, -.5, 0, .5, 1])) + k.backward() + self.assertTrue(allclose(h.grad, [nan, inf, 2, inf, nan], + equal_nan=True)) + + # test arcsin + k = h.arcsin() + k.forward(h=array([-1.01, 0, 1 / 2 ** .5, 1, 1.01])) + k.backward() + self.assertTrue(allclose(k.data, [nan, 0, pi / 4, pi / 2, nan], + equal_nan=True)) + self.assertTrue(allclose(h.grad, [nan, 1, 2 ** .5, inf, nan], + equal_nan=True)) + + def test_unary_ops_scalar_input(self): + + a = Value(shape=(), name='a') + + # test log + d = a.log() + d.forward(a=3) + d.backward() + self.assertTrue(allclose(d.data, 1.09861229)) + self.assertTrue(allclose(a.grad, 1 / 3)) + + # test log1p + d = a.log1p() + d.forward(a=3) + d.backward() + self.assertTrue(allclose(d.data, 1.38629436)) + self.assertTrue(allclose(a.grad, 0.25)) + + # test transpose + g = a.T ** 2 + g.forward(a=3) + g.backward() + self.assertTrue(allclose(a.grad, 6)) + + # test tanh + k = a.tanh() + k.forward(a=2) + k.backward() + self.assertTrue(allclose(k.data, 0.96402758)) + self.assertTrue(allclose(a.grad, 0.07065082)) + + # test arctanh + k = (a * 2).arctanh() + k.forward(a=.5) + k.backward() + self.assertTrue(allclose(a.grad, inf)) + + # test arcsin + k = a.arcsin() + k.forward(a=-1 / 2 ** .5) + k.backward() + self.assertTrue(allclose(a.grad, 2 ** .5)) + + def test_sum_op(self): + + a = Value(shape=(2, 2, 3), name='a') + b = (a.sum(axis=(0, 2)) - 31).relu() + b.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + b.backward() + self.assertTrue(allclose(b.data, [0, 17])) + self.assertTrue(allclose(a.grad, [[[0, 0, 0], [1, 1, 1]], + [[0, 0, 0], [1, 1, 1]]])) + + b = (a.sum(axis=0) - 10).relu() + b.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + b.backward() + self.assertTrue(allclose(a.grad, [[[0, 0, 1], [1, 1, 1]], + [[0, 0, 1], [1, 1, 1]]])) + + b = (a.sum() - 77).relu() + c = (a.sum() - 79).relu() + b.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + b.backward() + self.assertTrue(allclose(a.grad, 1)) + + c.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + c.backward() + self.assertTrue(allclose(a.grad, 0)) + + def test_sum_op_neg_axis(self): + + a = Value(shape=(2, 2, 3), name='a') + b = (a.sum(axis=(0, -1)) - 31).relu() + b.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + b.backward() + self.assertTrue(allclose(b.data, [0, 17])) + self.assertTrue(allclose(a.grad, [[[0, 0, 0], [1, 1, 1]], + [[0, 0, 0], [1, 1, 1]]])) + + b = (a.sum(axis=-3) - 10).relu() + b.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + b.backward() + self.assertTrue(allclose(a.grad, [[[0, 0, 1], [1, 1, 1]], + [[0, 0, 1], [1, 1, 1]]])) + + b = (a.sum() - 77).relu() + c = (a.sum() - 79).relu() + b.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + b.backward() + self.assertTrue(allclose(a.grad, 1)) + + c.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + c.backward() + self.assertTrue(allclose(a.grad, 0)) + + def test_sum_op_scalar_input(self): + + a = Value(shape=(), name='a') + b = (a ** 2).sum() + b.forward(a=2.5) + b.backward() + self.assertTrue(allclose(b.data, 6.25)) + self.assertTrue(allclose(a.grad, 5)) + + b = (a ** 2).sum(axis=()) + b.forward(a=2.5) + b.backward() + self.assertTrue(allclose(b.data, 6.25)) + self.assertTrue(allclose(a.grad, 5)) + + def test_mean_op(self): + + a = Value(shape=(2, 2, 3), name='a') + self.assertTrue(a.mean()._op == '*') + + b = (a.mean(axis=(0, 2)) - 6).relu() + b.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + b.backward() + y = 1 / 6 + self.assertTrue(allclose(b.data, [0, 2])) + self.assertTrue(allclose(a.grad, [[[0, 0, 0], [y, y, y]], + [[0, 0, 0], [y, y, y]]])) + + b = (a.mean(axis=0) - 5).relu() + b.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + b.backward() + y = .5 + self.assertTrue(allclose(a.grad, [[[0, 0, y], [y, y, y]], + [[0, 0, y], [y, y, y]]])) + + b = (a.mean() - 6).relu() + c = (a.mean() - 7).relu() + b.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + b.backward() + y = 1 / 12 + self.assertTrue(allclose(a.grad, y)) + + c.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + c.backward() + self.assertTrue(allclose(a.grad, 0)) + + def test_mean_op_neg_axis(self): + + a = Value(shape=(2, 2, 3), name='a') + self.assertTrue(a.mean()._op == '*') + + b = (a.mean(axis=(0, -1)) - 6).relu() + b.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + b.backward() + y = 1 / 6 + self.assertTrue(allclose(b.data, [0, 2])) + self.assertTrue(allclose(a.grad, [[[0, 0, 0], [y, y, y]], + [[0, 0, 0], [y, y, y]]])) + + b = (a.mean(axis=-3) - 5).relu() + b.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + b.backward() + y = .5 + self.assertTrue(allclose(a.grad, [[[0, 0, y], [y, y, y]], + [[0, 0, y], [y, y, y]]])) + + b = (a.mean() - 6).relu() + c = (a.mean() - 7).relu() + b.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + b.backward() + y = 1 / 12 + self.assertTrue(allclose(a.grad, y)) + + c.forward(a=array([[[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]]])) + c.backward() + self.assertTrue(allclose(a.grad, 0)) + + def test_mean_op_scalar_input(self): + + a = Value(shape=(), name='a') + b = (a ** 2).mean() + b.forward(a=2.5) + b.backward() + self.assertTrue(allclose(b.data, 6.25)) + self.assertTrue(allclose(a.grad, 5)) + + b = (a ** 2).mean(axis=()) + b.forward(a=2.5) + b.backward() + self.assertTrue(allclose(b.data, 6.25)) + self.assertTrue(allclose(a.grad, 5)) + + def test_tensordot_op(self): + + a = Value(empty((2, 3, 4))) + b = Value(empty((4, 3, 5))) + c = tensordot(a, b, 1) + c.backward() + self.assertTrue(allclose(c.shape, (2, 3, 3, 5))) + + c = tensordot(a, b, 2) + c.backward() + self.assertTrue(allclose(c.shape, (2, 5))) + + a = Value(empty((2,))) + b = Value(empty((3,))) + c = tensordot(a, b, 0) + c.backward() + self.assertTrue(allclose(c.shape, (2, 3))) + + # test inner product + a = Value(array([2, 3])) + b = Value(array([3, 4])) + c = (a @ b) ** 2 + self.assertTrue(c.data == 18 ** 2) + c.backward() + self.assertTrue(allclose(a.grad, [108, 144])) + self.assertTrue(allclose(b.grad, [72, 108])) + + def test_tensordot_op_scalar_input(self): + + a = Value(3) + b = Value(5) + c = tensordot(a, b, 0) + c.backward() + self.assertTrue(allclose(c.data, 15)) + self.assertTrue(allclose(a.grad, 5)) + self.assertTrue(allclose(b.grad, 3)) + + f = Value(array([[2, 3], [4, 5]])) + g = tensordot(a, f, 0) + g.backward() + self.assertTrue(allclose(g.data, array([[6, 9], [12, 15]]))) + self.assertTrue(allclose(a.grad, 14)) + self.assertTrue(allclose(f.grad, a.data)) + + g = tensordot(f - 2, b, 0) + g.backward() + self.assertTrue(allclose(g.data, array([[0, 5], [10, 15]]))) + self.assertTrue(allclose(b.grad, 6)) + self.assertTrue(allclose(f.grad, b.data)) + + def test_chain_ops(self): + x = Value(-4.0) + z = 2 * x + 2 + x + q = z.relu() + z * x + h = (z * z).relu() + y = h + q + q * x + y.backward() + xmg, ymg = x, y + + self.assertTrue(isclose(xmg.data, -4)) + self.assertTrue(isclose(xmg.grad, 46)) + self.assertTrue(isclose(ymg.data, -20)) + self.assertTrue(isclose(ymg.grad, 1)) + + def test_more_ops(self): + + a = Value(-4.0) + b = Value(2.0) + c = a + b + d = a * b + b**3 + c += c + 1 + c += 1 + c + (-a) + d += d * 2 + (b + a).relu() + d += 3 * d + (b - a).relu() + e = c - d + f = e**2 + g = f / 2.0 + g += 10.0 / f + g.backward() + amg, bmg, gmg = a, b, g + + self.assertTrue(isclose(amg.data, -4)) + self.assertTrue(isclose(amg.grad, 138.83381924)) + self.assertTrue(isclose(bmg.data, 2)) + self.assertTrue(isclose(bmg.grad, 645.57725947)) + self.assertTrue(isclose(gmg.data, 24.704081632)) + self.assertTrue(isclose(gmg.grad, 1)) diff --git a/tests/test_vs_torch.py b/tests/test_vs_torch.py new file mode 100644 index 00000000..b4423372 --- /dev/null +++ b/tests/test_vs_torch.py @@ -0,0 +1,202 @@ +from unittest import TestCase +from micrograd import Value, tensordot +from torch import Tensor, tensordot as torch_tensordot +from numpy import array, allclose +import numpy.random + +class AutodiffTest(TestCase): + + def test_sanity_check(self): + + x = Value(-4.0) + z = 2 * x + 2 + x + q = z.relu() + z * x + h = (z * z).relu() + y = h + q + q * x + y.backward() + xmg, ymg = x, y + + x = Tensor([-4.0]).double() + x.requires_grad = True + z = 2 * x + 2 + x + q = z.relu() + z * x + h = (z * z).relu() + y = h + q + q * x + y.backward() + xpt, ypt = x, y + + # forward pass went well + self.assertTrue(ymg.data == ypt.data.item()) + # backward pass went well + self.assertTrue(xmg.grad == xpt.grad.item()) + + def test_more_ops(self): + + a = Value(-4.0) + b = Value(2.0) + c = a + b + d = a * b + b**3 + c += c + 1 + c += 1 + c + (-a) + d += d * 2 + (b + a).relu() + d += 3 * d + (b - a).relu() + e = c - d + f = e**2 + g = f / 2.0 + g += 10.0 / f + g.backward() + amg, bmg, gmg = a, b, g + + a = Tensor([-4.0]).double() + b = Tensor([2.0]).double() + a.requires_grad = True + b.requires_grad = True + c = a + b + d = a * b + b**3 + c = c + c + 1 + c = c + 1 + c + (-a) + d = d + d * 2 + (b + a).relu() + d = d + 3 * d + (b - a).relu() + e = c - d + f = e**2 + g = f / 2.0 + g = g + 10.0 / f + g.backward() + apt, bpt, gpt = a, b, g + + tol = 1e-6 + # forward pass went well + self.assertTrue(abs(gmg.data - gpt.data.item()) < tol) + # backward pass went well + print(abs(amg.grad - apt.grad.item())) + print(abs(bmg.grad - bpt.grad.item())) + self.assertTrue(abs(amg.grad - apt.grad.item()) < tol) + self.assertTrue(abs(bmg.grad - bpt.grad.item()) < tol) + + def test_unary_ops(self): + + a = Value(array([.3, .8])) + b = a.tanh().log1p().sum() + c = a.arctanh().log().sum() + d = a.arcsin().sum() + + a2 = Tensor([.3, .8]) + a2.requires_grad = True + b2 = a2.tanh().log1p().sum() + c2 = a2.arctanh().log().sum() + d2 = a2.arcsin().sum() + + b.backward() + b2.backward() + self.assertTrue(allclose(b.data, b2.data)) + self.assertTrue(allclose(a.grad, a2.grad)) + + c.backward() + a2.grad = None + c2.backward() + self.assertTrue(allclose(c.data, c2.data)) + self.assertTrue(allclose(a.grad, a2.grad)) + + d.backward() + a2.grad = None + d2.backward() + self.assertTrue(allclose(d.data, d2.data)) + self.assertTrue(allclose(a.grad, a2.grad)) + + def test_tensordot(self): + + m1 = numpy.random.rand(2, 3, 4) + m2 = numpy.random.rand(4, 3, 5) + + a = Value(m1) + b = Value(m2) + c = tensordot(a, b, 1).sum() + + a2 = Tensor(m1) + a2.requires_grad = True + b2 = Tensor(m2) + b2.requires_grad = True + c2 = torch_tensordot(a2, b2, 1).sum() + + c.backward() + c2.backward() + self.assertTrue(allclose(c.data, c2.data)) + self.assertTrue(allclose(a.grad, a2.grad)) + self.assertTrue(allclose(b.grad, b2.grad)) + + c = tensordot(a, b, 2).sum() + a2.grad = None + b2.grad = None + c2 = torch_tensordot(a2, b2, ([-1, -2], [0, 1])).sum() + c.backward() + c2.backward() + self.assertTrue(allclose(c.data, c2.data)) + self.assertTrue(allclose(a.grad, a2.grad)) + self.assertTrue(allclose(b.grad, b2.grad)) + + v1 = numpy.random.rand(2) + v2 = numpy.random.rand(3) + + a = Value(v1) + b = Value(v2) + c = tensordot(a, b, 0).sum() + + a2 = Tensor(v1) + a2.requires_grad = True + b2 = Tensor(v2) + b2.requires_grad = True + c2 = torch_tensordot(a2, b2, 0).sum() + + c.backward() + c2.backward() + self.assertTrue(allclose(c.data, c2.data)) + self.assertTrue(allclose(a.grad, a2.grad)) + self.assertTrue(allclose(b.grad, b2.grad)) + + def test_reduce_ops(self): + + a = Value(array([[[1, 2, -2], [2, 1, 0]], + [[-2, 1, 0], [3, 2, 1]]])) + b = a.mean(axis=(0, 2)).relu().sum() + c = a.mean(axis=(0, 1)).relu().mean() + + a2 = Tensor([[[1, 2, -2], [2, 1, 0]], + [[-2, 1, 0], [3, 2, 1]]]) + a2.requires_grad = True + b2 = a2.mean(axis=(0, 2)).relu().sum() + c2 = a2.mean(axis=(0, 1)).relu().mean() + + b.backward() + b2.backward() + self.assertTrue(allclose(b.data, b2.data)) + self.assertTrue(allclose(a.grad, a2.grad)) + + a2.grad = None + c.backward() + c2.backward() + self.assertTrue(allclose(c.data, c2.data)) + self.assertTrue(allclose(a.grad, a2.grad)) + + def test_reduce_ops_neg_axis(self): + + a = Value(array([[[1, 2, -2], [2, 1, 0]], + [[-2, 1, 0], [3, 2, 1]]])) + b = a.mean(axis=(0, -1)).relu().sum() + c = a.mean(axis=(-3, -2)).relu().mean() + + a2 = Tensor([[[1, 2, -2], [2, 1, 0]], + [[-2, 1, 0], [3, 2, 1]]]) + a2.requires_grad = True + b2 = a2.mean(axis=(0, -1)).relu().sum() + c2 = a2.mean(axis=(-3, -2)).relu().mean() + + b.backward() + b2.backward() + self.assertTrue(allclose(b.data, b2.data)) + self.assertTrue(allclose(a.grad, a2.grad)) + + a2.grad = None + c.backward() + c2.backward() + self.assertTrue(allclose(c.data, c2.data)) + self.assertTrue(allclose(a.grad, a2.grad))