1use std::borrow::Borrow;
4
5use super::{Matrix, RowVector};
6
7pub struct LinearLayer<const I: usize, const O: usize> {
9 weights: Matrix<I, O>,
10 biases: RowVector<O>,
11 learning_rate: f32,
12}
13
14impl<const I: usize, const O: usize> LinearLayer<I, O> {
15 #[must_use]
17 pub fn new(learning_rate: f32) -> Self {
18 #[allow(
21 clippy::cast_precision_loss,
22 reason = "We're not dealing with huge matrices here."
23 )]
24 let scale = (2.0 / (I + O) as f32).sqrt();
25 let mut weights = Matrix::<I, O>::new_random();
26 weights.apply(|x| *x = (*x - 0.5) * 2.0 * scale);
27 let biases = RowVector::zeros();
28 Self {
29 weights,
30 biases,
31 learning_rate,
32 }
33 }
34
35 pub fn feedforward(&self, input: &RowVector<I>) -> (RowVector<O>, RowVector<O>) {
37 let output = input * self.weights + self.biases;
38 let activated_output = output.map(sigmoid);
39 (output, activated_output)
40 }
41
42 pub fn backpropagate(
55 &mut self,
56 input: &RowVector<I>,
57 output: &RowVector<O>,
58 error_next: &RowVector<O>,
59 ) -> RowVector<I> {
60 let gradient = output.map(d_sigmoid).component_mul(error_next);
62
63 let weights_delta = input.transpose() * gradient;
65
66 self.weights -= weights_delta * self.learning_rate;
68 self.biases -= gradient * self.learning_rate;
69
70 error_next * self.weights.transpose()
72 }
73}
74
75pub trait NeuralNetwork<const I: usize, const O: usize> {
91 type LayerOutputs: LayerOutputs<O>;
93
94 fn feedforward(&self, input: &RowVector<I>) -> Self::LayerOutputs;
96
97 fn backpropagate(
105 &mut self,
106 input: &RowVector<I>,
107 output: &RowVector<O>,
108 layer_outputs: Self::LayerOutputs,
109 ) -> f32;
110
111 fn train_once<BI, BO>(&mut self, input: BI, output: BO) -> f32
113 where
114 BI: Borrow<RowVector<I>>,
115 BO: Borrow<RowVector<O>>,
116 {
117 let (input, output) = (input.borrow(), output.borrow());
118 let layer_outputs = self.feedforward(input);
119 self.backpropagate(input, output, layer_outputs)
120 }
121
122 fn train<D, T>(&mut self, data: D, mut callback: Option<impl FnMut(usize, f32)>) -> (usize, f32)
124 where
125 D: IntoIterator<Item = T>,
126 T: Borrow<(RowVector<I>, RowVector<O>)>,
127 {
128 let data = data.into_iter();
129 let mut total_loss = 0.0;
130 let mut count = 0;
131
132 for item in data {
133 let (input, output) = item.borrow();
134 let single_loss = self.train_once(input, output);
135 total_loss += single_loss;
136 if let Some(ref mut cb) = callback {
137 cb(count, single_loss);
138 }
139 count += 1;
140 }
141
142 if count > 0 {
143 #[allow(
144 clippy::cast_precision_loss,
145 reason = "We're calculating ratios, so precision loss is acceptable."
146 )]
147 (count, total_loss / count as f32)
148 } else {
149 (0, 0.0)
150 }
151 }
152
153 fn predict(&self, input: &RowVector<I>) -> RowVector<O> {
155 let layer_outputs = self.feedforward(input);
156 layer_outputs.get_output()
157 }
158}
159
160pub trait LayerOutputs<const O: usize> {
162 fn get_output(self) -> RowVector<O>;
164}
165
166pub struct SimpleNeuralNetwork<const I: usize, const H: usize, const O: usize> {
168 input_layer: LinearLayer<I, H>,
169 output_layer: LinearLayer<H, O>,
170}
171
172impl<const I: usize, const H: usize, const O: usize> NeuralNetwork<I, O>
173 for SimpleNeuralNetwork<I, H, O>
174{
175 type LayerOutputs = SimpleLayerOutputs<H, O>;
176
177 fn feedforward(&self, input: &RowVector<I>) -> Self::LayerOutputs {
178 let (hidden_output, activated_hidden_output) = self.input_layer.feedforward(input);
179 let (final_output, activated_final_output) =
180 self.output_layer.feedforward(&activated_hidden_output);
181 SimpleLayerOutputs {
182 hidden_output,
183 final_output,
184 activated_hidden_output,
185 activated_final_output,
186 }
187 }
188
189 fn backpropagate(
190 &mut self,
191 input: &RowVector<I>,
192 output: &RowVector<O>,
193 layer_outputs: Self::LayerOutputs,
194 ) -> f32 {
195 let error_output = layer_outputs.activated_final_output - output;
197
198 let error_hidden = self.output_layer.backpropagate(
200 &layer_outputs.activated_hidden_output,
201 &layer_outputs.final_output,
202 &error_output,
203 );
204 self.input_layer
205 .backpropagate(input, &layer_outputs.hidden_output, &error_hidden);
206
207 error_output.map(|e| e * e).sum()
209 }
210}
211
212impl<const I: usize, const H: usize, const O: usize> SimpleNeuralNetwork<I, H, O> {
213 #[must_use]
215 pub fn new(learning_rate: f32) -> Self {
216 Self {
217 input_layer: LinearLayer::new(learning_rate),
218 output_layer: LinearLayer::new(learning_rate),
219 }
220 }
221}
222
223#[allow(
225 clippy::struct_field_names,
226 reason = "Names reflect their purpose in the network."
227)]
228pub struct SimpleLayerOutputs<const H: usize, const O: usize> {
229 hidden_output: RowVector<H>,
230 final_output: RowVector<O>,
231 activated_hidden_output: RowVector<H>,
232 activated_final_output: RowVector<O>, }
234
235impl<const H: usize, const O: usize> LayerOutputs<O> for SimpleLayerOutputs<H, O> {
236 fn get_output(self) -> RowVector<O> {
237 self.activated_final_output }
239}
240
241fn sigmoid(x: f32) -> f32 {
244 1.0 / (1.0 + (-x).exp())
245}
246
247fn d_sigmoid(x: f32) -> f32 {
248 let y = sigmoid(x);
249 y * (1.0 - y)
250}