# Genann: a two-file feedforward neural network library in C99

> Genann is a single-header-and-source C99 library for training and running small feedforward networks. It is aimed at embedded and C projects that want backpropagation without a dependency tree.

**codeplea/genann** — simple neural network library in C99

- Repository: https://github.com/codeplea/genann
- Website: https://codeplea.com/genann
- Stars: 2,293 · Forks: 271
- Language: C
- License: Zlib
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/codeplea-genann

## What Genann is for

Genann is a C99 library for training and running feedforward artificial neural networks. The README describes its focus as being simple, fast, reliable and hackable, achieved by providing only the necessary functions and little extra. That framing is accurate: the library gives you network creation, a single backpropagation update step, a forward pass, and text-based save and load. Everything else, including how you iterate over your data, how you detect overfitting and how you schedule the learning rate, is left to the caller.

The intended user is someone writing C or C++ who wants a small network inside an existing program rather than a separate training pipeline. The repository ships four example programs: XOR with backpropagation, XOR with random search, loading and running a saved network, and training on the IRIS data set with backpropagation. Those four cover the whole surface area of the library, which tells you something about its scope. If your problem needs convolutional layers, recurrent state or automatic differentiation, this is the wrong tool and the README does not pretend otherwise.

## The architecture: one struct, one contiguous weight block

A network is created with genann_init(inputs, hidden_layers, hidden, outputs), which returns a genann struct pointer. The arguments fix the topology at creation time: number of inputs, number of hidden layers, neurons per hidden layer, and number of outputs. There is no dynamic growth and no per-layer variation in width, so a network where layer one has 64 neurons and layer two has 16 is not expressible through the public constructor.

The design decision worth understanding is memory layout. According to the README, a primary design goal was to store all network weights in one contiguous block. Every genann struct exposes int total_weights and double *weight, where weight points to an array of total_weights doubles holding every weight in the network. This is what makes the library usable with training methods other than backpropagation. Hill climbing, genetic algorithms and simulated annealing can all operate on the weight array directly, because the weights are a flat vector rather than a set of per-layer matrices. example2.c demonstrates random hill climbing search on the weight array.

Activation functions are per-network members, not compile-time choices. Each network has activation_hidden and activation_output fields that can be set to genann_act_sigmoid_cached, genann_act_tanh, genann_act_relu, genann_act_linear or genann_act_threshold. The default is sigmoid. Here the README is explicit about a boundary: backpropagation training knows the derivatives of the built-in activation functions only, and if you substitute your own function, genann_train() will assume the sigmoid derivative. That means a custom activation trained with backpropagation is silently wrong rather than rejected, which is a design choice you should be aware of. The alternative training methods work with any activation.

## Building Genann and training XOR

Genann is self-contained in two files, genann.c and genann.h. The README says to add those two files to your project. There is no configure step, no package manager entry and no generated build system. The repository's Makefile builds the test binary and the four examples, and it links against libm through LDLIBS = -lm, so on a system where the math library is separate you need that flag.

To build the examples and run the test suite from a checkout, the Makefile provides these targets. The all target depends on check and the four example binaries:

```bash
make all
make check
```

The check target runs ./test, which is the test binary built from test.c and genann.o. The Makefile's clean target removes the object files, the test binary, the four examples, any .exe files and persist.txt, which is the file name example3 appears to use.

The quick example in the README creates a network with two inputs, one hidden layer of three neurons and two outputs, then trains it for 300 passes over 100 samples at a learning rate of 0.1:

```c
#include "genann.h"

genann *ann = genann_init(2, 1, 3, 2);

for (i = 0; i < 300; ++i) {
    for (j = 0; j < 100; ++j)
        genann_train(ann, training_data_input[j], training_data_output[j], 0.1);
}

double const *prediction = genann_run(ann, test_data_input[0]);
printf("Output for the first test data point is: %f, %f\n", prediction[0], prediction[1]);

genann_free(ann);
```

genann_run returns a pointer to an array of predicted outputs whose length is ann->outputs, so you read the values immediately rather than holding the pointer past the next call. The README notes that this snippet is API usage rather than good machine learning practice: in a real application you would shuffle the training data and monitor learning to prevent overfitting. It also states that the default sigmoid activation expects outputs between 0 and 1 and that inputs should be scaled to roughly plus or minus one.

## Saving, loading and the text format

Persistence is two functions: genann_write writes a network to a FILE pointer and genann_read reads one back. The README describes the format as text-based, and the example directory contains xor.ann, which is the artefact example3 loads. There is no version field documented, no schema and no stated guarantee about forward or backward compatibility between releases. That matters because the library moved from v1.0.0 in September 2020 to v1.1.0 and v1.1.1 in August 2026; the README does not document whether a file written by v1.0.0 is readable by v1.1.1. If you persist trained networks, that is the first thing to verify against your own files rather than assume.

The text format is also a size and precision trade-off. A double written as decimal text is larger than its binary representation and round-trips through the C library's parsing, which is fine for the small networks Genann targets but not what you would choose for a large weight matrix. The README does not discuss precision loss on round-trip, so treat that as unverified.

