///|
pub(all) struct ReliabilityBin {
index : Int
count : Int
weight : Double
mean_prediction : Double
event_rate : Double
gap : Double
} derive(Debug)
///|
pub(all) struct Reliability {
bins : Array[ReliabilityBin]
brier : Double
ece : Double
maximum_gap : Double
} derive(Debug)
///|
fn check_probability_data(
labels : Array[Double],
predictions : Array[Double],
weights : Array[Double],
) -> Result[Unit, String] {
match validate_series(labels, weights) {
Err(e) => return Err(e)
Ok(_) => ()
}
if labels.length() != predictions.length() {
return Err("probability length mismatch")
}
for i = 0; i < labels.length(); i = i + 1 {
if labels[i] < 0.0 || labels[i] > 1.0 {
return Err("event rates outside [0,1]")
}
if !finite(predictions[i]) || predictions[i] < 0.0 || predictions[i] > 1.0 {
return Err("probabilities outside [0,1]")
}
}
Ok(())
}
///|
/// Fixed-width bins; exactly 1 belongs to the final bin. Empty bins are omitted.
pub fn reliability(
labels : Array[Double],
predictions : Array[Double],
weights : Array[Double],
bin_count? : Int = 10,
) -> Result[Reliability, String] {
match check_probability_data(labels, predictions, weights) {
Err(e) => return Err(e)
Ok(_) => ()
}
if bin_count <= 0 || bin_count > 10000 {
return Err("bin count outside [1,10000]")
}
let counts = Array::make(bin_count, 0)
let sums = Array::make(bin_count, 0.0)
let events = Array::make(bin_count, 0.0)
let totals = Array::make(bin_count, 0.0)
let mut total_weight = 0.0
for i = 0; i < labels.length(); i = i + 1 {
let raw = (predictions[i] * bin_count.to_double()).to_int()
let b = if raw >= bin_count { bin_count - 1 } else { raw }
counts[b] = counts[b] + 1
sums[b] = sums[b] + weights[i] * predictions[i]
events[b] = events[b] + weights[i] * labels[i]
totals[b] = totals[b] + weights[i]
total_weight = total_weight + weights[i]
}
let bins : Array[ReliabilityBin] = []
let mut ece = 0.0
let mut maximum_gap = 0.0
for i = 0; i < bin_count; i = i + 1 {
if counts[i] == 0 {
continue
}
let mean_prediction = sums[i] / totals[i]
let event_rate = events[i] / totals[i]
let gap = (mean_prediction - event_rate).abs()
ece = ece + totals[i] / total_weight * gap
if gap > maximum_gap {
maximum_gap = gap
}
bins.push({
index: i,
count: counts[i],
weight: totals[i],
mean_prediction,
event_rate,
gap,
})
}
let e = match evaluate_predictions(labels, predictions, weights) {
Err(e) => return Err(e)
Ok(v) => v
}
Ok({ bins, brier: e.mse, ece, maximum_gap, })
}
///|
pub fn train_probability(
samples : Array[Sample],
direction? : Direction = Increasing,
) -> Result[Model, String] {
match validate_samples(samples) {
Err(e) => return Err(e)
Ok(_) => ()
}
for s in samples {
if s.y < 0.0 || s.y > 1.0 {
return Err("event rates outside [0,1]")
}
}
train_l2(samples, direction~)
}
///|
#deprecated
pub extend ReliabilityBin with @debug.Debug::{to_repr}
///|
#deprecated
pub extend Reliability with @debug.Debug::{to_repr}