Skip to main content

friedrich/gaussian_process/
builder.rs

1use super::GaussianProcess;
2use crate::conversion::Input;
3use crate::parameters::kernel::Kernel;
4use crate::parameters::prior::Prior;
5use chrono::Duration;
6use nalgebra::{DMatrix, DVector};
7
8/// Builder to set the parameters of a gaussian process.
9///
10/// This class is meant to be produced by the `builder` method of the gaussian process and can be used to select the various parameters of the gaussian process :
11///
12/// ```rust
13/// # use friedrich::gaussian_process::GaussianProcess;
14/// # use friedrich::prior::*;
15/// # use friedrich::kernel::*;
16/// // Training data.
17/// let training_inputs = vec![vec![0.8], vec![1.2], vec![3.8], vec![4.2]];
18/// let training_outputs = vec![3.0, 4.0, -2.0, -2.0];
19///
20/// // Model parameters.
21/// let input_dimension = 1;
22/// let output_noise = 0.1;
23/// let exponential_kernel = Exponential::default();
24/// let linear_prior = LinearPrior::default(input_dimension);
25///
26/// // Defining and training a model.
27/// let gp = GaussianProcess::builder(training_inputs, training_outputs)
28///     .set_noise(output_noise)
29///     .set_kernel(exponential_kernel)
30///     .fit_kernel()
31///     .set_prior(linear_prior)
32///     .fit_prior()
33///     .train();
34/// ```
35pub struct GaussianProcessBuilder<KernelType: Kernel, PriorType: Prior>
36{
37    /// Value to which the process will regress in the absence of information.
38    prior: PriorType,
39    /// Kernel used to fit the process on the data.
40    kernel: KernelType,
41    /// Amplitude of the noise of the data.
42    noise: f64,
43    cholesky_epsilon: Option<f64>,
44    /// Type of fit to be applied.
45    should_fit_kernel: bool,
46    should_fit_prior: bool,
47    /// Fit parameters.
48    max_iter: usize,
49    convergence_fraction: f64,
50    max_time: Duration,
51    /// Data use for training.
52    training_inputs: DMatrix<f64>,
53    training_outputs: DVector<f64>
54}
55
56impl<KernelType: Kernel, PriorType: Prior> GaussianProcessBuilder<KernelType, PriorType>
57{
58    /// Builds a new gaussian process with default parameters.
59    ///
60    /// The defaults are:
61    /// - constant prior (0 unless fitted)
62    /// - a gaussian kernel
63    /// - a noise of 10% of the output standard deviation (might be re-fitted in the absence of user provided value)
64    /// - does not fit parameters
65    /// - fit will run for a maximum of 100 iteration or one hour unless all gradients are below 5% time their associated parameter
66    pub fn new<T: Input>(training_inputs: T, training_outputs: T::InVector) -> Self
67    {
68        let training_inputs = T::into_dmatrix(training_inputs);
69        let training_outputs = T::into_dvector(training_outputs);
70        // makes builder
71        let prior = PriorType::default(training_inputs.ncols());
72        let kernel = KernelType::default();
73        let noise = 0.1 * training_outputs.row_variance()[0].sqrt(); // 10% of output std by default
74        let should_fit_kernel = false;
75        let should_fit_prior = false;
76        let max_iter = 100;
77        let convergence_fraction = 0.05;
78        let max_time = Duration::seconds(3600);
79        // In most cases no Cholesky epsilon is needed, especially if user has
80        // has some noise set which is also the default. If some epsilon value
81        // turns out to be needed, we point the in the right direction via a
82        // runtime error message.
83        let cholesky_epsilon = None;
84        GaussianProcessBuilder { prior,
85                                 kernel,
86                                 noise,
87                                 cholesky_epsilon,
88                                 should_fit_kernel,
89                                 should_fit_prior,
90                                 max_iter,
91                                 convergence_fraction,
92                                 max_time,
93                                 training_inputs,
94                                 training_outputs }
95    }
96
97    //----------------------------------------------------------------------------------------------
98    // SETTERS
99
100    /// Sets a new prior.
101    /// See the documentation on priors for more information.
102    pub fn set_prior<NewPriorType: Prior>(self,
103                                          prior: NewPriorType)
104                                          -> GaussianProcessBuilder<KernelType, NewPriorType>
105    {
106        GaussianProcessBuilder { prior,
107                                 kernel: self.kernel,
108                                 noise: self.noise,
109                                 cholesky_epsilon: self.cholesky_epsilon,
110                                 should_fit_kernel: self.should_fit_kernel,
111                                 should_fit_prior: self.should_fit_prior,
112                                 max_iter: self.max_iter,
113                                 convergence_fraction: self.convergence_fraction,
114                                 max_time: self.max_time,
115                                 training_inputs: self.training_inputs,
116                                 training_outputs: self.training_outputs }
117    }
118
119    /// Sets the noise parameter.
120    /// It correspond to the standard deviation of the noise in the outputs of the training set.
121    pub fn set_noise(self, noise: f64) -> Self
122    {
123        assert!(noise >= 0., "The noise parameter should non-negative but we tried to set it to {}", noise);
124        GaussianProcessBuilder { noise, ..self }
125    }
126
127    /// Changes the kernel of the gaussian process.
128    /// See the documentations on Kernels for more information.
129    pub fn set_kernel<NewKernelType: Kernel>(self,
130                                             kernel: NewKernelType)
131                                             -> GaussianProcessBuilder<NewKernelType, PriorType>
132    {
133        GaussianProcessBuilder { prior: self.prior,
134                                 kernel,
135                                 noise: self.noise,
136                                 cholesky_epsilon: self.cholesky_epsilon,
137                                 should_fit_kernel: self.should_fit_kernel,
138                                 should_fit_prior: self.should_fit_prior,
139                                 max_iter: self.max_iter,
140                                 convergence_fraction: self.convergence_fraction,
141                                 max_time: self.max_time,
142                                 training_inputs: self.training_inputs,
143                                 training_outputs: self.training_outputs }
144    }
145
146    /// When set to some strictly positive value the Cholesky decomposition is
147    /// guaranteed to suceed. This value will only be used when the Cholesky
148    /// decomposition fails, it will then be used in place of the diagonal term.
149    /// Otherwise it is ignored and the decomposition is done as usual.
150    ///
151    /// We recommend trying something along one percent of the noise squared
152    /// (both very large values and small values could lead to undesirable
153    /// approximations in the Cholesky decomposition).
154    ///
155    /// See <https://github.com/nestordemeure/friedrich/issues/43> for details.
156    pub fn set_cholesky_epsilon(self, cholesky_epsilon: Option<f64>) -> Self
157    {
158        GaussianProcessBuilder { cholesky_epsilon, ..self }
159    }
160
161    /// Modifies the stopping criteria of the gradient descent used to fit the noise and kernel parameters.
162    ///
163    /// The optimizer runs for a maximum of `max_iter` iterations and stops prematurely if all gradients are below `convergence_fraction` time their associated parameter
164    /// or if it runs for more than `max_time`.
165    pub fn set_fit_parameters(self, max_iter: usize, convergence_fraction: f64) -> Self
166    {
167        GaussianProcessBuilder { max_iter, convergence_fraction, ..self }
168    }
169
170    /// Asks for the parameters of the kernel to be fitted on the training data.
171    /// The fitting will be done when the `train` method is called.
172    pub fn fit_kernel(self) -> Self
173    {
174        GaussianProcessBuilder { should_fit_kernel: true, ..self }
175    }
176
177    /// Asks for the prior to be fitted on the training data.
178    /// The fitting will be done when the `train` method is called.
179    pub fn fit_prior(self) -> Self
180    {
181        GaussianProcessBuilder { should_fit_prior: true, ..self }
182    }
183
184    //----------------------------------------------------------------------------------------------
185    // TRAIN
186
187    /// Trains the gaussian process.
188    /// Fits the parameters if requested.
189    pub fn train(mut self) -> GaussianProcess<KernelType, PriorType>
190    {
191        // prepare kernel and noise values using heuristics
192        // TODO how to detect if values have been entered by the user meaning that he does not want an heuristic ?
193        if self.should_fit_kernel
194        {
195            self.kernel.heuristic_fit(&self.training_inputs, &self.training_outputs);
196        }
197
198        // Builds a gp.
199        let mut gp = GaussianProcess::<KernelType, PriorType>::new(self.prior,
200                                                                   self.kernel,
201                                                                   self.noise,
202                                                                   self.cholesky_epsilon,
203                                                                   self.training_inputs,
204                                                                   self.training_outputs);
205
206        // Fits the model, if requested, on the training data.
207        gp.fit_parameters(self.should_fit_prior,
208                          self.should_fit_kernel,
209                          self.max_iter,
210                          self.convergence_fraction,
211                          self.max_time);
212
213        gp
214    }
215}