fetish_lib/
fourier_feature_collection.rs

1extern crate ndarray;
2extern crate ndarray_linalg;
3
4use ndarray::*;
5
6use crate::feature_collection::*;
7use crate::linalg_utils::*;
8use rand::prelude::*;
9use crate::params::*;
10
11///A feature collection of random fourier features
12#[derive(Clone)]
13pub struct FourierFeatureCollection {
14    in_dimensions : usize,
15    num_features : usize,
16    alpha : f32,
17    ws : Array2<f32> //Matrix which is num_features x in_dimensions
18}
19
20impl FourierFeatureCollection {
21    ///Constructs a new collection of (alpha-scaled) random fourier features for the given number of
22    ///input dimensions, the given number of features, and the given closure which samples random
23    ///coefficient vectors for the fourier features.
24    ///Concretely, the features are of the form `x -> alpha * <w_i, x>`, where each `w_i` is
25    ///a sampled coefficient vector.
26    pub fn new(in_dimensions: usize, num_features : usize,
27               alpha : f32,
28               generator : fn(&mut ThreadRng, usize) -> Array1<f32>) -> FourierFeatureCollection {
29
30        let mut ws = Array::zeros((num_features, in_dimensions));
31        let mut rng = rand::thread_rng();
32        for i in 0..num_features {
33            let feature = generator(&mut rng, in_dimensions);
34            for j in 0..in_dimensions {
35                ws[[i, j]] = feature[[j,]];
36            }
37        }
38
39        FourierFeatureCollection {
40            in_dimensions,
41            num_features,
42            alpha,
43            ws
44        }
45    }
46}
47
48impl FeatureCollection for FourierFeatureCollection {
49    fn get_features(&self, in_vec: ArrayView1<f32>) -> Array1<f32> {
50        let dotted = self.ws.dot(&in_vec);
51        let sine = dotted.mapv(f32::sin);
52        let cosine = dotted.mapv(f32::cos);
53        
54        let result = stack(Axis(0), &[sine.view(), cosine.view()]).unwrap();
55
56        self.alpha * result
57    }
58
59    fn get_jacobian(&self, in_vec: ArrayView1<f32>) -> Array2<f32> {
60        //The derivative is of the form d/dx f(Wx) = J_f(Wx) W x
61        //only here, J_f(Wx) is the concatenation of two diagonal mats
62        //Get the dotted vector, and compute the components of J_f(Wx)
63        let dotted = self.ws.dot(&in_vec);
64        let cos = dotted.mapv(f32::cos);
65        let neg_sine = -dotted.mapv(f32::sin);
66        
67        let part_one = scale_rows(self.ws.view(), cos.view());
68        let part_two = scale_rows(self.ws.view(), neg_sine.view());
69        
70        let result = stack(Axis(0), &[part_one.view(), part_two.view()]).unwrap()
71            .into_dimensionality::<Ix2>().unwrap();
72
73        self.alpha * result
74    }
75
76    fn get_in_dimensions(&self) -> usize {
77        self.in_dimensions
78    }
79    
80    fn get_dimension(&self) -> usize {
81        self.num_features * 2
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use crate::test_utils::*;
89    use crate::rand_utils::*;
90
91    #[test]
92    fn empirical_jacobian_is_jacobian() {
93        let mut successes : usize = 0;
94        for _ in 0..10 {
95            let fourier_feature_collection = FourierFeatureCollection::new(10, 15, 1.0f32, gen_nsphere_random);
96            let in_vec = random_vector(10);
97            let jacobian = fourier_feature_collection.get_jacobian(in_vec.view());
98            let empirical_jacobian = empirical_jacobian(|x| fourier_feature_collection.get_features(x),
99                                                            in_vec.view());
100            let test = are_equal_matrices_to_within(jacobian.view(), empirical_jacobian.view(), 1.0f32, false);
101            if (test) {
102                successes += 1;
103            }
104        }
105        if (successes < 5) {
106            panic!();
107        }
108    }
109}