Rust记忆算法库FSRS的使用:高效间隔重复系统实现智能学习与记忆管理
Rust记忆算法库FSRS的使用:高效间隔重复系统实现智能学习与记忆管理
FSRS for Rust
这个crate包含了一个Rust API,用于训练FSRS参数,以及使用它们来安排卡片。
Free Spaced Repetition Scheduler (FSRS)是一个现代间隔重复算法。它基于Piotr Wozniak(SuperMemo的创建者)提出的DSR模型。
FSRS-rs是FSRS的Rust实现。它设计用于Anki(一个流行的间隔重复软件)。Anki 23.10已经将FSRS集成为替代调度器。
快速开始
// 选择你喜欢的保留率(见上文)
let optimal_retention = 0.75;
// 使用默认参数/权重作为调度器
let fsrs = FSRS::new(Some(&[]))?;
// 创建一个全新的卡片
let day1_states = fsrs.next_states(None, optimal_retention, 0)?;
// 第一天评为"困难"
let day1 = day1_states.hard;
dbg!(&day1); // 安排在"4天后"
// 现在我们在2天后复习这张卡片
let day3_states = fsrs.next_states(Some(day1.memory), optimal_retention, 2)?;
// 这次评为"良好"
let day3 = day3_states.good;
dbg!(day3);
完整示例
use fsrs::FSRS;
use fsrs::model::State;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. 初始化FSRS调度器
let optimal_retention = 0.9; // 设置更高的记忆保留率目标
let fsrs = FSRS::new(Some(&[]))?; // 使用默认参数
// 2. 创建新卡片并获取初始状态
let initial_states = fsrs.next_states(None, optimal_retention, 0)?;
println!("新卡片初始状态:");
print_states(&initial_states);
// 3. 用户第一次复习(选择困难)
let first_review = initial_states.hard;
println!("\n第一次复习(选择困难)后:");
println!("下次复习间隔: {}天", first_review.interval);
println!("记忆稳定性: {}", first_review.memory.stability);
// 4. 模拟5天后进行第二次复习
let second_review_states = fsrs.next_states(
Some(first_review.memory),
optimal_retention,
5 // 距离上次复习的天数
)?;
println!("\n第二次复习可选状态:");
print_states(&second_review_states);
// 5. 用户第二次复习(选择良好)
let second_review = second_review_states.good;
println!("\n第二次复习(选择良好)后:");
println!("下次复习间隔: {}天", second_review.interval);
println!("记忆稳定性: {}", second_review.memory.stability);
// 6. 模拟10天后进行第三次复习
let third_review_states = fsrs.next_states(
Some(second_review.memory),
optimal_retention,
10
)?;
println!("\n第三次复习可选状态:");
print_states(&third_review_states);
Ok(())
}
fn print_states(states: &fsrs::model::States) {
println!("容易: {}天后", states.easy.interval);
println!("良好: {}天后", states.good.interval);
println!("困难: {}天后", states.hard.interval);
println!("重学: {}天后", states.again.interval);
}
本地开发
将以下内容添加到.git/hooks/pre-commit
中,然后执行chmod +x .git/hooks/pre-commit
:
#!/bin/sh
cargo fmt
cargo clippy -- -D warnings
git add .
常见问题
-
与rs-fsrs有什么区别?
- 如果你想安排卡片,使用[语言]-fsrs或绑定
- 如果你要做优化,使用这个crate或其绑定
-
为什么不合并为一个crate?
- 计算权重涉及张量操作。所以初始数据类型不同(Tensor vs Vec/Slice)
- 另一个原因是,当使用Tensor时,其他语言很难移植它们的版本
绑定支持
- C
- Python
- Nodejs
- Dart
- PHP
1 回复
Rust记忆算法库FSRS的使用:高效间隔重复系统实现智能学习与记忆管理
什么是FSRS
FSRS(Free Spaced Repetition Scheduler)是一个基于Rust实现的高效间隔重复算法库,它可以帮助开发者构建智能的学习和记忆管理系统。间隔重复是一种科学的学习方法,通过在最合适的时机复习内容,最大化长期记忆保留率。
主要特性
- 基于Rust实现,高性能且内存安全
- 算法基于最新的记忆研究
- 可预测的记忆保留率计算
- 灵活可定制的调度参数
- 跨平台支持
安装方法
在Cargo.toml中添加依赖:
[dependencies]
fsrs = "0.1" # 请检查最新版本号
基本使用方法
1. 创建卡片和复习记录
use fsrs::FSRS;
use fsrs::card::Card;
use fsrs::review::ReviewRating;
fn main() {
// 初始化FSRS调度器
let fsrs = FSRS::new();
// 创建一张新卡片
let mut card = Card::new();
// 模拟用户复习
let review = fsrs.schedule(&card, ReviewRating::Good);
println!("下次复习时间: {:?}", review.scheduled_days);
// 更新卡片状态
card.update(&review);
}
2. 自定义参数
use fsrs::parameters::Parameters;
let custom_params = Parameters {
w: [1.0, 1.0, 5.0, -0.5, -0.5, 0.2, 1.4, -0.12, 0.8, 2.0, -0.2, 0.2, 1.0],
// 其他参数...
};
let fsrs = FSRS::with_parameters(custom_params);
3. 批量处理复习
use fsrs::review::ReviewLog;
let mut cards = vec![Card::new(), Card::new(), Card::new()];
let mut logs = Vec::new();
for card in &mut cards {
let review = fsrs.schedule(card, ReviewRating::Good);
logs.push(ReviewLog::new(card.id, ReviewRating::Good, review.scheduled_days));
card.update(&review);
}
高级功能
记忆状态预测
let retention = fsrs.predict_retention(&card, 30); // 预测30天后的记忆保留率
println!("30天后记忆保留率: {:.2}%", retention * 100.0);
难度和稳定性分析
println!("当前卡片难度: {:.2}", card.difficulty);
println!("当前记忆稳定性: {:.2}", card.stability);
完整示例:构建一个功能完整的学习应用
use fsrs::{FSRS, Card, ReviewRating, Parameters};
use chrono::{Local, Duration};
use std::collections::HashMap;
use std::io;
// 学习应用数据结构
struct LearningApp {
fsrs: FSRS,
cards: Vec<Card>,
card_contents: HashMap<u64, String>, // 卡片ID到内容的映射
next_id: u64,
}
impl LearningApp {
fn new() -> Self {
// 使用默认参数初始化FSRS
let fsrs = FSRS::new();
Self {
fsrs,
cards: Vec::new(),
card_contents: HashMap::new(),
next_id: 1,
}
}
// 使用自定义参数初始化
fn with_parameters(params: Parameters) -> Self {
let fsrs = FSRS::with_parameters(params);
Self {
fsrs,
cards: Vec::new(),
card_contents: HashMap::new(),
next_id: 1,
}
}
// 添加新卡片
fn add_card(&mut self, content: String) {
let mut card = Card::new();
card.id = self.next_id;
self.cards.push(card);
self.card_contents.insert(self.next_id, content);
self.next_id += 1;
println!("已添加卡片: {}", content);
}
// 列出所有卡片
fn list_cards(&self) {
println!("\n当前卡片列表:");
for (i, card) in self.cards.iter().enumerate() {
if let Some(content) = self.card_contents.get(&card.id) {
println!("[{}] {} (难度: {:.2}, 稳定性: {:.2})",
i, content, card.difficulty, card.stability);
}
}
}
// 复习卡片
fn review_card(&mut self) {
self.list_cards();
println!("\n请输入要复习的卡片编号:");
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
if let Ok(index) = input.trim().parse::<usize>() {
if index < self.cards.len() {
println!("\n请为这张卡片评分:");
println!("1. 困难");
println!("2. 一般");
println!("3. 简单");
println!("4. 完美");
let mut rating_input = String::new();
io::stdin().read_line(&mut rating_input).unwrap();
let rating = match rating_input.trim() {
"1" => ReviewRating::Hard,
"2" => ReviewRating::Good,
"3" => ReviewRating::Easy,
"4" => ReviewRating::Perfect,
_ => {
println!("无效输入,使用默认评分: 一般");
ReviewRating::Good
}
};
let card = &mut self.cards[index];
let review = self.fsrs.schedule(card, rating);
card.update(&review);
let next_review = Local::today() + Duration::days(review.scheduled_days as i64);
println!("\n复习完成!下次复习时间: {}",
next_review.format("%Y-%m-%d"));
// 预测记忆保留率
let retention = self.fsrs.predict_retention(card, 30);
println!("预测30天后记忆保留率: {:.2}%", retention * 100.0);
} else {
println!("无效的卡片编号");
}
} else {
println!("无效的输入");
}
}
// 批量导入卡片
fn import_cards(&mut self, contents: Vec<String>) {
for content in contents {
self.add_card(content);
}
}
// 导出学习数据
fn export_data(&self) -> Vec<(String, f64, f64, i64)> {
self.cards.iter().map(|card| {
let content = self.card_contents.get(&card.id)
.unwrap_or(&String::new()).clone();
(content, card.difficulty, card.stability, card.elapsed_days)
}).collect()
}
}
fn main() {
println!("FSRS学习应用演示");
// 初始化应用
let mut app = LearningApp::new();
// 添加示例卡片
app.add_card("Rust所有权概念".to_string());
app.add_card("借用规则".to_string());
app.add_card("生命周期注解".to_string());
// 主循环
loop {
println!("\n请选择操作:");
println!("1. 列出所有卡片");
println!("2. 复习卡片");
println!("3. 添加新卡片");
println!("4. 退出");
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
match input.trim() {
"1" => app.list_cards(),
"2" => app.review_card(),
"3" => {
println!("请输入卡片内容:");
let mut content = String::new();
io::stdin().read_line(&mut content).unwrap();
app.add_card(content.trim().to_string());
},
"4" => break,
_ => println!("无效的选择"),
}
}
// 导出数据示例
let exported_data = app.export_data();
println!("\n导出数据:");
for data in exported_data {
println!("内容: {}, 难度: {:.2}, 稳定性: {:.2}, 已过天数: {}",
data.0, data.1, data.2, data.3);
}
}
注意事项
- FSRS算法基于用户的复习表现不断优化调度,因此需要持续记录用户的复习反馈
- 对于新卡片,初始几次复习对算法调整很重要
- 可以定期(如每月)导出卡片数据备份
- 不同学习材料可能需要微调参数以获得最佳效果
性能建议
- 对于大规模卡片集(10万+),考虑分批处理
- 持久化存储卡片状态,避免每次重新计算
- 可以预计算未来几天的复习计划,减少实时计算压力
FSRS为Rust开发者提供了一个强大的工具来构建高效的学习和记忆管理应用,结合科学的记忆算法和Rust的性能优势,可以创建出响应迅速、效果显著的学习系统。