#[derive(Debug, Clone)]
pub struct Linear {
pub w: Vec<f32>,
pub b: Vec<f32>,
pub in_dim: usize,
pub out_dim: usize,
}
impl Linear {
pub fn zeros(in_dim: usize, out_dim: usize) -> Self {
Self {
w: vec![0.0; out_dim * in_dim],
b: vec![0.0; out_dim],
in_dim,
out_dim,
}
}
pub fn xavier(in_dim: usize, out_dim: usize, seed: u64) -> Self {
let mut rng = seed;
let limit = (6.0f32 / (in_dim + out_dim) as f32).sqrt();
let mut w = vec![0.0f32; out_dim * in_dim];
for slot in &mut w {
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
let u = (rng >> 33) as f32 / (u32::MAX as f32);
*slot = (u * 2.0 - 1.0) * limit;
}
Self {
w,
b: vec![0.0; out_dim],
in_dim,
out_dim,
}
}
pub fn forward(&self, x: &[f32]) -> Vec<f32> {
debug_assert_eq!(x.len(), self.in_dim);
let mut y = self.b.clone();
for o in 0..self.out_dim {
let row = o * self.in_dim;
let mut acc = y[o];
for i in 0..self.in_dim {
acc += self.w[row + i] * x[i];
}
y[o] = acc;
}
y
}
pub fn backward(
&self,
x: &[f32],
dy: &[f32],
dw: &mut [f32],
db: &mut [f32],
) -> Vec<f32> {
debug_assert_eq!(x.len(), self.in_dim);
debug_assert_eq!(dy.len(), self.out_dim);
debug_assert_eq!(dw.len(), self.w.len());
debug_assert_eq!(db.len(), self.b.len());
for o in 0..self.out_dim {
db[o] += dy[o];
let row = o * self.in_dim;
for i in 0..self.in_dim {
dw[row + i] += dy[o] * x[i];
}
}
let mut dx = vec![0.0f32; self.in_dim];
for o in 0..self.out_dim {
let row = o * self.in_dim;
for i in 0..self.in_dim {
dx[i] += self.w[row + i] * dy[o];
}
}
dx
}
pub fn sgd_step(&mut self, dw: &[f32], db: &[f32], lr: f32, weight_decay: f32) {
for i in 0..self.w.len() {
self.w[i] -= lr * (dw[i] + weight_decay * self.w[i]);
}
for i in 0..self.b.len() {
self.b[i] -= lr * db[i];
}
}
}
#[derive(Debug, Clone)]
pub struct TemporalGlu {
pub proj: Linear,
pub window: usize,
pub hidden: usize,
}
impl TemporalGlu {
pub fn new(window: usize, hidden: usize, seed: u64) -> Self {
Self {
proj: Linear::xavier(window, 2 * hidden, seed),
window,
hidden,
}
}
pub fn forward(&self, series: &[f32]) -> Vec<f32> {
assert_eq!(series.len(), self.window);
let z = self.proj.forward(series);
glu_activate(&z, self.hidden)
}
pub fn backward(
&self,
series: &[f32],
dy: &[f32],
dproj_w: &mut [f32],
dproj_b: &mut [f32],
) -> Vec<f32> {
let z = self.proj.forward(series);
let dz = glu_backward(&z, dy, self.hidden);
self.proj.backward(series, &dz, dproj_w, dproj_b)
}
}
fn sigmoid(x: f32) -> f32 {
1.0 / (1.0 + (-x).exp())
}
fn glu_activate(z: &[f32], hidden: usize) -> Vec<f32> {
debug_assert_eq!(z.len(), 2 * hidden);
let mut y = vec![0.0f32; hidden];
for i in 0..hidden {
y[i] = z[i] * sigmoid(z[hidden + i]);
}
y
}
fn glu_backward(z: &[f32], dy: &[f32], hidden: usize) -> Vec<f32> {
debug_assert_eq!(z.len(), 2 * hidden);
debug_assert_eq!(dy.len(), hidden);
let mut dz = vec![0.0f32; 2 * hidden];
for i in 0..hidden {
let a = z[i];
let s = sigmoid(z[hidden + i]);
dz[i] = dy[i] * s;
dz[hidden + i] = dy[i] * a * s * (1.0 - s);
}
dz
}
#[derive(Debug, Clone)]
pub struct GraphSageLayer {
pub w_self: Linear,
pub w_neigh: Linear,
pub in_dim: usize,
pub hidden: usize,
}
impl GraphSageLayer {
pub fn new(in_dim: usize, hidden: usize, seed: u64) -> Self {
Self {
w_self: Linear::xavier(in_dim, hidden, seed),
w_neigh: Linear::xavier(in_dim, hidden, seed.wrapping_add(17)),
in_dim,
hidden,
}
}
pub fn forward(&self, x_self: &[f32], x_agg: &[f32]) -> Vec<f32> {
let a = self.w_self.forward(x_self);
let b = self.w_neigh.forward(x_agg);
let mut h = vec![0.0f32; self.hidden];
for i in 0..self.hidden {
h[i] = relu(a[i] + b[i]);
}
h
}
pub fn backward(
&self,
x_self: &[f32],
x_agg: &[f32],
dy: &[f32],
dw_self_w: &mut [f32],
dw_self_b: &mut [f32],
dw_neigh_w: &mut [f32],
dw_neigh_b: &mut [f32],
) -> (Vec<f32>, Vec<f32>) {
let a = self.w_self.forward(x_self);
let b = self.w_neigh.forward(x_agg);
let mut dpre = vec![0.0f32; self.hidden];
for i in 0..self.hidden {
let pre = a[i] + b[i];
dpre[i] = if pre > 0.0 { dy[i] } else { 0.0 };
}
let dx_self = self
.w_self
.backward(x_self, &dpre, dw_self_w, dw_self_b);
let dx_agg = self
.w_neigh
.backward(x_agg, &dpre, dw_neigh_w, dw_neigh_b);
(dx_self, dx_agg)
}
}
fn relu(x: f32) -> f32 {
if x > 0.0 {
x
} else {
0.0
}
}
pub fn softmax(logits: &[f32]) -> Vec<f32> {
let max = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let mut exps: Vec<f32> = logits.iter().map(|v| (v - max).exp()).collect();
let sum: f32 = exps.iter().sum();
if sum > 0.0 {
for e in &mut exps {
*e /= sum;
}
}
exps
}
pub fn cross_entropy(probs: &[f32], label: usize) -> f32 {
let p = probs.get(label).copied().unwrap_or(0.0).clamp(1e-8, 1.0);
-p.ln()
}
pub fn softmax_ce_grad(probs: &[f32], label: usize) -> Vec<f32> {
let mut g = probs.to_vec();
if label < g.len() {
g[label] -= 1.0;
}
g
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn linear_forward_identity_bias() {
let mut layer = Linear::zeros(2, 2);
layer.w = vec![1.0, 0.0, 0.0, 1.0];
layer.b = vec![1.0, -1.0];
let y = layer.forward(&[3.0, 4.0]);
assert!((y[0] - 4.0).abs() < 1e-5);
assert!((y[1] - 3.0).abs() < 1e-5);
}
#[test]
fn linear_backward_matches_finite_diff() {
let layer = Linear::xavier(3, 2, 42);
let x = vec![0.5f32, -0.2, 1.0];
let dy = vec![1.0f32, -0.5];
let mut dw = vec![0.0; layer.w.len()];
let mut db = vec![0.0; layer.b.len()];
let _dx = layer.backward(&x, &dy, &mut dw, &mut db);
let eps = 1e-3f32;
let mut layer_p = layer.clone();
layer_p.w[0] += eps;
let mut layer_m = layer.clone();
layer_m.w[0] -= eps;
let loss = |l: &Linear| {
let y = l.forward(&x);
y[0] * dy[0] + y[1] * dy[1]
};
let fd = (loss(&layer_p) - loss(&layer_m)) / (2.0 * eps);
assert!((dw[0] - fd).abs() < 1e-2, "dw[0]={:.4} fd={:.4}", dw[0], fd);
}
#[test]
fn glu_forward_gate_zero() {
let mut glu = TemporalGlu::new(4, 2, 1);
glu.proj = Linear::zeros(4, 4);
let y = glu.forward(&[1.0, 2.0, 3.0, 4.0]);
assert_eq!(y.len(), 2);
assert!((y[0]).abs() < 1e-6);
assert!((y[1]).abs() < 1e-6);
}
#[test]
fn graphsage_relu_positive_path() {
let mut sage = GraphSageLayer::new(2, 2, 7);
sage.w_self = Linear::zeros(2, 2);
sage.w_self.w = vec![1.0, 0.0, 0.0, 1.0];
sage.w_neigh = Linear::zeros(2, 2);
let h = sage.forward(&[1.0, 2.0], &[0.0, 0.0]);
assert!((h[0] - 1.0).abs() < 1e-5);
assert!((h[1] - 2.0).abs() < 1e-5);
}
#[test]
fn softmax_sums_to_one() {
let p = softmax(&[1.0, 2.0, 3.0]);
let s: f32 = p.iter().sum();
assert!((s - 1.0).abs() < 1e-5);
assert!(p[2] > p[1] && p[1] > p[0]);
}
}