## Where Genann stops being the right tool

The topology constraint is the first real limitation. genann_init takes a single hidden count, so every hidden layer has the same width. Networks that need a tapering encoder shape, or a layer with a different activation from its neighbours, cannot be built through the documented API. You could reach into the struct, but the README does not document the internal layout beyond total_weights and weight, so any such work is on you to maintain.

The second limitation is the custom activation trap described above. If you set activation_hidden to your own function and then call genann_train(), the README states that the sigmoid derivative is assumed. The training will run and produce numbers; they will not be the gradient of your function. This is the kind of failure that shows up as a model that trains slowly rather than as an error message.

The third is the absence of a training loop. Genann performs one update per genann_train call. Batching, shuffling, learning rate schedules, validation splits and early stopping are all outside the library. The README's own example acknowledges this by warning that the snippet is not good machine learning practice. If you want those facilities provided, Genann is the wrong layer of abstraction; it is a numerical kernel with an API, not a framework.

## Genann compared with FANN and tinn

The README points to two alternatives directly. tinn is described as an even smaller single-hidden-layer library, so the difference is architectural: tinn fixes the depth at one hidden layer, while Genann lets you pass a hidden_layers count to genann_init. If your problem genuinely needs only one hidden layer, tinn is the smaller dependency; if you expect to experiment with depth, Genann's constructor is the reason to pick it.

FANN is described in the README as a heavier, more opinionated neural network library in C. The difference in approach is where the work lives. Genann gives you a weight array and a training step and expects you to write the loop; FANN is opinionated, which in practice means it carries more of the training and data handling itself. The README also mentions Peter van Rossum's Lightweight Neural Network, noting that despite the name it is heavier and has more features than Genann. That is a useful calibration: the naming in this space does not track the actual size of the libraries.

The honest summary is that Genann's differentiator is not capability but surface area. Two files, no dependencies, a flat weight vector and a documented set of activation functions. Everything the alternatives have beyond that is something Genann deliberately does not do.

## Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-08-08. Release v1.1.1 is dated 2026-08-06, v1.1.0 the same day, and v1.0.0 goes back to 2020-09-22. So the project sat at v1.0.0 for roughly six years and then produced two releases in one day. The README does not include a changelog, so the difference between v1.0.0 and v1.1.1 is not documented in the repository's own release notes. Anyone upgrading across that gap should read the commit history rather than rely on release notes that do not appear in the README.

The licence is zlib, which the README describes as free for nearly any use. zlib is a permissive licence, and the practical implication for a C library you compile into your binary is that attribution requirements are lighter than under a copyleft licence. This is not legal advice, and the specific obligations depend on your distribution model; read the LICENSE file in the repository rather than the README's one-line summary.

Upgrade cost is low by construction. Because the library is two files that you copy into your project, upgrading means replacing genann.c and genann.h and rebuilding. The risk is concentrated in two places: the text save format, which the README does not version, and the activation function behaviour, which the README documents as assuming the sigmoid derivative for custom functions. Both are worth a test before you swap the files in a production tree.

## Conclusion

Genann suits C or C++ projects that need a small feedforward network with backpropagation and no dependency tree, and it is worth trying when the network fits in memory and the training loop is yours to write. It is not the right tool if you want a framework that handles data loading, batching, GPU execution or model serialisation beyond the text format the README describes. Before adopting it, check the activation function you intend to use against the training path: the README states that genann_train() assumes the sigmoid derivative for any custom function, so verify that limitation against your planned architecture and confirm the zlib licence terms against your own distribution requirements.

## FAQ

### How can I implement a neural network in C with Genann?

Add genann.c and genann.h to your project, call genann_init with the number of inputs, hidden layers, neurons per hidden layer and outputs, then call genann_train once per sample per pass. The README's quick example trains a 2-1x3-2 network for 300 passes over 100 samples at a learning rate of 0.1.

### How do I install Genann?

There is no install step. The README states that Genann is self-contained in genann.c and genann.h and that you simply add those two files to your project. The Makefile in the repository builds the test binary and the four example programs.

### Can Genann train with something other than backpropagation?

Yes. The README states that all weights are stored in one contiguous block, exposed as int total_weights and double *weight, which makes direct-search methods such as hill climbing, genetic algorithms and simulated annealing usable by searching the weight array. example2.c demonstrates random hill climbing.

### What happens if I use a custom activation function with genann_train?

The README states that backpropagation training knows the derivatives of the built-in activation functions only, and that if you substitute your own function genann_train will assume the sigmoid derivative. Other training methods work with any activation.

### Can Genann save and load a trained network?

Yes. genann_write saves a network to a FILE pointer and genann_read loads one, using a text-based format. The example directory contains xor.ann, which example3 loads and runs.

## Sources

- [codeplea/genann on GitHub](https://github.com/codeplea/genann)
- [License: Zlib](https://github.com/codeplea/genann/blob/master/LICENSE)
- [Project website](https://codeplea.com/genann)
- [README](https://github.com/codeplea/genann/blob/master/README.md)
- [Releases](https://github.com/codeplea/genann/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/codeplea-genann
