///|
pub fn FoldEvaluation::index(self : FoldEvaluation) -> Int {
self.fold_number
}
///|
pub fn FoldEvaluation::train_indices(self : FoldEvaluation) -> Array[Int] {
self.training_rows.copy()
}
///|
pub fn FoldEvaluation::test_indices(self : FoldEvaluation) -> Array[Int] {
self.testing_rows.copy()
}
///|
pub fn FoldEvaluation::actual(self : FoldEvaluation) -> Array[Int] {
self.actual_labels.copy()
}
///|
pub fn FoldEvaluation::predicted(self : FoldEvaluation) -> Array[Int] {
self.predicted_labels.copy()
}
///|
pub fn FoldEvaluation::metrics(self : FoldEvaluation) -> ClassificationMetrics {
self.fold_metrics
}
///|
pub fn FoldEvaluation::scaler(self : FoldEvaluation) -> FeatureScaler? {
self.fitted_scaler
}
///|
pub fn ValidationReport::fold_count(self : ValidationReport) -> Int {
self.fold_evaluations.length()
}
///|
pub fn ValidationReport::folds(
self : ValidationReport,
) -> Array[FoldEvaluation] {
self.fold_evaluations.copy()
}
///|
pub fn ValidationReport::metrics(
self : ValidationReport,
) -> ClassificationMetrics {
self.aggregate_metrics
}
///|
pub fn ValidationReport::observation_count(self : ValidationReport) -> Int {
self.evaluated_observations
}
///|
pub fn ValidationReport::scaling(self : ValidationReport) -> ScalingPlan {
self.scaling_plan
}
///|
fn scaled_fold_data(
training_data : Dataset,
testing_data : Dataset,
scaling : ScalingPlan,
) -> Result[(Dataset, Dataset, FeatureScaler?), SvmError] {
match scaling {
NoScaling => Ok((training_data, testing_data, None))
StandardScaling => {
let scaler = match fit_scaler(training_data, StandardScale) {
Err(error) => return Err(error)
Ok(value) => value
}
let scaled_training = match
scaler.transform_dataset(training_data, "scaled training fold") {
Err(error) => return Err(error)
Ok(value) => value
}
let scaled_testing = match
scaler.transform_dataset(testing_data, "scaled testing fold") {
Err(error) => return Err(error)
Ok(value) => value
}
Ok((scaled_training, scaled_testing, Some(scaler)))
}
MinMaxScaling(lower, upper) => {
let scaler = match fit_scaler(training_data, MinMaxScale(lower, upper)) {
Err(error) => return Err(error)
Ok(value) => value
}
let scaled_training = match
scaler.transform_dataset(training_data, "scaled training fold") {
Err(error) => return Err(error)
Ok(value) => value
}
let scaled_testing = match
scaler.transform_dataset(testing_data, "scaled testing fold") {
Err(error) => return Err(error)
Ok(value) => value
}
Ok((scaled_training, scaled_testing, Some(scaler)))
}
}
}
///|
/// Evaluates held-out predictions with preprocessing fitted inside each fold.
pub fn cross_validate(
data : Dataset,
config : BinaryConfig,
fold_count : Int,
scaling : ScalingPlan,
) -> Result[ValidationReport, SvmError] {
let partitions = match stratified_folds(data, fold_count) {
Err(error) => return Err(error)
Ok(value) => value
}
let evaluations : Array[FoldEvaluation] = []
let aggregate_actual : Array[Int] = []
let aggregate_predicted : Array[Int] = []
for partition in partitions {
let training_data = match
data.subset(
partition.train_indices(),
"training fold \{partition.index()}",
) {
Err(error) => return Err(error)
Ok(value) => value
}
let testing_data = match
data.subset(partition.test_indices(), "testing fold \{partition.index()}") {
Err(error) => return Err(error)
Ok(value) => value
}
let (fit_data, predict_data, scaler) = match
scaled_fold_data(training_data, testing_data, scaling) {
Err(error) => return Err(error)
Ok(value) => value
}
let model = match train_multiclass(fit_data, config) {
Err(error) => return Err(error)
Ok(value) => value
}
let actual = predict_data.labels()
let predicted = match model.predict_batch(predict_data.features()) {
Err(error) => return Err(error)
Ok(value) => value
}
let metrics = match classification_metrics(actual, predicted) {
Err(error) => return Err(error)
Ok(value) => value
}
for label in actual {
aggregate_actual.push(label)
}
for label in predicted {
aggregate_predicted.push(label)
}
evaluations.push({
fold_number: partition.index(),
training_rows: partition.train_indices(),
testing_rows: partition.test_indices(),
actual_labels: actual,
predicted_labels: predicted,
fold_metrics: metrics,
fitted_scaler: scaler,
})
}
let aggregate = match
classification_metrics(aggregate_actual, aggregate_predicted) {
Err(error) => return Err(error)
Ok(value) => value
}
Ok({
fold_evaluations: evaluations,
aggregate_metrics: aggregate,
evaluated_observations: data.row_count(),
scaling_plan: scaling,
})
}