1use crate::optimizers::Optimizer;
2use crate::random::random_array;
3use ndarray::Array2;
4use ndarray_rand::rand_distr::Uniform;
5
6#[derive(Clone, Debug)]
8pub struct LinearGradients {
9 pub weight: Array2<f64>,
10 pub bias: Array2<f64>,
11}
12
13#[derive(Clone, Debug)]
18pub struct LinearLayer {
19 pub weight: Array2<f64>, pub bias: Array2<f64>, pub input_size: usize,
22 pub output_size: usize,
23 input_cache: Option<Array2<f64>>, }
25
26impl LinearLayer {
27 pub fn new(input_size: usize, output_size: usize) -> Self {
36 let scale = (2.0 / (input_size + output_size) as f64).sqrt();
38 let weight_range = scale;
39
40 let weight = random_array(
41 (output_size, input_size),
42 Uniform::new(-weight_range, weight_range),
43 );
44 let bias = Array2::zeros((output_size, 1));
45
46 Self {
47 weight,
48 bias,
49 input_size,
50 output_size,
51 input_cache: None,
52 }
53 }
54
55 pub fn new_zeros(input_size: usize, output_size: usize) -> Self {
57 let weight = Array2::zeros((output_size, input_size));
58 let bias = Array2::zeros((output_size, 1));
59
60 Self {
61 weight,
62 bias,
63 input_size,
64 output_size,
65 input_cache: None,
66 }
67 }
68
69 pub fn from_weights(weight: Array2<f64>, bias: Array2<f64>) -> Self {
71 let (output_size, input_size) = weight.dim();
72 assert_eq!(
73 bias.shape(),
74 &[output_size, 1],
75 "Bias shape must be (output_size, 1)"
76 );
77
78 Self {
79 weight,
80 bias,
81 input_size,
82 output_size,
83 input_cache: None,
84 }
85 }
86
87 pub fn forward(&mut self, input: &Array2<f64>) -> Array2<f64> {
95 let (input_features, _batch_size) = input.dim();
96 assert_eq!(
97 input_features, self.input_size,
98 "Input size {} doesn't match layer input size {}",
99 input_features, self.input_size
100 );
101
102 self.input_cache = Some(input.clone());
104
105 &self.weight.dot(input) + &self.bias
107 }
108
109 pub fn backward(&self, grad_output: &Array2<f64>) -> (LinearGradients, Array2<f64>) {
119 let input = self
120 .input_cache
121 .as_ref()
122 .expect("Input cache not found for backward pass");
123 let (output_features, batch_size) = grad_output.dim();
124 let (input_features, input_batch_size) = input.dim();
125
126 assert_eq!(
127 output_features, self.output_size,
128 "Gradient output size mismatch"
129 );
130 assert_eq!(input_features, self.input_size, "Input size mismatch");
131 assert_eq!(batch_size, input_batch_size, "Batch size mismatch");
132
133 let weight_grad = grad_output.dot(&input.t());
135
136 let bias_grad = grad_output
138 .sum_axis(ndarray::Axis(1))
139 .insert_axis(ndarray::Axis(1));
140
141 let input_grad = self.weight.t().dot(grad_output);
143
144 let gradients = LinearGradients {
145 weight: weight_grad,
146 bias: bias_grad,
147 };
148
149 (gradients, input_grad)
150 }
151
152 pub fn update_parameters<O: Optimizer>(
154 &mut self,
155 gradients: &LinearGradients,
156 optimizer: &mut O,
157 prefix: &str,
158 ) {
159 optimizer.update(
160 &format!("{}_weight", prefix),
161 &mut self.weight,
162 &gradients.weight,
163 );
164 optimizer.update(&format!("{}_bias", prefix), &mut self.bias, &gradients.bias);
165 }
166
167 pub fn zero_gradients(&self) -> LinearGradients {
169 LinearGradients {
170 weight: Array2::zeros(self.weight.raw_dim()),
171 bias: Array2::zeros(self.bias.raw_dim()),
172 }
173 }
174
175 pub fn num_parameters(&self) -> usize {
177 self.weight.len() + self.bias.len()
178 }
179
180 pub fn dimensions(&self) -> (usize, usize) {
182 (self.input_size, self.output_size)
183 }
184
185 pub fn train(&mut self) {
187 }
189
190 pub fn eval(&mut self) {
192 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use crate::optimizers::SGD;
200 use ndarray::arr2;
201
202 #[test]
203 fn test_linear_layer_creation() {
204 let layer = LinearLayer::new(10, 5);
205 assert_eq!(layer.input_size, 10);
206 assert_eq!(layer.output_size, 5);
207 assert_eq!(layer.weight.shape(), &[5, 10]);
208 assert_eq!(layer.bias.shape(), &[5, 1]);
209 }
210
211 #[test]
212 fn test_linear_layer_forward() {
213 let mut layer = LinearLayer::new_zeros(3, 2);
214 let input = arr2(&[[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]); let output = layer.forward(&input);
217 assert_eq!(output.shape(), &[2, 2]); assert!(output.iter().all(|&x| x == 0.0));
221 }
222
223 #[test]
224 fn test_linear_layer_backward() {
225 let mut layer = LinearLayer::new(3, 2);
226 let input = arr2(&[[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]); let grad_output = arr2(&[[1.0, 1.0], [1.0, 1.0]]); let _output = layer.forward(&input);
231
232 let (gradients, input_grad) = layer.backward(&grad_output);
233
234 assert_eq!(gradients.weight.shape(), &[2, 3]);
235 assert_eq!(gradients.bias.shape(), &[2, 1]);
236 assert_eq!(input_grad.shape(), &[3, 2]);
237 }
238
239 #[test]
240 fn test_linear_layer_with_optimizer() {
241 let mut layer = LinearLayer::new(2, 1);
242 let mut optimizer = SGD::new(0.1);
243
244 let input = arr2(&[[1.0], [2.0]]); let target = arr2(&[[3.0]]); let output = layer.forward(&input);
249
250 let grad_output = &output - ⌖
252
253 let (gradients, _) = layer.backward(&grad_output);
255
256 layer.update_parameters(&gradients, &mut optimizer, "linear");
258
259 assert!(layer.weight.iter().any(|&x| x != 0.0) || layer.bias.iter().any(|&x| x != 0.0));
261 }
262
263 #[test]
264 fn test_linear_layer_dimensions() {
265 let layer = LinearLayer::new(128, 10);
266 assert_eq!(layer.dimensions(), (128, 10));
267 assert_eq!(layer.num_parameters(), 128 * 10 + 10); }
269
270 #[test]
271 fn test_from_weights() {
272 let weight = arr2(&[[1.0, 2.0], [3.0, 4.0]]);
273 let bias = arr2(&[[0.5], [-0.5]]);
274
275 let layer = LinearLayer::from_weights(weight.clone(), bias.clone());
276 assert_eq!(layer.weight, weight);
277 assert_eq!(layer.bias, bias);
278 assert_eq!(layer.input_size, 2);
279 assert_eq!(layer.output_size, 2);
280 }
281}