diff options
author | jeanne <jeanne@localhost.localdomain> | 2022-05-11 09:54:38 -0700 |
---|---|---|
committer | jeanne <jeanne@localhost.localdomain> | 2022-05-11 09:54:38 -0700 |
commit | 411f66a2540fa17c736116d865e0ceb0cfe5623b (patch) | |
tree | fa92c69ec627642c8452f928798ff6eccd24ddd6 /src/lib/src/neuralnet.c | |
parent | 7705b07456dfd4b89c272613e98eda36cc787254 (diff) |
Initial commit.
Diffstat (limited to 'src/lib/src/neuralnet.c')
-rw-r--r-- | src/lib/src/neuralnet.c | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/src/lib/src/neuralnet.c b/src/lib/src/neuralnet.c new file mode 100644 index 0000000..cac611a --- /dev/null +++ b/src/lib/src/neuralnet.c | |||
@@ -0,0 +1,228 @@ | |||
1 | #include <neuralnet/neuralnet.h> | ||
2 | |||
3 | #include <neuralnet/matrix.h> | ||
4 | #include "activation.h" | ||
5 | #include "neuralnet_impl.h" | ||
6 | |||
7 | #include <assert.h> | ||
8 | #include <stdlib.h> | ||
9 | |||
10 | nnNeuralNetwork* nnMakeNet(int num_layers, const int* layer_sizes, const nnActivation* activations) { | ||
11 | assert(num_layers > 0); | ||
12 | assert(layer_sizes); | ||
13 | assert(activations); | ||
14 | |||
15 | nnNeuralNetwork* net = calloc(1, sizeof(nnNeuralNetwork)); | ||
16 | if (net == 0) { | ||
17 | return 0; | ||
18 | } | ||
19 | |||
20 | net->num_layers = num_layers; | ||
21 | |||
22 | net->weights = calloc(num_layers, sizeof(nnMatrix)); | ||
23 | net->biases = calloc(num_layers, sizeof(nnMatrix)); | ||
24 | net->activations = calloc(num_layers, sizeof(nnActivation)); | ||
25 | if ( (net->weights == 0) || (net->biases == 0) || (net->activations == 0) ) { | ||
26 | nnDeleteNet(&net); | ||
27 | return 0; | ||
28 | } | ||
29 | |||
30 | for (int l = 0; l < num_layers; ++l) { | ||
31 | // layer_sizes = { input layer size, first hidden layer size, ...} | ||
32 | const int layer_input_size = layer_sizes[l]; | ||
33 | const int layer_output_size = layer_sizes[l+1]; | ||
34 | |||
35 | // We store the transpose of the weight matrix as written in textbooks. | ||
36 | // Our vectors are row vectors and the matrices row-major. | ||
37 | const int rows = layer_input_size; | ||
38 | const int cols = layer_output_size; | ||
39 | |||
40 | net->weights[l] = nnMatrixMake(rows, cols); | ||
41 | net->biases[l] = nnMatrixMake(1, cols); | ||
42 | net->activations[l] = activations[l]; | ||
43 | } | ||
44 | |||
45 | return net; | ||
46 | } | ||
47 | |||
48 | void nnDeleteNet(nnNeuralNetwork** net) { | ||
49 | if ( (!net) || (!(*net)) ) { | ||
50 | return; | ||
51 | } | ||
52 | if ((*net)->weights != 0) { | ||
53 | for (int l = 0; l < (*net)->num_layers; ++l) { | ||
54 | nnMatrixDel(&(*net)->weights[l]); | ||
55 | } | ||
56 | free((*net)->weights); | ||
57 | (*net)->weights = 0; | ||
58 | } | ||
59 | if ((*net)->biases != 0) { | ||
60 | for (int l = 0; l < (*net)->num_layers; ++l) { | ||
61 | nnMatrixDel(&(*net)->biases[l]); | ||
62 | } | ||
63 | free((*net)->biases); | ||
64 | (*net)->biases = 0; | ||
65 | } | ||
66 | if ((*net)->activations) { | ||
67 | free((*net)->activations); | ||
68 | (*net)->activations = 0; | ||
69 | } | ||
70 | free(*net); | ||
71 | *net = 0; | ||
72 | } | ||
73 | |||
74 | void nnSetWeights(nnNeuralNetwork* net, const R* weights) { | ||
75 | assert(net); | ||
76 | assert(weights); | ||
77 | |||
78 | for (int l = 0; l < net->num_layers; ++l) { | ||
79 | nnMatrix* layer_weights = &net->weights[l]; | ||
80 | R* layer_values = layer_weights->values; | ||
81 | |||
82 | for (int j = 0; j < layer_weights->rows * layer_weights->cols; ++j) { | ||
83 | *layer_values++ = *weights++; | ||
84 | } | ||
85 | } | ||
86 | } | ||
87 | |||
88 | void nnSetBiases(nnNeuralNetwork* net, const R* biases) { | ||
89 | assert(net); | ||
90 | assert(biases); | ||
91 | |||
92 | for (int l = 0; l < net->num_layers; ++l) { | ||
93 | nnMatrix* layer_biases = &net->biases[l]; | ||
94 | R* layer_values = layer_biases->values; | ||
95 | |||
96 | for (int j = 0; j < layer_biases->rows * layer_biases->cols; ++j) { | ||
97 | *layer_values++ = *biases++; | ||
98 | } | ||
99 | } | ||
100 | } | ||
101 | |||
102 | void nnQuery(const nnNeuralNetwork* net, nnQueryObject* query, const nnMatrix* input) { | ||
103 | assert(net); | ||
104 | assert(query); | ||
105 | assert(input); | ||
106 | assert(net->num_layers == query->num_layers); | ||
107 | assert(input->rows <= query->network_outputs->rows); | ||
108 | assert(input->cols == nnNetInputSize(net)); | ||
109 | |||
110 | for (int i = 0; i < input->rows; ++i) { | ||
111 | // Not mutating the input, but we need the cast to borrow. | ||
112 | nnMatrix input_vector = nnMatrixBorrowRows((nnMatrix*)input, i, 1); | ||
113 | |||
114 | for (int l = 0; l < net->num_layers; ++l) { | ||
115 | const nnMatrix* layer_weights = &net->weights[l]; | ||
116 | const nnMatrix* layer_biases = &net->biases[l]; | ||
117 | // Y^T = (W*X)^T = X^T*W^T | ||
118 | // | ||
119 | // TODO: If we had a row-row matrix multiplication, we could compute: | ||
120 | // Y^T = W ** X^T | ||
121 | // The row-row multiplication could be more cache-friendly. We just need | ||
122 | // to store W as is, without transposing. | ||
123 | // We could also rewrite the original Mul function to go row x row, | ||
124 | // decomposing the multiplication. Preserving the original meaning of Mul | ||
125 | // makes everything clearer. | ||
126 | nnMatrix output_vector = nnMatrixBorrowRows(&query->layer_outputs[l], i, 1); | ||
127 | nnMatrixMul(&input_vector, layer_weights, &output_vector); | ||
128 | nnMatrixAddRow(&output_vector, layer_biases, &output_vector); | ||
129 | |||
130 | switch (net->activations[l]) { | ||
131 | case nnIdentity: | ||
132 | break; // Nothing to do for the identity function. | ||
133 | case nnSigmoid: | ||
134 | sigmoid_array(output_vector.values, output_vector.values, output_vector.cols); | ||
135 | break; | ||
136 | case nnRelu: | ||
137 | relu_array(output_vector.values, output_vector.values, output_vector.cols); | ||
138 | break; | ||
139 | default: | ||
140 | assert(0); | ||
141 | } | ||
142 | |||
143 | input_vector = output_vector; // Borrow. | ||
144 | } | ||
145 | } | ||
146 | } | ||
147 | |||
148 | void nnQueryArray(const nnNeuralNetwork* net, nnQueryObject* query, const R* input, R* output) { | ||
149 | assert(net); | ||
150 | assert(query); | ||
151 | assert(input); | ||
152 | assert(output); | ||
153 | assert(net->num_layers > 0); | ||
154 | |||
155 | nnMatrix input_vector = nnMatrixMake(net->weights[0].cols, 1); | ||
156 | nnMatrixInit(&input_vector, input); | ||
157 | nnQuery(net, query, &input_vector); | ||
158 | nnMatrixRowToArray(query->network_outputs, 0, output); | ||
159 | } | ||
160 | |||
161 | nnQueryObject* nnMakeQueryObject(const nnNeuralNetwork* net, int num_inputs) { | ||
162 | assert(net); | ||
163 | assert(num_inputs > 0); | ||
164 | assert(net->num_layers > 0); | ||
165 | |||
166 | nnQueryObject* query = calloc(1, sizeof(nnQueryObject)); | ||
167 | if (!query) { | ||
168 | return 0; | ||
169 | } | ||
170 | |||
171 | query->num_layers = net->num_layers; | ||
172 | |||
173 | // Allocate the intermediate layer output matrices. | ||
174 | query->layer_outputs = calloc(net->num_layers, sizeof(nnMatrix)); | ||
175 | if (!query->layer_outputs) { | ||
176 | free(query); | ||
177 | return 0; | ||
178 | } | ||
179 | for (int l = 0; l < net->num_layers; ++l) { | ||
180 | const nnMatrix* layer_weights = &net->weights[l]; | ||
181 | const int layer_output_size = nnLayerOutputSize(layer_weights); | ||
182 | query->layer_outputs[l] = nnMatrixMake(num_inputs, layer_output_size); | ||
183 | } | ||
184 | query->network_outputs = &query->layer_outputs[net->num_layers - 1]; | ||
185 | |||
186 | return query; | ||
187 | } | ||
188 | |||
189 | void nnDeleteQueryObject(nnQueryObject** query) { | ||
190 | if ( (!query) || (!(*query)) ) { | ||
191 | return; | ||
192 | } | ||
193 | if ((*query)->layer_outputs != 0) { | ||
194 | for (int l = 0; l < (*query)->num_layers; ++l) { | ||
195 | nnMatrixDel(&(*query)->layer_outputs[l]); | ||
196 | } | ||
197 | } | ||
198 | free((*query)->layer_outputs); | ||
199 | free(*query); | ||
200 | *query = 0; | ||
201 | } | ||
202 | |||
203 | const nnMatrix* nnNetOutputs(const nnQueryObject* query) { | ||
204 | assert(query); | ||
205 | return query->network_outputs; | ||
206 | } | ||
207 | |||
208 | int nnNetInputSize(const nnNeuralNetwork* net) { | ||
209 | assert(net); | ||
210 | assert(net->num_layers > 0); | ||
211 | return net->weights[0].rows; | ||
212 | } | ||
213 | |||
214 | int nnNetOutputSize(const nnNeuralNetwork* net) { | ||
215 | assert(net); | ||
216 | assert(net->num_layers > 0); | ||
217 | return net->weights[net->num_layers - 1].cols; | ||
218 | } | ||
219 | |||
220 | int nnLayerInputSize(const nnMatrix* weights) { | ||
221 | assert(weights); | ||
222 | return weights->rows; | ||
223 | } | ||
224 | |||
225 | int nnLayerOutputSize(const nnMatrix* weights) { | ||
226 | assert(weights); | ||
227 | return weights->cols; | ||
228 | } | ||