Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# .github/workflows/ci.yml
name: C/C++ CI
name: Neural Network CI

on:
pull_request:
Expand All @@ -12,17 +11,29 @@ jobs:
steps:
- uses: actions/checkout@v2

- name: Cache build dependencies
uses: actions/cache@v2
with:
path: |
~/.cache/pip
build
key: ${{ runner.os }}-build-${{ hashFiles('**/Makefile') }}
restore-keys: |
${{ runner.os }}-build-

- name: Install build tools and linter
run: sudo apt-get update && sudo apt-get install -y clang-format cppcheck
run: sudo apt-get update && sudo apt-get install -y clang-format cppcheck libcunit1-dev

- name: Check code formatting
run: |
mkdir -p ../temp_repo
cp -r . ../temp_repo
find ../temp_repo -name "*.c" -o -name "*.h" | xargs clang-format -i --style=Google
diff -u -r ../temp_repo .
find . -name "*.c" -o -name "*.h" | xargs clang-format --dry-run --Werror --style=Google

- name: Build project
run: make all

- name: Run tests
run: make test

- name: Run static code analysis
run: |
# The suppress flag tells cppcheck to ignore unused function warnings.
cppcheck --enable=all -I nn/include --suppress=missingIncludeSystem --suppress=unusedFunction --inconclusive --error-exitcode=1 .
cppcheck --enable=all -I nn/include --suppress=missingIncludeSystem --inconclusive --error-exitcode=1 .
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
build

# Doxygen output
docs/api/
docs/api/

# Object files
*.o
44 changes: 44 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Compiler and flags
CC = gcc
CFLAGS = -I nn/include -I tests -Wall -Wextra -Werror -Wpedantic -Wstrict-prototypes -Wold-style-definition -g $(CU_CFLAGS)

# Source files
SRCS = $(shell find nn/src -name '*.c' -not -path 'nn/src/main.c')
TEST_SRCS = tests/core_cunit.c tests/nn_cunit.c tests/cunit_runner.c tests/test_utils.c

# Object files
OBJS = $(SRCS:.c=.o)
TEST_OBJS = $(TEST_SRCS:.c=.o)

