///|
fn absolute_value(value : Double) -> Double {
if value < 0.0 {
-value
} else {
value
}
}
///|
pub fn SupportContribution::support_index(self : SupportContribution) -> Int {
self.retained_index
}
///|
pub fn SupportContribution::support_vector(
self : SupportContribution,
) -> Array[Double] {
self.retained_row.copy()
}
///|
pub fn SupportContribution::class_label(self : SupportContribution) -> Int {
self.retained_label
}
///|
pub fn SupportContribution::alpha(self : SupportContribution) -> Double {
self.retained_alpha
}
///|
pub fn SupportContribution::kernel_value(self : SupportContribution) -> Double {
self.evaluated_kernel_value
}
///|
pub fn SupportContribution::contribution(self : SupportContribution) -> Double {
self.signed_decision_contribution
}
///|
pub fn BinaryPredictionExplanation::predicted_class(
self : BinaryPredictionExplanation,
) -> Int {
self.output_class
}
///|
pub fn BinaryPredictionExplanation::decision_value(
self : BinaryPredictionExplanation,
) -> Double {
self.signed_decision_value
}
///|
pub fn BinaryPredictionExplanation::bias(
self : BinaryPredictionExplanation,
) -> Double {
self.model_bias
}
///|
pub fn BinaryPredictionExplanation::contributions(
self : BinaryPredictionExplanation,
) -> Array[SupportContribution] {
self.ordered_contributions.copy()
}
///|
fn sort_contributions_by_magnitude(
contributions : Array[SupportContribution],
) -> Unit {
for index = 1; index < contributions.length(); index = index + 1 {
let selected = contributions[index]
let selected_magnitude = absolute_value(selected.contribution())
let mut position = index
while position > 0 &&
absolute_value(contributions[position - 1].contribution()) <
selected_magnitude {
contributions[position] = contributions[position - 1]
position = position - 1
}
contributions[position] = selected
}
}
///|
/// Decomposes a checked binary prediction into support-vector contributions.
pub fn explain_binary_prediction(
model : BinaryModel,
row : Array[Double],
) -> Result[BinaryPredictionExplanation, SvmError] {
let decision = match model.decision_value(row) {
Err(error) => return Err(error)
Ok(value) => value
}
let vectors = model.support_vectors()
let labels = model.support_labels()
let alphas = model.support_alphas()
let contributions : Array[SupportContribution] = []
for index, vector in vectors {
let evaluated = match kernel_value(model.kernel(), vector, row) {
Err(error) => return Err(error)
Ok(value) => value
}
let sign = if labels[index] == model.positive_class() { 1.0 } else { -1.0 }
contributions.push({
retained_index: index,
retained_row: vector.copy(),
retained_label: labels[index],
retained_alpha: alphas[index],
evaluated_kernel_value: evaluated,
signed_decision_contribution: sign * alphas[index] * evaluated,
})
}
sort_contributions_by_magnitude(contributions)
Ok({
output_class: if decision >= 0.0 {
model.positive_class()
} else {
model.negative_class()
},
signed_decision_value: decision,
model_bias: model.bias(),
ordered_contributions: contributions,
})
}
///|
pub fn MulticlassPredictionExplanation::predicted_class(
self : MulticlassPredictionExplanation,
) -> Int {
self.output_class
}
///|
pub fn MulticlassPredictionExplanation::classes(
self : MulticlassPredictionExplanation,
) -> Array[Int] {
self.ordered_classes.copy()
}
///|
pub fn MulticlassPredictionExplanation::scores(
self : MulticlassPredictionExplanation,
) -> Array[Double] {
self.ordered_scores.copy()
}
///|
/// Returns class-aligned OvR scores and the deterministic winning class.
pub fn explain_multiclass_prediction(
model : MulticlassModel,
row : Array[Double],
) -> Result[MulticlassPredictionExplanation, SvmError] {
let scores = match model.decision_values(row) {
Err(error) => return Err(error)
Ok(value) => value
}
let classes = model.classes()
let mut best = 0
for index = 1; index < scores.length(); index = index + 1 {
if scores[index] > scores[best] {
best = index
}
}
Ok({
output_class: classes[best],
ordered_classes: classes,
ordered_scores: scores,
})
}