<h1>
<img style="width: 4.8rem; height: 4.8rem; vertical-align: middle;" src="data:image/svg+xml,%3Csvg viewBox='-40 -40 480.00 480.00' fill='none' xmlns='http://www.w3.org/2000/svg' stroke='%23000000'%3E%3Cg id='SVGRepo_bgCarrier' stroke-width='0'%3E%3C/g%3E%3Cg id='SVGRepo_tracerCarrier' stroke-linecap='round' stroke-linejoin='round'%3E%3C/g%3E%3Cg id='SVGRepo_iconCarrier'%3E%3Cpath d='M268.515 54.6046C267.911 54.3126 267.23 54.246 266.57 54.1153C266.127 54.0288 265.662 54 265.19 54C264.39 54 263.573 54.0847 262.815 54.1225C260.034 54.2541 257.259 54.4424 254.478 54.601C250.563 54.7136 246.646 54.6325 242.73 54.7172C238.177 54.8154 233.642 55.0686 229.099 55.3506C226.532 55.4263 223.965 55.4172 221.394 55.4497C218.417 55.4911 215.44 55.6605 212.463 55.8236C210.183 55.9047 207.914 55.9173 205.632 55.865C203.467 55.8155 201.301 55.7065 199.136 55.6749C194.431 55.6073 189.73 55.583 185.024 55.5055C180.186 55.4281 175.359 55.4776 170.517 55.5974C165.945 55.7137 161.368 55.8326 156.792 55.8119C154.675 55.8011 152.557 55.6569 150.44 55.5866C149.269 55.5479 148.102 55.5623 146.935 55.5794C146.113 55.5902 145.292 55.601 144.475 55.5902C143.341 55.5758 142.325 55.8011 141.431 56.5119C140.558 57.2021 140.002 58.2229 139.913 59.3032' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M271.889 148.131C272.223 147.423 272.471 146.728 272.49 145.994C272.511 145.398 272.417 144.707 272.29 144.098C272.129 143.286 270.242 66.3936 270.075 64.0228C269.962 62.4876 269.888 60.9556 269.586 59.4288C269.312 58.0506 268.911 56.6914 268.516 55.3258' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M142.565 56.6516C141.734 56.8254 140.983 57.3752 140.496 58.1649C139.967 59.0245 139.842 59.9795 139.947 61.0015C140.076 62.2295 140.153 63.4556 140.213 64.6884C140.327 71.0399 141.203 131.121 141.358 133.881C141.437 136.033 141.496 138.187 141.59 140.342C141.702 142.954 141.795 145.567 142.286 148.131' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M142.562 150.783C144.124 151.036 145.638 151.171 147.21 151.154C148.775 151.137 261.266 153.632 264.01 153.422C265.101 153.339 266.188 153.226 267.275 153.112C267.753 153.06 268.231 153.016 268.705 152.938C269.442 152.816 270.602 152.197 271.164 151.607' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M196.653 98.0262C196.297 94.527 196.842 90.3005 196.99 87.1448' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M220.116 98.0262C220.15 94.6313 220.18 91.2581 220.285 87.8701' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M191.62 121.827C204.464 131.012 218.049 130.621 228.742 118.814' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M346.735 174.823C346.906 172.743 295.705 189.823 294.533 189.215C294.15 189.016 263.894 175.227 245.33 175.787C220.957 176.523 187.468 177.66 162.074 178.531C141.435 179.239 114.101 189.215 113.396 189.215' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M109.413 188.988C110.068 191.587 54.7896 172.403 55.0625 174.866C55.1664 175.817 109.286 188.614 109.413 188.988Z' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M171.734 267.452C171.621 269.854 171.436 278.005 171.295 280.401C171.154 282.786 171.09 285.176 170.995 287.565C170.796 292.518 170.171 328.674 169.97 331.335C169.782 333.782 169.583 336.233 169.508 338.684C169.46 340.12 169.478 341.566 169.652 342.995C169.712 343.501 157.898 345.326 157.15 345.674' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M241.998 346.847C241.715 346.821 235.23 346.799 232.779 347C232.273 347.04 235.062 305.826 234.994 296.185C234.926 286.569 234.885 278.395 235.032 268.778' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M362.021 164.677C361.757 163.638 361.094 162.729 360.185 162.168C359.186 161.548 358.164 161.503 357.041 161.688C355.817 161.89 354.593 162.088 353.37 162.298C352.885 162.379 352.401 162.475 351.917 162.57C349.916 162.97 348.058 164.256 347.511 166.337C347.058 168.065 347.157 169.849 347.108 171.621C347.072 172.803 347.035 173.984 347.002 175.164C346.963 176.481 346.999 177.807 347.091 179.129C347.171 180.273 347.294 181.413 347.447 182.542C347.513 183.033 347.583 183.521 347.657 184.008C347.82 185.036 347.916 186.208 348.459 187.112C349.227 188.39 350.174 189.33 351.518 189.973C352.854 190.613 354.232 191.012 355.672 191.325C356.779 191.564 357.815 191.528 358.826 190.961C359.785 190.427 360.501 189.516 360.801 188.452' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M36.6414 182.134C36.6271 183.206 37.0322 184.256 37.7647 185.032C38.5695 185.89 39.5442 186.198 40.6772 186.309C41.9117 186.431 43.1455 186.555 44.3814 186.669C44.8706 186.717 45.3629 186.749 45.855 186.782C47.8911 186.914 50.0192 186.152 51.0868 184.284C51.9718 182.731 52.3383 180.982 52.8444 179.283C53.1855 178.152 53.5267 177.02 53.8641 175.889C54.2431 174.626 54.5521 173.337 54.8052 172.036C55.024 170.91 55.2014 169.777 55.346 168.647C55.4088 168.156 55.4675 167.666 55.5223 167.176C55.6316 166.141 55.8426 164.985 55.5514 163.971C55.1411 162.538 54.4698 161.385 53.3382 160.416C52.2131 159.453 50.9859 158.71 49.6759 158.037C48.6687 157.519 47.6579 157.286 46.5348 157.572C45.4707 157.839 44.5432 158.534 43.9772 159.484' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M167.19 183.088C166.54 184.012 166.477 184.93 166.544 186.014C166.558 186.227 166.571 186.44 166.583 186.652C166.982 195.868 166.528 205.092 166.86 214.307C167.035 219.247 167.21 224.183 167.291 229.122C167.365 233.668 167.431 238.236 167.056 242.771C166.825 245.57 166.593 248.366 166.494 251.173C166.396 253.904 166.382 256.71 166.607 259.439C166.698 260.562 166.978 261.799 167.729 262.667C168.754 263.85 170.052 264.389 171.599 264.449C172.628 264.491 173.69 264.29 174.711 264.148C175.893 263.985 177.065 263.765 178.227 263.482C179.503 263.169 207.234 262.274 209.139 262.337C211.311 262.408 213.454 262.55 215.615 262.724C219.225 263.014 222.814 263.588 226.421 263.904C228.386 264.073 230.35 264.229 232.319 264.346C233.864 264.436 235.449 264.412 236.97 264.715L237.265 264.779C238.22 264.867 238.715 264.679 239.595 264.385C240.675 264.028 241.577 262.883 241.956 261.827C242.383 260.642 242.186 259.338 242.082 258.094C241.933 253.698 242.016 249.324 242.278 244.933C242.347 243.788 243.999 230.918 244.84 216.072C245.717 200.601 245.783 183.138 245.813 182.34C245.837 181.581 245.869 180.823 245.901 180.068C245.953 178.694 246.157 177.252 245.532 175.973' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M201.994 238.437C192.449 230.705 176.125 210.992 188.117 199.493C190.679 197.036 193.997 195.462 197.708 195.971C201.454 196.484 204.074 200.765 204.851 203.995C204.965 204.467 205.801 211.04 206.076 211.04C206.483 211.04 206.076 210.257 206.076 209.865C206.076 208.493 206.327 207.302 206.483 205.951C206.886 202.479 209.401 199.097 212.606 197.341C220.532 192.998 226.613 198.852 227.504 206.538C229.414 223.023 209.866 231.222 208.116 239.611' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M291.748 183.081C291.6 183.413 291.472 183.757 291.367 184.113C290.411 187.331 289.429 190.542 288.258 193.693L288.174 193.898C287.954 194.454 287.738 195.012 287.515 195.572' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M109.42 194.534C109.015 194.534 107.1 180.642 107.015 180.18C106.958 179.872 106.876 179.874 106.769 180.185' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M177.037 303.283C175.283 303.277 173.526 303.249 171.771 303.249C171.201 303.249 170.631 303.253 170.061 303.26C167.453 303.303 165.008 303.866 162.453 304.575' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M243.323 303.283C241.569 303.277 239.812 303.249 238.057 303.249C237.487 303.249 236.917 303.253 236.347 303.26C233.739 303.303 231.294 303.866 228.739 304.575' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M271.165 96.4253L284.714 97.7511L293.703 96.4253' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M133.286 95.7625H114.725' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M294.036 87.4895V103.782' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3Cpath d='M113.905 87.4895V103.782' stroke='%23808080' stroke-opacity='1.0' stroke-width='16' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3C/g%3E%3C/svg%3E" />
Simple Artificial Neural Networks</h1>

