feat: add filtered_log_evidence
Scores every event on what was known before it, rather than on priors that carry information from events which had not happened yet. This is the quantity HistoryBuilder::online promised and never delivered. The pass walks slices in time order carrying its own forward messages, and per slice runs the unmodified production sweep on a scratch copy whose backward message is left improper. Reusing iterate_to_convergence rather than reimplementing inference means a competitor playing twice at one time is handled by the same within-slice EP that converge() uses, instead of being approximated the way the old evidence paths approximated it. Nothing is stored on Skill and nothing on self is mutated, so the result is independent of whether converge() has run — the property a stored field cannot have.
This commit is contained in:
+40
-1
@@ -13,7 +13,7 @@ use crate::{
|
||||
sort_time,
|
||||
storage::CompetitorStore,
|
||||
time::Time,
|
||||
time_slice::{self, EventKind, TimeSlice},
|
||||
time_slice::{self, EventKind, FilteredStep, TimeSlice},
|
||||
tuple_gt, tuple_max,
|
||||
};
|
||||
|
||||
@@ -416,6 +416,45 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
|
||||
self.log_evidence_internal(false, &targets)
|
||||
}
|
||||
|
||||
/// Walk the slices in time order carrying forward messages only.
|
||||
///
|
||||
/// This is the forward half of `iteration` with the backward half never
|
||||
/// run. It reads `self` and mutates nothing.
|
||||
fn filtered_pass(&self) -> Vec<(T, FilteredStep)> {
|
||||
let mut messages: HashMap<Index, Gaussian> = HashMap::new();
|
||||
|
||||
let mut pass = Vec::with_capacity(self.time_slices.len());
|
||||
|
||||
for slice in &self.time_slices {
|
||||
let step = slice.filtered_step(&messages, &self.agents);
|
||||
|
||||
for &(agent, posterior) in &step.posteriors {
|
||||
messages.insert(agent, posterior);
|
||||
}
|
||||
|
||||
pass.push((slice.time, step));
|
||||
}
|
||||
|
||||
pass
|
||||
}
|
||||
|
||||
/// Total log-evidence under forward-only (filtering) information.
|
||||
///
|
||||
/// Each event is scored using only what was known before it, which is the
|
||||
/// right quantity for prequential scoring and model comparison. Contrast
|
||||
/// `log_evidence`, whose per-event priors carry information from events
|
||||
/// that had not happened yet.
|
||||
///
|
||||
/// Runs a full forward pass per call and caches nothing. The result does
|
||||
/// not depend on whether `converge` has been called.
|
||||
#[must_use]
|
||||
pub fn filtered_log_evidence(&self) -> f64 {
|
||||
self.filtered_pass()
|
||||
.iter()
|
||||
.map(|(_, step)| step.log_evidence)
|
||||
.sum()
|
||||
}
|
||||
|
||||
/// Draw-probability quality metric for the given teams (key slices).
|
||||
///
|
||||
/// Values range roughly [0, 1]; 1 == perfectly matched. Supports any
|
||||
|
||||
Reference in New Issue
Block a user