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}