Simple Artificial Neural Networks (sann) is a naive Python implementation of
an artificial neural network (ANN) that's useful for educational purposes
and clarifying the concepts of feed-forward neural networks, backpropagation,
neuro-evolution of weights and biases, and genetic algorithms. This
implementation is not intended for production use or performance-critical
applications. 😉

See [Behind the AI Curtain](https://ntoll.org/article/ai-curtain/) for a 
comprehensive exploration of the concepts behind this code. This project's
code and assets are [hosted on GitHub](https://github.com/ntoll/sann). The
code works with both [CPython](https://python.org) and
[MicroPython](https://micropython.org/).

Try a couple of examples of this library online via
[PyScript](https://pyscript.net/):

* [Backpropagated numeral recognition](examples/digit_recognition/index.md) - 
  a neural network that underwent supervised training will categorise hand
  written numerals from a corpus of unseen test data. ✍️⁉️
* [Neuro-evolved snake game](examples/snaike/index.md) - 
  the classic "SNAKE" game, but played by a neural network that underwent
  unsupervised neuro-evolution. Play with the arrow keys, or click the
  `[]`🤖 checkbox to toggle the AI autopilot. It's a snAIke. 🤖🐍

## Installation 📦

If you're using CPython:

1. Create a virtual environment.
2. `pip install sann`

For MicroPython, just copy the `sann.py` file somewhere on your Python path. If
space is limited with MicroPython, use `make minify` to create a minified version
of the module.

## Usage 💪

**SANN is for educational use only.**

☠️☠️☠️ Do not use this code in production. ☠️☠️☠️

### Create a neural network ✨

The `create_network` function takes a list defining the number of nodes in each
layer. It returns a representation of a fully connected feed-forward neural
network.

This example creates a test network with three layers: an input layer with two
nodes, a hidden layer with three nodes, and an output layer with one node.

```python
import sann


my_nn = sann.create_network([2, 3, 1])
```

The network is expressed as a dictionary with three attributes:

1. `structure` - a list defining the number of nodes in each layer of the
   network (i.e. what was passed into the `create_network` function to create 
   it).
2. `fitness` - by default set to `None`, but used during neuro-evolution to
   indicate the arbitrary fitness score of the network during unsupervised
   training.
3. `layers` - a list of layers, with each layer containing a Python dict for 
   each node in the layer. Each dict contains a list of incoming weights and 
   a bias value, all of which are initialised with a random value between -1
   and 1. Since the input layer doesn't have associated weights nor bias
   (because its values are the raw input data), it is ignored.

```python
{
  "structure": [2, 3, 1],
  "fitness": None,
  "layers": [
    # No definition of the input layer needed, because its values are the raw 
    # input data
    [ # Hidden layer. Each hidden node has a bias and
      # two input weights: one each from the nodes in
      # the input layer.
      { # Node 1
        'bias': -0.08932407876323856,
        'weights': [
          0.9318837478301161,
          -0.3259188141579621
        ]
      },
      { # Node 2
        'bias': -0.7449380314648402,
        'weights': [
          -0.15786850474033964,
          -0.9455648956883143
        ]
      },
      { # Node 3
        'bias': 0.5168993191227431,
        'weights': [
          -0.8359684467197377,
          0.09538722516032427
        ]
      }
    ],
    [ # Output layer.
      { # A single node with a bias and input weights
        # from each of the three nodes in the hidden
        # layer.
        'bias': -0.46255520816058215,
        'weights': [
          0.991047585915775,
          -0.9995162202419827,
          0.15538558263904179
        ]
      }
    ],
  ],
}
```

The representation of the neural network is designed to be JSON serializable.

### Training 🎓

Training is the process through which the artificial neural network, created
with randomly generated weights and biases, is modified and refined so that 
it achieves some useful outcome. There are broadly two ways to do this:

* [Supervised training](https://en.wikipedia.org/wiki/Supervised_learning): 
  where the network is trained with labelled data. Put
  simply, given many examples of training input, the network is adjusted so it 
  produces the expected (labelled) output provided by humans. Once trained
  the network is tested with previously unseen labelled test data to check it
  correctly produces the expected results to the right level of accuracy. SANN
  provides [backpropagation](https://en.wikipedia.org/wiki/Backpropagation) 
  capabilities as an example of this sort of training.
* [Unsupervised training](https://en.wikipedia.org/wiki/Unsupervised_learning):
  where the network is trained on data that is NOT labelled. This usually
  involves a training procedure that measures the accuracy or efficiency of
  the behaviour of the network in some way, combined with a process of 
  adjustment used to improve the network's outcomes. SANN provides a 
  [genetic algorithm](https://en.wikipedia.org/wiki/Genetic_algorithm) based 
  [neuro-evolution](https://en.wikipedia.org/wiki/Neuroevolution) capability 
  as an example of this sort of training.

SANN provides a means of achieving both types of training in the following
ways:

#### Supervised training (backpropagation)... 🎛️

With supervised training, for the network to be useful it needs to be trained
with labelled example data that indicates how inputs relate to outputs. Such
data should be expressed as a list of pairs of values: the training input, and
the expected values in the output layer. Please see the
`examples/digit_recognition/train.py` file for an example of this process.

Use the `train` function to do exactly what it says:

```python
trained_nn = sann.train(
    my_nn, training_data, epochs=1000, learning_rate=0.1, log=print
)
```

The `train` function takes the initial randomly generated neural network, and
the `training_data` expressed as pairs of input/expected output, as described
above. The `epochs` value (default: 1000) defines the number of times the
training data is iterated over. The `learning_rate` (default: `0.1`) defines 
by how much weights and bias values are changed as errors are corrected. 
Finally, the optional `log` argument references a callable used to log
messages as the training process unfolds. It defaults to a no-op function with
no side-effect if it is not given.

The output of the `train` function is a representation of the network with the
refined weights and bias values.

Once trained, it is usual to check and evaluate the resulting neural network
with as-yet unseen test data. The `examples/digit_recognition/train.py` file
contains an example of this (see: `evaluate_model`). If the neural network is
not accurate enough, perhaps consider adjusting the `epochs` or
`learning_rate` values, and re-train.

#### Unsupervised training (neuro-evolution) 🧬

Alternatively, and usually because supervised training is not possible due to
the context in which the neural network is used, unsupervised training via the 
evolution of a population of networks is required. This is illustrated in the
`examples/snaike/train.py` file.

Use the `evolve` function in combination with a fitness function and halting
function to generate an evolved ANN. This is the minimal viable way of using
`evolve`:

```python
evolved_population = sann.evolve(
  layers=[3, 5, 2],
  population_size=1000,
  fitness_function=fitness_function,
  halt_function=halt,
  log=print,
)
```

The `layers` defines the structure of the neural networks to evolve. This is
the same as the argument passed into `create_network`. The `population_size`
defines how many networks exist in each generation of the genetic algorithm.
More networks in a generation allows for a greater variety of solutions, but
is slower to evolve. 

The `fitness_function` should be a callable that takes
two arguments: a reference to the ANN whose fitness is being measured and a
reference to the current generation (of siblings). This final argument is 
sometimes needed because the network's fitness may depend on competitive
performance *between* all the members of the current generation. For example,
a board game playing network might be assessed by how well it performs when
playing all the other networks in its generation. The `fitness_function`
should return a numerical value so the networks can be sorted by fitness. The 
`halt_function` should be a callable that also takes two arguments: a 
reference to the current population of networks, along with an integer 
representing the current generation number. The halt function simply defines
when the genetic algorithm should stop and returns a boolean value where
`True` means stop.

Just like the `train` function, the optional
`log` argument takes a callable used to log messages as the process of
evolution unfolds.

Additional optional arguments (not shown in this example) include the
`generate_function` that should take a list of the current population
sorted by fitness, along with the optional `fittest_proportion` that
determines the proportion of the fittest individuals to retain. The
`mutation_chance`, and `mutation_amount` parameters are used to control
the mutation process. The `generate_function` returns a new unsorted
population for the next generation. By default SANN will use the built-in
`simple_generate` function which will be good for most purposes. Finally,
the `reverse` flag indicates if the fittest ANN has the highest (True) 
or lowest (False) fitness score.

Please see the [API documentation](api.md) for more details.

### Use the network 🛠️

Given a representation of a trained or evolved neural network and a list of
input values, use the `run_network` function to retrieve a list of output 
values caused by passing the inputs through the neural network.

That's it!

```python
import sann


# Load the pre-trained neural network from somewhere.
with open("my_nn.json", "r") as f:
  nn = json.load(f)

# Get the input data from somewhere.
input_data = get_input_data_from_somewhere()

# Gather the result by running input data through the network.
result = sann.run_network(nn, input_data)

# Interpret / react to the result in some meaningful manner.
do_stuff_with(result)
```

## Developer Setup 🧑‍💻

Before continuing, please read the statement about 
[care of community](./CARE_OF_COMMUNITY.md) ~ we hope everyone involved
in this project will abide by the ethos expressed therein.

1. Clone [the repository](https://github.com/ntoll/sann).
2. Create a virtual environment.
3. `pip install -r requirements.txt`
4. Run the test suite: `make check`
5. Educational examples in the `examples` directory.
6. To build the docs: `make docs`

See [Behind the AI Curtain](https://ntoll.org/article/ai-curtain/) for more
information.

## Feedback / Bugs 🗣️

Simply [create an issue](https://github.com/ntoll/sann/issues) in the GitHub
repository. If you're using this code for something fun, please let me know
because I'd love to include more examples from real-world creative
explorations.

Thank you! 🤗
