variational_regression/
lib.rs

1//!
2//! This crate provides functionality for regression models trained using variational inference
3//!
4
5pub mod distribution;
6pub mod error;
7pub mod linear;
8pub mod logistic;
9mod math;
10
11pub use distribution::{ScalarDistribution, GammaDistribution, GaussianDistribution, BernoulliDistribution};
12pub use linear::{VariationalLinearRegression, LinearTrainConfig};
13pub use logistic::{VariationalLogisticRegression, LogisticTrainConfig};
14pub use error::RegressionError;
15
16use serde::{Serialize, Deserialize};
17use nalgebra::{DMatrix, DVector};
18type DenseMatrix = DMatrix<f64>;
19type DenseVector  = DVector<f64>;
20
21///
22/// Represents a trained variational regression model with
23/// the specified predictive distribution type
24/// 
25pub trait VariationalRegression<D: ScalarDistribution> {
26
27    ///
28    /// Computes the predictive distribution for the provided features
29    /// 
30    /// # Arguments
31    /// 
32    /// `features` - The input features
33    /// 
34    fn predict(&self, features: &[f64]) -> Result<D, RegressionError>;
35
36    ///
37    /// Provides the estimated model weights
38    /// 
39    fn weights(&self) -> &[f64];
40
41    ///
42    /// Provides the estimated model bias, if specified for training
43    /// 
44    fn bias(&self) -> Option<f64>;
45}
46
47///
48/// Represents two dimensional, dense feature data
49/// 
50pub trait Features {
51
52    ///
53    /// Processes the features into a dense matrix
54    /// 
55    /// # Arguments
56    /// 
57    /// `bias` - Whether or not to include a bias term, which
58    ///          results in prepending a column of 1's
59    ///          
60    fn into_matrix(self, bias: bool) -> DenseMatrix;
61}
62
63impl Features for Vec<Vec<f64>> {
64    fn into_matrix(self, bias: bool) -> DenseMatrix {
65        (&self).into_matrix(bias)
66    }
67}
68
69impl Features for &Vec<Vec<f64>> {
70    fn into_matrix(self, bias: bool) -> DenseMatrix {
71        let temp: Vec<_> = self.iter().map(|row| row.as_slice()).collect();
72        temp.as_slice().into_matrix(bias)
73    }
74}
75
76impl Features for &[&[f64]] {
77    fn into_matrix(self, bias: bool) -> DenseMatrix {
78        let offset = if bias { 1 } else { 0 };
79        let n = self.len();
80        let d = self[0].len() + offset;
81        let mut x = DenseMatrix::zeros(n, d);
82        for i in 0..n {
83            if bias {
84                x[(i, 0)] = 1.0;
85            }
86            for j in offset..d {
87                x[(i, j)] = self[i][j - offset];
88            }
89        }
90        return x;
91    }
92}
93
94///
95/// Represents continuous, real-valued label data
96/// 
97pub trait RealLabels {
98
99    ///
100    /// Processes the labels into a dense vector
101    /// 
102    fn into_vector(self) -> DenseVector;
103}
104
105impl RealLabels for Vec<f64> {
106    fn into_vector(self) -> DenseVector {
107        DenseVector::from_vec(self)
108    }
109}
110
111impl RealLabels for &Vec<f64> {
112    fn into_vector(self) -> DenseVector {
113        self.as_slice().into_vector()
114    }
115}
116
117impl RealLabels for &[f64] {
118    fn into_vector(self) -> DenseVector {
119        DenseVector::from_column_slice(self)
120    }
121}
122
123///
124/// Represents binary label data
125/// 
126pub trait BinaryLabels {
127
128    ///
129    /// Processes the labels into a dense vector
130    /// 
131    fn into_vector(self) -> DenseVector;
132}
133
134impl BinaryLabels for Vec<bool> {
135    fn into_vector(self) -> DenseVector {
136        self.as_slice().into_vector()
137    }
138}
139
140impl BinaryLabels for &Vec<bool> {
141    fn into_vector(self) -> DenseVector {
142        self.as_slice().into_vector()
143    }
144}
145
146impl BinaryLabels for &[bool] {
147    fn into_vector(self) -> DenseVector {
148        let iter = self.iter().map(|&label| if label { 1.0 } else { 0.0 });
149        DenseVector::from_iterator(self.len(), iter)
150    }
151}
152
153pub (crate) fn design_vector(features: &[f64], bias: bool) -> DenseVector {
154    let offset = if bias { 1 } else { 0 };
155    let d = features.len() + offset;
156    let mut x = DenseVector::zeros(d);
157    if bias {
158        x[0] = 1.0;
159    }
160    for j in offset..d {
161        x[j] = features[j - offset];
162    }
163    return x;
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub (crate) struct Stats {
168    pub mean: f64,
169    pub sd: f64
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub (crate) struct Standardizer {
174    stats: Vec<Stats>
175}
176
177impl Standardizer {
178
179    pub fn fit(features: &DenseMatrix) -> Standardizer {
180        let stats = (0..features.ncols()).map(|j| {
181            let mut sum = 0.0;
182            for i in 0..features.nrows() {
183                sum += features[(i, j)];
184            }
185            let mean = sum / features.nrows() as f64;
186            let mut sse = 0.0;
187            for i in 0..features.nrows() {
188                let val = features[(i, j)];
189                sse += (val - mean) * (val - mean);
190            }
191            let sd = (sse / (features.nrows() - 1) as f64).sqrt();
192            Stats { mean, sd }
193        }).collect();
194        Standardizer { stats }
195    }
196
197    pub fn transform_matrix(&self, features: &mut DenseMatrix) {
198        for j in 0..features.ncols() {
199            let stats = &self.stats[j];
200            if stats.sd > 0.0 {
201                for i in 0..features.nrows() {
202                    let val = features[(i, j)];
203                    features[(i, j)] = (val - stats.mean) / stats.sd;
204                }
205            }
206        }
207    }
208
209    pub fn transform_vector(&self, features: &mut DenseVector) {
210        for j in 0..features.len() {
211            let stats = &self.stats[j];
212            if stats.sd > 0.0 {
213                let val = features[j];
214                features[j] = (val - stats.mean) / stats.sd;
215            }
216        }
217    }
218}
219
220pub (crate) fn get_weights<'a>(includes_bias: bool, params: &'a DenseVector) -> &'a [f64] {
221    let offset = if includes_bias { 1 } else { 0 };
222    &params.as_slice()[offset..]
223}
224
225pub (crate) fn get_bias(includes_bias: bool, params: &DenseVector) -> Option<f64> {
226    if includes_bias {
227        Some( params[0])
228    } else {
229        None
230    }
231}