# Executables
EXAMPLE_SRCS = $(wildcard nn/src/examples/*.c)
EXAMPLE_OBJS = $(EXAMPLE_SRCS:.c=.o)
EXAMPLE_EXECS = $(EXAMPLE_SRCS:.c=)
TEST_RUNNER = tests/cunit_runner_exec

# CUnit flags
CU_CFLAGS = $(shell pkg-config --cflags cunit)
CU_LIBS = $(shell pkg-config --libs cunit)

# Targets
all: $(EXAMPLE_EXECS) $(TEST_RUNNER)

$(EXAMPLE_EXECS): nn/src/examples/%: $(filter-out nn/src/examples/%.o, $(OBJS)) nn/src/examples/%.o
$(CC) $(CFLAGS) -o $@ $^ -lm

$(TEST_RUNNER): $(filter-out $(EXAMPLE_OBJS), $(OBJS)) $(TEST_OBJS)
$(CC) $(CFLAGS) $(CU_CFLAGS) -o $@ $^ $(CU_LIBS) -lm

%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<

clean:
rm -f $(OBJS) $(TEST_OBJS) $(EXAMPLE_OBJS) $(EXAMPLE_EXECS) $(TEST_RUNNER)

test: $(TEST_RUNNER)
./$(TEST_RUNNER)

format:
clang-format -i $(SRCS) $(TEST_SRCS) nn/include/*.h

.PHONY: all clean test format
204 changes: 130 additions & 74 deletions nn/include/activation.h
Original file line number Diff line number Diff line change
@@ -1,121 +1,177 @@
#pragma once

#include "linalg.h"

/**
* @file activation.h
* @brief Activation functions and their derivatives operating on matrices.
* @brief Header for activation functions used in neural networks.
*
* All functions return newly allocated matrices; callers own and must free
* the results. Derivatives are provided for use in backpropagation.
* This file defines an enumeration for various activation functions and
* declares their corresponding matrix-based functions and their derivatives.
* These functions are crucial for introducing non-linearity into neural
* networks.
*/

//============================
// Activation Functions
//============================
#ifndef NN_ACTIVATION_H
#define NN_ACTIVATION_H

#include <stddef.h>

#include "linalg.h" // Assumes Matrix struct is defined here

/**
* @brief Enum for different activation functions.
*
* This enumeration lists the types of activation functions supported
* within the neural network framework. Each enumerator corresponds to
* a specific non-linear function applied to the output of a layer.
*/
typedef enum {
RELU, /**< Rectified Linear Unit activation. */
SIGMOID, /**< Sigmoid activation. */
SOFTMAX, /**< Softmax activation, typically used in the output layer for
multi-class classification. */
TANH, /**< Hyperbolic Tangent activation. */
LEAKY_RELU, /**< Leaky Rectified Linear Unit activation. */
SIGN, /**< Sign activation. */
IDENTITY, /**< Identity activation. */
HARD_TANH /**< Hard Tanh activation. */
} activation_function;

// Activation functions

/**
* @brief Sigmoid activation applied elementwise.
* @param m Input matrix (m x n).
* @return New matrix (m x n) with sigmoid applied.
* @brief Applies the Sigmoid activation function element-wise to a matrix.
* @param m A pointer to the input Matrix.
* @return A new Matrix with the sigmoid function applied to each element.
*/
Matrix* sigmoid(Matrix* m);

/**
* @brief Derivative of sigmoid applied elementwise.
* @param m Input matrix (m x n).
* @return New matrix (m x n) with sigmoid derivative applied.
* @brief Applies the ReLU (Rectified Linear Unit) activation function
* element-wise to a matrix.
* @param m A pointer to the input Matrix.
* @return A new Matrix with the ReLU function applied to each element.
*/
Matrix* sigmoid_prime(Matrix* m);
Matrix* relu(Matrix* m);

/**
* @brief ReLU activation applied elementwise.
* @param m Input matrix (m x n).
* @return New matrix (m x n) with ReLU applied.
* @brief Applies the Hyperbolic Tangent (tanh) activation function
* element-wise to a matrix.
* @param m A pointer to the input Matrix.
* @return A new Matrix with the tanh function applied to each element.
*/
Matrix* relu(Matrix* m);
Matrix* tanh_activation(Matrix* m);

/**
* @brief Derivative of ReLU applied elementwise.
* @param m Input matrix (m x n).
* @return New matrix (m x n) with ReLU derivative applied.
* @brief Applies the Leaky ReLU activation function element-wise to a matrix.
* @param m A pointer to the input Matrix.
* @param leak_parameter The leak parameter (alpha) for the Leaky ReLU.
* @return A new Matrix with the Leaky ReLU function applied to each element.
*/
Matrix* relu_prime(Matrix* m);
Matrix* leaky_relu(Matrix* m, double leak_parameter);

/**
* @brief Hyperbolic tangent activation applied elementwise.
* @param m Input matrix (m x n).
* @return New matrix (m x n) with tanh applied.
* @brief Applies the Sign activation function element-wise to a matrix.
* @param m A pointer to the input Matrix.
* @return A new Matrix with the Sign function applied to each element.
*/
Matrix* tanh_activation(Matrix* m);
Matrix* sign_activation(Matrix* m);

/**
* @brief Derivative of tanh applied elementwise.
* @param m Input matrix (m x n).
* @return New matrix (m x n) with tanh derivative applied.
* @brief Applies the Identity activation function element-wise to a matrix.
* @param m A pointer to the input Matrix.
* @return A new Matrix that is a copy of the input matrix.
*/
Matrix* tanh_prime(Matrix* m);
Matrix* identity_activation(Matrix* m);

/**
* @brief Leaky ReLU activation (alpha=0.01) applied elementwise.
* @param m Input matrix (m x n).
* @return New matrix (m x n) with Leaky ReLU applied.
* @brief Applies the Hard Tanh activation function element-wise to a matrix.
* @param m A pointer to the input Matrix.
* @return A new Matrix with the Hard Tanh function applied to each element.
*/
Matrix* leaky_relu(Matrix* m);
Matrix* hard_tanh(Matrix* m);

/**
* @brief Derivative of Leaky ReLU (alpha=0.01) applied elementwise.
* @param m Input matrix (m x n).
* @return New matrix (m x n) with Leaky ReLU derivative applied.
* @brief Applies the Softmax activation function to a matrix.
* This function is typically used in the output layer of a neural network for
* multi-class classification. It normalizes the input values into a probability
* distribution.
* @param m A pointer to the input Matrix.
* @return A new Matrix with the Softmax function applied.
*/
Matrix* leaky_relu_prime(Matrix* m);
Matrix* softmax(Matrix* m);

// Derivatives of activation functions

/**
* @brief Leaky ReLU with custom alpha (leak_parameter).
* @param m Input matrix (m x n).
* @param leak_parameter The alpha value for the leak.
* @return New matrix (m x n) with Leaky ReLU applied.
* @brief Computes the derivative of the Sigmoid activation function
* element-wise to a matrix.
* @param m A pointer to the input Matrix (output of the sigmoid function).
* @return A new Matrix with the sigmoid derivative applied to each element.
*/
Matrix* leaky_relu_with_alpha(Matrix* m, double leak_parameter);
Matrix* sigmoid_prime(Matrix* m);

/**
* @brief Derivative of Leaky ReLU with custom alpha.
* @param m Input matrix (m x n).
* @param leak_parameter The alpha value for the leak.
* @return New matrix (m x n) with Leaky ReLU derivative applied.
* @brief Computes the derivative of the ReLU activation function element-wise
* to a matrix.
* @param m A pointer to the input Matrix.
* @return A new Matrix with the ReLU derivative applied to each element.
*/
Matrix* leaky_relu_prime_with_alpha(Matrix* m, double leak_parameter);
Matrix* relu_prime(Matrix* m);

/**
* @brief Sign activation: -1, 0, or +1 elementwise.
* @param m Input matrix (m x n).
* @return New matrix (m x n) with sign activation applied.
* @brief Computes the derivative of the Hyperbolic Tangent (tanh) activation
* function element-wise to a matrix.
* @param m A pointer to the input Matrix (output of the tanh function).
* @return A new Matrix with the tanh derivative applied to each element.
*/
Matrix* sign_activation(Matrix* m);
Matrix* tanh_prime(Matrix* m);

/**
* @brief Derivative of sign (0 everywhere; undefined at 0).
* @param m Input matrix (m x n).
* @return New matrix (m x n) of zeros.
* @brief Computes the derivative of the Leaky ReLU activation function
* element-wise to a matrix.
* @param m A pointer to the input Matrix.
* @param leak_parameter The leak parameter (alpha) for the Leaky ReLU.
* @return A new Matrix with the Leaky ReLU derivative applied to each element.
*/
Matrix* sign_prime(Matrix* m);
Matrix* leaky_relu_prime(Matrix* m, double leak_parameter);

/**
* @brief Identity activation (returns a copy).
* @param m Input matrix (m x n).
* @return New matrix (m x n), a copy of the input.
* @brief Computes the derivative of the Sign activation function element-wise
* to a matrix.
* @param m A pointer to the input Matrix.
* @return A new Matrix with the Sign derivative applied to each element.
*/
Matrix* identity_activation(Matrix* m);
Matrix* sign_prime(Matrix* m);

/**
* @brief Derivative of identity (ones).
* @param m Input matrix (m x n).
* @return New matrix (m x n) of ones.
* @brief Computes the derivative of the Identity activation function
* element-wise to a matrix.
* @param m A pointer to the input Matrix.
* @return A new Matrix of the same size as the input, with all elements set
* to 1.0.
*/
Matrix* identity_prime(Matrix* m);

/**
* @brief Hard Tanh activation clamped to [-1, 1].
* @param m Input matrix (m x n).
* @return New matrix (m x n) with hard tanh applied.
* @brief Computes the derivative of the Hard Tanh activation function
* element-wise to a matrix.
* @param m A pointer to the input Matrix.
* @return A new Matrix with the Hard Tanh derivative applied to each element.
*/
Matrix* hard_tanh(Matrix* m);
Matrix* hard_tanh_prime(Matrix* m);

/**
* @brief Derivative of Hard Tanh (1 in (-1,1), else 0).
* @param m Input matrix (m x n).
* @return New matrix (m x n) with hard tanh derivative applied.
* @brief Computes the derivative of the Softmax activation function
* element-wise to a matrix. This is typically used in conjunction with a loss
* function like cross-entropy where the derivative simplifies to output * (1 -
* output).
* @param m A pointer to the input Matrix (output of the softmax function).
* @return A new Matrix with the Softmax derivative applied to each element.
*/
Matrix* hard_tanh_prime(Matrix* m);
Matrix* softmax_prime(Matrix* m);

#endif // NN_ACTIVATION_H
/**
* @brief Converts an activation function enum to its string representation.
* @param func The activation function enum.
* @return A string representing the activation function.
*/
const char* activation_to_string(activation_function func);
4 changes: 2 additions & 2 deletions nn/include/backprop.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
* @brief Compute layer-wise deltas and cache them for gradient evaluation.
* @param nn Pointer to network.
* @param y_true Ground-truth labels/targets.
* @param loss_func Loss function (optional here; for monitoring).
* @param loss_type Loss function (optional here; for monitoring).
* @param loss_func_grad Gradient of loss w.r.t predictions (required).
*/
void backpropagate(NeuralNetwork* nn, const Matrix* y_true,
LossFunction loss_func, LossFunctionGrad loss_func_grad);
LossFunctionType loss_type, LossFunctionGrad loss_func_grad);

