use scirs2_core::ndarray::{Array1, Array2, Axis};
use sklears_core::{
error::{Result as SklResult, SklearsError},
traits::{Estimator, Fit, Predict, Untrained},
types::Float,
};
use crate::kernels::Kernel;
#[derive(Debug, Clone)]
pub struct GaussianProcessRegressorConfig {
pub alpha: f64,
pub optimizer: Option<String>,
pub n_restarts_optimizer: usize,
pub normalize_y: bool,
pub copy_x_train: bool,
pub random_state: Option<u64>,
}
impl Default for GaussianProcessRegressorConfig {
fn default() -> Self {
Self {
alpha: 1e-10,
optimizer: Some("fmin_l_bfgs_b".to_string()),
n_restarts_optimizer: 0,
normalize_y: false,
copy_x_train: true,
random_state: None,
}
}
}
#[derive(Debug, Clone)]
pub struct GaussianProcessRegressor<S = Untrained> {
state: S,
kernel: Option<Box<dyn Kernel>>,
config: GaussianProcessRegressorConfig,
}
#[derive(Debug, Clone)]
#[allow(non_snake_case)]
pub struct GprTrained {
pub X_train: Option<Array2<f64>>, pub y_train: Array1<f64>, pub y_mean: f64, pub y_std: f64, pub alpha: Array1<f64>, pub L: Array2<f64>, pub K: Array2<f64>, pub kernel: Box<dyn Kernel>, pub log_marginal_likelihood_value: f64, }
impl GaussianProcessRegressor<Untrained> {
pub fn new() -> Self {
Self {
state: Untrained,
kernel: None,
config: GaussianProcessRegressorConfig::default(),
}
}
pub fn kernel(mut self, kernel: Box<dyn Kernel>) -> Self {
self.kernel = Some(kernel);
self
}
pub fn alpha(mut self, alpha: f64) -> Self {
self.config.alpha = alpha;
self
}
pub fn optimizer(mut self, optimizer: Option<String>) -> Self {
self.config.optimizer = optimizer;
self
}
pub fn n_restarts_optimizer(mut self, n_restarts: usize) -> Self {
self.config.n_restarts_optimizer = n_restarts;
self
}
pub fn normalize_y(mut self, normalize_y: bool) -> Self {
self.config.normalize_y = normalize_y;
self
}
pub fn copy_x_train(mut self, copy_x_train: bool) -> Self {
self.config.copy_x_train = copy_x_train;
self
}
pub fn random_state(mut self, random_state: Option<u64>) -> Self {
self.config.random_state = random_state;
self
}
}
impl Estimator for GaussianProcessRegressor<Untrained> {
type Config = GaussianProcessRegressorConfig;
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&self.config
}
}
impl Estimator for GaussianProcessRegressor<GprTrained> {
type Config = GaussianProcessRegressorConfig;
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&self.config
}
}
impl Fit<Array2<f64>, Array1<f64>> for GaussianProcessRegressor<Untrained> {
type Fitted = GaussianProcessRegressor<GprTrained>;
#[allow(non_snake_case)]
fn fit(self, X: &Array2<f64>, y: &Array1<f64>) -> SklResult<Self::Fitted> {
if X.nrows() != y.len() {
return Err(SklearsError::InvalidInput(
"X and y must have the same number of samples".to_string(),
));
}
let kernel = self
.kernel
.ok_or_else(|| SklearsError::InvalidInput("Kernel must be specified".to_string()))?;
let (y_normalized, y_mean, y_std) = if self.config.normalize_y {
let mean = y.mean().unwrap_or(0.0);
let std = y.std(0.0);
let std = if std == 0.0 { 1.0 } else { std };
let y_norm = (y - mean) / std;
(y_norm, mean, std)
} else {
(y.to_owned(), 0.0, 1.0)
};
let K = kernel.compute_kernel_matrix(X, None)?;
let mut K_reg = K.clone();
for i in 0..K_reg.nrows() {
K_reg[[i, i]] += self.config.alpha;
}
let L = crate::utils::robust_cholesky(&K_reg)?;
let alpha = crate::utils::triangular_solve(&L, &y_normalized)?;
let log_marginal_likelihood_value =
crate::utils::log_marginal_likelihood(&L, &alpha, &y_normalized);
let X_train = if self.config.copy_x_train {
Some(X.to_owned())
} else {
None
};
Ok(GaussianProcessRegressor {
state: GprTrained {
X_train,
y_train: y_normalized,
y_mean,
y_std,
alpha,
L,
K: K_reg,
kernel,
log_marginal_likelihood_value,
},
kernel: None,
config: self.config,
})
}
}
#[allow(non_snake_case)]
impl Predict<Array2<f64>, Array1<f64>> for GaussianProcessRegressor<GprTrained> {
fn predict(&self, X: &Array2<f64>) -> SklResult<Array1<f64>> {
let (mean, _) = self.predict_with_std(X)?;
Ok(mean)
}
}
impl GaussianProcessRegressor<GprTrained> {
#[allow(non_snake_case)]
pub fn predict_with_std(&self, X: &Array2<f64>) -> SklResult<(Array1<f64>, Array1<f64>)> {
let X_train = self
.state
.X_train
.as_ref()
.ok_or_else(|| SklearsError::NotFitted {
operation: "predict_with_std".to_string(),
})?;
let K_star = self.state.kernel.compute_kernel_matrix(X_train, Some(X))?;
let mean_normalized = K_star.t().dot(&self.state.alpha);
let mean = if self.config.normalize_y {
mean_normalized * self.state.y_std + self.state.y_mean
} else {
mean_normalized
};
let mut quad_form = Array1::<f64>::zeros(X.nrows());
for i in 0..X.nrows() {
let k_star_i = K_star.column(i).to_owned();
let v_i = crate::utils::triangular_solve(&self.state.L, &k_star_i)?;
quad_form[i] = k_star_i.dot(&v_i);
}
let K_star_star_diag = X
.axis_iter(Axis(0))
.map(|x| self.state.kernel.kernel(&x, &x))
.collect::<Array1<f64>>();
let var_normalized = K_star_star_diag - quad_form;
let var_normalized = var_normalized.mapv(|x| x.max(0.0));
let std = if self.config.normalize_y {
var_normalized.mapv(|x| (x * self.state.y_std.powi(2)).sqrt())
} else {
var_normalized.mapv(|x| x.sqrt())
};
Ok((mean, std))
}
pub fn log_marginal_likelihood(&self) -> f64 {
self.state.log_marginal_likelihood_value
}
}
impl Default for GaussianProcessRegressor<Untrained> {
fn default() -> Self {
Self::new()
}
}