From 01511920e26c5c5bfe6c2e746d67e31ed84b542c Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 18 Dec 2017 12:23:27 +1030 Subject: [PATCH 1/7] Makefile: Fix CFLAGS variable name CCFLAGS is non-standard, and thus ignored now that standard make rules are used. Signed-off-by: Andrew Jeffery --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c8f10ab..b6f6f2d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -CCFLAGS = -Wall -Wshadow -O2 -g +CFLAGS = -Wall -Wshadow -O2 -g LDLIBS = -lm From ad8bbaa9795b494d7f7d9cb8304f0de86b8f01fe Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 18 Dec 2017 12:25:48 +1030 Subject: [PATCH 2/7] Makefile: Add test and example programs to clean target Signed-off-by: Andrew Jeffery --- Makefile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Makefile b/Makefile index b6f6f2d..725d9fe 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,8 @@ CFLAGS = -Wall -Wshadow -O2 -g LDLIBS = -lm - all: test example1 example2 example3 example4 - test: test.o genann.o check: test @@ -21,5 +19,5 @@ example4: example4.o genann.o clean: $(RM) *.o - $(RM) *.exe + $(RM) test example1 example2 example3 example4 *.exe $(RM) persist.txt From b79a5ce751c119e53d0507487177876c73d4514e Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 18 Dec 2017 12:46:46 +1030 Subject: [PATCH 3/7] Makefile: Increase optimisation Signed-off-by: Andrew Jeffery --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 725d9fe..ab6191d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -CFLAGS = -Wall -Wshadow -O2 -g +CFLAGS = -Wall -Wshadow -O3 -g -march=native LDLIBS = -lm all: test example1 example2 example3 example4 From 6574bddf6b1c950a20ecf40bc4dc39d2c6b32a5a Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Sun, 17 Dec 2017 10:29:40 +1030 Subject: [PATCH 4/7] genann: Use reciprocal interval value to strength reduce divide to multiply This gives a reduction of roughly 2.5 million instructions in the execution trace of example4. genann_act_sigmoid_cached() previously divided by interval to calculate the lookup index. Divide is a expensive operation, so instead use the reciprocal of the existing interval calculation to reduce the divide to a multiply. Building with the following configuration: ``` $ head /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 6 model : 61 model name : Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz stepping : 4 microcode : 0x25 cpu MHz : 2593.871 cache size : 4096 KB physical id : 0 $ cat /etc/os-release NAME="Ubuntu" VERSION="17.10 (Artful Aardvark)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 17.10" VERSION_ID="17.10" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=artful UBUNTU_CODENAME=artful $ cc --version gcc (Ubuntu 7.2.0-8ubuntu3) 7.2.0 Copyright (C) 2017 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ``` on my Lenovo X1 Carbon Gen 3 machine sees the following: ``` $ make CFLAGS="-g -O3 -march=native -DNDEBUG" cc -g -O3 -march=native -DNDEBUG -c -o test.o test.c cc -g -O3 -march=native -DNDEBUG -c -o genann.o genann.c cc -g -O3 -march=native -DNDEBUG -c -o example1.o example1.c cc -g -O3 -march=native -DNDEBUG -c -o example2.o example2.c cc -g -O3 -march=native -DNDEBUG -c -o example3.o example3.c cc -g -O3 -march=native -DNDEBUG -c -o example4.o example4.c cc -g -O3 -march=native -DNDEBUG -c -o strings.o strings.c cc test.o genann.o -lm -o test cc example1.o genann.o -lm -o example1 cc example4.o genann.o -lm -o example4 cc example3.o genann.o -lm -o example3 cc example2.o genann.o -lm -o example2 cc strings.o genann.o -lm -o strings $ for i in `seq 0 10`; do ./example4 > /dev/null; done; sudo perf stat record ./example4 GENANN example 4. Train an ANN on the IRIS dataset using backpropagation. Loading 150 data points from example/iris.data Training for 5000 loops over data. 147/150 correct (98.0%). Performance counter stats for './example4': 101.369081 task-clock (msec) # 0.998 CPUs utilized 1 context-switches # 0.010 K/sec 0 cpu-migrations # 0.000 K/sec 79 page-faults # 0.779 K/sec 320,197,883 cycles # 3.159 GHz 1,121,174,423 instructions # 3.50 insn per cycle 223,257,752 branches # 2202.425 M/sec 62,680 branch-misses # 0.03% of all branches 0.101595114 seconds time elapsed ``` Prior to the change, we see something like: ``` $ make CFLAGS="-g -O3 -march=native" cc -g -O3 -march=native -c -o test.o test.c cc -g -O3 -march=native -c -o genann.o genann.c cc -g -O3 -march=native -c -o example1.o example1.c cc -g -O3 -march=native -c -o example2.o example2.c cc -g -O3 -march=native -c -o example3.o example3.c cc -g -O3 -march=native -c -o example4.o example4.c cc -g -O3 -march=native -c -o strings.o strings.c cc test.o genann.o -lm -o test cc example1.o genann.o -lm -o example1 cc example3.o genann.o -lm -o example3 cc example4.o genann.o -lm -o example4 cc strings.o genann.o -lm -o strings cc example2.o genann.o -lm -o example2 $ for i in `seq 0 10`; do ./example4 > /dev/null; done; sudo perf stat record ./example4 GENANN example 4. Train an ANN on the IRIS dataset using backpropagation. Loading 150 data points from example/iris.data Training for 5000 loops over data. 147/150 correct (98.0%). Performance counter stats for './example4': 104.644198 task-clock (msec) # 0.998 CPUs utilized 0 context-switches # 0.000 K/sec 0 cpu-migrations # 0.000 K/sec 79 page-faults # 0.755 K/sec 330,340,554 cycles # 3.157 GHz 1,123,669,767 instructions # 3.40 insn per cycle 215,441,809 branches # 2058.803 M/sec 62,406 branch-misses # 0.03% of all branches 0.104891323 seconds time elapsed ``` Signed-off-by: Andrew Jeffery --- genann.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/genann.c b/genann.c index 0ec9b72..3fca1d4 100644 --- a/genann.c +++ b/genann.c @@ -54,20 +54,27 @@ double genann_act_sigmoid_cached(double a) { /* Calculate entire lookup table on first run. */ if (!initialized) { - interval = (max - min) / LOOKUP_SIZE; + const double f = (max - min) / LOOKUP_SIZE; int i; + interval = LOOKUP_SIZE / (max - min); for (i = 0; i < LOOKUP_SIZE; ++i) { - lookup[i] = genann_act_sigmoid(min + interval * i); + lookup[i] = genann_act_sigmoid(min + f * i); } /* This is down here to make this thread safe. */ initialized = 1; } - int i; - i = (int)((a-min)/interval+0.5); - if (i <= 0) return lookup[0]; - if (i >= LOOKUP_SIZE) return lookup[LOOKUP_SIZE-1]; - return lookup[i]; + assert(!isnan(a)); + + if (a < min) return lookup[0]; + if (a >= max) return lookup[LOOKUP_SIZE - 1]; + + size_t j = (size_t)((a-min)*interval+0.5); + + if (j < 0) return lookup[0]; + if (j >= LOOKUP_SIZE) return lookup[LOOKUP_SIZE - 1]; + + return lookup[j]; } From b1f72be243f3a1afb659beb73c6c6bd7f32db093 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 18 Dec 2017 08:57:58 +1030 Subject: [PATCH 5/7] genann: Unroll loops via hoisting inner-loop conditions in genann_run() This gives a reduction of rougly 27 million instructions and 11 million branches in the execution trace of example4. On a Lenovo X1 Carbon Gen 3 machine (Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz) running Ubuntu 17.10 with GCC 7.2.0-8ubuntu3, using CFLAGS="-g -O3 -march=native -DNDEBUG" I see the following change in `perf stat`: Before: ``` Performance counter stats for './example4': 101.369081 task-clock (msec) # 0.998 CPUs utilized 1 context-switches # 0.010 K/sec 0 cpu-migrations # 0.000 K/sec 79 page-faults # 0.779 K/sec 320,197,883 cycles # 3.159 GHz 1,121,174,423 instructions # 3.50 insn per cycle 223,257,752 branches # 2202.425 M/sec 62,680 branch-misses # 0.03% of all branches 0.101595114 seconds time elapsed ``` After: ``` Performance counter stats for './example4': 98.988806 task-clock (msec) # 0.998 CPUs utilized 1 context-switches # 0.010 K/sec 0 cpu-migrations # 0.000 K/sec 79 page-faults # 0.798 K/sec 312,298,260 cycles # 3.155 GHz 1,094,183,752 instructions # 3.50 insn per cycle 212,007,732 branches # 2141.734 M/sec 62,774 branch-misses # 0.03% of all branches 0.099228100 seconds time elapsed ``` Signed-off-by: Andrew Jeffery --- genann.c | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/genann.c b/genann.c index 3fca1d4..8e549ab 100644 --- a/genann.c +++ b/genann.c @@ -203,18 +203,41 @@ double const *genann_run(genann const *ann, double const *inputs) { const genann_actfun act = ann->activation_hidden; const genann_actfun acto = ann->activation_output; + if (!ann->hidden_layers) { + double *ret = o; + for (j = 0; j < ann->outputs; ++j) { + double sum = *w++ * -1.0; + for (k = 0; k < ann->inputs; ++k) { + sum += *w++ * i[k]; + } + *o++ = acto(sum); + } + + return ret; + } + + /* Figure input layer */ + for (j = 0; j < ann->hidden; ++j) { + double sum = *w++ * -1.0; + for (k = 0; k < ann->inputs; ++k) { + sum += *w++ * i[k]; + } + *o++ = act(sum); + } + + i += ann->inputs; + /* Figure hidden layers, if any. */ - for (h = 0; h < ann->hidden_layers; ++h) { + for (h = 1; h < ann->hidden_layers; ++h) { for (j = 0; j < ann->hidden; ++j) { double sum = *w++ * -1.0; - for (k = 0; k < (h == 0 ? ann->inputs : ann->hidden); ++k) { + for (k = 0; k < ann->hidden; ++k) { sum += *w++ * i[k]; } *o++ = act(sum); } - - i += (h == 0 ? ann->inputs : ann->hidden); + i += ann->hidden; } double const *ret = o; @@ -222,7 +245,7 @@ double const *genann_run(genann const *ann, double const *inputs) { /* Figure output layer. */ for (j = 0; j < ann->outputs; ++j) { double sum = *w++ * -1.0; - for (k = 0; k < (ann->hidden_layers ? ann->hidden : ann->inputs); ++k) { + for (k = 0; k < ann->hidden; ++k) { sum += *w++ * i[k]; } *o++ = acto(sum); From db51375bb71be871ca17132576b4b12ec49b779e Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 18 Dec 2017 13:08:30 +1030 Subject: [PATCH 6/7] genann: Optionally resolve activation functions at link time Shave around 94 million instructions and 10 million branches off of execution trace of example4 if the sigmoid activation function is resolved at link-time. Before (`make`): ``` Performance counter stats for './example4': 98.988806 task-clock (msec) # 0.998 CPUs utilized 1 context-switches # 0.010 K/sec 0 cpu-migrations # 0.000 K/sec 79 page-faults # 0.798 K/sec 312,298,260 cycles # 3.155 GHz 1,094,183,752 instructions # 3.50 insn per cycle 212,007,732 branches # 2141.734 M/sec 62,774 branch-misses # 0.03% of all branches 0.099228100 seconds time elapsed ``` After: `make`: ``` Performance counter stats for './example4': 97.335180 task-clock (msec) # 0.998 CPUs utilized 0 context-switches # 0.000 K/sec 0 cpu-migrations # 0.000 K/sec 82 page-faults # 0.842 K/sec 306,722,357 cycles # 3.151 GHz 1,065,669,644 instructions # 3.47 insn per cycle 214,256,601 branches # 2201.225 M/sec 60,154 branch-misses # 0.03% of all branches 0.097577079 seconds time elapsed ``` `make sigmoid`: ``` Performance counter stats for './example4': 92.629610 task-clock (msec) # 0.997 CPUs utilized 0 context-switches # 0.000 K/sec 0 cpu-migrations # 0.000 K/sec 78 page-faults # 0.842 K/sec 291,863,801 cycles # 3.151 GHz 1,000,931,204 instructions # 3.43 insn per cycle 202,465,800 branches # 2185.757 M/sec 50,949 branch-misses # 0.03% of all branches 0.092889789 seconds time elapsed ``` Signed-off-by: Andrew Jeffery --- Makefile | 11 +++++++ genann.c | 96 +++++++++++++++++++++++++++++++------------------------- genann.h | 16 +++++----- test.c | 2 +- 4 files changed, 72 insertions(+), 53 deletions(-) diff --git a/Makefile b/Makefile index ab6191d..8a8d9f6 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,15 @@ LDLIBS = -lm all: test example1 example2 example3 example4 +sigmoid: CFLAGS += -Dgenann_act=genann_act_sigmoid_cached +sigmoid: all + +threshold: CFLAGS += -Dgenann_act=genann_act_threshold +threshold: all + +linear: CFLAGS += -Dgenann_act=genann_act_linear +linear: all + test: test.o genann.o check: test @@ -21,3 +30,5 @@ clean: $(RM) *.o $(RM) test example1 example2 example3 example4 *.exe $(RM) persist.txt + +.PHONY: sigmoid threshold linear clean diff --git a/genann.c b/genann.c index 8e549ab..c3c0aca 100644 --- a/genann.c +++ b/genann.c @@ -32,61 +32,71 @@ #include #include +#ifndef genann_act +#define genann_act_hidden genann_act_hidden_indirect +#define genann_act_output genann_act_output_indirect +#else +#define genann_act_hidden genann_act +#define genann_act_output genann_act +#endif + #define LOOKUP_SIZE 4096 -double genann_act_sigmoid(double a) { +double genann_act_hidden_indirect(const struct genann *ann, double a) { + return ann->activation_hidden(ann, a); +} + +double genann_act_output_indirect(const struct genann *ann, double a) { + return ann->activation_output(ann, a); +} + +const double sigmoid_dom_min = -15.0; +const double sigmoid_dom_max = 15.0; +double interval; +double lookup[LOOKUP_SIZE]; + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#define __unused __attribute__((unused)) + +double inline genann_act_sigmoid(const genann *ann __unused, double a) { if (a < -45.0) return 0; if (a > 45.0) return 1; return 1.0 / (1 + exp(-a)); } - -double genann_act_sigmoid_cached(double a) { - /* If you're optimizing for memory usage, just - * delete this entire function and replace references - * of genann_act_sigmoid_cached to genann_act_sigmoid - */ - const double min = -15.0; - const double max = 15.0; - static double interval; - static int initialized = 0; - static double lookup[LOOKUP_SIZE]; - - /* Calculate entire lookup table on first run. */ - if (!initialized) { - const double f = (max - min) / LOOKUP_SIZE; +void genann_init_sigmoid_lookup(const genann *ann) { + const double f = (sigmoid_dom_max - sigmoid_dom_min) / LOOKUP_SIZE; int i; - interval = LOOKUP_SIZE / (max - min); - for (i = 0; i < LOOKUP_SIZE; ++i) { - lookup[i] = genann_act_sigmoid(min + f * i); - } - /* This is down here to make this thread safe. */ - initialized = 1; - } + interval = LOOKUP_SIZE / (sigmoid_dom_max - sigmoid_dom_min); + for (i = 0; i < LOOKUP_SIZE; ++i) { + lookup[i] = genann_act_sigmoid(ann, sigmoid_dom_min + f * i); + } +} + +double inline genann_act_sigmoid_cached(const genann *ann __unused, double a) { assert(!isnan(a)); - if (a < min) return lookup[0]; - if (a >= max) return lookup[LOOKUP_SIZE - 1]; + if (a < sigmoid_dom_min) return lookup[0]; + if (a >= sigmoid_dom_max) return lookup[LOOKUP_SIZE - 1]; - size_t j = (size_t)((a-min)*interval+0.5); + size_t j = (size_t)((a-sigmoid_dom_min)*interval+0.5); - if (j < 0) return lookup[0]; - if (j >= LOOKUP_SIZE) return lookup[LOOKUP_SIZE - 1]; + /* Because floating point... */ + if (unlikely(j < 0)) return lookup[0]; + if (unlikely(j >= LOOKUP_SIZE)) return lookup[LOOKUP_SIZE - 1]; return lookup[j]; } - -double genann_act_threshold(double a) { - return a > 0; -} - - -double genann_act_linear(double a) { +double inline genann_act_linear(const struct genann *ann __unused, double a) { return a; } +double inline genann_act_threshold(const struct genann *ann __unused, double a) { + return a > 0; +} genann *genann_init(int inputs, int hidden_layers, int hidden, int outputs) { if (hidden_layers < 0) return 0; @@ -124,6 +134,8 @@ genann *genann_init(int inputs, int hidden_layers, int hidden, int outputs) { ret->activation_hidden = genann_act_sigmoid_cached; ret->activation_output = genann_act_sigmoid_cached; + genann_init_sigmoid_lookup(ret); + return ret; } @@ -200,9 +212,6 @@ double const *genann_run(genann const *ann, double const *inputs) { int h, j, k; - const genann_actfun act = ann->activation_hidden; - const genann_actfun acto = ann->activation_output; - if (!ann->hidden_layers) { double *ret = o; for (j = 0; j < ann->outputs; ++j) { @@ -210,7 +219,7 @@ double const *genann_run(genann const *ann, double const *inputs) { for (k = 0; k < ann->inputs; ++k) { sum += *w++ * i[k]; } - *o++ = acto(sum); + *o++ = genann_act_output(ann, sum); } return ret; @@ -222,7 +231,7 @@ double const *genann_run(genann const *ann, double const *inputs) { for (k = 0; k < ann->inputs; ++k) { sum += *w++ * i[k]; } - *o++ = act(sum); + *o++ = genann_act_hidden(ann, sum); } i += ann->inputs; @@ -234,7 +243,7 @@ double const *genann_run(genann const *ann, double const *inputs) { for (k = 0; k < ann->hidden; ++k) { sum += *w++ * i[k]; } - *o++ = act(sum); + *o++ = genann_act_hidden(ann, sum); } i += ann->hidden; @@ -248,7 +257,7 @@ double const *genann_run(genann const *ann, double const *inputs) { for (k = 0; k < ann->hidden; ++k) { sum += *w++ * i[k]; } - *o++ = acto(sum); + *o++ = genann_act_output(ann, sum); } /* Sanity check that we used all weights and wrote all outputs. */ @@ -273,7 +282,8 @@ void genann_train(genann const *ann, double const *inputs, double const *desired /* Set output layer deltas. */ - if (ann->activation_output == genann_act_linear) { + if (genann_act_output == genann_act_linear || + ann->activation_output == genann_act_linear) { for (j = 0; j < ann->outputs; ++j) { *d++ = *t++ - *o++; } diff --git a/genann.h b/genann.h index 3678ab6..499bda0 100644 --- a/genann.h +++ b/genann.h @@ -39,9 +39,9 @@ extern "C" { #define GENANN_RANDOM() (((double)rand())/RAND_MAX) #endif +struct genann; -typedef double (*genann_actfun)(double a); - +typedef double (*genann_actfun)(const struct genann *ann, double a); typedef struct genann { /* How many inputs, outputs, and hidden neurons. */ @@ -70,8 +70,6 @@ typedef struct genann { } genann; - - /* Creates and returns a new ann. */ genann *genann_init(int inputs, int hidden_layers, int hidden, int outputs); @@ -96,11 +94,11 @@ void genann_train(genann const *ann, double const *inputs, double const *desired /* Saves the ann. */ void genann_write(genann const *ann, FILE *out); - -double genann_act_sigmoid(double a); -double genann_act_sigmoid_cached(double a); -double genann_act_threshold(double a); -double genann_act_linear(double a); +void genann_init_sigmoid_lookup(const genann *ann); +double genann_act_sigmoid(const genann *ann, double a); +double genann_act_sigmoid_cached(const genann *ann, double a); +double genann_act_threshold(const genann *ann, double a); +double genann_act_linear(const genann *ann, double a); #ifdef __cplusplus diff --git a/test.c b/test.c index 08db0a9..4fea704 100644 --- a/test.c +++ b/test.c @@ -248,7 +248,7 @@ void sigmoid() { const double d = .0001; while (i < max) { - lfequal(genann_act_sigmoid(i), genann_act_sigmoid_cached(i)); + lfequal(genann_act_sigmoid(NULL, i), genann_act_sigmoid_cached(NULL, i)); i += d; } } From d21d0f301bd882cfbf3c91fcc6743b4308a7faf3 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 18 Dec 2017 17:39:25 +1030 Subject: [PATCH 7/7] genann: Remove branching from back-propagation inner-loop This saves approximately 80 million instructions and 44 million branches in the trace of example4, shaving off around 8ms: Before: ``` Performance counter stats for './example4': 92.629610 task-clock (msec) # 0.997 CPUs utilized 0 context-switches # 0.000 K/sec 0 cpu-migrations # 0.000 K/sec 78 page-faults # 0.842 K/sec 291,863,801 cycles # 3.151 GHz 1,000,931,204 instructions # 3.43 insn per cycle 202,465,800 branches # 2185.757 M/sec 50,949 branch-misses # 0.03% of all branches 0.092889789 seconds time elapsed ``` After: ``` Performance counter stats for './example4': 84.473035 task-clock (msec) # 0.997 CPUs utilized 3 context-switches # 0.036 K/sec 0 cpu-migrations # 0.000 K/sec 81 page-faults # 0.959 K/sec 265,472,170 cycles # 3.143 GHz 919,372,488 instructions # 3.46 insn per cycle 158,754,885 branches # 1879.356 M/sec 65,337 branch-misses # 0.04% of all branches 0.084755458 seconds time elapsed ``` Signed-off-by: Andrew Jeffery --- genann.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/genann.c b/genann.c index c3c0aca..08034b2 100644 --- a/genann.c +++ b/genann.c @@ -344,12 +344,9 @@ void genann_train(genann const *ann, double const *inputs, double const *desired /* Set output layer weights. */ for (j = 0; j < ann->outputs; ++j) { - for (k = 0; k < (ann->hidden_layers ? ann->hidden : ann->inputs) + 1; ++k) { - if (k == 0) { - *w++ += *d * learning_rate * -1.0; - } else { - *w++ += *d * learning_rate * i[k-1]; - } + *w++ += *d * learning_rate * -1.0; + for (k = 1; k < (ann->hidden_layers ? ann->hidden : ann->inputs) + 1; ++k) { + *w++ += *d * learning_rate * i[k-1]; } ++d; @@ -377,12 +374,9 @@ void genann_train(genann const *ann, double const *inputs, double const *desired for (j = 0; j < ann->hidden; ++j) { - for (k = 0; k < (h == 0 ? ann->inputs : ann->hidden) + 1; ++k) { - if (k == 0) { - *w++ += *d * learning_rate * -1.0; - } else { - *w++ += *d * learning_rate * i[k-1]; - } + *w++ += *d * learning_rate * -1.0; + for (k = 1; k < (h == 0 ? ann->inputs : ann->hidden) + 1; ++k) { + *w++ += *d * learning_rate * i[k-1]; } ++d; }