#[cfg(not(feature="gurobi"))]
use super::lp_model::LPModel;
#[cfg(feature="gurobi")]
use super::gurobi_lp_model::LPModel;
use crate::{
Sample,
Booster,
WeakLearner,
Classifier,
WeightedMajority,
common::utils,
common::checker,
research::Research,
};
use std::cell::RefCell;
use std::ops::ControlFlow;
pub struct LPBoost<'a, F> {
sample: &'a Sample,
dist: Vec<f64>,
gamma_hat: f64,
tolerance: f64,
n_sample: usize,
nu: f64,
lp_model: Option<RefCell<LPModel>>,
hypotheses: Vec<F>,
weights: Vec<f64>,
terminated: usize,
}
impl<'a, F> LPBoost<'a, F>
where F: Classifier
{
pub fn init(sample: &'a Sample) -> Self {
let n_sample = sample.shape().0;
let uni = 1.0 / n_sample as f64;
Self {
sample,
dist: Vec::new(),
gamma_hat: 1.0,
tolerance: uni,
n_sample,
nu: 1.0,
lp_model: None,
hypotheses: Vec::new(),
weights: Vec::new(),
terminated: usize::MAX,
}
}
pub fn nu(mut self, nu: f64) -> Self {
checker::check_nu(nu, self.n_sample);
self.nu = nu;
self
}
fn init_solver(&mut self) {
let n_sample = self.sample.shape().0 as f64;
assert!((1.0..=n_sample).contains(&self.nu));
let upper_bound = 1.0 / self.nu;
let lp_model = RefCell::new(LPModel::init(self.n_sample, upper_bound));
self.lp_model = Some(lp_model);
}
#[inline(always)]
pub fn tolerance(mut self, tolerance: f64) -> Self {
self.tolerance = tolerance;
self
}
#[inline(always)]
pub fn terminated(&self) -> usize {
self.terminated
}
#[inline(always)]
fn update_distribution_mut(&self, h: &F) -> f64
{
self.lp_model.as_ref()
.expect("Failed to call `.as_ref()` to `self.lp_model`")
.borrow_mut()
.update(self.sample, h)
}
}
impl<F> Booster<F> for LPBoost<'_, F>
where F: Classifier + Clone,
{
type Output = WeightedMajority<F>;
fn name(&self) -> &str {
"LPBoost"
}
fn info(&self) -> Option<Vec<(&str, String)>> {
let (n_sample, n_feature) = self.sample.shape();
let ratio = self.nu * 100f64 / n_sample as f64;
let nu = utils::format_unit(self.nu);
let info = Vec::from([
("# of examples", format!("{n_sample}")),
("# of features", format!("{n_feature}")),
("Tolerance", format!("{}", self.tolerance)),
("Max iteration", format!("-")),
("Capping (outliers)", format!("{nu} ({ratio: >7.3} %)"))
]);
Some(info)
}
fn preprocess<W>(
&mut self,
_weak_learner: &W,
)
where W: WeakLearner<Hypothesis = F>
{
self.sample.is_valid_binary_instance();
let n_sample = self.sample.shape().0;
let uni = 1.0_f64 / self.n_sample as f64;
self.init_solver();
self.n_sample = n_sample;
self.dist = vec![uni; n_sample];
self.gamma_hat = 1.0;
self.hypotheses = Vec::new();
self.terminated = usize::MAX;
}
fn boost<W>(
&mut self,
weak_learner: &W,
iteration: usize,
) -> ControlFlow<usize>
where W: WeakLearner<Hypothesis = F>,
{
let h = weak_learner.produce(self.sample, &self.dist);
let ghat = utils::edge_of_hypothesis(self.sample, &self.dist[..], &h);
self.gamma_hat = ghat.min(self.gamma_hat);
let gamma_star = self.update_distribution_mut(&h);
if gamma_star >= self.gamma_hat - self.tolerance {
self.hypotheses.push(h);
self.terminated = self.hypotheses.len();
return ControlFlow::Break(iteration);
}
self.hypotheses.push(h);
self.dist = self.lp_model.as_ref()
.expect("Failed to call `.as_ref()` to `self.lp_model`")
.borrow()
.distribution();
ControlFlow::Continue(())
}
fn postprocess<W>(
&mut self,
_weak_learner: &W,
) -> Self::Output
where W: WeakLearner<Hypothesis = F>
{
self.weights = self.lp_model.as_ref()
.expect("Failed to call `.as_ref()` to `self.lp_model`")
.borrow()
.weight()
.collect::<Vec<_>>();
WeightedMajority::from_slices(&self.weights[..], &self.hypotheses[..])
}
}
impl<H> Research for LPBoost<'_, H>
where H: Classifier + Clone,
{
type Output = WeightedMajority<H>;
fn current_hypothesis(&self) -> Self::Output {
let weights = self.lp_model.as_ref()
.expect("Failed to call `.as_ref()` to `self.lp_model`")
.borrow()
.weight()
.collect::<Vec<_>>();
WeightedMajority::from_slices(&weights[..], &self.hypotheses[..])
}
}