From 82b65ae2a18f0698c24cd7143754a6541dd351cf Mon Sep 17 00:00:00 2001 From: Louis Gallet Date: Fri, 27 Sep 2024 18:00:01 +0200 Subject: [PATCH] feat: :sparkles: Create Neural network for XOR --- .gitignore | 297 +++++++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE | 21 ++++ README.md | 1 + main.c | 237 ++++++++++++++++++++++++++++++++++++++++++ main.h | 26 +++++ use.c | 98 ++++++++++++++++++ use.h | 18 ++++ 7 files changed, 698 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 main.c create mode 100644 main.h create mode 100644 use.c create mode 100644 use.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03816eb --- /dev/null +++ b/.gitignore @@ -0,0 +1,297 @@ +*.csv +# Created by https://www.toptal.com/developers/gitignore/api/c,cmake,jetbrains,clion,macos +# Edit at https://www.toptal.com/developers/gitignore?templates=c,cmake,jetbrains,clion,macos + +### C ### +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +### CLion ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### CLion Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### CMake ### +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +### CMake Patch ### +# External projects +*-prefix/ + +### JetBrains ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff + +# AWS User-specific + +# Generated files + +# Sensitive or high-churn files + +# Gradle + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake + +# Mongo Explorer plugin + +# File-based project format + +# IntelliJ + +# mpeltonen/sbt-idea plugin + +# JIRA plugin + +# Cursive Clojure plugin + +# SonarLint plugin + +# Crashlytics plugin (for Android Studio and IntelliJ) + +# Editor-based Rest Client + +# Android studio 3.1+ serialized cache file + +### JetBrains Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +# End of https://www.toptal.com/developers/gitignore/api/c,cmake,jetbrains,clion,macos diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e464a16 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Louis Gallet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6431409 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# XOR Neural Network \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..700a7c4 --- /dev/null +++ b/main.c @@ -0,0 +1,237 @@ +// +// Created by Louis Gallet on 24/09/2024. +// + +#include "main.h" +#include + + +double sigmoid(double x) +{ + if(x > 20) return 1.0; + if(x < -20) return 0.0; + + double z = exp(-x); + return 1.0 / (1.0 + z); +} + +double sigmoid_derivative(double x) +{ + return x * (1.0 - x); +} +double init_weights() +{ + double placeholder = ((double) rand()) / ((double) RAND_MAX); + return placeholder; +} + +void shuffle(int *array, size_t n){ + if(n > 1) { + size_t i; + for(i = 0; i < n - 1; i++) { + size_t j = i + rand() / (RAND_MAX / (n - i) + 1); + int t = array[j]; + array[j] = array[i]; + array[i] = t; + } + } +} + +void backup_weights(char filename[], double hiddenWeights[NUM_INPUTS][NUM_HIDDEN], double hiddenLayerBias[NUM_HIDDEN], + double outputWeights[NUM_HIDDEN][NUM_OUTPUTS], double outputLayerBias[NUM_OUTPUTS]) +{ + FILE *fpt; + fpt = fopen(filename, "w+"); + for(int j = 0; j < NUM_HIDDEN; j++){ + for(int k = 0; k < NUM_INPUTS; k++){ + fprintf(fpt, "%f,", hiddenWeights[k][j]); + } + } + printf("Final Hidden Biases: \n"); + for(int j = 0; j < NUM_HIDDEN; j++){ + fprintf(fpt, "%f,", hiddenLayerBias[j]); + } + printf("Final Output Weights: \n"); + for(int j = 0; j < NUM_OUTPUTS; j++){ + for(int k = 0; k < NUM_HIDDEN; k++){ + fprintf(fpt, "%f,", outputWeights[k][j]); + } + } + printf("Final Output Biases: \n"); + for(int j = 0; j < NUM_OUTPUTS; j++){ + fprintf(fpt, "%f,", outputLayerBias[j]); + } + + + +} + + +int main(){ + //learning rate + const double lr = 0.1f; + + double hiddenLayer[NUM_HIDDEN]; + double outputLayer[NUM_OUTPUTS]; + + double hiddenLayerBias[NUM_HIDDEN]; + double outputLayerBias[NUM_OUTPUTS]; + + double hiddenWeights[NUM_INPUTS][NUM_HIDDEN]; + double outputWeights[NUM_HIDDEN][NUM_OUTPUTS]; + + double trainingInputs[NUM_TRAINING_SETS][NUM_INPUTS] = {{0.0f, 0.0f}, {1.0f, 0.0f}, + {0.0f, 1.0f}, {1.0f, 1.0f}}; + + double trainingOutputs[NUM_TRAINING_SETS][NUM_OUTPUTS] = {{0.0f}, {1.0f}, {1.0f}, {0.0f}}; + + for(int i = 0; i < NUM_INPUTS; i++){ + for(int j = 0; j < NUM_HIDDEN; j++){ + hiddenWeights[i][j] = init_weights(); + } + } + + for(int i = 0; i < NUM_HIDDEN; i++){ + for(int j = 0; j < NUM_OUTPUTS; j++){ + outputWeights[i][j] = init_weights(); + } + } + + for(int i = 0; i < NUM_OUTPUTS; i++){ + outputLayerBias[i] = init_weights(); + } + + for(int i = 0; i < NUM_HIDDEN; i++){ + hiddenLayerBias[i] = init_weights(); + } + + int trainingSetOrder[] = {0,1,2,3}; + + int numEpochs = 100000; + + //training loop + for(int epoch = 0; epoch < numEpochs; epoch++){ + shuffle(trainingSetOrder, NUM_TRAINING_SETS); + + for(int x = 0; x < NUM_TRAINING_SETS; x++){ + int i = trainingSetOrder[x]; + + //forward pass + //compute hidden layer activation + for(int j = 0; j < NUM_HIDDEN; j++){ + double activation = hiddenLayerBias[j]; + + for(int k = 0; k < NUM_INPUTS; k++){ + activation += trainingInputs[i][k] * hiddenWeights[k][j]; + } + //printf("Hidden layer activation[%d] = %f\n", j, activation); + hiddenLayer[j] = sigmoid(activation); + + if(isnan(hiddenLayer[j])){ + //printf("NaN in hidden layer at index %d\n", j); + } + } + + //compute output layer activation + for(int j = 0; j < NUM_OUTPUTS; j++){ + double activation = outputLayerBias[j]; + for(int k = 0; k < NUM_HIDDEN; k++){ + activation += hiddenLayer[k] * outputWeights[k][j]; + } + //printf("Output layer activation[%d] = %f\n", j, activation); + outputLayer[j] = sigmoid(activation); + if(isnan(outputLayer[j])){ + //printf("NaN in output layer at index %d\n", j); + } + } + + printf("Input: %g %g Output: %g Predicted Output: %g\n", trainingInputs[i][0], trainingInputs[i][1], outputLayer[0], trainingOutputs[i][0]); + + //Back prop + //compute change in output weights + + double deltaOutput[NUM_OUTPUTS]; + + for(int j = 0; j < NUM_OUTPUTS; j++){ + double error = (trainingOutputs[i][j] - outputLayer[j]); + deltaOutput[j] = error * sigmoid_derivative(outputLayer[j]); + if(isnan(deltaOutput[j])){ + //printf("NaN in deltaOutput at index %d\n", j); + } + } + + //compute change in hidden weights + + double deltaHidden[NUM_HIDDEN]; + + for(int j = 0; j < NUM_HIDDEN; j++){ + double error = 0.0f; + for(int k = 0; k < NUM_OUTPUTS; k++){ + error += deltaOutput[k] * outputWeights[j][k]; + } + deltaHidden[j] = error * sigmoid_derivative(hiddenLayer[j]); + if(isnan(deltaHidden[j])){ + //printf("NaN in deltaHidden at index %d\n", j); + } + } + + //apply change in output weights + for(int j = 0; j < NUM_OUTPUTS; j++){ + outputLayerBias[j] += deltaOutput[j] * lr; + + for(int k = 0; k < NUM_HIDDEN; k++){ + outputWeights[k][j] += hiddenLayer[k] * deltaOutput[j] * lr; + if(isnan(outputWeights[k][j])){ + //printf("NaN in outputWeights at [%d][%d]\n", k, j); + } + } + } + + for(int j = 0; j < NUM_HIDDEN; j++){ + hiddenLayerBias[j] += deltaHidden[j] * lr; + for(int k = 0; k < NUM_INPUTS; k++){ + hiddenWeights[k][j] += trainingInputs[i][k] * deltaHidden[j] * lr; + if(isnan(hiddenWeights[k][j])){ + //printf("NaN in hiddenWeights at [%d][%d]\n", k, j); + } + } + } + } + } + //print final weights after training + printf("\nFinal Hidden Weights: \n"); + for(int j = 0; j < NUM_HIDDEN; j++){ + for(int k = 0; k < NUM_INPUTS; k++){ + printf("%f ", hiddenWeights[k][j]); + } + } + + printf("\n"); + + printf("Final Hidden Biases: \n"); + for(int j = 0; j < NUM_HIDDEN; j++){ + printf("%f ", hiddenLayerBias[j]); + } + + printf("\n\n\n"); + + printf("Final Output Weights: \n"); + for(int j = 0; j < NUM_OUTPUTS; j++){ + for(int k = 0; k < NUM_HIDDEN; k++){ + printf("%f ", outputWeights[k][j]); + } + } + + printf("\n"); + + + printf("Final Output Biases: \n"); + for(int j = 0; j < NUM_OUTPUTS; j++){ + printf("%f ", outputLayerBias[j]); + } + + backup_weights("weights.csv", hiddenWeights, hiddenLayerBias, outputWeights, outputLayerBias); + + return 0; + +} \ No newline at end of file diff --git a/main.h b/main.h new file mode 100644 index 0000000..8088753 --- /dev/null +++ b/main.h @@ -0,0 +1,26 @@ +// +// Created by Louis Gallet on 24/09/2024. +// + +#ifndef MAIN_H +#define MAIN_H +#include +#include +#include + +#define NUM_INPUTS 2 +#define NUM_HIDDEN 2 +#define NUM_OUTPUTS 1 +#define NUM_TRAINING_SETS 4 + +double sigmoid(double x); +double sigmoid_derivative(double x); +double init_weights(); +void shuffle(int *array, size_t n); +void backup_weights(char filename[], double hiddenWeights[NUM_INPUTS][NUM_HIDDEN], double hiddenLayerBias[NUM_HIDDEN], + double outputWeights[NUM_HIDDEN][NUM_OUTPUTS], double outputLayerBias[NUM_OUTPUTS]); + + +int main(); + +#endif //MAIN_H diff --git a/use.c b/use.c new file mode 100644 index 0000000..9d3ac7b --- /dev/null +++ b/use.c @@ -0,0 +1,98 @@ +#include +#include +#include +#include + +// Function prototypes +void getvalues(char filename[], double **HiddenWeights, double *HiddenBiases, double **FinalOutputWeights, double *FinalOutputBiases); +double sigmoid(double x); +double sigmoid_derivative(double x); + +int main() { + // Allocate memory for weights and biases + double **HiddenWeights = (double **)malloc(2 * sizeof(double *)); + for (int i = 0; i < 2; i++) { + HiddenWeights[i] = (double *)malloc(2 * sizeof(double)); + } + double *HiddenBiases = (double *)malloc(2 * sizeof(double)); + double **FinalOutputWeights = (double **)malloc(2 * sizeof(double *)); + for (int i = 0; i < 2; i++) { + FinalOutputWeights[i] = (double *)malloc(1 * sizeof(double)); + } + double *FinalOutputBiases = (double *)malloc(1 * sizeof(double)); + + // Load weights and biases from CSV file + getvalues("weights.csv", HiddenWeights, HiddenBiases, FinalOutputWeights, FinalOutputBiases); + + // Prompt user for input values + double input1, input2; + printf("Enter two values to compute XOR: "); + scanf("%lf %lf", &input1, &input2); + + // Perform forward pass + double hiddenLayer[2]; + double outputLayer[1]; + + // Compute hidden layer activation + for (int j = 0; j < 2; j++) { + double activation = HiddenBiases[j]; + activation += input1 * HiddenWeights[0][j]; + activation += input2 * HiddenWeights[1][j]; + hiddenLayer[j] = sigmoid(activation); + } + + // Compute output layer activation + for (int j = 0; j < 1; j++) { + double activation = FinalOutputBiases[j]; + activation += hiddenLayer[0] * FinalOutputWeights[0][j]; + activation += hiddenLayer[1] * FinalOutputWeights[1][j]; + outputLayer[j] = sigmoid(activation); + } + + // Print the result + printf("Input: %g %g Predicted Output: %g Output round: %g\n", input1, input2, outputLayer[0], round(outputLayer[0])); + + // Free allocated memory + for (int i = 0; i < 2; i++) { + free(HiddenWeights[i]); + free(FinalOutputWeights[i]); + } + free(HiddenWeights); + free(HiddenBiases); + free(FinalOutputWeights); + free(FinalOutputBiases); + + return 0; +} + +void getvalues(char filename[], double **HiddenWeights, double *HiddenBiases, double **FinalOutputWeights, double *FinalOutputBiases) { + FILE *fp = fopen(filename, "r"); + if (fp == NULL) { + errx(EXIT_FAILURE, "Could not open file %s", filename); + } + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + fscanf(fp, "%lf,", &HiddenWeights[i][j]); + } + } + for (int i = 0; i < 2; i++) { + fscanf(fp, "%lf,", &HiddenBiases[i]); + } + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 1; j++) { + fscanf(fp, "%lf,", &FinalOutputWeights[i][j]); + } + } + for (int i = 0; i < 1; i++) { + fscanf(fp, "%lf,", &FinalOutputBiases[i]); + } + fclose(fp); +} + +double sigmoid(double x) { + return 1.0 / (1.0 + exp(-x)); +} + +double sigmoid_derivative(double x) { + return x * (1.0 - x); +} \ No newline at end of file diff --git a/use.h b/use.h new file mode 100644 index 0000000..4e8f4d7 --- /dev/null +++ b/use.h @@ -0,0 +1,18 @@ +// +// Created by Louis Gallet on 27/09/2024. +// + +#ifndef USE_H +#define USE_H + +#define NUM_INPUTS 2 +#define NUM_HIDDEN 2 +#define NUM_OUTPUTS 1 +#define NUM_TRAINING_SETS 4 + + +void getvalues(char filename[], double **HiddenWeights, double *HiddenBiases, double **FinalOutputWeights, double *FinalOutputBiases); +int main(); + + +#endif //USE_H