clawft_kernel/sensor_graph/
layers.rs1#[derive(Debug, Clone)]
7pub struct Linear {
8 pub w: Vec<f32>,
10 pub b: Vec<f32>,
12 pub in_dim: usize,
13 pub out_dim: usize,
14}
15
16impl Linear {
17 pub fn zeros(in_dim: usize, out_dim: usize) -> Self {
19 Self {
20 w: vec![0.0; out_dim * in_dim],
21 b: vec![0.0; out_dim],
22 in_dim,
23 out_dim,
24 }
25 }
26
27 pub fn xavier(in_dim: usize, out_dim: usize, seed: u64) -> Self {
29 let mut rng = seed;
30 let limit = (6.0f32 / (in_dim + out_dim) as f32).sqrt();
31 let mut w = vec![0.0f32; out_dim * in_dim];
32 for slot in &mut w {
33 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
34 let u = (rng >> 33) as f32 / (u32::MAX as f32);
35 *slot = (u * 2.0 - 1.0) * limit;
36 }
37 Self {
38 w,
39 b: vec![0.0; out_dim],
40 in_dim,
41 out_dim,
42 }
43 }
44
45 pub fn forward(&self, x: &[f32]) -> Vec<f32> {
47 debug_assert_eq!(x.len(), self.in_dim);
48 let mut y = self.b.clone();
49 for o in 0..self.out_dim {
50 let row = o * self.in_dim;
51 let mut acc = y[o];
52 for i in 0..self.in_dim {
53 acc += self.w[row + i] * x[i];
54 }
55 y[o] = acc;
56 }
57 y
58 }
59
60 pub fn backward(
64 &self,
65 x: &[f32],
66 dy: &[f32],
67 dw: &mut [f32],
68 db: &mut [f32],
69 ) -> Vec<f32> {
70 debug_assert_eq!(x.len(), self.in_dim);
71 debug_assert_eq!(dy.len(), self.out_dim);
72 debug_assert_eq!(dw.len(), self.w.len());
73 debug_assert_eq!(db.len(), self.b.len());
74
75 for o in 0..self.out_dim {
76 db[o] += dy[o];
77 let row = o * self.in_dim;
78 for i in 0..self.in_dim {
79 dw[row + i] += dy[o] * x[i];
80 }
81 }
82
83 let mut dx = vec![0.0f32; self.in_dim];
84 for o in 0..self.out_dim {
85 let row = o * self.in_dim;
86 for i in 0..self.in_dim {
87 dx[i] += self.w[row + i] * dy[o];
88 }
89 }
90 dx
91 }
92
93 pub fn sgd_step(&mut self, dw: &[f32], db: &[f32], lr: f32, weight_decay: f32) {
95 for i in 0..self.w.len() {
96 self.w[i] -= lr * (dw[i] + weight_decay * self.w[i]);
97 }
98 for i in 0..self.b.len() {
99 self.b[i] -= lr * db[i];
100 }
101 }
102}
103
104#[derive(Debug, Clone)]
106pub struct TemporalGlu {
107 pub proj: Linear,
109 pub window: usize,
110 pub hidden: usize,
111}
112
113impl TemporalGlu {
114 pub fn new(window: usize, hidden: usize, seed: u64) -> Self {
115 Self {
116 proj: Linear::xavier(window, 2 * hidden, seed),
117 window,
118 hidden,
119 }
120 }
121
122 pub fn forward(&self, series: &[f32]) -> Vec<f32> {
124 assert_eq!(series.len(), self.window);
125 let z = self.proj.forward(series);
126 glu_activate(&z, self.hidden)
127 }
128
129 pub fn backward(
131 &self,
132 series: &[f32],
133 dy: &[f32],
134 dproj_w: &mut [f32],
135 dproj_b: &mut [f32],
136 ) -> Vec<f32> {
137 let z = self.proj.forward(series);
138 let dz = glu_backward(&z, dy, self.hidden);
139 self.proj.backward(series, &dz, dproj_w, dproj_b)
140 }
141}
142
143fn sigmoid(x: f32) -> f32 {
144 1.0 / (1.0 + (-x).exp())
145}
146
147fn glu_activate(z: &[f32], hidden: usize) -> Vec<f32> {
148 debug_assert_eq!(z.len(), 2 * hidden);
149 let mut y = vec![0.0f32; hidden];
150 for i in 0..hidden {
151 y[i] = z[i] * sigmoid(z[hidden + i]);
152 }
153 y
154}
155
156fn glu_backward(z: &[f32], dy: &[f32], hidden: usize) -> Vec<f32> {
157 debug_assert_eq!(z.len(), 2 * hidden);
158 debug_assert_eq!(dy.len(), hidden);
159 let mut dz = vec![0.0f32; 2 * hidden];
160 for i in 0..hidden {
161 let a = z[i];
162 let s = sigmoid(z[hidden + i]);
163 dz[i] = dy[i] * s;
165 dz[hidden + i] = dy[i] * a * s * (1.0 - s);
166 }
167 dz
168}
169
170#[derive(Debug, Clone)]
175pub struct GraphSageLayer {
176 pub w_self: Linear,
177 pub w_neigh: Linear,
178 pub in_dim: usize,
179 pub hidden: usize,
180}
181
182impl GraphSageLayer {
183 pub fn new(in_dim: usize, hidden: usize, seed: u64) -> Self {
184 Self {
185 w_self: Linear::xavier(in_dim, hidden, seed),
186 w_neigh: Linear::xavier(in_dim, hidden, seed.wrapping_add(17)),
187 in_dim,
188 hidden,
189 }
190 }
191
192 pub fn forward(&self, x_self: &[f32], x_agg: &[f32]) -> Vec<f32> {
194 let a = self.w_self.forward(x_self);
195 let b = self.w_neigh.forward(x_agg);
196 let mut h = vec![0.0f32; self.hidden];
197 for i in 0..self.hidden {
198 h[i] = relu(a[i] + b[i]);
199 }
200 h
201 }
202
203 pub fn backward(
205 &self,
206 x_self: &[f32],
207 x_agg: &[f32],
208 dy: &[f32],
209 dw_self_w: &mut [f32],
210 dw_self_b: &mut [f32],
211 dw_neigh_w: &mut [f32],
212 dw_neigh_b: &mut [f32],
213 ) -> (Vec<f32>, Vec<f32>) {
214 let a = self.w_self.forward(x_self);
215 let b = self.w_neigh.forward(x_agg);
216 let mut dpre = vec![0.0f32; self.hidden];
218 for i in 0..self.hidden {
219 let pre = a[i] + b[i];
220 dpre[i] = if pre > 0.0 { dy[i] } else { 0.0 };
221 }
222 let dx_self = self
223 .w_self
224 .backward(x_self, &dpre, dw_self_w, dw_self_b);
225 let dx_agg = self
226 .w_neigh
227 .backward(x_agg, &dpre, dw_neigh_w, dw_neigh_b);
228 (dx_self, dx_agg)
229 }
230}
231
232fn relu(x: f32) -> f32 {
233 if x > 0.0 {
234 x
235 } else {
236 0.0
237 }
238}
239
240pub fn softmax(logits: &[f32]) -> Vec<f32> {
242 let max = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
243 let mut exps: Vec<f32> = logits.iter().map(|v| (v - max).exp()).collect();
244 let sum: f32 = exps.iter().sum();
245 if sum > 0.0 {
246 for e in &mut exps {
247 *e /= sum;
248 }
249 }
250 exps
251}
252
253pub fn cross_entropy(probs: &[f32], label: usize) -> f32 {
255 let p = probs.get(label).copied().unwrap_or(0.0).clamp(1e-8, 1.0);
256 -p.ln()
257}
258
259pub fn softmax_ce_grad(probs: &[f32], label: usize) -> Vec<f32> {
261 let mut g = probs.to_vec();
262 if label < g.len() {
263 g[label] -= 1.0;
264 }
265 g
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[test]
273 fn linear_forward_identity_bias() {
274 let mut layer = Linear::zeros(2, 2);
275 layer.w = vec![1.0, 0.0, 0.0, 1.0];
277 layer.b = vec![1.0, -1.0];
278 let y = layer.forward(&[3.0, 4.0]);
279 assert!((y[0] - 4.0).abs() < 1e-5);
280 assert!((y[1] - 3.0).abs() < 1e-5);
281 }
282
283 #[test]
284 fn linear_backward_matches_finite_diff() {
285 let layer = Linear::xavier(3, 2, 42);
286 let x = vec![0.5f32, -0.2, 1.0];
287 let dy = vec![1.0f32, -0.5];
288 let mut dw = vec![0.0; layer.w.len()];
289 let mut db = vec![0.0; layer.b.len()];
290 let _dx = layer.backward(&x, &dy, &mut dw, &mut db);
291
292 let eps = 1e-3f32;
294 let mut layer_p = layer.clone();
295 layer_p.w[0] += eps;
296 let mut layer_m = layer.clone();
297 layer_m.w[0] -= eps;
298 let loss = |l: &Linear| {
299 let y = l.forward(&x);
300 y[0] * dy[0] + y[1] * dy[1]
301 };
302 let fd = (loss(&layer_p) - loss(&layer_m)) / (2.0 * eps);
303 assert!((dw[0] - fd).abs() < 1e-2, "dw[0]={:.4} fd={:.4}", dw[0], fd);
304 }
305
306 #[test]
307 fn glu_forward_gate_zero() {
308 let mut glu = TemporalGlu::new(4, 2, 1);
309 glu.proj = Linear::zeros(4, 4);
311 let y = glu.forward(&[1.0, 2.0, 3.0, 4.0]);
312 assert_eq!(y.len(), 2);
313 assert!((y[0]).abs() < 1e-6);
314 assert!((y[1]).abs() < 1e-6);
315 }
316
317 #[test]
318 fn graphsage_relu_positive_path() {
319 let mut sage = GraphSageLayer::new(2, 2, 7);
320 sage.w_self = Linear::zeros(2, 2);
321 sage.w_self.w = vec![1.0, 0.0, 0.0, 1.0];
322 sage.w_neigh = Linear::zeros(2, 2);
323 let h = sage.forward(&[1.0, 2.0], &[0.0, 0.0]);
325 assert!((h[0] - 1.0).abs() < 1e-5);
326 assert!((h[1] - 2.0).abs() < 1e-5);
327 }
328
329 #[test]
330 fn softmax_sums_to_one() {
331 let p = softmax(&[1.0, 2.0, 3.0]);
332 let s: f32 = p.iter().sum();
333 assert!((s - 1.0).abs() < 1e-5);
334 assert!(p[2] > p[1] && p[1] > p[0]);
335 }
336}