diff options
-rw-r--r-- | src/lib/include/neuralnet/matrix.h | 15 | ||||
-rw-r--r-- | src/lib/include/neuralnet/neuralnet.h | 8 | ||||
-rw-r--r-- | src/lib/include/neuralnet/train.h | 20 | ||||
-rw-r--r-- | src/lib/src/activation.h | 12 | ||||
-rw-r--r-- | src/lib/src/matrix.c | 82 | ||||
-rw-r--r-- | src/lib/src/neuralnet.c | 65 | ||||
-rw-r--r-- | src/lib/src/neuralnet_impl.h | 12 | ||||
-rw-r--r-- | src/lib/src/train.c | 236 |
8 files changed, 237 insertions, 213 deletions
diff --git a/src/lib/include/neuralnet/matrix.h b/src/lib/include/neuralnet/matrix.h index 0cb40cf..b7281bf 100644 --- a/src/lib/include/neuralnet/matrix.h +++ b/src/lib/include/neuralnet/matrix.h | |||
@@ -33,7 +33,8 @@ void nnMatrixToArray(const nnMatrix* in, R* out); | |||
33 | void nnMatrixRowToArray(const nnMatrix* in, int row, R* out); | 33 | void nnMatrixRowToArray(const nnMatrix* in, int row, R* out); |
34 | 34 | ||
35 | /// Copy a column from a source to a target matrix. | 35 | /// Copy a column from a source to a target matrix. |
36 | void nnMatrixCopyCol(const nnMatrix* in, nnMatrix* out, int col_in, int col_out); | 36 | void nnMatrixCopyCol( |
37 | const nnMatrix* in, nnMatrix* out, int col_in, int col_out); | ||
37 | 38 | ||
38 | /// Mutable borrow of a matrix. | 39 | /// Mutable borrow of a matrix. |
39 | nnMatrix nnMatrixBorrow(nnMatrix* in); | 40 | nnMatrix nnMatrixBorrow(nnMatrix* in); |
@@ -56,20 +57,24 @@ void nnMatrixMul(const nnMatrix* left, const nnMatrix* right, nnMatrix* out); | |||
56 | /// | 57 | /// |
57 | /// This function multiples two matrices row-by-row instead of row-by-column. | 58 | /// This function multiples two matrices row-by-row instead of row-by-column. |
58 | /// nnMatrixMul(A, B, O) == nnMatrixMulRows(A, B^T, O). | 59 | /// nnMatrixMul(A, B, O) == nnMatrixMulRows(A, B^T, O). |
59 | void nnMatrixMulRows(const nnMatrix* left, const nnMatrix* right, nnMatrix* out); | 60 | void nnMatrixMulRows( |
61 | const nnMatrix* left, const nnMatrix* right, nnMatrix* out); | ||
60 | 62 | ||
61 | /// Matrix multiply-add. | 63 | /// Matrix multiply-add. |
62 | /// | 64 | /// |
63 | /// out = left + (right * scale) | 65 | /// out = left + (right * scale) |
64 | void nnMatrixMulAdd(const nnMatrix* left, const nnMatrix* right, R scale, nnMatrix* out); | 66 | void nnMatrixMulAdd( |
67 | const nnMatrix* left, const nnMatrix* right, R scale, nnMatrix* out); | ||
65 | 68 | ||
66 | /// Matrix multiply-subtract. | 69 | /// Matrix multiply-subtract. |
67 | /// | 70 | /// |
68 | /// out = left - (right * scale) | 71 | /// out = left - (right * scale) |
69 | void nnMatrixMulSub(const nnMatrix* left, const nnMatrix* right, R scale, nnMatrix* out); | 72 | void nnMatrixMulSub( |
73 | const nnMatrix* left, const nnMatrix* right, R scale, nnMatrix* out); | ||
70 | 74 | ||
71 | /// Hadamard product of two matrices. | 75 | /// Hadamard product of two matrices. |
72 | void nnMatrixMulPairs(const nnMatrix* left, const nnMatrix* right, nnMatrix* out); | 76 | void nnMatrixMulPairs( |
77 | const nnMatrix* left, const nnMatrix* right, nnMatrix* out); | ||
73 | 78 | ||
74 | /// Add two matrices. | 79 | /// Add two matrices. |
75 | void nnMatrixAdd(const nnMatrix* left, const nnMatrix* right, nnMatrix* out); | 80 | void nnMatrixAdd(const nnMatrix* left, const nnMatrix* right, nnMatrix* out); |
diff --git a/src/lib/include/neuralnet/neuralnet.h b/src/lib/include/neuralnet/neuralnet.h index 1cf1c53..05c9406 100644 --- a/src/lib/include/neuralnet/neuralnet.h +++ b/src/lib/include/neuralnet/neuralnet.h | |||
@@ -5,7 +5,7 @@ | |||
5 | typedef struct nnMatrix nnMatrix; | 5 | typedef struct nnMatrix nnMatrix; |
6 | 6 | ||
7 | typedef struct nnNeuralNetwork nnNeuralNetwork; | 7 | typedef struct nnNeuralNetwork nnNeuralNetwork; |
8 | typedef struct nnQueryObject nnQueryObject; | 8 | typedef struct nnQueryObject nnQueryObject; |
9 | 9 | ||
10 | /// Neuron activation. | 10 | /// Neuron activation. |
11 | typedef enum nnActivation { | 11 | typedef enum nnActivation { |
@@ -15,7 +15,8 @@ typedef enum nnActivation { | |||
15 | } nnActivation; | 15 | } nnActivation; |
16 | 16 | ||
17 | /// Create a network. | 17 | /// Create a network. |
18 | nnNeuralNetwork* nnMakeNet(int num_layers, const int* layer_sizes, const nnActivation* activations); | 18 | nnNeuralNetwork* nnMakeNet( |
19 | int num_layers, const int* layer_sizes, const nnActivation* activations); | ||
19 | 20 | ||
20 | /// Delete the network and free its internal memory. | 21 | /// Delete the network and free its internal memory. |
21 | void nnDeleteNet(nnNeuralNetwork**); | 22 | void nnDeleteNet(nnNeuralNetwork**); |
@@ -36,7 +37,8 @@ void nnSetBiases(nnNeuralNetwork*, const R* biases); | |||
36 | void nnQuery(const nnNeuralNetwork*, nnQueryObject*, const nnMatrix* input); | 37 | void nnQuery(const nnNeuralNetwork*, nnQueryObject*, const nnMatrix* input); |
37 | 38 | ||
38 | /// Query the network, array version. | 39 | /// Query the network, array version. |
39 | void nnQueryArray(const nnNeuralNetwork*, nnQueryObject*, const R* input, R* output); | 40 | void nnQueryArray( |
41 | const nnNeuralNetwork*, nnQueryObject*, const R* input, R* output); | ||
40 | 42 | ||
41 | /// Create a query object. | 43 | /// Create a query object. |
42 | /// | 44 | /// |
diff --git a/src/lib/include/neuralnet/train.h b/src/lib/include/neuralnet/train.h index 79f8e7b..6d811c2 100644 --- a/src/lib/include/neuralnet/train.h +++ b/src/lib/include/neuralnet/train.h | |||
@@ -14,18 +14,18 @@ typedef struct nnMatrix nnMatrix; | |||
14 | /// activation with many inputs. Thus, a (0,1) initialization is really | 14 | /// activation with many inputs. Thus, a (0,1) initialization is really |
15 | /// (0,scale), for example. | 15 | /// (0,scale), for example. |
16 | typedef enum nnWeightInitStrategy { | 16 | typedef enum nnWeightInitStrategy { |
17 | nnWeightInit01, // (0,1) range. | 17 | nnWeightInit01, // (0,1) range. |
18 | nnWeightInit11, // (-1,+1) range. | 18 | nnWeightInit11, // (-1,+1) range. |
19 | nnWeightInitNormal, // Normal distribution. | 19 | nnWeightInitNormal, // Normal distribution. |
20 | } nnWeightInitStrategy; | 20 | } nnWeightInitStrategy; |
21 | 21 | ||
22 | /// Network training parameters. | 22 | /// Network training parameters. |
23 | typedef struct nnTrainingParams { | 23 | typedef struct nnTrainingParams { |
24 | R learning_rate; | 24 | R learning_rate; |
25 | int max_iterations; | 25 | int max_iterations; |
26 | uint64_t seed; | 26 | uint64_t seed; |
27 | nnWeightInitStrategy weight_init; | 27 | nnWeightInitStrategy weight_init; |
28 | bool debug; | 28 | bool debug; |
29 | } nnTrainingParams; | 29 | } nnTrainingParams; |
30 | 30 | ||
31 | /// Train the network. | 31 | /// Train the network. |
@@ -36,7 +36,5 @@ typedef struct nnTrainingParams { | |||
36 | /// |targets| is a matrix of targets, one row per target and as many columns as | 36 | /// |targets| is a matrix of targets, one row per target and as many columns as |
37 | /// the target's dimension. | 37 | /// the target's dimension. |
38 | void nnTrain( | 38 | void nnTrain( |
39 | nnNeuralNetwork*, | 39 | nnNeuralNetwork*, const nnMatrix* inputs, const nnMatrix* targets, |
40 | const nnMatrix* inputs, | 40 | const nnTrainingParams*); |
41 | const nnMatrix* targets, | ||
42 | const nnTrainingParams*); | ||
diff --git a/src/lib/src/activation.h b/src/lib/src/activation.h index 42ab73f..b56a69e 100644 --- a/src/lib/src/activation.h +++ b/src/lib/src/activation.h | |||
@@ -4,17 +4,13 @@ | |||
4 | 4 | ||
5 | #include <math.h> | 5 | #include <math.h> |
6 | 6 | ||
7 | static inline R sigmoid(R x) { | 7 | static inline R sigmoid(R x) { return 1. / (1. + exp(-x)); } |
8 | return 1. / (1. + exp(-x)); | ||
9 | } | ||
10 | 8 | ||
11 | static inline R relu(R x) { | 9 | static inline R relu(R x) { return fmax(0, x); } |
12 | return fmax(0, x); | ||
13 | } | ||
14 | 10 | ||
15 | #define NN_MAP_ARRAY(f, in, out, size) \ | 11 | #define NN_MAP_ARRAY(f, in, out, size) \ |
16 | for (int i = 0; i < size; ++i) { \ | 12 | for (int i = 0; i < size; ++i) { \ |
17 | out[i] = f(in[i]); \ | 13 | out[i] = f(in[i]); \ |
18 | } | 14 | } |
19 | 15 | ||
20 | #define sigmoid_array(in, out, size) NN_MAP_ARRAY(sigmoid, in, out, size) | 16 | #define sigmoid_array(in, out, size) NN_MAP_ARRAY(sigmoid, in, out, size) |
diff --git a/src/lib/src/matrix.c b/src/lib/src/matrix.c index f937c01..174504f 100644 --- a/src/lib/src/matrix.c +++ b/src/lib/src/matrix.c | |||
@@ -8,10 +8,10 @@ nnMatrix nnMatrixMake(int rows, int cols) { | |||
8 | R* values = calloc(rows * cols, sizeof(R)); | 8 | R* values = calloc(rows * cols, sizeof(R)); |
9 | assert(values != 0); | 9 | assert(values != 0); |
10 | 10 | ||
11 | return (nnMatrix) { | 11 | return (nnMatrix){ |
12 | .rows = rows, | 12 | .rows = rows, |
13 | .cols = cols, | 13 | .cols = cols, |
14 | .values = values, | 14 | .values = values, |
15 | }; | 15 | }; |
16 | } | 16 | } |
17 | 17 | ||
@@ -21,8 +21,8 @@ void nnMatrixDel(nnMatrix* matrix) { | |||
21 | if (matrix->values != 0) { | 21 | if (matrix->values != 0) { |
22 | free(matrix->values); | 22 | free(matrix->values); |
23 | matrix->values = 0; | 23 | matrix->values = 0; |
24 | matrix->rows = 0; | 24 | matrix->rows = 0; |
25 | matrix->cols = 0; | 25 | matrix->cols = 0; |
26 | } | 26 | } |
27 | } | 27 | } |
28 | 28 | ||
@@ -30,12 +30,12 @@ void nnMatrixMove(nnMatrix* in, nnMatrix* out) { | |||
30 | assert(in); | 30 | assert(in); |
31 | assert(out); | 31 | assert(out); |
32 | 32 | ||
33 | out->rows = in->rows; | 33 | out->rows = in->rows; |
34 | out->cols = in->cols; | 34 | out->cols = in->cols; |
35 | out->values = in->values; | 35 | out->values = in->values; |
36 | 36 | ||
37 | in->rows = 0; | 37 | in->rows = 0; |
38 | in->cols = 0; | 38 | in->cols = 0; |
39 | in->values = 0; | 39 | in->values = 0; |
40 | } | 40 | } |
41 | 41 | ||
@@ -45,8 +45,8 @@ void nnMatrixCopy(const nnMatrix* in, nnMatrix* out) { | |||
45 | assert(in->rows == out->rows); | 45 | assert(in->rows == out->rows); |
46 | assert(in->cols == out->cols); | 46 | assert(in->cols == out->cols); |
47 | 47 | ||
48 | const R* in_value = in->values; | 48 | const R* in_value = in->values; |
49 | R* out_value = out->values; | 49 | R* out_value = out->values; |
50 | 50 | ||
51 | for (int i = 0; i < in->rows * in->cols; ++i) { | 51 | for (int i = 0; i < in->rows * in->cols; ++i) { |
52 | *out_value++ = *in_value++; | 52 | *out_value++ = *in_value++; |
@@ -73,7 +73,8 @@ void nnMatrixRowToArray(const nnMatrix* in, int row, R* out) { | |||
73 | } | 73 | } |
74 | } | 74 | } |
75 | 75 | ||
76 | void nnMatrixCopyCol(const nnMatrix* in, nnMatrix* out, int col_in, int col_out) { | 76 | void nnMatrixCopyCol( |
77 | const nnMatrix* in, nnMatrix* out, int col_in, int col_out) { | ||
77 | assert(in); | 78 | assert(in); |
78 | assert(out); | 79 | assert(out); |
79 | assert(in->rows == out->rows); | 80 | assert(in->rows == out->rows); |
@@ -89,8 +90,8 @@ nnMatrix nnMatrixBorrow(nnMatrix* in) { | |||
89 | assert(in); | 90 | assert(in); |
90 | 91 | ||
91 | nnMatrix out; | 92 | nnMatrix out; |
92 | out.rows = in->rows; | 93 | out.rows = in->rows; |
93 | out.cols = in->cols; | 94 | out.cols = in->cols; |
94 | out.values = in->values; | 95 | out.values = in->values; |
95 | return out; | 96 | return out; |
96 | } | 97 | } |
@@ -101,8 +102,8 @@ nnMatrix nnMatrixBorrowRows(nnMatrix* in, int row_start, int num_rows) { | |||
101 | assert(row_start + num_rows <= in->rows); | 102 | assert(row_start + num_rows <= in->rows); |
102 | 103 | ||
103 | nnMatrix out; | 104 | nnMatrix out; |
104 | out.rows = num_rows; | 105 | out.rows = num_rows; |
105 | out.cols = in->cols; | 106 | out.cols = in->cols; |
106 | out.values = nnMatrixRow_mut(in, row_start); | 107 | out.values = nnMatrixRow_mut(in, row_start); |
107 | return out; | 108 | return out; |
108 | } | 109 | } |
@@ -139,9 +140,9 @@ void nnMatrixMul(const nnMatrix* left, const nnMatrix* right, nnMatrix* out) { | |||
139 | const R* p_left_value = &left->values[i * left->cols]; | 140 | const R* p_left_value = &left->values[i * left->cols]; |
140 | 141 | ||
141 | for (int j = 0; j < left->cols; ++j) { | 142 | for (int j = 0; j < left->cols; ++j) { |
142 | const R left_value = *p_left_value; | 143 | const R left_value = *p_left_value; |
143 | const R* right_value = &right->values[j * right->cols]; | 144 | const R* right_value = &right->values[j * right->cols]; |
144 | R* out_value = &out->values[i * out->cols]; | 145 | R* out_value = &out->values[i * out->cols]; |
145 | 146 | ||
146 | for (int k = 0; k < right->cols; ++k) { | 147 | for (int k = 0; k < right->cols; ++k) { |
147 | *out_value++ += left_value * *right_value++; | 148 | *out_value++ += left_value * *right_value++; |
@@ -152,7 +153,8 @@ void nnMatrixMul(const nnMatrix* left, const nnMatrix* right, nnMatrix* out) { | |||
152 | } | 153 | } |
153 | } | 154 | } |
154 | 155 | ||
155 | void nnMatrixMulRows(const nnMatrix* left, const nnMatrix* right, nnMatrix* out) { | 156 | void nnMatrixMulRows( |
157 | const nnMatrix* left, const nnMatrix* right, nnMatrix* out) { | ||
156 | assert(left != 0); | 158 | assert(left != 0); |
157 | assert(right != 0); | 159 | assert(right != 0); |
158 | assert(out != 0); | 160 | assert(out != 0); |
@@ -165,7 +167,7 @@ void nnMatrixMulRows(const nnMatrix* left, const nnMatrix* right, nnMatrix* out) | |||
165 | R* out_value = out->values; | 167 | R* out_value = out->values; |
166 | 168 | ||
167 | for (int i = 0; i < left->rows; ++i) { | 169 | for (int i = 0; i < left->rows; ++i) { |
168 | const R* left_row = &left->values[i * left->cols]; | 170 | const R* left_row = &left->values[i * left->cols]; |
169 | const R* right_value = right->values; | 171 | const R* right_value = right->values; |
170 | 172 | ||
171 | for (int j = 0; j < right->rows; ++j) { | 173 | for (int j = 0; j < right->rows; ++j) { |
@@ -181,7 +183,8 @@ void nnMatrixMulRows(const nnMatrix* left, const nnMatrix* right, nnMatrix* out) | |||
181 | } | 183 | } |
182 | } | 184 | } |
183 | 185 | ||
184 | void nnMatrixMulAdd(const nnMatrix* left, const nnMatrix* right, R scale, nnMatrix* out) { | 186 | void nnMatrixMulAdd( |
187 | const nnMatrix* left, const nnMatrix* right, R scale, nnMatrix* out) { | ||
185 | assert(left); | 188 | assert(left); |
186 | assert(right); | 189 | assert(right); |
187 | assert(out); | 190 | assert(out); |
@@ -190,16 +193,17 @@ void nnMatrixMulAdd(const nnMatrix* left, const nnMatrix* right, R scale, nnMatr | |||
190 | assert(left->rows == out->rows); | 193 | assert(left->rows == out->rows); |
191 | assert(left->cols == out->cols); | 194 | assert(left->cols == out->cols); |
192 | 195 | ||
193 | const R* left_value = left->values; | 196 | const R* left_value = left->values; |
194 | const R* right_value = right->values; | 197 | const R* right_value = right->values; |
195 | R* out_value = out->values; | 198 | R* out_value = out->values; |
196 | 199 | ||
197 | for (int i = 0; i < left->rows * left->cols; ++i) { | 200 | for (int i = 0; i < left->rows * left->cols; ++i) { |
198 | *out_value++ = *left_value++ + *right_value++ * scale; | 201 | *out_value++ = *left_value++ + *right_value++ * scale; |
199 | } | 202 | } |
200 | } | 203 | } |
201 | 204 | ||
202 | void nnMatrixMulSub(const nnMatrix* left, const nnMatrix* right, R scale, nnMatrix* out) { | 205 | void nnMatrixMulSub( |
206 | const nnMatrix* left, const nnMatrix* right, R scale, nnMatrix* out) { | ||
203 | assert(left); | 207 | assert(left); |
204 | assert(right); | 208 | assert(right); |
205 | assert(out); | 209 | assert(out); |
@@ -208,16 +212,17 @@ void nnMatrixMulSub(const nnMatrix* left, const nnMatrix* right, R scale, nnMatr | |||
208 | assert(left->rows == out->rows); | 212 | assert(left->rows == out->rows); |
209 | assert(left->cols == out->cols); | 213 | assert(left->cols == out->cols); |
210 | 214 | ||
211 | const R* left_value = left->values; | 215 | const R* left_value = left->values; |
212 | const R* right_value = right->values; | 216 | const R* right_value = right->values; |
213 | R* out_value = out->values; | 217 | R* out_value = out->values; |
214 | 218 | ||
215 | for (int i = 0; i < left->rows * left->cols; ++i) { | 219 | for (int i = 0; i < left->rows * left->cols; ++i) { |
216 | *out_value++ = *left_value++ - *right_value++ * scale; | 220 | *out_value++ = *left_value++ - *right_value++ * scale; |
217 | } | 221 | } |
218 | } | 222 | } |
219 | 223 | ||
220 | void nnMatrixMulPairs(const nnMatrix* left, const nnMatrix* right, nnMatrix* out) { | 224 | void nnMatrixMulPairs( |
225 | const nnMatrix* left, const nnMatrix* right, nnMatrix* out) { | ||
221 | assert(left != 0); | 226 | assert(left != 0); |
222 | assert(right != 0); | 227 | assert(right != 0); |
223 | assert(out != 0); | 228 | assert(out != 0); |
@@ -226,9 +231,9 @@ void nnMatrixMulPairs(const nnMatrix* left, const nnMatrix* right, nnMatrix* out | |||
226 | assert(left->rows == out->rows); | 231 | assert(left->rows == out->rows); |
227 | assert(left->cols == out->cols); | 232 | assert(left->cols == out->cols); |
228 | 233 | ||
229 | R* left_value = left->values; | 234 | R* left_value = left->values; |
230 | R* right_value = right->values; | 235 | R* right_value = right->values; |
231 | R* out_value = out->values; | 236 | R* out_value = out->values; |
232 | 237 | ||
233 | for (int i = 0; i < left->rows * left->cols; ++i) { | 238 | for (int i = 0; i < left->rows * left->cols; ++i) { |
234 | *out_value++ = *left_value++ * *right_value++; | 239 | *out_value++ = *left_value++ * *right_value++; |
@@ -244,9 +249,9 @@ void nnMatrixAdd(const nnMatrix* left, const nnMatrix* right, nnMatrix* out) { | |||
244 | assert(left->rows == out->rows); | 249 | assert(left->rows == out->rows); |
245 | assert(left->cols == out->cols); | 250 | assert(left->cols == out->cols); |
246 | 251 | ||
247 | const R* left_value = left->values; | 252 | const R* left_value = left->values; |
248 | const R* right_value = right->values; | 253 | const R* right_value = right->values; |
249 | R* out_value = out->values; | 254 | R* out_value = out->values; |
250 | 255 | ||
251 | for (int i = 0; i < left->rows * left->cols; ++i) { | 256 | for (int i = 0; i < left->rows * left->cols; ++i) { |
252 | *out_value++ = *left_value++ + *right_value++; | 257 | *out_value++ = *left_value++ + *right_value++; |
@@ -262,16 +267,17 @@ void nnMatrixSub(const nnMatrix* left, const nnMatrix* right, nnMatrix* out) { | |||
262 | assert(left->rows == out->rows); | 267 | assert(left->rows == out->rows); |
263 | assert(left->cols == out->cols); | 268 | assert(left->cols == out->cols); |
264 | 269 | ||
265 | const R* left_value = left->values; | 270 | const R* left_value = left->values; |
266 | const R* right_value = right->values; | 271 | const R* right_value = right->values; |
267 | R* out_value = out->values; | 272 | R* out_value = out->values; |
268 | 273 | ||
269 | for (int i = 0; i < left->rows * left->cols; ++i) { | 274 | for (int i = 0; i < left->rows * left->cols; ++i) { |
270 | *out_value++ = *left_value++ - *right_value++; | 275 | *out_value++ = *left_value++ - *right_value++; |
271 | } | 276 | } |
272 | } | 277 | } |
273 | 278 | ||
274 | void nnMatrixAddRow(const nnMatrix* matrix, const nnMatrix* row, nnMatrix* out) { | 279 | void nnMatrixAddRow( |
280 | const nnMatrix* matrix, const nnMatrix* row, nnMatrix* out) { | ||
275 | assert(matrix); | 281 | assert(matrix); |
276 | assert(row); | 282 | assert(row); |
277 | assert(out); | 283 | assert(out); |
@@ -281,7 +287,7 @@ void nnMatrixAddRow(const nnMatrix* matrix, const nnMatrix* row, nnMatrix* out) | |||
281 | assert(matrix->cols == out->cols); | 287 | assert(matrix->cols == out->cols); |
282 | 288 | ||
283 | const R* matrix_value = matrix->values; | 289 | const R* matrix_value = matrix->values; |
284 | R* out_value = out->values; | 290 | R* out_value = out->values; |
285 | 291 | ||
286 | for (int i = 0; i < matrix->rows; ++i) { | 292 | for (int i = 0; i < matrix->rows; ++i) { |
287 | const R* row_value = row->values; | 293 | const R* row_value = row->values; |
@@ -320,8 +326,8 @@ void nnMatrixGt(const nnMatrix* in, R threshold, nnMatrix* out) { | |||
320 | assert(in->rows == out->rows); | 326 | assert(in->rows == out->rows); |
321 | assert(in->cols == out->cols); | 327 | assert(in->cols == out->cols); |
322 | 328 | ||
323 | const R* in_value = in->values; | 329 | const R* in_value = in->values; |
324 | R* out_value = out->values; | 330 | R* out_value = out->values; |
325 | 331 | ||
326 | for (int i = 0; i < in->rows * in->cols; ++i) { | 332 | for (int i = 0; i < in->rows * in->cols; ++i) { |
327 | *out_value++ = (*in_value++) > threshold ? 1 : 0; | 333 | *out_value++ = (*in_value++) > threshold ? 1 : 0; |
diff --git a/src/lib/src/neuralnet.c b/src/lib/src/neuralnet.c index cac611a..a5fc59b 100644 --- a/src/lib/src/neuralnet.c +++ b/src/lib/src/neuralnet.c | |||
@@ -1,13 +1,14 @@ | |||
1 | #include <neuralnet/neuralnet.h> | 1 | #include <neuralnet/neuralnet.h> |
2 | 2 | ||
3 | #include <neuralnet/matrix.h> | ||
4 | #include "activation.h" | 3 | #include "activation.h" |
5 | #include "neuralnet_impl.h" | 4 | #include "neuralnet_impl.h" |
5 | #include <neuralnet/matrix.h> | ||
6 | 6 | ||
7 | #include <assert.h> | 7 | #include <assert.h> |
8 | #include <stdlib.h> | 8 | #include <stdlib.h> |
9 | 9 | ||
10 | nnNeuralNetwork* nnMakeNet(int num_layers, const int* layer_sizes, const nnActivation* activations) { | 10 | nnNeuralNetwork* nnMakeNet( |
11 | int num_layers, const int* layer_sizes, const nnActivation* activations) { | ||
11 | assert(num_layers > 0); | 12 | assert(num_layers > 0); |
12 | assert(layer_sizes); | 13 | assert(layer_sizes); |
13 | assert(activations); | 14 | assert(activations); |
@@ -19,10 +20,10 @@ nnNeuralNetwork* nnMakeNet(int num_layers, const int* layer_sizes, const nnActiv | |||
19 | 20 | ||
20 | net->num_layers = num_layers; | 21 | net->num_layers = num_layers; |
21 | 22 | ||
22 | net->weights = calloc(num_layers, sizeof(nnMatrix)); | 23 | net->weights = calloc(num_layers, sizeof(nnMatrix)); |
23 | net->biases = calloc(num_layers, sizeof(nnMatrix)); | 24 | net->biases = calloc(num_layers, sizeof(nnMatrix)); |
24 | net->activations = calloc(num_layers, sizeof(nnActivation)); | 25 | net->activations = calloc(num_layers, sizeof(nnActivation)); |
25 | if ( (net->weights == 0) || (net->biases == 0) || (net->activations == 0) ) { | 26 | if ((net->weights == 0) || (net->biases == 0) || (net->activations == 0)) { |
26 | nnDeleteNet(&net); | 27 | nnDeleteNet(&net); |
27 | return 0; | 28 | return 0; |
28 | } | 29 | } |
@@ -30,15 +31,15 @@ nnNeuralNetwork* nnMakeNet(int num_layers, const int* layer_sizes, const nnActiv | |||
30 | for (int l = 0; l < num_layers; ++l) { | 31 | for (int l = 0; l < num_layers; ++l) { |
31 | // layer_sizes = { input layer size, first hidden layer size, ...} | 32 | // layer_sizes = { input layer size, first hidden layer size, ...} |
32 | const int layer_input_size = layer_sizes[l]; | 33 | const int layer_input_size = layer_sizes[l]; |
33 | const int layer_output_size = layer_sizes[l+1]; | 34 | const int layer_output_size = layer_sizes[l + 1]; |
34 | 35 | ||
35 | // We store the transpose of the weight matrix as written in textbooks. | 36 | // We store the transpose of the weight matrix as written in textbooks. |
36 | // Our vectors are row vectors and the matrices row-major. | 37 | // Our vectors are row vectors and the matrices row-major. |
37 | const int rows = layer_input_size; | 38 | const int rows = layer_input_size; |
38 | const int cols = layer_output_size; | 39 | const int cols = layer_output_size; |
39 | 40 | ||
40 | net->weights[l] = nnMatrixMake(rows, cols); | 41 | net->weights[l] = nnMatrixMake(rows, cols); |
41 | net->biases[l] = nnMatrixMake(1, cols); | 42 | net->biases[l] = nnMatrixMake(1, cols); |
42 | net->activations[l] = activations[l]; | 43 | net->activations[l] = activations[l]; |
43 | } | 44 | } |
44 | 45 | ||
@@ -46,7 +47,7 @@ nnNeuralNetwork* nnMakeNet(int num_layers, const int* layer_sizes, const nnActiv | |||
46 | } | 47 | } |
47 | 48 | ||
48 | void nnDeleteNet(nnNeuralNetwork** net) { | 49 | void nnDeleteNet(nnNeuralNetwork** net) { |
49 | if ( (!net) || (!(*net)) ) { | 50 | if ((!net) || (!(*net))) { |
50 | return; | 51 | return; |
51 | } | 52 | } |
52 | if ((*net)->weights != 0) { | 53 | if ((*net)->weights != 0) { |
@@ -77,7 +78,7 @@ void nnSetWeights(nnNeuralNetwork* net, const R* weights) { | |||
77 | 78 | ||
78 | for (int l = 0; l < net->num_layers; ++l) { | 79 | for (int l = 0; l < net->num_layers; ++l) { |
79 | nnMatrix* layer_weights = &net->weights[l]; | 80 | nnMatrix* layer_weights = &net->weights[l]; |
80 | R* layer_values = layer_weights->values; | 81 | R* layer_values = layer_weights->values; |
81 | 82 | ||
82 | for (int j = 0; j < layer_weights->rows * layer_weights->cols; ++j) { | 83 | for (int j = 0; j < layer_weights->rows * layer_weights->cols; ++j) { |
83 | *layer_values++ = *weights++; | 84 | *layer_values++ = *weights++; |
@@ -91,7 +92,7 @@ void nnSetBiases(nnNeuralNetwork* net, const R* biases) { | |||
91 | 92 | ||
92 | for (int l = 0; l < net->num_layers; ++l) { | 93 | for (int l = 0; l < net->num_layers; ++l) { |
93 | nnMatrix* layer_biases = &net->biases[l]; | 94 | nnMatrix* layer_biases = &net->biases[l]; |
94 | R* layer_values = layer_biases->values; | 95 | R* layer_values = layer_biases->values; |
95 | 96 | ||
96 | for (int j = 0; j < layer_biases->rows * layer_biases->cols; ++j) { | 97 | for (int j = 0; j < layer_biases->rows * layer_biases->cols; ++j) { |
97 | *layer_values++ = *biases++; | 98 | *layer_values++ = *biases++; |
@@ -99,7 +100,8 @@ void nnSetBiases(nnNeuralNetwork* net, const R* biases) { | |||
99 | } | 100 | } |
100 | } | 101 | } |
101 | 102 | ||
102 | void nnQuery(const nnNeuralNetwork* net, nnQueryObject* query, const nnMatrix* input) { | 103 | void nnQuery( |
104 | const nnNeuralNetwork* net, nnQueryObject* query, const nnMatrix* input) { | ||
103 | assert(net); | 105 | assert(net); |
104 | assert(query); | 106 | assert(query); |
105 | assert(input); | 107 | assert(input); |
@@ -123,29 +125,34 @@ void nnQuery(const nnNeuralNetwork* net, nnQueryObject* query, const nnMatrix* i | |||
123 | // We could also rewrite the original Mul function to go row x row, | 125 | // We could also rewrite the original Mul function to go row x row, |
124 | // decomposing the multiplication. Preserving the original meaning of Mul | 126 | // decomposing the multiplication. Preserving the original meaning of Mul |
125 | // makes everything clearer. | 127 | // makes everything clearer. |
126 | nnMatrix output_vector = nnMatrixBorrowRows(&query->layer_outputs[l], i, 1); | 128 | nnMatrix output_vector = |
129 | nnMatrixBorrowRows(&query->layer_outputs[l], i, 1); | ||
127 | nnMatrixMul(&input_vector, layer_weights, &output_vector); | 130 | nnMatrixMul(&input_vector, layer_weights, &output_vector); |
128 | nnMatrixAddRow(&output_vector, layer_biases, &output_vector); | 131 | nnMatrixAddRow(&output_vector, layer_biases, &output_vector); |
129 | 132 | ||
130 | switch (net->activations[l]) { | 133 | switch (net->activations[l]) { |
131 | case nnIdentity: | 134 | case nnIdentity: |
132 | break; // Nothing to do for the identity function. | 135 | break; // Nothing to do for the identity function. |
133 | case nnSigmoid: | 136 | case nnSigmoid: |
134 | sigmoid_array(output_vector.values, output_vector.values, output_vector.cols); | 137 | sigmoid_array( |
135 | break; | 138 | output_vector.values, output_vector.values, output_vector.cols); |
136 | case nnRelu: | 139 | break; |
137 | relu_array(output_vector.values, output_vector.values, output_vector.cols); | 140 | case nnRelu: |
138 | break; | 141 | relu_array( |
139 | default: | 142 | output_vector.values, output_vector.values, output_vector.cols); |
140 | assert(0); | 143 | break; |
144 | default: | ||
145 | assert(0); | ||
141 | } | 146 | } |
142 | 147 | ||
143 | input_vector = output_vector; // Borrow. | 148 | input_vector = output_vector; // Borrow. |
144 | } | 149 | } |
145 | } | 150 | } |
146 | } | 151 | } |
147 | 152 | ||
148 | void nnQueryArray(const nnNeuralNetwork* net, nnQueryObject* query, const R* input, R* output) { | 153 | void nnQueryArray( |
154 | const nnNeuralNetwork* net, nnQueryObject* query, const R* input, | ||
155 | R* output) { | ||
149 | assert(net); | 156 | assert(net); |
150 | assert(query); | 157 | assert(query); |
151 | assert(input); | 158 | assert(input); |
@@ -177,9 +184,9 @@ nnQueryObject* nnMakeQueryObject(const nnNeuralNetwork* net, int num_inputs) { | |||
177 | return 0; | 184 | return 0; |
178 | } | 185 | } |
179 | for (int l = 0; l < net->num_layers; ++l) { | 186 | for (int l = 0; l < net->num_layers; ++l) { |
180 | const nnMatrix* layer_weights = &net->weights[l]; | 187 | const nnMatrix* layer_weights = &net->weights[l]; |
181 | const int layer_output_size = nnLayerOutputSize(layer_weights); | 188 | const int layer_output_size = nnLayerOutputSize(layer_weights); |
182 | query->layer_outputs[l] = nnMatrixMake(num_inputs, layer_output_size); | 189 | query->layer_outputs[l] = nnMatrixMake(num_inputs, layer_output_size); |
183 | } | 190 | } |
184 | query->network_outputs = &query->layer_outputs[net->num_layers - 1]; | 191 | query->network_outputs = &query->layer_outputs[net->num_layers - 1]; |
185 | 192 | ||
@@ -187,7 +194,7 @@ nnQueryObject* nnMakeQueryObject(const nnNeuralNetwork* net, int num_inputs) { | |||
187 | } | 194 | } |
188 | 195 | ||
189 | void nnDeleteQueryObject(nnQueryObject** query) { | 196 | void nnDeleteQueryObject(nnQueryObject** query) { |
190 | if ( (!query) || (!(*query)) ) { | 197 | if ((!query) || (!(*query))) { |
191 | return; | 198 | return; |
192 | } | 199 | } |
193 | if ((*query)->layer_outputs != 0) { | 200 | if ((*query)->layer_outputs != 0) { |
diff --git a/src/lib/src/neuralnet_impl.h b/src/lib/src/neuralnet_impl.h index 26107b5..18694f4 100644 --- a/src/lib/src/neuralnet_impl.h +++ b/src/lib/src/neuralnet_impl.h | |||
@@ -14,10 +14,10 @@ | |||
14 | /// | 14 | /// |
15 | /// w11 w12 w21 w22 | 15 | /// w11 w12 w21 w22 |
16 | typedef struct nnNeuralNetwork { | 16 | typedef struct nnNeuralNetwork { |
17 | int num_layers; // Number of non-input layers (hidden + output). | 17 | int num_layers; // Number of non-input layers (hidden + output). |
18 | nnMatrix* weights; // One matrix per non-input layer. | 18 | nnMatrix* weights; // One matrix per non-input layer. |
19 | nnMatrix* biases; // One vector per non-input layer. | 19 | nnMatrix* biases; // One vector per non-input layer. |
20 | nnActivation* activations; // One per non-input layer. | 20 | nnActivation* activations; // One per non-input layer. |
21 | } nnNeuralNetwork; | 21 | } nnNeuralNetwork; |
22 | 22 | ||
23 | /// A query object that holds all the memory necessary to query a network. | 23 | /// A query object that holds all the memory necessary to query a network. |
@@ -31,6 +31,6 @@ typedef struct nnNeuralNetwork { | |||
31 | /// convenience. | 31 | /// convenience. |
32 | typedef struct nnQueryObject { | 32 | typedef struct nnQueryObject { |
33 | int num_layers; | 33 | int num_layers; |
34 | nnMatrix* layer_outputs; // Output matrices, one output per layer. | 34 | nnMatrix* layer_outputs; // Output matrices, one output per layer. |
35 | nnMatrix* network_outputs; // Points to the last output matrix. | 35 | nnMatrix* network_outputs; // Points to the last output matrix. |
36 | } nnTrainingQueryObject; | 36 | } nnTrainingQueryObject; |
diff --git a/src/lib/src/train.c b/src/lib/src/train.c index 3061a99..9244907 100644 --- a/src/lib/src/train.c +++ b/src/lib/src/train.c | |||
@@ -1,7 +1,7 @@ | |||
1 | #include <neuralnet/train.h> | 1 | #include <neuralnet/train.h> |
2 | 2 | ||
3 | #include <neuralnet/matrix.h> | ||
4 | #include "neuralnet_impl.h" | 3 | #include "neuralnet_impl.h" |
4 | #include <neuralnet/matrix.h> | ||
5 | 5 | ||
6 | #include <random/mt19937-64.h> | 6 | #include <random/mt19937-64.h> |
7 | #include <random/normal.h> | 7 | #include <random/normal.h> |
@@ -14,13 +14,13 @@ | |||
14 | #define LOGD printf | 14 | #define LOGD printf |
15 | 15 | ||
16 | // If debug mode is requested, we will show progress every this many iterations. | 16 | // If debug mode is requested, we will show progress every this many iterations. |
17 | static const int PROGRESS_THRESHOLD = 5; // % | 17 | static const int PROGRESS_THRESHOLD = 5; // % |
18 | 18 | ||
19 | /// Computes the total MSE from the output error matrix. | 19 | /// Computes the total MSE from the output error matrix. |
20 | R ComputeMSE(const nnMatrix* errors) { | 20 | R ComputeMSE(const nnMatrix* errors) { |
21 | R sum_sq = 0; | 21 | R sum_sq = 0; |
22 | const int N = errors->rows * errors->cols; | 22 | const int N = errors->rows * errors->cols; |
23 | const R* value = errors->values; | 23 | const R* value = errors->values; |
24 | for (int i = 0; i < N; ++i) { | 24 | for (int i = 0; i < N; ++i) { |
25 | sum_sq += *value * *value; | 25 | sum_sq += *value * *value; |
26 | value++; | 26 | value++; |
@@ -30,7 +30,7 @@ R ComputeMSE(const nnMatrix* errors) { | |||
30 | 30 | ||
31 | /// Holds the bits required to compute a sigmoid gradient. | 31 | /// Holds the bits required to compute a sigmoid gradient. |
32 | typedef struct nnSigmoidGradientElements { | 32 | typedef struct nnSigmoidGradientElements { |
33 | nnMatrix ones; // A vector of just ones, same size as the layer. | 33 | nnMatrix ones; // A vector of just ones, same size as the layer. |
34 | } nnSigmoidGradientElements; | 34 | } nnSigmoidGradientElements; |
35 | 35 | ||
36 | /// Holds the various elements required to compute gradients. These depend on | 36 | /// Holds the various elements required to compute gradients. These depend on |
@@ -49,7 +49,8 @@ typedef struct nnGradientElements { | |||
49 | } nnGradientElements; | 49 | } nnGradientElements; |
50 | 50 | ||
51 | // Initialize the network's weights randomly and set their biases to 0. | 51 | // Initialize the network's weights randomly and set their biases to 0. |
52 | void nnInitNet(nnNeuralNetwork* net, uint64_t seed, const nnWeightInitStrategy strategy) { | 52 | void nnInitNet( |
53 | nnNeuralNetwork* net, uint64_t seed, const nnWeightInitStrategy strategy) { | ||
53 | assert(net); | 54 | assert(net); |
54 | 55 | ||
55 | mt19937_64 rng = mt19937_64_make(); | 56 | mt19937_64 rng = mt19937_64_make(); |
@@ -60,41 +61,42 @@ void nnInitNet(nnNeuralNetwork* net, uint64_t seed, const nnWeightInitStrategy s | |||
60 | nnMatrix* biases = &net->biases[l]; | 61 | nnMatrix* biases = &net->biases[l]; |
61 | 62 | ||
62 | const R layer_size = (R)nnLayerInputSize(weights); | 63 | const R layer_size = (R)nnLayerInputSize(weights); |
63 | const R scale = 1. / layer_size; | 64 | const R scale = 1. / layer_size; |
64 | const R stdev = 1. / sqrt((R)layer_size); | 65 | const R stdev = 1. / sqrt((R)layer_size); |
65 | const R sigma = stdev * stdev; | 66 | const R sigma = stdev * stdev; |
66 | 67 | ||
67 | R* value = weights->values; | 68 | R* value = weights->values; |
68 | for (int k = 0; k < weights->rows * weights->cols; ++k) { | 69 | for (int k = 0; k < weights->rows * weights->cols; ++k) { |
69 | switch (strategy) { | 70 | switch (strategy) { |
70 | case nnWeightInit01: { | 71 | case nnWeightInit01: { |
71 | const R x01 = mt19937_64_gen_real3(&rng); // (0, +1) interval. | 72 | const R x01 = mt19937_64_gen_real3(&rng); // (0, +1) interval. |
72 | *value++ = scale * x01; | 73 | *value++ = scale * x01; |
73 | break; | 74 | break; |
74 | } | 75 | } |
75 | case nnWeightInit11: { | 76 | case nnWeightInit11: { |
76 | const R x11 = mt19937_64_gen_real4(&rng); // (-1, +1) interval. | 77 | const R x11 = mt19937_64_gen_real4(&rng); // (-1, +1) interval. |
77 | *value++ = scale * x11; | 78 | *value++ = scale * x11; |
78 | break; | 79 | break; |
80 | } | ||
81 | case nnWeightInitNormal: { | ||
82 | // Using initialization with a normal distribution of standard | ||
83 | // deviation 1 / sqrt(num_layer_weights) to prevent saturation when | ||
84 | // multiplying inputs. | ||
85 | const R u01 = mt19937_64_gen_real3(&rng); // (0, +1) interval. | ||
86 | const R v01 = mt19937_64_gen_real3(&rng); // (0, +1) interval. | ||
87 | R z0, z1; | ||
88 | normal2(u01, v01, &z0, &z1); | ||
89 | z0 = normal_transform(z0, /*mu=*/0, sigma); | ||
90 | z1 = normal_transform(z1, /*mu=*/0, sigma); | ||
91 | *value++ = z0; | ||
92 | if (k < weights->rows * weights->cols - 1) { | ||
93 | *value++ = z1; | ||
94 | ++k; | ||
79 | } | 95 | } |
80 | case nnWeightInitNormal: | 96 | break; |
81 | // Using initialization with a normal distribution of standard | 97 | } |
82 | // deviation 1 / sqrt(num_layer_weights) to prevent saturation when | 98 | default: |
83 | // multiplying inputs. | 99 | assert(false); |
84 | const R u01 = mt19937_64_gen_real3(&rng); // (0, +1) interval. | ||
85 | const R v01 = mt19937_64_gen_real3(&rng); // (0, +1) interval. | ||
86 | R z0, z1; | ||
87 | normal2(u01, v01, &z0, &z1); | ||
88 | z0 = normal_transform(z0, /*mu=*/0, sigma); | ||
89 | z1 = normal_transform(z1, /*mu=*/0, sigma); | ||
90 | *value++ = z0; | ||
91 | if (k < weights->rows * weights->cols - 1) { | ||
92 | *value++ = z1; | ||
93 | ++k; | ||
94 | } | ||
95 | break; | ||
96 | default: | ||
97 | assert(false); | ||
98 | } | 100 | } |
99 | } | 101 | } |
100 | 102 | ||
@@ -112,9 +114,7 @@ void nnInitNet(nnNeuralNetwork* net, uint64_t seed, const nnWeightInitStrategy s | |||
112 | // | 114 | // |
113 | // For now, each iteration trains with one sample (row) at a time. | 115 | // For now, each iteration trains with one sample (row) at a time. |
114 | void nnTrain( | 116 | void nnTrain( |
115 | nnNeuralNetwork* net, | 117 | nnNeuralNetwork* net, const nnMatrix* inputs, const nnMatrix* targets, |
116 | const nnMatrix* inputs, | ||
117 | const nnMatrix* targets, | ||
118 | const nnTrainingParams* params) { | 118 | const nnTrainingParams* params) { |
119 | assert(net); | 119 | assert(net); |
120 | assert(inputs); | 120 | assert(inputs); |
@@ -129,34 +129,35 @@ void nnTrain( | |||
129 | nnMatrix* errors = calloc(net->num_layers, sizeof(nnMatrix)); | 129 | nnMatrix* errors = calloc(net->num_layers, sizeof(nnMatrix)); |
130 | 130 | ||
131 | // Allocate the weight transpose matrices up front for backpropagation. | 131 | // Allocate the weight transpose matrices up front for backpropagation. |
132 | //nnMatrix* weights_T = calloc(net->num_layers, sizeof(nnMatrix)); | 132 | // nnMatrix* weights_T = calloc(net->num_layers, sizeof(nnMatrix)); |
133 | 133 | ||
134 | // Allocate the weight delta matrices. | 134 | // Allocate the weight delta matrices. |
135 | nnMatrix* weight_deltas = calloc(net->num_layers, sizeof(nnMatrix)); | 135 | nnMatrix* weight_deltas = calloc(net->num_layers, sizeof(nnMatrix)); |
136 | 136 | ||
137 | // Allocate the data structures required to compute gradients. | 137 | // Allocate the data structures required to compute gradients. |
138 | // This depends on each layer's activation type. | 138 | // This depends on each layer's activation type. |
139 | nnGradientElements* gradient_elems = calloc(net->num_layers, sizeof(nnGradientElements)); | 139 | nnGradientElements* gradient_elems = |
140 | calloc(net->num_layers, sizeof(nnGradientElements)); | ||
140 | 141 | ||
141 | // Allocate the output transpose vectors for weight delta calculation. | 142 | // Allocate the output transpose vectors for weight delta calculation. |
142 | // This is one column vector per layer. | 143 | // This is one column vector per layer. |
143 | nnMatrix* outputs_T = calloc(net->num_layers, sizeof(nnMatrix)); | 144 | nnMatrix* outputs_T = calloc(net->num_layers, sizeof(nnMatrix)); |
144 | 145 | ||
145 | assert(errors != 0); | 146 | assert(errors != 0); |
146 | //assert(weights_T != 0); | 147 | // assert(weights_T != 0); |
147 | assert(weight_deltas != 0); | 148 | assert(weight_deltas != 0); |
148 | assert(gradient_elems); | 149 | assert(gradient_elems); |
149 | assert(outputs_T); | 150 | assert(outputs_T); |
150 | 151 | ||
151 | for (int l = 0; l < net->num_layers; ++l) { | 152 | for (int l = 0; l < net->num_layers; ++l) { |
152 | const nnMatrix* layer_weights = &net->weights[l]; | 153 | const nnMatrix* layer_weights = &net->weights[l]; |
153 | const int layer_output_size = net->weights[l].cols; | 154 | const int layer_output_size = net->weights[l].cols; |
154 | const nnActivation activation = net->activations[l]; | 155 | const nnActivation activation = net->activations[l]; |
155 | 156 | ||
156 | errors[l] = nnMatrixMake(1, layer_weights->cols); | 157 | errors[l] = nnMatrixMake(1, layer_weights->cols); |
157 | 158 | ||
158 | //weights_T[l] = nnMatrixMake(layer_weights->cols, layer_weights->rows); | 159 | // weights_T[l] = nnMatrixMake(layer_weights->cols, layer_weights->rows); |
159 | //nnMatrixTranspose(layer_weights, &weights_T[l]); | 160 | // nnMatrixTranspose(layer_weights, &weights_T[l]); |
160 | 161 | ||
161 | weight_deltas[l] = nnMatrixMake(layer_weights->rows, layer_weights->cols); | 162 | weight_deltas[l] = nnMatrixMake(layer_weights->rows, layer_weights->cols); |
162 | 163 | ||
@@ -164,21 +165,21 @@ void nnTrain( | |||
164 | 165 | ||
165 | // Allocate the gradient elements and vectors for weight delta calculation. | 166 | // Allocate the gradient elements and vectors for weight delta calculation. |
166 | nnGradientElements* elems = &gradient_elems[l]; | 167 | nnGradientElements* elems = &gradient_elems[l]; |
167 | elems->type = activation; | 168 | elems->type = activation; |
168 | switch (activation) { | 169 | switch (activation) { |
169 | case nnIdentity: | 170 | case nnIdentity: |
170 | break; // Gradient vector will be borrowed, no need to allocate. | 171 | break; // Gradient vector will be borrowed, no need to allocate. |
171 | 172 | ||
172 | case nnSigmoid: | 173 | case nnSigmoid: |
173 | elems->gradient = nnMatrixMake(1, layer_output_size); | 174 | elems->gradient = nnMatrixMake(1, layer_output_size); |
174 | // Allocate the 1s vectors. | 175 | // Allocate the 1s vectors. |
175 | elems->sigmoid.ones = nnMatrixMake(1, layer_output_size); | 176 | elems->sigmoid.ones = nnMatrixMake(1, layer_output_size); |
176 | nnMatrixInitConstant(&elems->sigmoid.ones, 1); | 177 | nnMatrixInitConstant(&elems->sigmoid.ones, 1); |
177 | break; | 178 | break; |
178 | 179 | ||
179 | case nnRelu: | 180 | case nnRelu: |
180 | elems->gradient = nnMatrixMake(1, layer_output_size); | 181 | elems->gradient = nnMatrixMake(1, layer_output_size); |
181 | break; | 182 | break; |
182 | } | 183 | } |
183 | } | 184 | } |
184 | 185 | ||
@@ -195,9 +196,9 @@ void nnTrain( | |||
195 | 196 | ||
196 | // If debug mode is requested, we will show progress every Nth iteration. | 197 | // If debug mode is requested, we will show progress every Nth iteration. |
197 | const int progress_frame = | 198 | const int progress_frame = |
198 | (params->max_iterations < PROGRESS_THRESHOLD) | 199 | (params->max_iterations < PROGRESS_THRESHOLD) |
199 | ? 1 | 200 | ? 1 |
200 | : (params->max_iterations * PROGRESS_THRESHOLD / 100); | 201 | : (params->max_iterations * PROGRESS_THRESHOLD / 100); |
201 | 202 | ||
202 | // --- TRAIN | 203 | // --- TRAIN |
203 | 204 | ||
@@ -209,8 +210,10 @@ void nnTrain( | |||
209 | for (int sample = 0; sample < inputs->rows; ++sample) { | 210 | for (int sample = 0; sample < inputs->rows; ++sample) { |
210 | // Slice the input and target matrices with the batch size. | 211 | // Slice the input and target matrices with the batch size. |
211 | // We are not mutating the inputs, but we need the cast to borrow. | 212 | // We are not mutating the inputs, but we need the cast to borrow. |
212 | nnMatrix training_inputs = nnMatrixBorrowRows((nnMatrix*)inputs, sample, 1); | 213 | nnMatrix training_inputs = |
213 | nnMatrix training_targets = nnMatrixBorrowRows((nnMatrix*)targets, sample, 1); | 214 | nnMatrixBorrowRows((nnMatrix*)inputs, sample, 1); |
215 | nnMatrix training_targets = | ||
216 | nnMatrixBorrowRows((nnMatrix*)targets, sample, 1); | ||
214 | 217 | ||
215 | // Will need the input transposed for backpropagation. | 218 | // Will need the input transposed for backpropagation. |
216 | // Assuming one training input per iteration for now. | 219 | // Assuming one training input per iteration for now. |
@@ -221,8 +224,10 @@ void nnTrain( | |||
221 | // part of the derivative, -2(t-o). Also, we compute o-t instead to | 224 | // part of the derivative, -2(t-o). Also, we compute o-t instead to |
222 | // remove that outer negative sign. | 225 | // remove that outer negative sign. |
223 | nnQuery(net, query, &training_inputs); | 226 | nnQuery(net, query, &training_inputs); |
224 | //nnMatrixSub(&training_targets, training_outputs, &errors[net->num_layers - 1]); | 227 | // nnMatrixSub(&training_targets, training_outputs, |
225 | nnMatrixSub(training_outputs, &training_targets, &errors[net->num_layers - 1]); | 228 | // &errors[net->num_layers - 1]); |
229 | nnMatrixSub( | ||
230 | training_outputs, &training_targets, &errors[net->num_layers - 1]); | ||
226 | 231 | ||
227 | // Update outputs_T, which we need during weight updates. | 232 | // Update outputs_T, which we need during weight updates. |
228 | for (int l = 0; l < net->num_layers; ++l) { | 233 | for (int l = 0; l < net->num_layers; ++l) { |
@@ -232,12 +237,12 @@ void nnTrain( | |||
232 | // Update weights and biases for each internal layer, backpropagating | 237 | // Update weights and biases for each internal layer, backpropagating |
233 | // errors along the way. | 238 | // errors along the way. |
234 | for (int l = net->num_layers - 1; l >= 0; --l) { | 239 | for (int l = net->num_layers - 1; l >= 0; --l) { |
235 | const nnMatrix* layer_output = &query->layer_outputs[l]; | 240 | const nnMatrix* layer_output = &query->layer_outputs[l]; |
236 | nnMatrix* layer_weights = &net->weights[l]; | 241 | nnMatrix* layer_weights = &net->weights[l]; |
237 | nnMatrix* layer_biases = &net->biases[l]; | 242 | nnMatrix* layer_biases = &net->biases[l]; |
238 | nnGradientElements* elems = &gradient_elems[l]; | 243 | nnGradientElements* elems = &gradient_elems[l]; |
239 | nnMatrix* gradient = &elems->gradient; | 244 | nnMatrix* gradient = &elems->gradient; |
240 | const nnActivation activation = net->activations[l]; | 245 | const nnActivation activation = net->activations[l]; |
241 | 246 | ||
242 | // Compute the gradient (the part of the expression that does not | 247 | // Compute the gradient (the part of the expression that does not |
243 | // contain the output of the previous layer). | 248 | // contain the output of the previous layer). |
@@ -246,55 +251,58 @@ void nnTrain( | |||
246 | // Sigmoid: G = error_k * output_k * (1 - output_k). | 251 | // Sigmoid: G = error_k * output_k * (1 - output_k). |
247 | // Relu: G = error_k * (output_k > 0 ? 1 : 0) | 252 | // Relu: G = error_k * (output_k > 0 ? 1 : 0) |
248 | switch (activation) { | 253 | switch (activation) { |
249 | case nnIdentity: | 254 | case nnIdentity: |
250 | // TODO: Just copy the pointer? | 255 | // TODO: Just copy the pointer? |
251 | *gradient = nnMatrixBorrow(&errors[l]); | 256 | *gradient = nnMatrixBorrow(&errors[l]); |
252 | break; | 257 | break; |
253 | case nnSigmoid: | 258 | case nnSigmoid: |
254 | nnMatrixSub(&elems->sigmoid.ones, layer_output, gradient); | 259 | nnMatrixSub(&elems->sigmoid.ones, layer_output, gradient); |
255 | nnMatrixMulPairs(layer_output, gradient, gradient); | 260 | nnMatrixMulPairs(layer_output, gradient, gradient); |
256 | nnMatrixMulPairs(&errors[l], gradient, gradient); | 261 | nnMatrixMulPairs(&errors[l], gradient, gradient); |
257 | break; | 262 | break; |
258 | case nnRelu: | 263 | case nnRelu: |
259 | nnMatrixGt(layer_output, 0, gradient); | 264 | nnMatrixGt(layer_output, 0, gradient); |
260 | nnMatrixMulPairs(&errors[l], gradient, gradient); | 265 | nnMatrixMulPairs(&errors[l], gradient, gradient); |
261 | break; | 266 | break; |
262 | } | 267 | } |
263 | 268 | ||
264 | // Outer product to compute the weight deltas. | 269 | // Outer product to compute the weight deltas. |
265 | const nnMatrix* output_T = (l == 0) ? &training_inputs_T : &outputs_T[l-1]; | 270 | const nnMatrix* output_T = |
271 | (l == 0) ? &training_inputs_T : &outputs_T[l - 1]; | ||
266 | nnMatrixMul(output_T, gradient, &weight_deltas[l]); | 272 | nnMatrixMul(output_T, gradient, &weight_deltas[l]); |
267 | 273 | ||
268 | // Backpropagate the error before updating weights. | 274 | // Backpropagate the error before updating weights. |
269 | if (l > 0) { | 275 | if (l > 0) { |
270 | // G * W^T == G *^T W. | 276 | // G * W^T == G *^T W. |
271 | //nnMatrixMul(gradient, &weights_T[l], &errors[l-1]); | 277 | // nnMatrixMul(gradient, &weights_T[l], &errors[l-1]); |
272 | nnMatrixMulRows(gradient, layer_weights, &errors[l-1]); | 278 | nnMatrixMulRows(gradient, layer_weights, &errors[l - 1]); |
273 | } | 279 | } |
274 | 280 | ||
275 | // Update weights. | 281 | // Update weights. |
276 | nnMatrixScale(&weight_deltas[l], params->learning_rate); | 282 | nnMatrixScale(&weight_deltas[l], params->learning_rate); |
277 | // The gradient has a negative sign from -(t - o), but we have computed | 283 | // The gradient has a negative sign from -(t - o), but we have computed |
278 | // e = o - t instead, so we can subtract directly. | 284 | // e = o - t instead, so we can subtract directly. |
279 | //nnMatrixAdd(layer_weights, &weight_deltas[l], layer_weights); | 285 | // nnMatrixAdd(layer_weights, &weight_deltas[l], layer_weights); |
280 | nnMatrixSub(layer_weights, &weight_deltas[l], layer_weights); | 286 | nnMatrixSub(layer_weights, &weight_deltas[l], layer_weights); |
281 | 287 | ||
282 | // Update weight transpose matrix for the next training iteration. | 288 | // Update weight transpose matrix for the next training iteration. |
283 | //nnMatrixTranspose(layer_weights, &weights_T[l]); | 289 | // nnMatrixTranspose(layer_weights, &weights_T[l]); |
284 | 290 | ||
285 | // Update biases. | 291 | // Update biases. |
286 | // This is the same formula as for weights, except that the o_j term is | 292 | // This is the same formula as for weights, except that the o_j term is |
287 | // just 1. We can simply re-use the gradient that we have already | 293 | // just 1. We can simply re-use the gradient that we have already |
288 | // computed for the weight update. | 294 | // computed for the weight update. |
289 | //nnMatrixMulAdd(layer_biases, gradient, params->learning_rate, layer_biases); | 295 | // nnMatrixMulAdd(layer_biases, gradient, params->learning_rate, |
290 | nnMatrixMulSub(layer_biases, gradient, params->learning_rate, layer_biases); | 296 | // layer_biases); |
297 | nnMatrixMulSub( | ||
298 | layer_biases, gradient, params->learning_rate, layer_biases); | ||
291 | } | 299 | } |
292 | 300 | ||
293 | // TODO: Add this under a verbose debugging mode. | 301 | // TODO: Add this under a verbose debugging mode. |
294 | // if (params->debug) { | 302 | // if (params->debug) { |
295 | // LOGD("Iter: %d, Sample: %d, Error: %f\n", iter, sample, ComputeMSE(&errors[net->num_layers - 1])); | 303 | // LOGD("Iter: %d, Sample: %d, Error: %f\n", iter, sample, |
296 | // LOGD("TGT: "); | 304 | // ComputeMSE(&errors[net->num_layers - 1])); LOGD("TGT: "); for (int i |
297 | // for (int i = 0; i < training_targets.cols; ++i) { | 305 | // = 0; i < training_targets.cols; ++i) { |
298 | // printf("%.3f ", training_targets.values[i]); | 306 | // printf("%.3f ", training_targets.values[i]); |
299 | // } | 307 | // } |
300 | // printf("\n"); | 308 | // printf("\n"); |
@@ -307,42 +315,44 @@ void nnTrain( | |||
307 | } | 315 | } |
308 | 316 | ||
309 | if (params->debug && ((iter % progress_frame) == 0)) { | 317 | if (params->debug && ((iter % progress_frame) == 0)) { |
310 | LOGD("Iter: %d/%d, Error: %f\n", | 318 | LOGD( |
311 | iter, params->max_iterations, ComputeMSE(&errors[net->num_layers - 1])); | 319 | "Iter: %d/%d, Error: %f\n", iter, params->max_iterations, |
320 | ComputeMSE(&errors[net->num_layers - 1])); | ||
312 | } | 321 | } |
313 | } | 322 | } |
314 | 323 | ||
315 | // Print the final error. | 324 | // Print the final error. |
316 | if (params->debug) { | 325 | if (params->debug) { |
317 | LOGD("Iter: %d/%d, Error: %f\n", | 326 | LOGD( |
318 | params->max_iterations, params->max_iterations, ComputeMSE(&errors[net->num_layers - 1])); | 327 | "Iter: %d/%d, Error: %f\n", params->max_iterations, |
328 | params->max_iterations, ComputeMSE(&errors[net->num_layers - 1])); | ||
319 | } | 329 | } |
320 | 330 | ||
321 | for (int l = 0; l < net->num_layers; ++l) { | 331 | for (int l = 0; l < net->num_layers; ++l) { |
322 | nnMatrixDel(&errors[l]); | 332 | nnMatrixDel(&errors[l]); |
323 | nnMatrixDel(&outputs_T[l]); | 333 | nnMatrixDel(&outputs_T[l]); |
324 | //nnMatrixDel(&weights_T[l]); | 334 | // nnMatrixDel(&weights_T[l]); |
325 | nnMatrixDel(&weight_deltas[l]); | 335 | nnMatrixDel(&weight_deltas[l]); |
326 | 336 | ||
327 | nnGradientElements* elems = &gradient_elems[l]; | 337 | nnGradientElements* elems = &gradient_elems[l]; |
328 | switch (elems->type) { | 338 | switch (elems->type) { |
329 | case nnIdentity: | 339 | case nnIdentity: |
330 | break; // Gradient vector is borrowed, no need to deallocate. | 340 | break; // Gradient vector is borrowed, no need to deallocate. |
331 | 341 | ||
332 | case nnSigmoid: | 342 | case nnSigmoid: |
333 | nnMatrixDel(&elems->gradient); | 343 | nnMatrixDel(&elems->gradient); |
334 | nnMatrixDel(&elems->sigmoid.ones); | 344 | nnMatrixDel(&elems->sigmoid.ones); |
335 | break; | 345 | break; |
336 | 346 | ||
337 | case nnRelu: | 347 | case nnRelu: |
338 | nnMatrixDel(&elems->gradient); | 348 | nnMatrixDel(&elems->gradient); |
339 | break; | 349 | break; |
340 | } | 350 | } |
341 | } | 351 | } |
342 | nnMatrixDel(&training_inputs_T); | 352 | nnMatrixDel(&training_inputs_T); |
343 | free(errors); | 353 | free(errors); |
344 | free(outputs_T); | 354 | free(outputs_T); |
345 | //free(weights_T); | 355 | // free(weights_T); |
346 | free(weight_deltas); | 356 | free(weight_deltas); |
347 | free(gradient_elems); | 357 | free(gradient_elems); |
348 | } | 358 | } |