memoryweave/rust/src/decay_calibrate.rs

143 lines
4.3 KiB
Rust

// 衰减模型校准 — 简单线性回归拟合 decay_rate
// 每月深度整合时运行:抽样 → 按类别分组 → 回归 → 更新 decay_rate
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecaySample {
pub category: String,
pub days_old: f64,
pub current_score: f64,
pub original_score: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalibrationResult {
pub rates: HashMap<String, f64>,
pub r_squared: HashMap<String, f64>,
pub sample_count: usize,
}
pub struct DecayCalibrator {
max_deviation: f64,
min_samples_per_category: usize,
}
impl DecayCalibrator {
pub fn new() -> Self {
Self {
max_deviation: 0.5,
min_samples_per_category: 5,
}
}
pub fn calibrate(
&self,
samples: &[DecaySample],
old_rates: &HashMap<String, f64>,
) -> CalibrationResult {
let mut by_category: HashMap<String, Vec<&DecaySample>> = HashMap::new();
for s in samples {
by_category
.entry(s.category.clone())
.or_insert_with(Vec::new)
.push(s);
}
let mut new_rates = HashMap::new();
let mut r_squared = HashMap::new();
for (category, cat_samples) in &by_category {
if cat_samples.len() < self.min_samples_per_category {
if let Some(&old) = old_rates.get(category) {
new_rates.insert(category.clone(), old);
}
continue;
}
// 简单 OLS: log(score) = a + b * days_old
// decay_rate = -b
let mut xs = Vec::new();
let mut ys = Vec::new();
for s in cat_samples {
if s.days_old > 0.0 && s.current_score > 0.0 {
let normalized = s.current_score / s.original_score.max(0.01);
xs.push(s.days_old);
ys.push(normalized.max(0.001).ln());
}
}
let (slope, r2) = simple_ols(&xs, &ys);
let raw_rate = (-slope).max(0.0).abs();
let old = old_rates.get(category).copied().unwrap_or(raw_rate);
let rate = if old > 0.0 {
let ratio = raw_rate / old;
if ratio > 1.0 + self.max_deviation {
old * (1.0 + self.max_deviation)
} else if ratio < 1.0 - self.max_deviation {
old * (1.0 - self.max_deviation)
} else {
raw_rate
}
} else {
raw_rate
};
new_rates.insert(category.clone(), rate);
r_squared.insert(category.clone(), r2);
}
let result = CalibrationResult {
rates: new_rates,
r_squared,
sample_count: samples.len(),
};
eprintln!(
"[decay] calibrated {} categories from {} samples",
result.rates.len(),
result.sample_count,
);
result
}
}
/// 简单 OLS 线性回归: y = a + b*x
fn simple_ols(xs: &[f64], ys: &[f64]) -> (f64, f64) {
let n = xs.len() as f64;
if n < 2.0 { return (0.0, 0.0); }
let sum_x: f64 = xs.iter().sum();
let sum_y: f64 = ys.iter().sum();
let sum_xy: f64 = xs.iter().zip(ys.iter()).map(|(x, y)| x * y).sum();
let sum_xx: f64 = xs.iter().map(|x| x * x).sum();
let denom = n * sum_xx - sum_x * sum_x;
if denom.abs() < 1e-10 { return (0.0, 0.0); }
let slope = (n * sum_xy - sum_x * sum_y) / denom;
let intercept = (sum_y - slope * sum_x) / n;
// R²
let mean_y = sum_y / n;
let ss_res: f64 = xs.iter().zip(ys.iter())
.map(|(x, y)| (y - (intercept + slope * x)).powi(2))
.sum();
let ss_tot: f64 = ys.iter().map(|y| (y - mean_y).powi(2)).sum();
let r2 = if ss_tot > 0.0 { 1.0 - ss_res / ss_tot } else { 0.0 };
(slope, r2.max(0.0).min(1.0))
}
pub fn default_decay_rates() -> HashMap<String, f64> {
let mut rates = HashMap::new();
rates.insert("system_fact".into(), 0.003);
rates.insert("user_pref".into(), 0.005);
rates.insert("proj_context".into(), 0.008);
rates.insert("tool_usage".into(), 0.010);
rates.insert("code_snippet".into(), 0.012);
rates
}