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 .

常见问题

  1. 与rs-fsrs有什么区别?

    • 如果你想安排卡片,使用[语言]-fsrs或绑定
    • 如果你要做优化,使用这个crate或其绑定
  2. 为什么不合并为一个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);
    }
}

注意事项

  1. FSRS算法基于用户的复习表现不断优化调度,因此需要持续记录用户的复习反馈
  2. 对于新卡片,初始几次复习对算法调整很重要
  3. 可以定期(如每月)导出卡片数据备份
  4. 不同学习材料可能需要微调参数以获得最佳效果

性能建议

  1. 对于大规模卡片集(10万+),考虑分批处理
  2. 持久化存储卡片状态,避免每次重新计算
  3. 可以预计算未来几天的复习计划,减少实时计算压力

FSRS为Rust开发者提供了一个强大的工具来构建高效的学习和记忆管理应用,结合科学的记忆算法和Rust的性能优势,可以创建出响应迅速、效果显著的学习系统。

回到顶部