/** @brief Calculate weight gradient for a specific layer. */
Matrix* calculate_weight_gradient(const Cache* cache, size_t layer_index,
Expand Down
7 changes: 5 additions & 2 deletions nn/include/cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ typedef struct Cache Cache;
/** @brief Create an empty cache. */
Cache* create_cache();

/** @brief Store a deep copy of matrix `m` under key `key`. */
void cache_put(Cache* cache, const char* key, const Matrix* m);
/** @brief Store matrix `m` under key `key`. The cache takes ownership of `m`,
* and `m` will be freed when the cache entry is overwritten or the
* cache is cleared. The caller must ensure `m` is a deep copy if a distinct
* version is needed. */
void cache_put(Cache* cache, const char* key, Matrix* m);

/** @brief Retrieve a deep copy of a matrix by key, or NULL if not found. */
Matrix* cache_get(Cache* cache, const char* key);
Expand Down
2 changes: 0 additions & 2 deletions nn/include/feedforward.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@ void free_network(NeuralNetwork* nn);

/**
* @brief Run the forward pass and cache intermediates for backprop.
*
* @param nn Network pointer (non-NULL).
* @param input Input matrix (batch_size x input_features).
* Shape must match the first layer's expected input shape.
* @return Output activation of the last layer (batch_size x output_features).
* Caller owns and must free.
*/
Expand Down
Loading
Loading