From e642eb535efa661118023a0c1db70f385ab987f3 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Thu, 5 Jun 2025 23:03:39 +0100 Subject: [PATCH] Adjusted loss values --- proto/anki/scheduler.proto | 2 +- rslib/src/scheduler/fsrs/params.rs | 34 ++++++++++++++++++++--- rslib/src/scheduler/service/mod.rs | 2 +- ts/routes/deck-options/FsrsOptions.svelte | 10 ++----- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto index 56708ddfb..b02315dd3 100644 --- a/proto/anki/scheduler.proto +++ b/proto/anki/scheduler.proto @@ -360,7 +360,7 @@ message ComputeFsrsParamsRequest { message ComputeFsrsParamsResponse { repeated float params = 1; uint32 fsrs_items = 2; - optional float log_loss = 3; + optional bool health_check_passed = 3; } message ComputeFsrsParamsFromItemsRequest { diff --git a/rslib/src/scheduler/fsrs/params.rs b/rslib/src/scheduler/fsrs/params.rs index d6853ca4e..6e99c50ce 100644 --- a/rslib/src/scheduler/fsrs/params.rs +++ b/rslib/src/scheduler/fsrs/params.rs @@ -51,6 +51,18 @@ pub(crate) fn ignore_revlogs_before_ms_from_config(config: &DeckConfig) -> Resul ignore_revlogs_before_date_to_ms(&config.inner.ignore_revlogs_before_date) } +/// r: retention +fn log_loss_adjustment(r: f32) -> f32 { + 0.621 * (4. * r * (1. - r)).powf(0.739) +} + +/// r: retention +/// +/// c: review count +fn rmse_adjustment(r: f32, c: u32) -> f32 { + 0.0417 / (r.powf(1.63) - 1.41) + 0.125 / ((c as f32 / 1000.).powf(0.655) + 1.22) + 0.102 +} + impl Collection { /// Note this does not return an error if there are less than 400 items - /// the caller should instead check the fsrs_items count in the return @@ -77,7 +89,7 @@ impl Collection { return Ok(ComputeFsrsParamsResponse { params: current_params.to_vec(), fsrs_items, - log_loss: None, + health_check_passed: None, }); } // adapt the progress handler to our built-in progress handling @@ -149,11 +161,25 @@ impl Collection { } } - let log_loss = if health_check { + let health_check_passed = if health_check { let fsrs = FSRS::new(None)?; fsrs.evaluate_with_time_series_splits(input, |_| true) .ok() - .map(|eval| eval.log_loss) + .map(|eval| { + let r = items.iter().fold(0, |p, item| { + p + (item + .reviews + .last() + .map(|reviews| reviews.rating) + .unwrap_or(0) + > 1) as u32 + }) as f32 + / fsrs_items as f32; + let adjusted_log_loss = eval.log_loss / log_loss_adjustment(r); + let adjusted_rmse = eval.rmse_bins / rmse_adjustment(r, fsrs_items); + + adjusted_log_loss < 1.10 && adjusted_rmse < 1.58 + }) } else { None }; @@ -161,7 +187,7 @@ impl Collection { Ok(ComputeFsrsParamsResponse { params, fsrs_items, - log_loss, + health_check_passed, }) } diff --git a/rslib/src/scheduler/service/mod.rs b/rslib/src/scheduler/service/mod.rs index fde64007f..50607ffbb 100644 --- a/rslib/src/scheduler/service/mod.rs +++ b/rslib/src/scheduler/service/mod.rs @@ -376,7 +376,7 @@ impl crate::services::BackendSchedulerService for Backend { Ok(ComputeFsrsParamsResponse { params, fsrs_items, - log_loss: None, + health_check_passed: None, }) } diff --git a/ts/routes/deck-options/FsrsOptions.svelte b/ts/routes/deck-options/FsrsOptions.svelte index 684c6711b..d06c09eb5 100644 --- a/ts/routes/deck-options/FsrsOptions.svelte +++ b/ts/routes/deck-options/FsrsOptions.svelte @@ -41,7 +41,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html const defaults = state.defaults; const fsrsReschedule = state.fsrsReschedule; const daysSinceLastOptimization = state.daysSinceLastOptimization; - const logLossBadThreshold = 0.5; $: lastOptimizationWarning = $daysSinceLastOptimization > 30 ? tr.deckConfigTimeToOptimize() : ""; @@ -191,13 +190,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html )) || resp.params.length === 0; - const highLogLoss = - resp.logLoss && resp.logLoss > logLossBadThreshold; + const health_check_passed = + resp.healthCheckPassed && !resp.healthCheckPassed; - if (resp.logLoss) { - console.log(`FSRS-test-train-split-log-loss = ${resp.logLoss}`); - } - if (highLogLoss) { + if (health_check_passed) { setTimeout(() => alert(tr.deckConfigFsrsBadFitWarning())); } else if (already_optimal) { const msg = resp.fsrsItems