use petgraph::EdgeType;
use petgraph::graph::NodeIndex;
use petgraph::stable_graph::StableGraph;
#[derive(Debug, Clone, Copy)]
pub struct OjaConfig {
pub learning_rate: f64,
}
impl Default for OjaConfig {
fn default() -> Self {
Self {
learning_rate: 0.01,
}
}
}
pub fn oja_update<N, Ty: EdgeType>(
graph: &mut StableGraph<N, f64, Ty>,
pre_activations: &[(NodeIndex, f64)],
post_activations: &[(NodeIndex, f64)],
config: &OjaConfig,
) -> usize {
let mut modified = 0;
for &(post_node, y) in post_activations {
for &(pre_node, x) in pre_activations {
if pre_node == post_node {
continue;
}
if let Some(idx) = graph.find_edge(pre_node, post_node)
&& let Some(w) = graph.edge_weight_mut(idx)
{
*w += config.learning_rate * y * (x - *w * y);
modified += 1;
}
}
}
modified
}
#[cfg(test)]
mod tests {
use super::*;
use petgraph::stable_graph::StableDiGraph;
#[test]
fn oja_converges_toward_normalization() {
let mut g = StableDiGraph::<(), f64>::new();
let a = g.add_node(());
let b = g.add_node(());
g.add_edge(a, b, 0.5);
let config = OjaConfig { learning_rate: 0.1 };
for _ in 0..100 {
oja_update(&mut g, &[(a, 1.0)], &[(b, 1.0)], &config);
}
let w = *g.edge_weight(0.into()).unwrap();
assert!((w - 1.0).abs() < 0.01);
}
#[test]
fn oja_no_edge_no_effect() {
let mut g = StableDiGraph::<(), f64>::new();
let a = g.add_node(());
let b = g.add_node(());
let modified = oja_update(&mut g, &[(a, 1.0)], &[(b, 1.0)], &OjaConfig::default());
assert_eq!(modified, 0);
assert_eq!(g.edge_count(), 0);
}
#[test]
fn oja_self_loop_skipped() {
let mut g = StableDiGraph::<(), f64>::new();
let a = g.add_node(());
g.add_edge(a, a, 0.5);
let modified = oja_update(&mut g, &[(a, 1.0)], &[(a, 1.0)], &OjaConfig::default());
assert_eq!(modified, 0);
assert_eq!(*g.edge_weight(0.into()).unwrap(), 0.5);
}
#[test]
fn oja_high_weight_decreases() {
let mut g = StableDiGraph::<(), f64>::new();
let a = g.add_node(());
let b = g.add_node(());
g.add_edge(a, b, 2.0);
let config = OjaConfig { learning_rate: 0.1 };
oja_update(&mut g, &[(a, 1.0)], &[(b, 1.0)], &config);
assert!(*g.edge_weight(0.into()).unwrap() < 2.0);
}
#[test]
fn oja_low_weight_increases() {
let mut g = StableDiGraph::<(), f64>::new();
let a = g.add_node(());
let b = g.add_node(());
g.add_edge(a, b, 0.1);
let config = OjaConfig { learning_rate: 0.1 };
oja_update(&mut g, &[(a, 1.0)], &[(b, 1.0)], &config);
assert!(*g.edge_weight(0.into()).unwrap() > 0.1);
}
}