vikos/
model.rs

1//! Implementations of `Model` trait
2
3use crate::{
4    array,
5    linear_algebra::{FixDimension, Vector},
6    Model,
7};
8use serde_derive::{Deserialize, Serialize};
9
10impl Model for f64 {
11    type Features = ();
12    type Target = f64;
13
14    fn num_coefficients(&self) -> usize {
15        1
16    }
17
18    fn coefficient(&mut self, coefficient: usize) -> &mut f64 {
19        match coefficient {
20            0 => self,
21            _ => panic!("coefficient index out of range"),
22        }
23    }
24
25    fn predict(&self, _: &()) -> f64 {
26        *self
27    }
28
29    fn gradient(&self, coefficient: usize, _: &()) -> f64 {
30        match coefficient {
31            0 => 1.0,
32            _ => panic!("coefficient index out of range"),
33        }
34    }
35}
36
37/// Models the target as `y = m * x + c`
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct Linear<V> {
40    /// Slope
41    pub m: V,
42    /// Offset
43    pub c: f64,
44}
45
46impl<V: Vector> Linear<V> {
47    /// Create a linear model whose features have the specified dimension. If the models internal
48    /// Vector type has a fixed dimension known at compile time you can use `default` instead.
49    pub fn with_feature_dimension(dimension: usize) -> Self {
50        Linear {
51            m: V::zero_from_dimension(dimension),
52            c: 0f64,
53        }
54    }
55}
56
57impl<V> Default for Linear<V>
58where
59    V: FixDimension,
60{
61    fn default() -> Self {
62        Linear {
63            m: V::zero(),
64            c: 0f64,
65        }
66    }
67}
68
69impl<V> Model for Linear<V>
70where
71    V: Vector,
72{
73    type Features = V;
74    type Target = f64;
75
76    fn num_coefficients(&self) -> usize {
77        self.m.dimension() + 1
78    }
79
80    fn coefficient(&mut self, coefficient: usize) -> &mut f64 {
81        if coefficient == self.m.dimension() {
82            &mut self.c
83        } else {
84            self.m.at_mut(coefficient)
85        }
86    }
87
88    fn predict(&self, input: &V) -> f64 {
89        self.m.dot(input) + self.c
90    }
91
92    fn gradient(&self, coefficient: usize, input: &V) -> f64 {
93        if coefficient == self.m.dimension() {
94            1.0 //derive by c
95        } else {
96            input.at(coefficient) //derive by m
97        }
98    }
99}
100
101/// Models target as `y = 1/(1+e^(m * x + c))`
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct Logistic<V>(Linear<V>);
104
105impl<V: Vector> Logistic<V> {
106    /// Create a linear model whose features have the specified dimension. If the models internal
107    /// Vector type has a fixed dimension known at compile time you can use `default` instead.
108    pub fn with_feature_dimension(dimension: usize) -> Self {
109        Logistic(Linear::with_feature_dimension(dimension))
110    }
111}
112
113impl<V> Default for Logistic<V>
114where
115    V: FixDimension,
116{
117    fn default() -> Self {
118        Logistic(Linear::default())
119    }
120}
121
122impl<V> Model for Logistic<V>
123where
124    Linear<V>: Model<Features = V, Target = f64>,
125{
126    type Features = V;
127    type Target = f64;
128
129    fn num_coefficients(&self) -> usize {
130        self.0.num_coefficients()
131    }
132
133    fn coefficient(&mut self, coefficient: usize) -> &mut f64 {
134        self.0.coefficient(coefficient)
135    }
136
137    fn predict(&self, input: &V) -> f64 {
138        1.0 / (1.0 + self.0.predict(input).exp())
139    }
140
141    fn gradient(&self, coefficient: usize, input: &V) -> f64 {
142        let p = self.predict(input);
143        -p * (1.0 - p) * self.0.gradient(coefficient, input)
144    }
145}
146
147/// Models the target as `y = g(m*x + c)`
148///
149/// # Example
150///
151/// Logistic regression implemented using a generalized linear model. This is just for
152/// demonstration purposes. For this usecase you would usally use `Logistic`.
153///
154/// ```
155/// # use vikos::{model, teacher, cost, learn_history};
156/// # let history = [(0.0, true)];
157/// let mut model = model::GeneralizedLinearModel::new(|x| 1.0 / (1.0 + x.exp()),
158///                                                    |x| -x.exp() / (1.0 + x.exp()).powi(2) );
159/// let teacher = teacher::GradientDescent { learning_rate: 0.3 };
160/// let cost = cost::MaxLikelihood {};
161///
162/// learn_history(&teacher,
163///               &cost,
164///               &mut model,
165///               history.iter().cloned());
166/// ```
167#[derive(Clone)]
168pub struct GeneralizedLinearModel<V, G, Dg> {
169    /// `Linear` term of the generalized linear `Model`
170    pub linear: Linear<V>,
171    /// Outer function applied to the result of `linear`
172    pub g: G,
173    /// Derivation of `g`
174    pub g_derivate: Dg,
175}
176
177impl<V, G, Dg> GeneralizedLinearModel<V, G, Dg>
178where
179    G: Fn(f64) -> f64,
180    Dg: Fn(f64) -> f64,
181{
182    /// Creates new model with the coefficients set to zero
183    pub fn new(g: G, g_derivate: Dg) -> GeneralizedLinearModel<V, G, Dg>
184    where
185        V: FixDimension,
186    {
187        GeneralizedLinearModel {
188            linear: Linear::default(),
189            g,
190            g_derivate,
191        }
192    }
193}
194
195impl<V, F, Df> Model for GeneralizedLinearModel<V, F, Df>
196where
197    F: Fn(f64) -> f64,
198    Df: Fn(f64) -> f64,
199    Linear<V>: Model<Features = V, Target = f64>,
200{
201    type Features = V;
202    type Target = f64;
203
204    fn num_coefficients(&self) -> usize {
205        self.linear.num_coefficients()
206    }
207
208    fn coefficient(&mut self, coefficient: usize) -> &mut f64 {
209        self.linear.coefficient(coefficient)
210    }
211
212    fn predict(&self, input: &V) -> f64 {
213        let f = &self.g;
214        f(self.linear.predict(&input))
215    }
216
217    fn gradient(&self, coefficient: usize, input: &V) -> f64 {
218        let f = &self.g_derivate;
219        f(self.linear.predict(&input)) * self.linear.gradient(coefficient, input)
220    }
221}
222
223/// One vs Rest strategy for multi classification.
224///
225/// This model combines indivual binary classifactors to a new multi classification model.
226///
227/// Implementation assumes that the number of coefficients is the same for all models.
228#[derive(Debug, Clone, Default, Serialize, Deserialize)]
229pub struct OneVsRest<T>(T);
230
231impl<T> OneVsRest<T> {
232    /// Create a new One vs Rest model from an array of existing models.
233    pub fn new(t: T) -> Self {
234        OneVsRest(t)
235    }
236}
237
238impl<T> Model for OneVsRest<T>
239where
240    T: array::Array,
241    T::Element: Model<Target = f64>,
242{
243    type Features = <T::Element as Model>::Features;
244    type Target = T::Vector;
245
246    fn num_coefficients(&self) -> usize {
247        let models = &self.0;
248        models.length() * models.at_ref(0).num_coefficients()
249    }
250
251    fn coefficient(&mut self, index: usize) -> &mut f64 {
252        // If our one vs Rest classifier consists of three models a,b,c with three coefficients 1,2
253        // ,3 each we list the coefficients of the combined model as a1,b1,c1,a2,b2,c2,a3,b3,c3.
254        let models = &mut self.0;
255        let class = index % models.length();
256        let n = index / models.length();
257        models.at_mut(class).coefficient(n)
258    }
259
260    fn predict(&self, input: &Self::Features) -> Self::Target {
261        let models = &self.0;
262        let mut result = Self::Target::zero_from_dimension(models.length());
263        for i in 0..models.length() {
264            *result.at_mut(i) = models.at_ref(i).predict(input);
265        }
266        result
267    }
268
269    fn gradient(&self, coefficient: usize, input: &Self::Features) -> Self::Target {
270        let models = &self.0;
271        let class = coefficient % models.length();
272        let mut result = Self::Target::zero_from_dimension(models.length());
273        *result.at_mut(class) = models
274            .at_ref(class)
275            .gradient(coefficient / models.length(), input);
276        result
277    }
278}