aboutsummaryrefslogtreecommitdiff
path: root/src/lib/src/neuralnet.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2023-12-16 10:21:16 -0800
committer3gg <3gg@shellblade.net>2023-12-16 10:21:16 -0800
commit653e98e029a0d0f110b0ac599e50406060bb0f87 (patch)
tree6f909215218f6720266bde1b3f49aeddad8b1da3 /src/lib/src/neuralnet.c
parent3df7b6fb0c65295eed4590e6f166d60e89b3c68e (diff)
Decouple activations from linear layer.
Diffstat (limited to 'src/lib/src/neuralnet.c')
-rw-r--r--src/lib/src/neuralnet.c218
1 files changed, 114 insertions, 104 deletions
diff --git a/src/lib/src/neuralnet.c b/src/lib/src/neuralnet.c
index a5fc59b..4322b8c 100644
--- a/src/lib/src/neuralnet.c
+++ b/src/lib/src/neuralnet.c
@@ -7,11 +7,65 @@
7#include <assert.h> 7#include <assert.h>
8#include <stdlib.h> 8#include <stdlib.h>
9 9
10static void MakeLayerImpl(
11 int prev_layer_output_size, const nnLayer* layer, nnLayerImpl* impl) {
12 impl->type = layer->type;
13
14 switch (layer->type) {
15 case nnLinear: {
16 const nnLinearParams* params = &layer->linear;
17 nnLinearImpl* linear = &impl->linear;
18
19 if ((params->input_size > 0) && (params->output_size > 0)) {
20 const int rows = params->input_size;
21 const int cols = params->output_size;
22 linear->weights = nnMatrixMake(rows, cols);
23 linear->biases = nnMatrixMake(1, cols);
24 linear->owned = true;
25 } else {
26 linear->weights = params->weights;
27 linear->biases = params->biases;
28 linear->owned = false;
29 }
30
31 impl->input_size = linear->weights.rows;
32 impl->output_size = linear->weights.cols;
33
34 break;
35 }
36
37 // Activation layers.
38 case nnRelu:
39 case nnSigmoid:
40 impl->input_size = prev_layer_output_size;
41 impl->output_size = prev_layer_output_size;
42 break;
43 }
44}
45
46static void DeleteLayer(nnLayerImpl* layer) {
47 switch (layer->type) {
48 case nnLinear: {
49 nnLinearImpl* linear = &layer->linear;
50 if (linear->owned) {
51 nnMatrixDel(&linear->weights);
52 nnMatrixDel(&linear->biases);
53 }
54 break;
55 }
56
57 // No parameters for these layers.
58 case nnRelu:
59 case nnSigmoid:
60 break;
61 }
62}
63
10nnNeuralNetwork* nnMakeNet( 64nnNeuralNetwork* nnMakeNet(
11 int num_layers, const int* layer_sizes, const nnActivation* activations) { 65 const nnLayer* layers, int num_layers, int input_size) {
66 assert(layers);
12 assert(num_layers > 0); 67 assert(num_layers > 0);
13 assert(layer_sizes); 68 assert(input_size > 0);
14 assert(activations);
15 69
16 nnNeuralNetwork* net = calloc(1, sizeof(nnNeuralNetwork)); 70 nnNeuralNetwork* net = calloc(1, sizeof(nnNeuralNetwork));
17 if (net == 0) { 71 if (net == 0) {
@@ -20,84 +74,38 @@ nnNeuralNetwork* nnMakeNet(
20 74
21 net->num_layers = num_layers; 75 net->num_layers = num_layers;
22 76
23 net->weights = calloc(num_layers, sizeof(nnMatrix)); 77 net->layers = calloc(num_layers, sizeof(nnLayerImpl));
24 net->biases = calloc(num_layers, sizeof(nnMatrix)); 78 if (net->layers == 0) {
25 net->activations = calloc(num_layers, sizeof(nnActivation));
26 if ((net->weights == 0) || (net->biases == 0) || (net->activations == 0)) {
27 nnDeleteNet(&net); 79 nnDeleteNet(&net);
28 return 0; 80 return 0;
29 } 81 }
30 82
83 int prev_layer_output_size = input_size;
31 for (int l = 0; l < num_layers; ++l) { 84 for (int l = 0; l < num_layers; ++l) {
32 // layer_sizes = { input layer size, first hidden layer size, ...} 85 MakeLayerImpl(prev_layer_output_size, &layers[l], &net->layers[l]);
33 const int layer_input_size = layer_sizes[l]; 86 prev_layer_output_size = net->layers[l].output_size;
34 const int layer_output_size = layer_sizes[l + 1];
35
36 // We store the transpose of the weight matrix as written in textbooks.
37 // Our vectors are row vectors and the matrices row-major.
38 const int rows = layer_input_size;
39 const int cols = layer_output_size;
40
41 net->weights[l] = nnMatrixMake(rows, cols);
42 net->biases[l] = nnMatrixMake(1, cols);
43 net->activations[l] = activations[l];
44 } 87 }
45 88
46 return net; 89 return net;
47} 90}
48 91
49void nnDeleteNet(nnNeuralNetwork** net) { 92void nnDeleteNet(nnNeuralNetwork** ppNet) {
50 if ((!net) || (!(*net))) { 93 if ((!ppNet) || (!(*ppNet))) {
51 return; 94 return;
52 } 95 }
53 if ((*net)->weights != 0) { 96 nnNeuralNetwork* net = *ppNet;
54 for (int l = 0; l < (*net)->num_layers; ++l) {
55 nnMatrixDel(&(*net)->weights[l]);
56 }
57 free((*net)->weights);
58 (*net)->weights = 0;
59 }
60 if ((*net)->biases != 0) {
61 for (int l = 0; l < (*net)->num_layers; ++l) {
62 nnMatrixDel(&(*net)->biases[l]);
63 }
64 free((*net)->biases);
65 (*net)->biases = 0;
66 }
67 if ((*net)->activations) {
68 free((*net)->activations);
69 (*net)->activations = 0;
70 }
71 free(*net);
72 *net = 0;
73}
74
75void nnSetWeights(nnNeuralNetwork* net, const R* weights) {
76 assert(net);
77 assert(weights);
78 97
79 for (int l = 0; l < net->num_layers; ++l) { 98 for (int l = 0; l < net->num_layers; ++l) {
80 nnMatrix* layer_weights = &net->weights[l]; 99 DeleteLayer(&net->layers[l]);
81 R* layer_values = layer_weights->values;
82
83 for (int j = 0; j < layer_weights->rows * layer_weights->cols; ++j) {
84 *layer_values++ = *weights++;
85 }
86 } 100 }
87}
88
89void nnSetBiases(nnNeuralNetwork* net, const R* biases) {
90 assert(net);
91 assert(biases);
92
93 for (int l = 0; l < net->num_layers; ++l) {
94 nnMatrix* layer_biases = &net->biases[l];
95 R* layer_values = layer_biases->values;
96 101
97 for (int j = 0; j < layer_biases->rows * layer_biases->cols; ++j) { 102 if (net->layers) {
98 *layer_values++ = *biases++; 103 free(net->layers);
99 } 104 net->layers = 0;
100 } 105 }
106
107 free(net);
108 *ppNet = 0;
101} 109}
102 110
103void nnQuery( 111void nnQuery(
@@ -114,35 +122,40 @@ void nnQuery(
114 nnMatrix input_vector = nnMatrixBorrowRows((nnMatrix*)input, i, 1); 122 nnMatrix input_vector = nnMatrixBorrowRows((nnMatrix*)input, i, 1);
115 123
116 for (int l = 0; l < net->num_layers; ++l) { 124 for (int l = 0; l < net->num_layers; ++l) {
117 const nnMatrix* layer_weights = &net->weights[l];
118 const nnMatrix* layer_biases = &net->biases[l];
119 // Y^T = (W*X)^T = X^T*W^T
120 //
121 // TODO: If we had a row-row matrix multiplication, we could compute:
122 // Y^T = W ** X^T
123 // The row-row multiplication could be more cache-friendly. We just need
124 // to store W as is, without transposing.
125 // We could also rewrite the original Mul function to go row x row,
126 // decomposing the multiplication. Preserving the original meaning of Mul
127 // makes everything clearer.
128 nnMatrix output_vector = 125 nnMatrix output_vector =
129 nnMatrixBorrowRows(&query->layer_outputs[l], i, 1); 126 nnMatrixBorrowRows(&query->layer_outputs[l], i, 1);
130 nnMatrixMul(&input_vector, layer_weights, &output_vector);
131 nnMatrixAddRow(&output_vector, layer_biases, &output_vector);
132 127
133 switch (net->activations[l]) { 128 switch (net->layers[l].type) {
134 case nnIdentity: 129 case nnLinear: {
135 break; // Nothing to do for the identity function. 130 const nnLinearImpl* linear = &net->layers[l].linear;
136 case nnSigmoid: 131 const nnMatrix* layer_weights = &linear->weights;
137 sigmoid_array( 132 const nnMatrix* layer_biases = &linear->biases;
138 output_vector.values, output_vector.values, output_vector.cols); 133
134 // Y^T = (W*X)^T = X^T*W^T
135 //
136 // TODO: If we had a row-row matrix multiplication, we could compute:
137 // Y^T = W ** X^T
138 //
139 // The row-row multiplication could be more cache-friendly. We just need
140 // to store W as is, without transposing.
141 //
142 // We could also rewrite the original Mul function to go row x row,
143 // decomposing the multiplication. Preserving the original meaning of
144 // Mul makes everything clearer.
145 nnMatrixMul(&input_vector, layer_weights, &output_vector);
146 nnMatrixAddRow(&output_vector, layer_biases, &output_vector);
139 break; 147 break;
148 }
140 case nnRelu: 149 case nnRelu:
150 assert(input_vector.cols == output_vector.cols);
141 relu_array( 151 relu_array(
142 output_vector.values, output_vector.values, output_vector.cols); 152 input_vector.values, output_vector.values, output_vector.cols);
153 break;
154 case nnSigmoid:
155 assert(input_vector.cols == output_vector.cols);
156 sigmoid_array(
157 input_vector.values, output_vector.values, output_vector.cols);
143 break; 158 break;
144 default:
145 assert(0);
146 } 159 }
147 160
148 input_vector = output_vector; // Borrow. 161 input_vector = output_vector; // Borrow.
@@ -159,15 +172,15 @@ void nnQueryArray(
159 assert(output); 172 assert(output);
160 assert(net->num_layers > 0); 173 assert(net->num_layers > 0);
161 174
162 nnMatrix input_vector = nnMatrixMake(net->weights[0].cols, 1); 175 nnMatrix input_vector = nnMatrixMake(1, nnNetInputSize(net));
163 nnMatrixInit(&input_vector, input); 176 nnMatrixInit(&input_vector, input);
164 nnQuery(net, query, &input_vector); 177 nnQuery(net, query, &input_vector);
165 nnMatrixRowToArray(query->network_outputs, 0, output); 178 nnMatrixRowToArray(query->network_outputs, 0, output);
166} 179}
167 180
168nnQueryObject* nnMakeQueryObject(const nnNeuralNetwork* net, int num_inputs) { 181nnQueryObject* nnMakeQueryObject(const nnNeuralNetwork* net, int batch_size) {
169 assert(net); 182 assert(net);
170 assert(num_inputs > 0); 183 assert(batch_size > 0);
171 assert(net->num_layers > 0); 184 assert(net->num_layers > 0);
172 185
173 nnQueryObject* query = calloc(1, sizeof(nnQueryObject)); 186 nnQueryObject* query = calloc(1, sizeof(nnQueryObject));
@@ -183,11 +196,12 @@ nnQueryObject* nnMakeQueryObject(const nnNeuralNetwork* net, int num_inputs) {
183 free(query); 196 free(query);
184 return 0; 197 return 0;
185 } 198 }
199
186 for (int l = 0; l < net->num_layers; ++l) { 200 for (int l = 0; l < net->num_layers; ++l) {
187 const nnMatrix* layer_weights = &net->weights[l]; 201 const int layer_output_size = nnLayerOutputSize(net, l);
188 const int layer_output_size = nnLayerOutputSize(layer_weights); 202 query->layer_outputs[l] = nnMatrixMake(batch_size, layer_output_size);
189 query->layer_outputs[l] = nnMatrixMake(num_inputs, layer_output_size);
190 } 203 }
204
191 query->network_outputs = &query->layer_outputs[net->num_layers - 1]; 205 query->network_outputs = &query->layer_outputs[net->num_layers - 1];
192 206
193 return query; 207 return query;
@@ -213,23 +227,19 @@ const nnMatrix* nnNetOutputs(const nnQueryObject* query) {
213} 227}
214 228
215int nnNetInputSize(const nnNeuralNetwork* net) { 229int nnNetInputSize(const nnNeuralNetwork* net) {
216 assert(net); 230 return nnLayerInputSize(net, 0);
217 assert(net->num_layers > 0);
218 return net->weights[0].rows;
219} 231}
220 232
221int nnNetOutputSize(const nnNeuralNetwork* net) { 233int nnNetOutputSize(const nnNeuralNetwork* net) {
222 assert(net); 234 return nnLayerOutputSize(net, net->num_layers - 1);
223 assert(net->num_layers > 0);
224 return net->weights[net->num_layers - 1].cols;
225} 235}
226 236
227int nnLayerInputSize(const nnMatrix* weights) { 237int nnLayerInputSize(const nnNeuralNetwork* net, int layer) {
228 assert(weights); 238 assert(net);
229 return weights->rows; 239 return net->layers[layer].input_size;
230} 240}
231 241
232int nnLayerOutputSize(const nnMatrix* weights) { 242int nnLayerOutputSize(const nnNeuralNetwork* net, int layer) {
233 assert(weights); 243 assert(net);
234 return weights->cols; 244 return net->layers[layer].output_size;
235} 245}