use crate::booster::config::MissingNodeTreatment;
use crate::booster::core::PerpetualBooster;
use crate::causal::objective::RLearnerObjective;
use crate::constraints::ConstraintMap;
use crate::data::Matrix;
use crate::errors::PerpetualError;
use crate::objective::Objective;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Serialize, Deserialize)]
pub struct UpliftBooster {
pub outcome_model: PerpetualBooster,
pub propensity_model: PerpetualBooster,
pub effect_model: PerpetualBooster,
}
impl UpliftBooster {
#[allow(clippy::too_many_arguments)]
pub fn new(
outcome_budget: f32,
propensity_budget: f32,
effect_budget: f32,
max_bin: u16,
num_threads: Option<usize>,
monotone_constraints: Option<ConstraintMap>,
interaction_constraints: Option<Vec<Vec<usize>>>,
force_children_to_bound_parent: bool,
missing: f64,
allow_missing_splits: bool,
create_missing_branch: bool,
terminate_missing_features: HashSet<usize>,
missing_node_treatment: MissingNodeTreatment,
log_iterations: usize,
seed: u64,
reset: Option<bool>,
categorical_features: Option<HashSet<usize>>,
timeout: Option<f32>,
iteration_limit: Option<usize>,
memory_limit: Option<f32>,
stopping_rounds: Option<usize>,
) -> Result<Self, PerpetualError> {
let base_params = |budget: f32, objective: Objective| -> Result<PerpetualBooster, PerpetualError> {
PerpetualBooster::new(
objective,
budget,
f64::NAN,
max_bin,
num_threads,
monotone_constraints.clone(),
interaction_constraints.clone(),
force_children_to_bound_parent,
missing,
allow_missing_splits,
create_missing_branch,
terminate_missing_features.clone(),
missing_node_treatment,
log_iterations,
seed,
reset,
categorical_features.clone(),
timeout,
iteration_limit,
memory_limit,
stopping_rounds,
false,
)
};
let outcome_model = base_params(outcome_budget, Objective::SquaredLoss)?; let propensity_model = base_params(propensity_budget, Objective::LogLoss)?;
let effect_model = base_params(effect_budget, Objective::SquaredLoss)?;
Ok(UpliftBooster {
outcome_model,
propensity_model,
effect_model,
})
}
pub fn fit(&mut self, x: &Matrix<f64>, w: &[f64], y: &[f64]) -> Result<(), PerpetualError> {
self.outcome_model.fit(x, y, None, None)?;
let mu_hat = self.outcome_model.predict(x, true);
self.propensity_model.fit(x, w, None, None)?;
let log_odds = self.propensity_model.predict(x, true);
let p_hat: Vec<f64> = log_odds.iter().map(|lo| 1.0 / (1.0 + (-lo).exp())).collect();
let r_obj_fn = RLearnerObjective::new(w.to_vec(), mu_hat, p_hat);
self.effect_model.cfg.objective = Objective::new_custom(r_obj_fn);
self.effect_model.fit(x, y, None, None)?;
Ok(())
}
pub fn predict(&self, x: &Matrix<f64>) -> Vec<f64> {
self.effect_model.predict(x, true)
}
}