diff --git a/include/cneuron/cneuron.h b/include/cneuron/cneuron.h index 76abe3aea..6cafbccdf 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. */ 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. */ 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 *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/main.c b/src/main.c index 1160198aa..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 = 4800000; - 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 826e328cf..978b2f184 100644 --- a/src/network.c +++ b/src/network.c @@ -21,26 +21,31 @@ 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 + 2) + 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->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]; - 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[i] = layers_length[i]; } + nn->prev_lengths_sums[network_length] = lengths_sums; + nn->prev_weights_sums[network_length] = weights_sums; + + 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; + nn->weights = nn->bias + lengths_sums; return nn; } @@ -51,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; @@ -65,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); } } @@ -86,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; } @@ -135,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"); @@ -149,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])); } } @@ -165,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, 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); } @@ -209,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) { @@ -262,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++) { @@ -299,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; } } @@ -330,22 +346,25 @@ bool save_network(const char *restrict filename, const neural_network *restrict 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; + goto fail; } 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) { + 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); - fclose(file); - return false; + goto fail; } } - fclose(file); return true; + +fail: + fclose(file); + return false; } bool load_network(const char *restrict filename, const neural_network *restrict nn) { @@ -360,45 +379,48 @@ bool load_network(const char *restrict filename, const neural_network *restrict 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; + 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 cleanup; + 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 cleanup; + 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 cleanup; + 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 cleanup; + goto fail; } - 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; + if (layer_length != len) { + fprintf(stderr, "Invalid layer length. Expected: %zu. But found: %llu\n", len, (unsigned long long)layer_length); + goto fail; } - 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) { + 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 cleanup; + goto fail; } } fclose(file); return true; -cleanup: +fail: fclose(file); return false; } @@ -411,7 +433,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; } 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);