From e0a43227fea02e991f335c7c0db5cf433e996d1b Mon Sep 17 00:00:00 2001 From: Hoonlim Lee Date: Wed, 1 Jul 2026 14:58:13 +0800 Subject: [PATCH 1/5] Changed neural_network struct and removed layer struct --- include/cneuron/cneuron.h | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/include/cneuron/cneuron.h b/include/cneuron/cneuron.h index 76abe3aea..b4d980dfc 100644 --- a/include/cneuron/cneuron.h +++ b/include/cneuron/cneuron.h @@ -108,26 +108,21 @@ void vector_apply_activation(const float *a, float *b, size_t length, float (*ac */ void hadamard_product(const float *restrict a, const float *restrict b, float *restrict c, size_t length); -/** - * @brief Represents a single layer in a neural network. - */ -typedef struct layer { - float *delta; /**< Error delta for backpropagation. */ - float *weighted_input; /**< Weighted input values for the layer. */ - float *weights; /**< Weights of the layer in column-major format. */ - float *bias; /**< Bias values for the layer. */ - float *output; /**< Output values from the layer. */ - size_t length; /**< Number of neurons in this layer. */ -} layer; - /** * @brief Represents a neural network with multiple layers. */ typedef struct { - layer *layers; /**< Array of struct to layers in the network. */ size_t length; /**< Number of layers in the network. */ + const size_t *layer_length; /**< Number of neuron in each layer. */ + const size_t *prev_length_sums; /**< Number of neuron in all previous layer. */ + const size_t *prev_weights_sums; /**< Number of weights in all previous layer. */ size_t inputs_length; /**< Number of inputs to the network. */ float (*activation_function)(float, bool); /**< Pointer to the activation function used in the network. */ + float *delta; /**< Error delta for backpropagation. */ + float *weighted_input; /**< Weighted input values for the layer. */ + float *weights; /**< Weights of the layer in column-major format. */ + float *bias; /**< Bias values for the layer. */ + float *output; /**< Output values from the layer. */ } neural_network; /** From 207a89df69ae336d508d5696cde27b4a266514da Mon Sep 17 00:00:00 2001 From: Hoonlim Lee Date: Wed, 1 Jul 2026 15:45:13 +0800 Subject: [PATCH 2/5] Changed the allocation function for network --- include/cneuron/cneuron.h | 10 +++++----- src/network.c | 35 +++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/include/cneuron/cneuron.h b/include/cneuron/cneuron.h index b4d980dfc..dac6a7acb 100644 --- a/include/cneuron/cneuron.h +++ b/include/cneuron/cneuron.h @@ -113,16 +113,16 @@ void hadamard_product(const float *restrict a, const float *restrict b, float *r */ typedef struct { size_t length; /**< Number of layers in the network. */ - const size_t *layer_length; /**< Number of neuron in each layer. */ - const size_t *prev_length_sums; /**< Number of neuron in all previous layer. */ - const size_t *prev_weights_sums; /**< Number of weights in all previous layer. */ size_t inputs_length; /**< Number of inputs to the network. */ + size_t *layer_lengths; /**< Number of neuron in each layer. */ + size_t *layer_lengths_sums; /**< Number of neuron until current layer. */ + size_t *layer_weights_sums; /**< Number of weights until current layer. */ float (*activation_function)(float, bool); /**< Pointer to the activation function used in the network. */ float *delta; /**< Error delta for backpropagation. */ float *weighted_input; /**< Weighted input values for the layer. */ - float *weights; /**< Weights of the layer in column-major format. */ - float *bias; /**< Bias values for the layer. */ float *output; /**< Output values from the layer. */ + float *bias; /**< Bias values for the layer. */ + float *weights; /**< Weights of the layer in column-major format. */ } neural_network; /** diff --git a/src/network.c b/src/network.c index 826e328cf..697c342c5 100644 --- a/src/network.c +++ b/src/network.c @@ -21,27 +21,30 @@ neural_network *alloc_neural_network(size_t network_length, const size_t *layers size_t prev_length = (i == 0) ? inputs_length : layers_length[i - 1]; total_float += layers_length[i] * 4 + layers_length[i] * prev_length; } - neural_network *nn = calloc(1, sizeof(neural_network) + sizeof(layer) * network_length + sizeof(float) * total_float); + neural_network *nn = calloc(1, sizeof(neural_network) + sizeof(size_t) * network_length * 3 + sizeof(float) * total_float); if (!nn) return NULL; - - nn->inputs_length = inputs_length; nn->length = network_length; - - nn->layers = (layer *)(nn + 1); - float *float_pointing = (float *)(nn->layers + network_length); - for (size_t i = 0; i < network_length; ++i) { - nn->layers[i].length = layers_length[i]; - + nn->inputs_length = inputs_length; + nn->layer_lengths = (size_t *)(nn + 1); + nn->layer_lengths_sums = nn->layer_lengths + network_length; + nn->layer_weights_sums = nn->layer_lengths_sums + network_length; + size_t lengths_sums = 0; + size_t weights_sums = 0; + for (size_t i = 0; i < network_length; i++) { + lengths_sums += layers_length[i]; size_t prev_length = (i == 0) ? inputs_length : layers_length[i - 1]; - layer *curr_layer = &nn->layers[i]; - curr_layer->delta = float_pointing; - curr_layer->weighted_input = float_pointing + layers_length[i]; - curr_layer->bias = float_pointing + 2 * layers_length[i]; - curr_layer->output = float_pointing + 3 * layers_length[i]; - curr_layer->weights = float_pointing + 4 * layers_length[i]; - float_pointing += 4 * layers_length[i] + layers_length[i] * prev_length; + weights_sums += layers_length[i] * prev_length; + nn->layer_lengths_sums[i] = lengths_sums; + nn->layer_weights_sums[i] = weights_sums; + nn->layer_lengths[i] = layers_length[i]; } + nn->delta = (float *)(nn->layer_weights_sums + network_length); + nn->weighted_input = nn->delta + lengths_sums; + nn->output = nn->weighted_input + lengths_sums; + nn->bias = nn->output + lengths_sums; + nn->weights = nn->bias + lengths_sums; + return nn; } From 10b41f0bf073695502d80b916986d790a96f4500 Mon Sep 17 00:00:00 2001 From: Hoonlim Lee Date: Thu, 2 Jul 2026 12:34:35 +0800 Subject: [PATCH 3/5] Rewritten network.c to use the flat arrays --- include/cneuron/cneuron.h | 6 +- src/network.c | 319 ++++++++++++++++++++------------------ 2 files changed, 170 insertions(+), 155 deletions(-) diff --git a/include/cneuron/cneuron.h b/include/cneuron/cneuron.h index dac6a7acb..f66dab4df 100644 --- a/include/cneuron/cneuron.h +++ b/include/cneuron/cneuron.h @@ -114,9 +114,9 @@ void hadamard_product(const float *restrict a, const float *restrict b, float *r typedef struct { size_t length; /**< Number of layers in the network. */ size_t inputs_length; /**< Number of inputs to the network. */ - size_t *layer_lengths; /**< Number of neuron in each layer. */ - size_t *layer_lengths_sums; /**< Number of neuron until current layer. */ - size_t *layer_weights_sums; /**< Number of weights until current layer. */ + size_t *layer_lengths; /**< Number of neuron in each layer. */ + size_t *prev_lengths_sums; /**< Number of neuron from all previous layer. */ + size_t *prev_weights_sums; /**< Number of weights from all previous layer. */ float (*activation_function)(float, bool); /**< Pointer to the activation function used in the network. */ float *delta; /**< Error delta for backpropagation. */ float *weighted_input; /**< Weighted input values for the layer. */ diff --git a/src/network.c b/src/network.c index 697c342c5..d7127af82 100644 --- a/src/network.c +++ b/src/network.c @@ -21,25 +21,27 @@ neural_network *alloc_neural_network(size_t network_length, const size_t *layers size_t prev_length = (i == 0) ? inputs_length : layers_length[i - 1]; total_float += layers_length[i] * 4 + layers_length[i] * prev_length; } - neural_network *nn = calloc(1, sizeof(neural_network) + sizeof(size_t) * network_length * 3 + sizeof(float) * total_float); + neural_network *nn = calloc(1, sizeof(neural_network) + sizeof(size_t) * (network_length * 3 + 2) + sizeof(float) * total_float); if (!nn) return NULL; nn->length = network_length; nn->inputs_length = inputs_length; nn->layer_lengths = (size_t *)(nn + 1); - nn->layer_lengths_sums = nn->layer_lengths + network_length; - nn->layer_weights_sums = nn->layer_lengths_sums + network_length; + nn->prev_lengths_sums = nn->layer_lengths + network_length; + nn->prev_weights_sums = nn->prev_lengths_sums + network_length + 1; size_t lengths_sums = 0; size_t weights_sums = 0; for (size_t i = 0; i < network_length; i++) { + nn->prev_lengths_sums[i] = lengths_sums; + nn->prev_weights_sums[i] = weights_sums; lengths_sums += layers_length[i]; size_t prev_length = (i == 0) ? inputs_length : layers_length[i - 1]; weights_sums += layers_length[i] * prev_length; - nn->layer_lengths_sums[i] = lengths_sums; - nn->layer_weights_sums[i] = weights_sums; nn->layer_lengths[i] = layers_length[i]; } + nn->prev_lengths_sums[network_length] = lengths_sums; + nn->prev_weights_sums[network_length] = weights_sums; - nn->delta = (float *)(nn->layer_weights_sums + network_length); + nn->delta = (float *)(nn->prev_weights_sums + network_length + 1); nn->weighted_input = nn->delta + lengths_sums; nn->output = nn->weighted_input + lengths_sums; nn->bias = nn->output + lengths_sums; @@ -54,12 +56,9 @@ neural_network *get_neural_network(size_t network_length, const size_t *layers_l neural_network *nn = alloc_neural_network(network_length, layers_length, inputs_length); if (!nn) return NULL; - for (size_t i = 0; i < network_length; i++) { - size_t prev_length = (i == 0) ? inputs_length : layers_length[i - 1]; - for (size_t j = 0; j < layers_length[i] * prev_length; j++) { - // Initialise weights to -1.0f - 1.0f - nn->layers[i].weights[j] = randf(2.0f, -1.0f); - } + for (size_t i = 0; i < nn->prev_weights_sums[nn->length]; i++) { + // Initialise weights to -1.0f - 1.0f + nn->weights[i] = randf(2.0f, -1.0f); } nn->activation_function = activation_function; @@ -68,18 +67,19 @@ neural_network *get_neural_network(size_t network_length, const size_t *layers_l void compute_network(const neural_network *restrict nn, const float *restrict inputs) { assert(nn && inputs); - - for (size_t i = 0; i < nn->length; i++) { - layer *curr = &nn->layers[i]; - if (i == 0) { - cblas_sgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, curr->length, 1, nn->inputs_length, 1.0f, curr->weights, curr->length, inputs, nn->inputs_length, 0.0f, curr->weighted_input, curr->length); - } else { - layer *prev = &nn->layers[i - 1]; - cblas_sgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, curr->length, 1, prev->length, 1.0f, curr->weights, curr->length, prev->output, prev->length, 0.0f, curr->weighted_input, curr->length); - } - - cblas_saxpy(curr->length, 1.0f, curr->bias, 1, curr->weighted_input, 1); - vector_apply_activation(curr->weighted_input, curr->output, curr->length, nn->activation_function, false); + cblas_sgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, nn->layer_lengths[0], 1, nn->inputs_length, 1.0f, nn->weights, nn->layer_lengths[0], inputs, nn->inputs_length, 0.0f, nn->weighted_input, nn->layer_lengths[0]); + cblas_saxpy(nn->layer_lengths[0], 1.0f, nn->bias, 1, nn->weighted_input, 1); + vector_apply_activation(nn->weighted_input, nn->output, nn->layer_lengths[0], nn->activation_function, false); + for (size_t i = 1; i < nn->length; i++) { + size_t len = nn->layer_lengths[i]; + size_t prev_len = nn->layer_lengths[i - 1]; + size_t w_sum = nn->prev_weights_sums[i]; + size_t l_sum = nn->prev_lengths_sums[i]; + size_t prev_l_sum = nn->prev_lengths_sums[i - 1]; + + cblas_sgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, len, 1, prev_len, 1.0f, &nn->weights[w_sum], len, &nn->output[prev_l_sum], prev_len, 0.0f, &nn->weighted_input[l_sum], len); + cblas_saxpy(len, 1.0f, &nn->bias[l_sum], 1, &nn->weighted_input[l_sum], 1); + vector_apply_activation(&nn->weighted_input[l_sum], &nn->output[l_sum], len, nn->activation_function, false); } } @@ -89,41 +89,43 @@ float softmax(const neural_network *nn, size_t neuron_index) { float sum = 0.0f; float max_output = -FLT_MAX; - layer *output_layer = &nn->layers[nn->length - 1]; - for (size_t i = 0; i < output_layer->length; i++) { - if (output_layer->output[i] > max_output) - max_output = output_layer->output[i]; + // Last layer + size_t len = nn->layer_lengths[nn->length - 1]; + size_t l_sum = nn->prev_lengths_sums[nn->length - 1]; + for (size_t i = 0; i < len; i++) { + if (nn->output[l_sum + i] > max_output) + max_output = nn->output[l_sum + i]; } - for (size_t i = 0; i < output_layer->length; i++) - sum += expf(output_layer->output[i] - max_output); + for (size_t i = 0; i < len; i++) + sum += expf(nn->output[l_sum + i] - max_output); - return expf(output_layer->output[neuron_index] - max_output) / sum; + return expf(nn->output[l_sum + neuron_index] - max_output) / sum; } void print_activation_percentages(const neural_network *nn) { assert(nn); - layer *output_layer = &nn->layers[nn->length - 1]; - float *percentages = malloc(sizeof(float) * output_layer->length); + size_t len = nn->layer_lengths[nn->length - 1]; + float *percentages = malloc(sizeof(float) * len); if (!percentages) return; - size_t *indices = malloc(sizeof(size_t) * output_layer->length); + size_t *indices = malloc(sizeof(size_t) * len); if (!indices) { free(percentages); return; } // Store the activation percentages and indices - for (size_t i = 0; i < output_layer->length; i++) { + for (size_t i = 0; i < len; i++) { percentages[i] = softmax(nn, i) * 100.0f; indices[i] = i; } // Selection sort for percentages and corresponding indices - for (size_t i = 0; i < output_layer->length - 1; i++) { + for (size_t i = 0; i < len - 1; i++) { int max_idx = i; - for (size_t j = i + 1; j < output_layer->length; j++) { + for (size_t j = i + 1; j < len; j++) { if (percentages[j] > percentages[max_idx]) max_idx = j; } @@ -138,7 +140,7 @@ void print_activation_percentages(const neural_network *nn) { } // Print the sorted percentages with neuron indices - for (size_t i = 0; i < output_layer->length; i++) + for (size_t i = 0; i < len; i++) printf(" (%zu = %.2f%%) ", indices[i], percentages[i]); printf("\n"); @@ -152,13 +154,14 @@ float cost(const neural_network *nn, const dataset *test_dataset, size_t num_tes float cost = 0.0f; - layer *output_layer = &nn->layers[nn->length - 1]; + size_t len = nn->layer_lengths[nn->length - 1]; + size_t l_sum = nn->prev_lengths_sums[nn->length - 1]; for (size_t i = 0; i < num_test; i++) { uint32_t randnum = randnum_u32(test_dataset->length, 0); float *test_data = &test_dataset->all_inputs[randnum * test_dataset->inputs_length]; compute_network(nn, test_data); - for (size_t j = 0; j < output_layer->length; j++) { - float output = output_layer->output[j]; + for (size_t j = 0; j < len; j++) { + float output = nn->output[l_sum + j]; cost += (output - (j == test_dataset->expected_indices[randnum])) * (output - (j == test_dataset->expected_indices[randnum])); } } @@ -168,43 +171,48 @@ float cost(const neural_network *nn, const dataset *test_dataset, size_t num_tes void print_result(const neural_network *nn) { assert(nn); - layer *output_layer = &nn->layers[nn->length - 1]; - for (size_t i = 0; i < output_layer->length; i++) - printf("%f ", output_layer->output[i]); + size_t len = nn->layer_lengths[nn->length - 1]; + size_t l_sum = nn->prev_lengths_sums[nn->length - 1]; + for (size_t i = 0; i < len; i++) + printf("%f ", nn->output[l_sum + i]); } void layer_learn(const neural_network *nn, size_t layer_index, float learn_rate, const float *data, const size_t data_expected_index) { assert(nn && data); - layer *curr_layer = &nn->layers[layer_index]; - + size_t len = nn->layer_lengths[layer_index]; + size_t l_sum = nn->prev_lengths_sums[layer_index]; + size_t w_sum = nn->prev_weights_sums[layer_index]; // f'(Z_i) in weighted_input - vector_apply_activation(curr_layer->weighted_input, curr_layer->weighted_input, curr_layer->length, nn->activation_function, true); + vector_apply_activation(&nn->weighted_input[l_sum], &nn->weighted_input[l_sum], len, nn->activation_function, true); if (layer_index == nn->length - 1) { // Error in output - curr_layer->output[data_expected_index] -= 1.0f; + nn->output[l_sum + data_expected_index] -= 1.0f; } else { // W^T_{i+1}δ_{i+1} in output - layer *next_layer = &nn->layers[layer_index + 1]; - cblas_sgemm(CblasColMajor, CblasTrans, CblasNoTrans, curr_layer->length, 1, next_layer->length, 1.0f, next_layer->weights, next_layer->length, next_layer->delta, next_layer->length, 0.0f, curr_layer->output, curr_layer->length); + size_t next_len = nn->layer_lengths[layer_index + 1]; + size_t next_l_sum = nn->prev_lengths_sums[layer_index + 1]; + size_t next_w_sum = nn->prev_weights_sums[layer_index + 1]; + cblas_sgemm(CblasColMajor, CblasTrans, CblasNoTrans, next_len, 1, next_len, 1.0f, &nn->weights[next_w_sum], next_len, &nn->delta[next_l_sum], next_len, 0.0f, &nn->output[l_sum], len); } - hadamard_product(curr_layer->weighted_input, curr_layer->output, curr_layer->delta, curr_layer->length); + hadamard_product(&nn->weighted_input[l_sum], &nn->output[l_sum], &nn->delta[l_sum], len); float *weight_gradient; if (layer_index == 0) { - weight_gradient = calloc(curr_layer->length * nn->inputs_length, sizeof(float)); - cblas_sger(CblasColMajor, curr_layer->length, nn->inputs_length, 1.0f, curr_layer->delta, 1, data, 1, weight_gradient, curr_layer->length); - cblas_saxpy(curr_layer->length * nn->inputs_length, -learn_rate, weight_gradient, 1, curr_layer->weights, 1); + weight_gradient = calloc(len * nn->inputs_length, sizeof(float)); + cblas_sger(CblasColMajor, len, nn->inputs_length, 1.0f, &nn->delta[l_sum], 1, data, 1, weight_gradient, len); + cblas_saxpy(len * nn->inputs_length, -learn_rate, weight_gradient, 1, &nn->weights[w_sum], 1); } else { - layer *prev_layer = &nn->layers[layer_index - 1]; - weight_gradient = calloc(curr_layer->length * prev_layer->length, sizeof(float)); - cblas_sger(CblasColMajor, curr_layer->length, prev_layer->length, 1.0f, curr_layer->delta, 1, prev_layer->output, 1, weight_gradient, curr_layer->length); - cblas_saxpy(curr_layer->length * prev_layer->length, -learn_rate, weight_gradient, 1, curr_layer->weights, 1); + size_t prev_len = nn->layer_lengths[layer_index - 1]; + size_t prev_l_sum = nn->prev_lengths_sums[layer_index - 1]; + weight_gradient = calloc(len * prev_len, sizeof(float)); + cblas_sger(CblasColMajor, len, prev_len, 1.0f, &nn->delta[l_sum], 1, &nn->output[prev_l_sum], 1, weight_gradient, len); + cblas_saxpy(len * prev_len, -learn_rate, weight_gradient, 1, &nn->weights[w_sum], 1); } // Bias update - cblas_saxpy(curr_layer->length, -learn_rate, curr_layer->delta, 1, curr_layer->bias, 1); + cblas_saxpy(len, -learn_rate, &nn->delta[l_sum], 1, &nn->bias[l_sum], 1); free(weight_gradient); } @@ -212,30 +220,33 @@ void layer_learn(const neural_network *nn, size_t layer_index, float learn_rate, void layer_learn_collect_gradient(const neural_network *nn, float *restrict layer_weights_gradients, float *restrict layer_bias_gradients, size_t layer_index, const float *data, size_t data_expected_index) { assert(nn && layer_weights_gradients && layer_bias_gradients && data); - layer *curr_layer = &nn->layers[layer_index]; - + size_t len = nn->layer_lengths[layer_index]; + size_t l_sum = nn->prev_lengths_sums[layer_index]; // f'(Z_i) in weighted_input - vector_apply_activation(curr_layer->weighted_input, curr_layer->weighted_input, curr_layer->length, nn->activation_function, true); + vector_apply_activation(&nn->weighted_input[l_sum], &nn->weighted_input[l_sum], len, nn->activation_function, true); if (layer_index == nn->length - 1) { // Error in output - curr_layer->output[data_expected_index] -= 1.0f; + nn->output[l_sum + data_expected_index] -= 1.0f; } else { + size_t next_len = nn->layer_lengths[layer_index + 1]; + size_t next_l_sum = nn->prev_lengths_sums[layer_index + 1]; + size_t next_w_sum = nn->prev_weights_sums[layer_index + 1]; // W^T_{i+1}δ_{i+1} in output - layer *next_layer = &nn->layers[layer_index + 1]; - cblas_sgemm(CblasColMajor, CblasTrans, CblasNoTrans, curr_layer->length, 1, next_layer->length, 1.0f, next_layer->weights, next_layer->length, next_layer->delta, next_layer->length, 0.0f, curr_layer->output, curr_layer->length); + cblas_sgemm(CblasColMajor, CblasTrans, CblasNoTrans, len, 1, next_len, 1.0f, &nn->weights[next_w_sum], next_len, &nn->delta[next_l_sum], next_len, 0.0f, &nn->output[l_sum], len); } - hadamard_product(curr_layer->weighted_input, curr_layer->output, curr_layer->delta, curr_layer->length); + hadamard_product(&nn->weighted_input[l_sum], &nn->output[l_sum], &nn->delta[l_sum], len); if (layer_index == 0) { - cblas_sger(CblasColMajor, curr_layer->length, nn->inputs_length, 1.0f, curr_layer->delta, 1, data, 1, layer_weights_gradients, curr_layer->length); + cblas_sger(CblasColMajor, len, nn->inputs_length, 1.0f, &nn->delta[l_sum], 1, data, 1, layer_weights_gradients, len); } else { - layer *prev_layer = &nn->layers[layer_index - 1]; - cblas_sger(CblasColMajor, curr_layer->length, prev_layer->length, 1.0f, curr_layer->delta, 1, prev_layer->output, 1, layer_weights_gradients, curr_layer->length); + size_t prev_len = nn->layer_lengths[layer_index - 1]; + size_t prev_l_sum = nn->prev_lengths_sums[layer_index - 1]; + cblas_sger(CblasColMajor, len, prev_len, 1.0f, &nn->delta[l_sum], 1, &nn->output[prev_l_sum], 1, layer_weights_gradients, len); } // Bias update - cblas_saxpy(curr_layer->length, 1.0f, curr_layer->delta, 1, layer_bias_gradients, 1); + cblas_saxpy(len, 1.0f, &nn->delta[l_sum], 1, layer_bias_gradients, 1); } void stochastic_gd(const neural_network *nn, float learn_rate, const float *data, size_t data_expected_index) { @@ -265,9 +276,9 @@ void *thread_worker(void *arg) { float **bias_gradients = args->bias_gradients; for (size_t i = 0; i < nn->length; i++) { - size_t weights_size = nn->layers[i].length * ((i == 0) ? nn->inputs_length : nn->layers[i - 1].length); + size_t weights_size = nn->layer_lengths[i] * ((i == 0) ? nn->inputs_length : nn->layer_lengths[i - 1]); weights_gradients[i] = calloc(weights_size, sizeof(float)); - bias_gradients[i] = calloc(nn->layers[i].length, sizeof(float)); + bias_gradients[i] = calloc(nn->layer_lengths[i], sizeof(float)); } for (size_t i = 0; i < args->data_batch->length; i++) { @@ -302,14 +313,16 @@ void mini_batch_gd(neural_network *nn, float learn_rate, const dataset *data_bat #endif for (size_t i = 0; i < nn->length; i++) { - size_t weights_size = nn->layers[i].length * ((i == 0) ? nn->inputs_length : nn->layers[i - 1].length); - + size_t len = nn->layer_lengths[i]; + size_t l_sum = nn->prev_lengths_sums[i]; + size_t w_sum = nn->prev_weights_sums[i]; + size_t weights_size = nn->prev_weights_sums[i + 1] - nn->prev_weights_sums[i]; for (size_t j = 0; j < weights_size; j++) { - nn->layers[i].weights[j] -= weights_gradients[i][j] / data_batch->length * learn_rate; + nn->weights[w_sum + j] -= weights_gradients[i][j] / data_batch->length * learn_rate; } - for (size_t j = 0; j < nn->layers[i].length; j++) { - nn->layers[i].bias[j] -= (bias_gradients[i][j] / data_batch->length) * learn_rate; + for (size_t j = 0; j < len; j++) { + nn->bias[l_sum + j] -= (bias_gradients[i][j] / data_batch->length) * learn_rate; } } @@ -324,86 +337,88 @@ void mini_batch_gd(neural_network *nn, float learn_rate, const dataset *data_bat bool save_network(const char *restrict filename, const neural_network *restrict nn) { assert(filename && nn); - FILE *file = fopen(filename, "wb"); - if (!file) { - fprintf(stderr, "Error opening file '%s' for writing neural network: %s\n", filename, strerror(errno)); - return false; - } - - if (fwrite(&(nn->inputs_length), sizeof(uint64_t), 1, file) != 1 || - fwrite(&(nn->length), sizeof(uint64_t), 1, file) != 1) { - fprintf(stderr, "Failed to write network metadata to '%s'\n", filename); - fclose(file); - return false; - } - - for (size_t i = 0; i < nn->length; i++) { - size_t weights_length = nn->layers[i].length * ((i == 0) ? nn->inputs_length : nn->layers[i - 1].length); - - if (fwrite(&(nn->layers[i].length), sizeof(uint64_t), 1, file) != 1 || fwrite(nn->layers[i].weights, sizeof(float), weights_length, file) != weights_length || fwrite(nn->layers[i].bias, sizeof(float), nn->layers[i].length, file) != nn->layers[i].length) { - fprintf(stderr, "Failed to write layer %zu data to '%s'\n", i, filename); - fclose(file); - return false; - } - } - - fclose(file); - return true; + return false; + // FILE *file = fopen(filename, "wb"); + // if (!file) { + // fprintf(stderr, "Error opening file '%s' for writing neural network: %s\n", filename, strerror(errno)); + // return false; + // } + // + // if (fwrite(&(nn->inputs_length), sizeof(uint64_t), 1, file) != 1 || + // fwrite(&(nn->length), sizeof(uint64_t), 1, file) != 1) { + // fprintf(stderr, "Failed to write network metadata to '%s'\n", filename); + // fclose(file); + // return false; + // } + // + // for (size_t i = 0; i < nn->length; i++) { + // size_t weights_length = nn->layer[i].length * ((i == 0) ? nn->inputs_length : nn->layers[i - 1].length); + // + // if (fwrite(&(nn->layers[i].length), sizeof(uint64_t), 1, file) != 1 || fwrite(nn->layers[i].weights, sizeof(float), weights_length, file) != weights_length || fwrite(nn->layers[i].bias, sizeof(float), nn->layers[i].length, file) != nn->layers[i].length) { + // fprintf(stderr, "Failed to write layer %zu data to '%s'\n", i, filename); + // fclose(file); + // return false; + // } + // } + // + // fclose(file); + // return true; } bool load_network(const char *restrict filename, const neural_network *restrict nn) { assert(filename && nn); - FILE *file = fopen(filename, "rb"); - if (!file) { - fprintf(stderr, "Error opening file '%s' for reading neural network: %s\n", filename, strerror(errno)); - return false; - } - - uint64_t inputs_length = 0; - if (fread(&inputs_length, sizeof(uint64_t), 1, file) != 1) { - fprintf(stderr, "Failed to read inputs_length from %s\n", filename); - goto cleanup; - } - if (inputs_length != nn->inputs_length) { - fprintf(stderr, "Invalid input layer length. Expected: %zu. But found: %llu\n", nn->inputs_length, (unsigned long long)inputs_length); - goto cleanup; - } - - uint64_t network_length = 0; - if (fread(&network_length, sizeof(uint64_t), 1, file) != 1) { - fprintf(stderr, "Failed to read network_length from %s\n", filename); - goto cleanup; - } - if (network_length != nn->length) { - fprintf(stderr, "Invalid network layer. Expected: %zu. But found: %llu\n", nn->length, (unsigned long long)network_length); - goto cleanup; - } - - for (size_t i = 0; i < nn->length; i++) { - uint64_t layer_length = 0; - if (fread(&layer_length, sizeof(uint64_t), 1, file) != 1) { - fprintf(stderr, "Failed to read layer_length from %s\n", filename); - goto cleanup; - } - if (layer_length != nn->layers[i].length) { - fprintf(stderr, "Invalid layer length. Expected: %zu. But found: %llu\n", nn->layers[i].length, (unsigned long long)layer_length); - goto cleanup; - } - - size_t weights_length = nn->layers[i].length * ((i == 0) ? nn->inputs_length : nn->layers[i - 1].length); - if (fread(nn->layers[i].weights, sizeof(float), weights_length, file) != weights_length || fread(nn->layers[i].bias, sizeof(float), nn->layers[i].length, file) != nn->layers[i].length) { - fprintf(stderr, "Failed to read layer %zu data from '%s'\n", i, filename); - goto cleanup; - } - } - - fclose(file); - return true; - -cleanup: - fclose(file); return false; +// FILE *file = fopen(filename, "rb"); +// if (!file) { +// fprintf(stderr, "Error opening file '%s' for reading neural network: %s\n", filename, strerror(errno)); +// return false; +// } +// +// uint64_t inputs_length = 0; +// if (fread(&inputs_length, sizeof(uint64_t), 1, file) != 1) { +// fprintf(stderr, "Failed to read inputs_length from %s\n", filename); +// goto cleanup; +// } +// if (inputs_length != nn->inputs_length) { +// fprintf(stderr, "Invalid input layer length. Expected: %zu. But found: %llu\n", nn->inputs_length, (unsigned long long)inputs_length); +// goto cleanup; +// } +// +// uint64_t network_length = 0; +// if (fread(&network_length, sizeof(uint64_t), 1, file) != 1) { +// fprintf(stderr, "Failed to read network_length from %s\n", filename); +// goto cleanup; +// } +// if (network_length != nn->length) { +// fprintf(stderr, "Invalid network layer. Expected: %zu. But found: %llu\n", nn->length, (unsigned long long)network_length); +// goto cleanup; +// } +// +// for (size_t i = 0; i < nn->length; i++) { +// uint64_t layer_length = 0; +// if (fread(&layer_length, sizeof(uint64_t), 1, file) != 1) { +// fprintf(stderr, "Failed to read layer_length from %s\n", filename); +// goto cleanup; +// } +// if (layer_length != nn->layers[i].length) { +// fprintf(stderr, "Invalid layer length. Expected: %zu. But found: %llu\n", nn->layers[i].length, (unsigned long long)layer_length); +// goto cleanup; +// } +// +// size_t weights_length = nn->layers[i].length * ((i == 0) ? nn->inputs_length : nn->layers[i - 1].length); +// if (fread(nn->layers[i].weights, sizeof(float), weights_length, file) != weights_length || fread(nn->layers[i].bias, sizeof(float), nn->layers[i].length, file) != nn->layers[i].length) { +// fprintf(stderr, "Failed to read layer %zu data from '%s'\n", i, filename); +// goto cleanup; +// } +// } +// +// fclose(file); +// return true; +// +// cleanup: +// fclose(file); +// return false; } float test_network_percent(const neural_network *nn, const dataset *test_dataset) { @@ -414,7 +429,7 @@ float test_network_percent(const neural_network *nn, const dataset *test_dataset for (size_t i = 0; i < test_dataset->length; i++) { compute_network(nn, &test_dataset->all_inputs[i * test_dataset->inputs_length]); size_t max = 0; - for (size_t j = 0; j < nn->layers[nn->length - 1].length; j++) { + for (size_t j = 0; j < nn->layer_lengths[nn->length - 1]; j++) { if (softmax(nn, j) > softmax(nn, max)) max = j; } From db8dbdaa40e8f85816d271dcd78f2273280dd139 Mon Sep 17 00:00:00 2001 From: Hoonlim Lee Date: Thu, 2 Jul 2026 20:35:53 +0800 Subject: [PATCH 4/5] Enabled saving and loading of neural network --- src/main.c | 2 +- src/network.c | 154 ++++++++++++++++++++++++++------------------------ 2 files changed, 80 insertions(+), 76 deletions(-) diff --git a/src/main.c b/src/main.c index 1160198aa..3403ca20f 100644 --- a/src/main.c +++ b/src/main.c @@ -141,7 +141,7 @@ int main(int argc, char **argv) { // Parameters float learn_rate = 1.5f; size_t batch_size = 30; - int learn_amount = 4800000; + int learn_amount = 50000000; int batch_amount = learn_amount / batch_size; int log_amount = 1000; // Log once reached a number of batch diff --git a/src/network.c b/src/network.c index d7127af82..3ffeb7081 100644 --- a/src/network.c +++ b/src/network.c @@ -337,88 +337,92 @@ void mini_batch_gd(neural_network *nn, float learn_rate, const dataset *data_bat bool save_network(const char *restrict filename, const neural_network *restrict nn) { assert(filename && nn); + FILE *file = fopen(filename, "wb"); + if (!file) { + fprintf(stderr, "Error opening file '%s' for writing neural network: %s\n", filename, strerror(errno)); + return false; + } + + if (fwrite(&(nn->inputs_length), sizeof(uint64_t), 1, file) != 1 || + fwrite(&(nn->length), sizeof(uint64_t), 1, file) != 1) { + fprintf(stderr, "Failed to write network metadata to '%s'\n", filename); + goto fail; + } + + for (size_t i = 0; i < nn->length; i++) { + size_t weights_size = nn->prev_weights_sums[i + 1] - nn->prev_weights_sums[i]; + size_t len = nn->layer_lengths[i]; + size_t l_sum = nn->prev_lengths_sums[i]; + size_t w_sum = nn->prev_weights_sums[i]; + if (fwrite(&(nn->layer_lengths[i]), sizeof(uint64_t), 1, file) != 1 || fwrite(&nn->weights[w_sum], sizeof(float), weights_size, file) != weights_size || fwrite(&nn->bias[l_sum], sizeof(float), len, file) != len) { + fprintf(stderr, "Failed to write layer %zu data to '%s'\n", i, filename); + goto fail; + } + } + fclose(file); + return true; + +fail: + fclose(file); return false; - // FILE *file = fopen(filename, "wb"); - // if (!file) { - // fprintf(stderr, "Error opening file '%s' for writing neural network: %s\n", filename, strerror(errno)); - // return false; - // } - // - // if (fwrite(&(nn->inputs_length), sizeof(uint64_t), 1, file) != 1 || - // fwrite(&(nn->length), sizeof(uint64_t), 1, file) != 1) { - // fprintf(stderr, "Failed to write network metadata to '%s'\n", filename); - // fclose(file); - // return false; - // } - // - // for (size_t i = 0; i < nn->length; i++) { - // size_t weights_length = nn->layer[i].length * ((i == 0) ? nn->inputs_length : nn->layers[i - 1].length); - // - // if (fwrite(&(nn->layers[i].length), sizeof(uint64_t), 1, file) != 1 || fwrite(nn->layers[i].weights, sizeof(float), weights_length, file) != weights_length || fwrite(nn->layers[i].bias, sizeof(float), nn->layers[i].length, file) != nn->layers[i].length) { - // fprintf(stderr, "Failed to write layer %zu data to '%s'\n", i, filename); - // fclose(file); - // return false; - // } - // } - // - // fclose(file); - // return true; } bool load_network(const char *restrict filename, const neural_network *restrict nn) { assert(filename && nn); + FILE *file = fopen(filename, "rb"); + if (!file) { + fprintf(stderr, "Error opening file '%s' for reading neural network: %s\n", filename, strerror(errno)); + return false; + } + + uint64_t inputs_length = 0; + if (fread(&inputs_length, sizeof(uint64_t), 1, file) != 1) { + fprintf(stderr, "Failed to read inputs_length from %s\n", filename); + goto fail; + } + if (inputs_length != nn->inputs_length) { + fprintf(stderr, "Invalid input layer length. Expected: %zu. But found: %llu\n", nn->inputs_length, (unsigned long long)inputs_length); + goto fail; + } + + uint64_t network_length = 0; + if (fread(&network_length, sizeof(uint64_t), 1, file) != 1) { + fprintf(stderr, "Failed to read network_length from %s\n", filename); + goto fail; + } + if (network_length != nn->length) { + fprintf(stderr, "Invalid network layer. Expected: %zu. But found: %llu\n", nn->length, (unsigned long long)network_length); + goto fail; + } + + for (size_t i = 0; i < nn->length; i++) { + size_t weights_size = nn->prev_weights_sums[i + 1] - nn->prev_weights_sums[i]; + size_t len = nn->layer_lengths[i]; + size_t l_sum = nn->prev_lengths_sums[i]; + size_t w_sum = nn->prev_weights_sums[i]; + uint64_t layer_length = 0; + if (fread(&layer_length, sizeof(uint64_t), 1, file) != 1) { + fprintf(stderr, "Failed to read layer_length from %s\n", filename); + goto fail; + } + if (layer_length != len) { + fprintf(stderr, "Invalid layer length. Expected: %zu. But found: %llu\n", len, (unsigned long long)layer_length); + goto fail; + } + + if (fread(&nn->weights[w_sum], sizeof(float), weights_size, file) != weights_size || fread(&nn->bias[l_sum], sizeof(float), len, file) != len) { + fprintf(stderr, "Failed to read layer %zu data from '%s'\n", i, filename); + goto fail; + } + } + + fclose(file); + return true; + +fail: + fclose(file); return false; -// FILE *file = fopen(filename, "rb"); -// if (!file) { -// fprintf(stderr, "Error opening file '%s' for reading neural network: %s\n", filename, strerror(errno)); -// return false; -// } -// -// uint64_t inputs_length = 0; -// if (fread(&inputs_length, sizeof(uint64_t), 1, file) != 1) { -// fprintf(stderr, "Failed to read inputs_length from %s\n", filename); -// goto cleanup; -// } -// if (inputs_length != nn->inputs_length) { -// fprintf(stderr, "Invalid input layer length. Expected: %zu. But found: %llu\n", nn->inputs_length, (unsigned long long)inputs_length); -// goto cleanup; -// } -// -// uint64_t network_length = 0; -// if (fread(&network_length, sizeof(uint64_t), 1, file) != 1) { -// fprintf(stderr, "Failed to read network_length from %s\n", filename); -// goto cleanup; -// } -// if (network_length != nn->length) { -// fprintf(stderr, "Invalid network layer. Expected: %zu. But found: %llu\n", nn->length, (unsigned long long)network_length); -// goto cleanup; -// } -// -// for (size_t i = 0; i < nn->length; i++) { -// uint64_t layer_length = 0; -// if (fread(&layer_length, sizeof(uint64_t), 1, file) != 1) { -// fprintf(stderr, "Failed to read layer_length from %s\n", filename); -// goto cleanup; -// } -// if (layer_length != nn->layers[i].length) { -// fprintf(stderr, "Invalid layer length. Expected: %zu. But found: %llu\n", nn->layers[i].length, (unsigned long long)layer_length); -// goto cleanup; -// } -// -// size_t weights_length = nn->layers[i].length * ((i == 0) ? nn->inputs_length : nn->layers[i - 1].length); -// if (fread(nn->layers[i].weights, sizeof(float), weights_length, file) != weights_length || fread(nn->layers[i].bias, sizeof(float), nn->layers[i].length, file) != nn->layers[i].length) { -// fprintf(stderr, "Failed to read layer %zu data from '%s'\n", i, filename); -// goto cleanup; -// } -// } -// -// fclose(file); -// return true; -// -// cleanup: -// fclose(file); -// return false; } float test_network_percent(const neural_network *nn, const dataset *test_dataset) { From 3479a74c2bf7abf45204c9c7303da8bd85444438 Mon Sep 17 00:00:00 2001 From: Hoonlim Lee Date: Thu, 2 Jul 2026 21:06:38 +0800 Subject: [PATCH 5/5] Rewritten the test --- include/cneuron/cneuron.h | 4 +-- src/main.c | 20 ++++++-------- src/network.c | 2 +- test/network.cpp | 55 +++++++++++++++++++++++++-------------- 4 files changed, 46 insertions(+), 35 deletions(-) diff --git a/include/cneuron/cneuron.h b/include/cneuron/cneuron.h index f66dab4df..6cafbccdf 100644 --- a/include/cneuron/cneuron.h +++ b/include/cneuron/cneuron.h @@ -115,8 +115,8 @@ typedef struct { size_t length; /**< Number of layers in the network. */ size_t inputs_length; /**< Number of inputs to the network. */ size_t *layer_lengths; /**< Number of neuron in each layer. */ - size_t *prev_lengths_sums; /**< Number of neuron from all previous layer. */ - size_t *prev_weights_sums; /**< Number of weights from all previous layer. */ + size_t *prev_lengths_sums; /**< Number of neuron from all previous layer. */ + size_t *prev_weights_sums; /**< Number of weights from all previous layer. */ float (*activation_function)(float, bool); /**< Pointer to the activation function used in the network. */ float *delta; /**< Error delta for backpropagation. */ float *weighted_input; /**< Weighted input values for the layer. */ diff --git a/src/main.c b/src/main.c index 3403ca20f..25e323997 100644 --- a/src/main.c +++ b/src/main.c @@ -130,20 +130,17 @@ int main(int argc, char **argv) { dataset *train_dataset = get_mnist(false); dataset *test_dataset = get_mnist(true); - size_t network_length = 3; - size_t *layers_length = malloc(sizeof(size_t) * network_length); - layers_length[0] = 100; - layers_length[1] = 16; - layers_length[2] = 10; + const size_t network_length = 3; + const size_t layer_lengths[] = {100, 16, 10}; - neural_network *nn = get_neural_network(network_length, layers_length, train_dataset->inputs_length, &sigmoid); + neural_network *nn = get_neural_network(network_length, layer_lengths, train_dataset->inputs_length, &sigmoid); // Parameters - float learn_rate = 1.5f; - size_t batch_size = 30; - int learn_amount = 50000000; - int batch_amount = learn_amount / batch_size; - int log_amount = 1000; // Log once reached a number of batch + const float learn_rate = 1.5f; + const size_t batch_size = 30; + const int learn_amount = 50000000; + const int batch_amount = learn_amount / batch_size; + const int log_amount = 1000; // Log once reached a number of batch char cmd[100]; FILE *fp; @@ -225,6 +222,5 @@ int main(int argc, char **argv) { free(train_dataset); free(test_dataset); free(nn); - free(layers_length); return 0; } diff --git a/src/network.c b/src/network.c index 3ffeb7081..978b2f184 100644 --- a/src/network.c +++ b/src/network.c @@ -193,7 +193,7 @@ void layer_learn(const neural_network *nn, size_t layer_index, float learn_rate, size_t next_len = nn->layer_lengths[layer_index + 1]; size_t next_l_sum = nn->prev_lengths_sums[layer_index + 1]; size_t next_w_sum = nn->prev_weights_sums[layer_index + 1]; - cblas_sgemm(CblasColMajor, CblasTrans, CblasNoTrans, next_len, 1, next_len, 1.0f, &nn->weights[next_w_sum], next_len, &nn->delta[next_l_sum], next_len, 0.0f, &nn->output[l_sum], len); + cblas_sgemm(CblasColMajor, CblasTrans, CblasNoTrans, len, 1, next_len, 1.0f, &nn->weights[next_w_sum], next_len, &nn->delta[next_l_sum], next_len, 0.0f, &nn->output[l_sum], len); } hadamard_product(&nn->weighted_input[l_sum], &nn->output[l_sum], &nn->delta[l_sum], len); diff --git a/test/network.cpp b/test/network.cpp index c91ea2c0d..5734dbf1e 100644 --- a/test/network.cpp +++ b/test/network.cpp @@ -21,25 +21,40 @@ TEST(NetworkTest, RandomFloat) { } TEST(NetworkTest, GetNeuralNetwork) { - size_t layer_length = 3; - size_t *layer_lengths = (size_t *)malloc(sizeof(size_t) * layer_length); - layer_lengths[0] = 2; - layer_lengths[1] = 3; - layer_lengths[2] = 4; - size_t inputs_length = 2; - neural_network *nn = get_neural_network(layer_length, layer_lengths, inputs_length, &sigmoid); + const size_t network_length = 3; + const size_t layer_lengths[] = {2, 3, 4}; + const size_t inputs_length = 2; + + neural_network *nn = get_neural_network(network_length, layer_lengths, inputs_length, &sigmoid); ASSERT_NE(nn, nullptr); - ASSERT_EQ(nn->length, layer_length); - ASSERT_EQ(nn->inputs_length, inputs_length); - ASSERT_EQ(nn->activation_function, &sigmoid); - ASSERT_NE(nn->layers, nullptr); - for (size_t i = 0; i < layer_length; i++) { - ASSERT_EQ(nn->layers[i].length, layer_lengths[i]); + EXPECT_EQ(nn->length, network_length); + EXPECT_EQ(nn->inputs_length, inputs_length); + EXPECT_EQ(nn->activation_function, &sigmoid); + ASSERT_NE(nn->layer_lengths, nullptr); + ASSERT_NE(nn->prev_lengths_sums, nullptr); + ASSERT_NE(nn->prev_weights_sums, nullptr); + ASSERT_NE(nn->delta, nullptr); + ASSERT_NE(nn->weighted_input, nullptr); + ASSERT_NE(nn->output, nullptr); + ASSERT_NE(nn->bias, nullptr); + ASSERT_NE(nn->weights, nullptr); + + for (size_t i = 0; i < network_length; i++) { + EXPECT_EQ(nn->layer_lengths[i], layer_lengths[i]); } + EXPECT_EQ(nn->prev_lengths_sums[0], 0); + EXPECT_EQ(nn->prev_lengths_sums[1], 2); + EXPECT_EQ(nn->prev_lengths_sums[2], 5); + EXPECT_EQ(nn->prev_lengths_sums[3], 9); + + EXPECT_EQ(nn->prev_weights_sums[0], 0); + EXPECT_EQ(nn->prev_weights_sums[1], 4); + EXPECT_EQ(nn->prev_weights_sums[2], 10); + EXPECT_EQ(nn->prev_weights_sums[3], 22); + free(nn); - free(layer_lengths); } TEST(NetworkTest, FreeDataset) { @@ -63,12 +78,12 @@ TEST(NetworkTest, ComputeNetwork) { float *inputs = (float *)malloc(sizeof(float) * inputs_length); inputs[0] = 0.2f; - nn->layers[0].weights[0] = 0.5f; - nn->layers[0].bias[0] = 0.3f; + nn->weights[0] = 0.5f; + nn->bias[0] = 0.3f; compute_network(nn, inputs); - ASSERT_FLOAT_EQ(nn->layers[0].output[0], 0.59868766f); + ASSERT_FLOAT_EQ(nn->output[0], 0.59868766f); free(inputs); free(nn); @@ -82,9 +97,9 @@ TEST(NetworkTest, Softmax) { layer_lengths[0] = 3; neural_network *nn = get_neural_network(layer_length, layer_lengths, inputs_length, &sigmoid); - nn->layers[0].output[0] = 0.2f; - nn->layers[0].output[1] = 0.3f; - nn->layers[0].output[2] = 0.5f; + nn->output[0] = 0.2f; + nn->output[1] = 0.3f; + nn->output[2] = 0.5f; ASSERT_FLOAT_EQ(softmax(nn, 0), 0.28943311f); ASSERT_FLOAT_EQ(softmax(nn, 1), 0.31987305f);