///|
pub fn svm_candidate(
name : String,
config : BinaryConfig,
scaling : ScalingPlan,
) -> SvmCandidate {
{
candidate_name: name,
binary_configuration: config,
candidate_scaling: scaling,
}
}
///|
pub fn SvmCandidate::name(self : SvmCandidate) -> String {
self.candidate_name
}
///|
pub fn SvmCandidate::config(self : SvmCandidate) -> BinaryConfig {
self.binary_configuration
}
///|
pub fn SvmCandidate::scaling(self : SvmCandidate) -> ScalingPlan {
self.candidate_scaling
}
///|
pub fn CandidateEvaluation::index(self : CandidateEvaluation) -> Int {
self.original_index
}
///|
pub fn CandidateEvaluation::name(self : CandidateEvaluation) -> String {
self.evaluated_name
}
///|
pub fn CandidateEvaluation::score(self : CandidateEvaluation) -> Double {
self.metric_score
}
///|
pub fn CandidateEvaluation::validation(
self : CandidateEvaluation,
) -> ValidationReport {
self.validation_report
}
///|
pub fn CandidateFailure::index(self : CandidateFailure) -> Int {
self.original_index
}
///|
pub fn CandidateFailure::name(self : CandidateFailure) -> String {
self.failed_name
}
///|
pub fn CandidateFailure::error(self : CandidateFailure) -> SvmError {
self.failure_error
}
///|
pub fn SearchResult::candidates(
self : SearchResult,
) -> Array[CandidateEvaluation] {
self.successful_candidates.copy()
}
///|
pub fn SearchResult::failures(self : SearchResult) -> Array[CandidateFailure] {
self.failed_candidates.copy()
}
///|
pub fn SearchResult::best_index(self : SearchResult) -> Int {
self.selected_original_index
}
///|
pub fn SearchResult::best_name(self : SearchResult) -> String {
self.selected_name
}
///|
pub fn SearchResult::best_score(self : SearchResult) -> Double {
self.selected_score
}
///|
pub fn SearchResult::metric(self : SearchResult) -> SelectionMetric {
self.selected_metric
}
///|
pub fn SearchResult::validation(self : SearchResult) -> ValidationReport {
self.selected_validation
}
///|
pub fn SearchResult::model(self : SearchResult) -> MulticlassModel {
self.selected_model
}
///|
pub fn SearchResult::scaler(self : SearchResult) -> FeatureScaler? {
self.selected_scaler
}
///|
pub fn SearchResult::predict(
self : SearchResult,
row : Array[Double],
) -> Result[Int, SvmError] {
let prepared = match self.selected_scaler {
None => row
Some(scaler) =>
match scaler.transform_row(row) {
Err(error) => return Err(error)
Ok(value) => value
}
}
self.selected_model.predict(prepared)
}
///|
fn selection_score(
metrics : ClassificationMetrics,
selection : SelectionMetric,
) -> Double {
match selection {
SelectAccuracy => metrics.accuracy()
SelectBalancedAccuracy => metrics.balanced_accuracy()
SelectMacroF1 => metrics.macro_f1()
SelectWeightedF1 => metrics.weighted_f1()
}
}
///|
fn fit_selected_data(
data : Dataset,
scaling : ScalingPlan,
) -> Result[(Dataset, FeatureScaler?), SvmError] {
match scaling {
NoScaling => Ok((data, None))
StandardScaling => {
let scaler = match fit_scaler(data, StandardScale) {
Err(error) => return Err(error)
Ok(value) => value
}
let transformed = match
scaler.transform_dataset(data, "selected full data") {
Err(error) => return Err(error)
Ok(value) => value
}
Ok((transformed, Some(scaler)))
}
MinMaxScaling(lower, upper) => {
let scaler = match fit_scaler(data, MinMaxScale(lower, upper)) {
Err(error) => return Err(error)
Ok(value) => value
}
let transformed = match
scaler.transform_dataset(data, "selected full data") {
Err(error) => return Err(error)
Ok(value) => value
}
Ok((transformed, Some(scaler)))
}
}
}
///|
/// Evaluates candidates in order and retains the earliest candidate on ties.
pub fn select_svm(
data : Dataset,
candidates : Array[SvmCandidate],
fold_count : Int,
metric : SelectionMetric,
) -> Result[SearchResult, SvmError] {
if candidates.is_empty() {
return Err(InvalidCandidateCount(0))
}
let successful : Array[CandidateEvaluation] = []
let failures : Array[CandidateFailure] = []
let mut best_success_index = -1
let mut best_score = 0.0
for index, candidate in candidates {
match
cross_validate(
data,
candidate.binary_configuration,
fold_count,
candidate.candidate_scaling,
) {
Err(error) =>
failures.push({
original_index: index,
failed_name: candidate.candidate_name,
failure_error: error,
})
Ok(validation) => {
let score = selection_score(validation.metrics(), metric)
successful.push({
original_index: index,
evaluated_name: candidate.candidate_name,
metric_score: score,
validation_report: validation,
})
let current_success = successful.length() - 1
if best_success_index < 0 || score > best_score {
best_success_index = current_success
best_score = score
}
}
}
}
if best_success_index < 0 {
return Err(NoValidCandidate)
}
let best_evaluation = successful[best_success_index]
let best_candidate = candidates[best_evaluation.original_index]
let (fit_data, scaler) = match
fit_selected_data(data, best_candidate.candidate_scaling) {
Err(error) => return Err(error)
Ok(value) => value
}
let model = match
train_multiclass(fit_data, best_candidate.binary_configuration) {
Err(error) => return Err(error)
Ok(value) => value
}
Ok({
successful_candidates: successful,
failed_candidates: failures,
selected_original_index: best_evaluation.original_index,
selected_name: best_evaluation.evaluated_name,
selected_score: best_evaluation.metric_score,
selected_metric: metric,
selected_validation: best_evaluation.validation_report,
selected_model: model,
selected_scaler: scaler,
})
}