Adjusted loss values

This commit is contained in:
Luc Mcgrady 2025-06-05 23:03:39 +01:00
parent 89bafdb47e
commit e642eb535e
No known key found for this signature in database
GPG key ID: 4F3D7A0B17CC3D9C
4 changed files with 35 additions and 13 deletions

View file

@ -360,7 +360,7 @@ message ComputeFsrsParamsRequest {
message ComputeFsrsParamsResponse { message ComputeFsrsParamsResponse {
repeated float params = 1; repeated float params = 1;
uint32 fsrs_items = 2; uint32 fsrs_items = 2;
optional float log_loss = 3; optional bool health_check_passed = 3;
} }
message ComputeFsrsParamsFromItemsRequest { message ComputeFsrsParamsFromItemsRequest {

View file

@ -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) 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 { impl Collection {
/// Note this does not return an error if there are less than 400 items - /// 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 /// the caller should instead check the fsrs_items count in the return
@ -77,7 +89,7 @@ impl Collection {
return Ok(ComputeFsrsParamsResponse { return Ok(ComputeFsrsParamsResponse {
params: current_params.to_vec(), params: current_params.to_vec(),
fsrs_items, fsrs_items,
log_loss: None, health_check_passed: None,
}); });
} }
// adapt the progress handler to our built-in progress handling // 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)?; let fsrs = FSRS::new(None)?;
fsrs.evaluate_with_time_series_splits(input, |_| true) fsrs.evaluate_with_time_series_splits(input, |_| true)
.ok() .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 { } else {
None None
}; };
@ -161,7 +187,7 @@ impl Collection {
Ok(ComputeFsrsParamsResponse { Ok(ComputeFsrsParamsResponse {
params, params,
fsrs_items, fsrs_items,
log_loss, health_check_passed,
}) })
} }

View file

@ -376,7 +376,7 @@ impl crate::services::BackendSchedulerService for Backend {
Ok(ComputeFsrsParamsResponse { Ok(ComputeFsrsParamsResponse {
params, params,
fsrs_items, fsrs_items,
log_loss: None, health_check_passed: None,
}) })
} }

View file

@ -41,7 +41,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const defaults = state.defaults; const defaults = state.defaults;
const fsrsReschedule = state.fsrsReschedule; const fsrsReschedule = state.fsrsReschedule;
const daysSinceLastOptimization = state.daysSinceLastOptimization; const daysSinceLastOptimization = state.daysSinceLastOptimization;
const logLossBadThreshold = 0.5;
$: lastOptimizationWarning = $: lastOptimizationWarning =
$daysSinceLastOptimization > 30 ? tr.deckConfigTimeToOptimize() : ""; $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; resp.params.length === 0;
const highLogLoss = const health_check_passed =
resp.logLoss && resp.logLoss > logLossBadThreshold; resp.healthCheckPassed && !resp.healthCheckPassed;
if (resp.logLoss) { if (health_check_passed) {
console.log(`FSRS-test-train-split-log-loss = ${resp.logLoss}`);
}
if (highLogLoss) {
setTimeout(() => alert(tr.deckConfigFsrsBadFitWarning())); setTimeout(() => alert(tr.deckConfigFsrsBadFitWarning()));
} else if (already_optimal) { } else if (already_optimal) {
const msg = resp.fsrsItems const msg = resp.fsrsItems