Neural NetworC is a Multilayer Perceptron (MLP) library implementation for C programming language. Neural NetworC contains two main modules: the MLP module, encharged of neural network operations, and the Bamboo module, which handles some data transformation for the network. New modules with different neural network architectures may arrive as my madness continues to deepen. Currently, the library is in alpha version, so bugs may arise and some features might not be generic enough. Open issues with suggestions or pull a request to collaborate.
- The dataset must contain only numeric data (integer and float/double). It will handle string data as 0, and therefore not cause errors, but will not behave properly.
- The dataset must be on dummy format for the get_result_report function to work. If you want to use a different encoding, you must implement the results handling by yourself. Later versions might be more generic or offer more variation.
To compile the library, run the following command on terminal (make sure you're in the same folder as the Makefile archive):
makeThis command generates files bamboo.o, MLP.o, and libneuralnetworc.a. After that, to compile your program that uses the library, run the following command, knowing that:
-Iindicates the path to the .h filesexamples/main.cis the path to the file that's being compiled-Lindicates the path to the library file-lneuralnetworcindicates that the library being used is defined in the file libneuralnetwork.a (which was generated when you compiled the library)-oindicates that the next parameter (your_program) will be the name of the executable file generated
gcc -I./include examples/main.c -L. -lneuralnetworc -o your_programTo run it after, you can just use the following command:
./your_programAlternatively, you can use a bash script to make the process easier for you. The file nnc (in linux) and nnc.ps1 (in powershell terminal) will allow you to compile your program by running the following command, with as many gcc options as you want:
./nnc examples/main.c -o programtypedef struct dataset
{
double **matrix;
int columns;
int rows;
} dataset;enum split_method{
PEREIRA,
FISCHER_YATES
};dataset *load_csv(FILE *file, char *delim, short header, int row_max_size, short force_not_null)
- Reads a csv file defined in
file, considering the delimiter char indelim, and returns a dataset.headerdefines wheter there's a header (1) or not (0),row_max_sizedefines the maximum amount of characters a line might have, andforce_not_nulldecides if the dataset can consider NULL data as 0 or stop the reading if it encounters a NULL data.
void min_max_scale_column(dataset *data, int pos)
- Apply Min-Max Scaling in the column of index equals to
posin the dataset provided indata.
void min_max_scale_data(dataset *data)
- Apply Min-Max Scaling in in the dataset provided in
data.
void x_y_split(dataset *original, dataset **x, dataset **y, int output_size);
- Divides the dataset pointed by
originalin x and y, and returns the splitted dataset in two other datasetsxandy, sent as parameters.xwill contain columns from index 0 to the number of columns in original minusoutput_sizeminus 1, and y will contain the lastoutput_sizecolumns in original.
void train_test_split(dataset *data, dataset **train, dataset **test, double train_ratio, int random_state, int output_size, enum split_method method);
- Divides the dataset in
datainto training and testing datasets, returned intrainandtestvariables.train_ratiomust be a number between 0 and 1 (not included), and it defines the percentage of instances that will be alocated to training and testing. 0.7 to 0.8 are recommended values.random_statedefines the random seed in the instance shuffling.output_sizedefines where the classes are encountered, as the method is stratified (tries to maintain the proportion between the classes).methoddefines what shuffling method will be used.PEREIRAmethod is slower but tends to get better results, whileFISCHER_YATESis very simple but fast.
void to_csv(dataset *data, char *dir);
- Creates a csv file of the dataset in
dataand saves it in the directory defined indir.
void dataframe_head(dataset *data, int head);
- Writes in console the
headfirst lines in the dataframe indata.
typedef struct weight_matrix
{
double **matrix;
int columns;
int rows;
} weight_matrix;
typedef struct metrics
{
double global_accuracy;
int classes;
int **confusion_matrix;
double **class_metrics; //rows equivalent to classes, 3 columns - precision, recall, f-1.
} metrics;
typedef struct layer
{
double *values;
int neurons;
} layer;
typedef struct neural_network
{
layer *layers;
layer *errors;
weight_matrix *weights;
metrics *report;
int input_neurons;
int output_neurons;
int hidden_layers;
int total_layers;
enum activation_function activation;
} neural_network;
enum activation_function {
SIGMOID,
RELU,
TANH
};
void get_dummies(dataset *data);
- Identify every categorical column in
dataand uses dummy codification in it.
int dummy(dataset *data, int column);
- Uses dummy codification in the column of index
columnin datasetdata.
neural_network *create_neural_network(dataset *data, int output_neurons, int hidden_layers, int *hidden_layers_size, enum activation_function activation_function, int random_state);
- Creates and returns a neural network using the struct
neural_network. The input layer will be based on the dataset you send as parameter indata. The outpur layer will be defined by theoutput_neuronsparameter.hidden_layersis an integer defining how many hidden layers there will be, whilehidden_layers_sizeis an integer array containing in each position how many neurons each of the hidden layers will have. If NULL, it will calculate automatically the size (not guaranteed to make good predictions).activation_functiondefines which of the enumerated options will be used as activation function in the trainment (SIGMOIDrecommended).random_statedefines the random seed for matrix initialization.
void train(neural_network *network, dataset *x_data, dataset *y_data, double learning_rate, double momentum, int epochs, int monitor);
- Trains the neural network in
networkwith the data inx_data, comparing the results obtained in each instance with the ones iny_data(expected target values).learning_ratereceives an integer between 0 and 1 that defines how fast the network learns (0.1 recommended).momentumdefines a multiplier in the network, and is also a value between 0 and 1.epochsdefines for how many epochs the data will be trained, whilemonitordefines how often the MSE loss will be shown in the screen (monitor in monitor epochs). The macrofitexpands to this function.
void train_with_early_stopping(neural_network *network, dataset *x_data, dataset *y_data, double learning_rate, double momentum, int epochs, int monitor, double min_loss, int patience);
- Works just like
train, but stops early if the algorhythm sees there's not enough gain in the MSE loss metric.min_lossdefines what is the minimum difference between two monitored losses. If the difference is lower than that forpatiencemonitored epochs, the algorhythm supposes there's not enough progress being made and stops training. The macrofit_with_early_stoppingexpands to this function.
void get_results_report(neural_network *network, dataset *x, dataset *y, short log);
- Generates a report of how well the neural network in
networkpredicts the data inx, considering the correct values iny. The results will be attributed to themetricsfield innetwork. Iflogis different than 0, the function will also print the results.
void print_confusion_matrix(metrics *report);
- Prints the confusion matrix after in
report.
int *predict(double **x, double **y, int instances, neural_network *network, int log);
- Using the neural network in
network, predicts every instance ofxand returns an array, with every position being what it predicted to the instance in the index.xmust be a matrix with columns equivalent to the number of input neurons in the network. Iflogis different than 0, the function will print the predicted class and the expected class, available iny(with the same amount of rows as x, and columns equivalent to the number of output neurons). Iflogis 0,yis irrelevant and can be NULL.
void export_neural_network(neural_network *nn, char *dir);
- Creates a txt file with the settings for the neural network in
nnand saves it in the directory defined indir.
neural_network * load_neural_network(FILE *source);
- Loads the neural network in the txt file pointed by
source.
#include <stdio.h>
#include <stdlib.h>
#include "../include/bamboo.h"
#include "../include/MLP.h"
int main(){
FILE *file = fopen("examples/data.csv", "r"); //reads the csv file
dataset *data = load_csv(file, ",", 1, 1000, 1); //transfer it to the dataset structure
min_max_scale_data(data); //scale the dataset
get_dummies(data); //get the dummy codification for the data
int output_neurons = 2, hidden_layers = 2; //defines the structure of the newtwork
dataset *test_data_splitted, *train_data_splitted; //creates datasets for the train and test datasets
train_test_split(data, &train_data_splitted, &test_data_splitted, 0.7, 39, output_neurons, PEREIRA); //divide the dataset in train and test
int layers_size[] = {20,10};
dataset *x, *y;
x_y_split(train_data_splitted, &x, &y, output_neurons); //splits train_data_splitted in x and y
neural_network *nn = create_neural_network(train_data_splitted, output_neurons, hidden_layers, layers_size, SIGMOID, NULL); //creates the neural network with random (NULL) seed
fit_with_early_stopping(nn, x, y, 0.1, 1, 1000, 10, 0.0001, 1); //train with early stoppping
dataset *test_x, *test_y;
x_y_split(test_data_splitted, &test_x, &test_y, output_neurons); //splits test_data_splitted in x and y
get_results_report(nn, test_x, test_y, 1); //tests the network and uses the log to see the results
print_confusion_matrix(nn->report); //prints the confusion matrix as well
export_neural_network(nn, "results/exported.txt"); //generates a txt file with the network so it can be loaded later
return 0;
}