///|
pub fn FeatureImportance::feature_index(self : FeatureImportance) -> Int {
self.source_feature_index
}
///|
pub fn FeatureImportance::baseline_accuracy(self : FeatureImportance) -> Double {
self.unpermuted_accuracy
}
///|
pub fn FeatureImportance::permuted_accuracy(self : FeatureImportance) -> Double {
self.permuted_accuracy_value
}
///|
pub fn FeatureImportance::accuracy_drop(self : FeatureImportance) -> Double {
self.measured_accuracy_drop
}
///|
pub fn PermutationImportance::baseline_accuracy(
self : PermutationImportance,
) -> Double {
self.original_accuracy
}
///|
pub fn PermutationImportance::feature_count(
self : PermutationImportance,
) -> Int {
self.ordered_importances.length()
}
///|
pub fn PermutationImportance::ranked_features(
self : PermutationImportance,
) -> Array[FeatureImportance] {
self.ordered_importances.copy()
}
///|
fn reversed_feature_rows(data : Dataset, feature : Int) -> Array[Array[Double]] {
let rows = data.features()
let count = rows.length()
for row = 0; row < count; row = row + 1 {
rows[row][feature] = data.feature_rows[count - row - 1][feature]
}
rows
}
///|
fn sort_feature_importances(values : Array[FeatureImportance]) -> Unit {
for index = 1; index < values.length(); index = index + 1 {
let selected = values[index]
let mut position = index
while position > 0 &&
(
values[position - 1].accuracy_drop() < selected.accuracy_drop() ||
(
values[position - 1].accuracy_drop() == selected.accuracy_drop() &&
values[position - 1].feature_index() > selected.feature_index()
)
) {
values[position] = values[position - 1]
position = position - 1
}
values[position] = selected
}
}
///|
fn importance_result(
baseline : Double,
permuted_accuracies : Array[Double],
) -> PermutationImportance {
let values : Array[FeatureImportance] = []
for feature, accuracy in permuted_accuracies {
values.push({
source_feature_index: feature,
unpermuted_accuracy: baseline,
permuted_accuracy_value: accuracy,
measured_accuracy_drop: baseline - accuracy,
})
}
sort_feature_importances(values)
{ original_accuracy: baseline, ordered_importances: values }
}
///|
fn checked_importance_dimensions(
expected : Int,
data : Dataset,
) -> Result[Unit, SvmError] {
if data.feature_count() != expected {
Err(PredictionDimensionMismatch(expected, data.feature_count()))
} else {
Ok(())
}
}
///|
/// Measures binary accuracy changes using a deterministic reverse permutation.
pub fn binary_permutation_importance(
model : BinaryModel,
data : Dataset,
) -> Result[PermutationImportance, SvmError] {
match checked_importance_dimensions(model.feature_count(), data) {
Err(error) => return Err(error)
Ok(_) => ()
}
let labels = data.labels()
let baseline_predictions = match model.predict_batch(data.features()) {
Err(error) => return Err(error)
Ok(value) => value
}
let baseline = match classification_metrics(labels, baseline_predictions) {
Err(error) => return Err(error)
Ok(value) => value.accuracy()
}
let permuted_accuracies : Array[Double] = []
for feature = 0; feature < data.feature_count(); feature = feature + 1 {
let predictions = match
model.predict_batch(reversed_feature_rows(data, feature)) {
Err(error) => return Err(error)
Ok(value) => value
}
let accuracy = match classification_metrics(labels, predictions) {
Err(error) => return Err(error)
Ok(value) => value.accuracy()
}
permuted_accuracies.push(accuracy)
}
Ok(importance_result(baseline, permuted_accuracies))
}
///|
/// Measures OvR accuracy changes using the same deterministic permutation.
pub fn multiclass_permutation_importance(
model : MulticlassModel,
data : Dataset,
) -> Result[PermutationImportance, SvmError] {
match checked_importance_dimensions(model.feature_count(), data) {
Err(error) => return Err(error)
Ok(_) => ()
}
let labels = data.labels()
let baseline_predictions = match model.predict_batch(data.features()) {
Err(error) => return Err(error)
Ok(value) => value
}
let baseline = match classification_metrics(labels, baseline_predictions) {
Err(error) => return Err(error)
Ok(value) => value.accuracy()
}
let permuted_accuracies : Array[Double] = []
for feature = 0; feature < data.feature_count(); feature = feature + 1 {
let predictions = match
model.predict_batch(reversed_feature_rows(data, feature)) {
Err(error) => return Err(error)
Ok(value) => value
}
let accuracy = match classification_metrics(labels, predictions) {
Err(error) => return Err(error)
Ok(value) => value.accuracy()
}
permuted_accuracies.push(accuracy)
}
Ok(importance_result(baseline, permuted_accuracies))
}