FSRS for Anki 的发展历程

  • FSRS 算法由作者在 Anki 中实现,历经多个版本迭代,通过社区反馈和数据分析不断优化,显著提升了记忆效果。
  • FSRS 的开发受益于 Anki 开发者 Dae 的支持和社区贡献者的积极参与,Rust 版本的优化器为集成到 Anki 奠定了基础。
  • FSRS 持续改进,通过引入短期记忆效应、调整难度更新和权重等方法,降低预测误差,并发布数据集促进研究。

好久不见!我最近正忙于 FSRS-6 的开发以及 Anki 25.5.x 的相关更新。由于工作上的一些变动,我将暂时从 FSRS 的工作中抽身休息一下。为了帮助更多人了解 FSRS 及其相关的研发工作,我写了这篇关于我和 FSRS 漫长历程的文章。

感谢 u/ClarityInMadness 简化了我的帖子,使其更易于普通读者理解。

为了获得更好的阅读体验(技术细节默认折叠),请在我的博客上阅读:The History of FSRS for Anki

我是 FSRS 的创建者,高中时使用 Anki 取得的成功激发了我对间隔重复算法的浓厚兴趣。

2022 年 8 月 19 日

一切始于我在 Reddit 上发的一个帖子。在我的论文被 ACM SIGKDD 接收后,我在 r/Anki 上分享了这件事:

A Stochastic Shortest Path Algorithm for Optimizing Spaced Repetition Scheduling | Proceedings of the 28th ACM SIGKDD Conference on Knowledge Discovery and Data Mining : r/Anki

但随后,一位评论者认为这不过是又一个“听起来很酷但没人会去实际实现的东西”。那条评论真的惹恼了我。于是,为了证明他们是错的,我决定在 Anki 中实现 FSRS 算法。

  • 技术细节
  • 那时我已经有一段时间没用 Anki 了。在此期间,它的代码库已经用 Rust 重写,并且开发者们引入了通过 JavaScript 实现自定义调度的支持。由于当时我对 Rust 完全不熟悉,我选择使用 Anki 基于 JavaScript 的自定义调度脚本功能来实现 FSRS。

2022 年 8 月 30 日

我很快遇到了第一个障碍:自定义调度不支持直接在卡片中存储记忆状态,而这对于实现 FSRS 至关重要。我在 Anki 论坛上报告了这个问题,Anki 的首席开发者 Dae 在 Anki 2.1.55 中实现了必要的功能。

讨论:Some problems in implementing a state-of-the-art SRS scheduler on Anki - Anki / Scheduling - Anki Forums

2022 年 9 月 8 日

我很快完成了论文中算法简化版的实现,并在 GitHub 上开源了调度器的代码。之后,那位最初不看好它的 Redditor 居然收回了他的话。有趣的是,他后来成为了 FSRS 社区中最活跃的贡献者之一。

Implement a new spaced repetition algorithm based on anki custom scheduling. : r/Anki

2022 年 9 月 18 日 (FSRS v1)

我通过 Google Colab 添加了一个优化器,创建了第一个可用的 FSRS 版本。

New progress in implementing the custom algorithm. : r/Anki

  • 技术细节
  • FSRS 必须从复习历史中学习个体的记忆模式。我无法在 Anki 的 JavaScript 调度器或插件中运行优化器,所以我使用 Google Colab 来托管机器学习代码。FSRS 优化器和调度器代码作为 FSRS v1 在 GitHub 上发布。

2022 年 9 月 21 日

我在 Colab 中构建了一个基于 Python 的 FSRS 模拟器来测试调度。这让我能够看到优化后的 FSRS 实际会如何安排复习。

2022 年 9 月 28 日 (FSRS v2)

我改进了模型,增加了更多参数,并使用了我论文中的遗忘后稳定性公式。巧合的是,这次更新恰逢 Anki 2.1.55 Beta 版发布。这个 Beta 版允许通过自定义调度脚本功能在卡片上存储自定义数据。

Anki 2.1.55 Beta is now available. : r/Anki

  • 技术细节
  • FSRS v1 使用了 SuperMemo 的 PLS 公式,但这并不适合我的数据。我移植了我论文中的 PLS 公式,为初始稳定性和难度添加了更多参数,并实现了难度均值回归以避免“Ease Hell”,将总参数数量从 7 个增加到 14 个。Anki 2.1.55 Beta 允许存储自定义数据。
  • Release v2.0.0 · open-spaced-repetition/fsrs4anki

2022 年 10 月 5 日 (FSRS v3 & Helper 插件)

我创建了一个插件来读取完整的复习日志并准确地重新计算记忆状态。

  • 技术细节
  • 脚本无法访问卡片的完整历史,因此将 SM-2 数据转换为 FSRS 状态只是近似的。此外,更新参数会导致累积误差。我构建了 FSRS Helper 插件来解析日志,用当前参数重新计算记忆状态,并调整间隔。
  • 从 Python 解析 JavaScript 代码被证明是一个主要的麻烦。我最终决定使用正则表达式直接从自定义调度脚本中提取参数。问题在于,在 FSRS v2 中,参数是根据它们所属的记忆公式分组的,这使得正则表达式匹配相当复杂。因此,我决定将所有参数存储在一个扁平的数组中。在为这个新的参数结构重构代码时,我还借此机会重新设计了 FSRS 中的难度计算,灵感来自 SM-18 的难度公式。
  • FSRS v3 有 13 个参数,而 FSRS v2 有 14 个。
  • FSRS v3 发布:Big update in FSRS4Anki v3.0.0 : r/Anki
  • 插件::gear:FSRS Helper (Postpone & Advance & Load Balance & Easy Days & Disperse Siblings) - AnkiWeb

2022 年 10 月 18 日

我开始向志愿者收集用于 SRS 研究的复习数据。

数据收集表:Collect review data for SRS research.

2022 年 11 月 16 日

FSRS v3 发布后,反馈增多,促使我专注于实现功能请求和修复错误。在此阶段,我添加了“建议保留率”功能,旨在最小化复习工作量。它采用了我论文中 SSP-MMC 优化方法的简化版本。

New features of FSRS4Anki from v3.0.0 to v3.6.0 : r/Anki

Introduce recent changes of FSRS4Anki, and want to collect some feedback : r/Anki

2023 年 1 月 28 日

我使用 SuperMemo 的经验突显了其 Advance(提前复习)和 Postpone(推迟复习)功能的价值。FSRS 提供了智能判断哪些特定卡片最能从提前或推迟复习中受益的能力。因此,我将这两个功能整合到了 FSRS Helper 插件中。

Let your review be freer: postpone & advance cards via FSRS4Anki Helper : r/Anki

2023 年 2 月 11 日

一些用户抱怨他们的每日复习量波动很大,而另一些用户则希望减少周末的复习量。虽然已经存在解决这些问题的插件,但它们通常需要很长时间才能生效。然而,FSRS 能够在重新调度期间批量修改卡片的到期日和间隔。应几位用户的请求,我将“负载均衡”和“空闲日”功能都整合到了 FSRS Helper 插件中。前者有助于平滑每日复习负载,后者则允许用户在一周中的特定日子安排较少的复习。

Load Balance & Free Weekend have been implemented in the FSRS4Anki helper add-on! : r/Anki

2023 年 3 月 16 日

随着社区内积极反馈的增多,越来越多的 Anki 用户开始使用 FSRS。因此,Anki 的开发者 Dae 开始考虑将 FSRS 直接集成到 Anki 中。对我来说,这无疑是最令人兴奋的消息,因为这意味着最受欢迎的开源间隔重复软件可能会使用我研究和开发的算法。这也激励我计划进一步改进 FSRS。

Integrate FSRS into Anki as an optional feature · Issue #2443 · ankitects/anki

2023 年 4 月 12 日

为了直观地识别 FSRS 的弱点,我将校准图引入了优化器。

Feat/Calibration graph by L-M-Sherlock · Pull Request #212 · open-spaced-repetition/fsrs4anki

2023 年 4 月 16 日

引入校准图成为了社区驱动改进 FSRS 算法的催化剂。从那时起,几位活跃的贡献者和我一起提出并测试了数十个改进想法。

与此同时,一些用户抱怨 FSRS 将“兄弟”卡片(siblings)安排得太近。我在 FSRS Helper 插件中实现了分散兄弟卡片(Disperse Siblings)的功能。

Calibration between actual retention and predicted retention is not great · Issue #215 · open-spaced-repetition/fsrs4anki

Feat/disperse siblings by L-M-Sherlock · Pull Request #61 · open-spaced-repetition/fsrs4anki-helper

2023 年 4 月 30 日

还记得我开头提到的那位评论者吗?他们引发了这些令人难以置信的讨论帖。

[Feature Request] Sharing ideas for further improvement of the algorithm · Issue #239 · open-spaced-repetition/fsrs4anki

[Feature Request] Improving the algorithm, continuation · Issue #282 · open-spaced-repetition/fsrs4anki

几位专注的用户在网上进行了数百轮的辩论,最终产生了一些关键想法,显著改进了 FSRS。

2023 年 6 月 9 日

我将优化器重构为一个独立的 Python 包,添加了详细的评估,并引入了 mini-batch 支持,将训练速度提高了约 10 倍。

Main updates of FSRS4Anki from v3.7.0 to v3.23.0 : r/Anki

  • 技术细节
  • 为了帮助社区调试和验证想法,我添加了详细的模型评估。在贡献者的帮助下,我们还将优化器重构为一个独立的、封装好的 Python 包,极大地简化了维护和开发。后来,为了提高优化速度,我添加了 mini-batch 支持,将训练时间缩短了约 10 倍。

2023 年 7 月 13 日 (FSRS v4)

我发布了 FSRS v4,采用了幂律遗忘曲线,改进了计算难度和记忆稳定性的公式,并加入了异常值过滤。

  • 技术细节
  • 主要变化:
    1. 指数 → 幂律遗忘曲线
    2. hard_penalty & easy_bonus 参数
    3. 四个独立的初始稳定性参数
    4. 基于首次复习的预训练
    5. 异常值过滤器
    6. 最佳轮次参数选择
  • 参数数量从 13 个增加到 17 个。
  • Release v4.0.0 · open-spaced-repetition/fsrs4anki

2023 年 7 月 14 日

FSRS 的难度计算公式相当简单,所以我们都认为那里有明显的改进空间。然而,大多数尝试都失败了。

[Enhancement] Improving the function for calculating difficulty · Issue #352 · open-spaced-repetition/fsrs4anki

2023 年 7 月 29 日 (FSRS-Optimizer)

我将优化器拆分到其自己的仓库,并开始定义标准的复习日志格式,以促进更广泛的应用。

  • 技术细节
  • 为了简化开发和维护,我将优化器代码从 fsrs4anki 仓库提取到一个专门的仓库——fsrs-optimizer。与此同时,我开始着手定义间隔重复复习日志的标准格式。这种标准化工作的目的是让各种 SRS 应用能够采用 FSRS,并利用它们各自的用户数据进行算法优化。
  • PyPI 上的 FSRS-Optimizer:FSRS-Optimizer · PyPI

2023 年 8 月 17 日 (FSRS-rs)

我的朋友 (Asuka Minato) 和我开始开发优化器的 Rust 版本。他有很强的 Rust 基础但缺乏机器学习知识,而我有机器学习背景但不懂 Rust。这似乎是完美的组合,所以我们决定合作开发 FSRS 优化器的 Rust 版本,特别是为最终将 FSRS 集成到 Anki 做准备。

  • 技术细节
  • 最初,我们尝试使用 tch crate。然而,它对 libtorch 的依赖导致编译后的文件大约有 200MB——几乎是 Anki 本身体积的一半——这显然是不可接受的。这个挫折几乎让我们放弃了 Rust 的方案。之后,Minato 向我推荐了 tinygrad。因为它不依赖 torch,所以看起来很有希望在 Anki 中使用。但在不懈的努力之后,我发现它的性能太差并且充满了 bug,迫使我放弃了这条路。此后,Minato 再次介入帮助评估不同的 crate。他探索了 dfdx、candle 和 burn。最终,burn 被证明是最用户友好且适合我们需求的。于是,FSRS-rs 的开发正式启动。
  • WIP/rewrite FSRS in burn · open-spaced-repetition/fsrs-rs@a9cc7df
  • 从 Asuka Minato 的视角:陪伴是最长情的告白(contribute to anki)
  • 顺便说一句,当时 GPT-4 在写代码方面非常有用。它让完全不懂 Rust 的我能够用它将 Python 代码翻译成 Rust。我也在这个过程中开始学习 Rust,Minato 也教了我不少。我估计大约 60% 的初始 FSRS-rs 代码是 AI 生成的。

2023 年 8 月 23 日

我发现校准图是可以被操控(或“取巧”)的。这意味着仅基于校准图的指标可能会产生误导。对数损失(Log loss)成为了首选的黄金标准指标。

Calibration graph can be cheated by the algorithm which always predicts the average. · Issue #1 · open-spaced-repetition/spaced-repetition-algorithm-metric

2023 年 9 月 6 日 (SRS Benchmark)

我使用 66 个志愿者的复习数据集合创建了一个基准测试套件,用于评估 FSRS 和未来的模型。

  • 技术细节
  • 在 FSRS v4 的改进过程中,我们已经摘取了许多“低垂的果实”,使得进一步的进展越来越困难。此外,当时用于评估模型的数据集仅来自少数活跃贡献者,这使得可靠地验证较小的改进变得困难。在与社区成员讨论后,我开始着手创建一个基准测试。目标是使用我之前收集的更大规模的复习数据集(当时包含 66 个集合)来评估 FSRS v4 和未来的改进。
  • [Doc] Introduction for FSRS v4 · Issue #351 · open-spaced-repetition/fsrs4anki
  • SRS Benchmark 的首次提交:build dataset from anki file · open-spaced-repetition/srs-benchmark@450ee90
  • 这个基准测试也帮助我将 FSRS-rs 与 FSRS-Optimizer 对齐,使得两者产生几乎相同的结果。

2023 年 9 月 8 日

修复了一些问题后,FSRS-rs 实现了完整的优化器功能,并开始了集成到 Anki 的工作。

2023 年 9 月 14 日

又一次,数百轮的辩论开始了。

[Feature Request] Ideas to further improve the accuracy of the algorithm · Issue #461 · open-spaced-repetition/fsrs4anki

我无法在此总结所有内容,但关键的结果是改变遗忘曲线的形状,使其更平坦。

2023 年 11 月 1 日

Anki 23.10 发布,标志着第一个内置 FSRS 的官方版本。这意味着使用 FSRS 算法的用户数量预计将迅速增长。这也显著提高了 FSRS 在开发者中的知名度,导致用其他编程语言实现的 FSRS 算法库逐渐涌现,并被越来越多的其他间隔重复软件采用。

Release 23.10 · ankitects/anki

2023 年 11 月 22 日 (来自 Anki 的数据集)

我非常感谢 Dae。根据 Anki 允许将复习数据用于研究的隐私政策,他提供了来自 20,000 个用户集合的原始数据,其中包含惊人的 14 亿条复习日志——这是间隔重复领域迄今为止最大的数据集。

2023 年 12 月 26 日 (FSRS 4.5)

基于之前的辩论和分析,更平坦的遗忘曲线想法被接受,我发布了包含此更改的 FSRS-4.5。

Feat/update to FSRS-4.5 by L-M-Sherlock · Pull Request #568 · open-spaced-repetition/fsrs4anki

2024 年 1 月 6 日

我对短期复习效应的研究揭示了一个关键发现:当用户在首次学习的当天多次复习一张新卡片时,评分序列会显著影响卡片的初始稳定性。这一见解随后促成了 FSRS-5 中使用当日复习来更新稳定性的方法。

First day’s series of ratings may have significant impact on initial stability · Issue #2 · open-spaced-repetition/short-term-memory-research

2024 年 1 月 29 日

我将 FSRS-rs v0.1.0 发布到了 crates.io

Release v0.1.0 · open-spaced-repetition/fsrs-rs

fsrs - crates.io: Rust Package Registry

2024 年 2 月 23 日

随着 AnkiDroid 2.17.0 的发布,原生 FSRS 支持在所有主要平台(桌面、iOS 和 Android)上都已完成。

AnkiDroid Changelog Version 2.17.0 (20240223)

2024 年 2 月 24 日 (FSRS-Anki-20k)

为了吸引更多研究人员,我发布了用于 FSRS 开发的 20,000 个 Anki 集合的数据集,命名为 FSRS-Anki-20k。

open-spaced-repetition/FSRS-Anki-20k · Datasets at Hugging Face

2024 年 3 月 1 日

为了使指标直观且难以被操控,我重新设计了 RMSE(bins)。

2024 年 4 月 6 日

在研究了几个月的短期记忆模型后,我放弃了。尝试用 FSRS 预测短期记忆的关键教训是,短期和长期记忆的工作机制截然不同。最终,我采用了一种简化的方法:使用短期复习来优化与长期记忆相关的预测。

  • 技术细节
  • 短期复习本身的结果并未用于模型优化。换句话说,我将短期复习的日志包含在时间序列特征中,但将它们排除在用于训练的标签之外。此外,由于没有专门的短期记忆模型,我也忽略了这些短期复习的具体时间间隔。这种简化的解决方案导致 FSRS 对长期保留率的预测误差略有减少。但这仍然不值得进行一次主版本更新。
  • Feat/FSRS-5 by L-M-Sherlock · Pull Request #114 · open-spaced-repetition/fsrs-optimizer

2024 年 5 月 17 日

我将初始难度建模为初始评分的指数函数,略微提高了 FSRS 的准确性。

  • 技术细节
  • 在分析 FSRS 参数分布时,我注意到对应于“Easy”按钮的初始稳定性非常高。具体来说,“Easy”和“Good”的初始稳定性之间的差异(或差距)远大于“Good”和“Hard”之间的差异。同样的模式也适用于“Hard”和“Again”的初始稳定性之间的差距。这使我假设初始难度可能遵循类似的模式。因此,我进行了一项实验,将初始难度建模为初始评分的指数函数。结果确实显示 FSRS 的误差略有减少。
  • Feat/FSRS-5 by L-M-Sherlock · Pull Request #114 · open-spaced-repetition/fsrs-optimizer

2024 年 6 月 13 日

我更新了模拟器,通过对每天的(短期复习)次数和评分取平均值来近似短期复习。

2024 年 7 月 10 日 (FSRS 5)

我发布了 FSRS 5,增加了短期复习效应和改进的初始难度,将预测误差降低了约 4%。

2024 年 9 月 7 日 (FSRS Megathread)

随着关于 FSRS 的讨论越来越频繁,Anki Discord 服务器上创建了 FSRS Megathread(集中讨论帖),为这些对话提供了一个中心场所。这吸引了更多的贡献者,并为改进 FSRS 产生了更多想法。

https://discord.com/channels/368267295601983490/1282005522513530952

2024 年 10 月 11 日

一位贡献者重构了 Rust 模拟器,将速度提高了约 8 倍。

  • 技术细节
  • 最初,FSRS-rs 模拟器与其 Python 版本非常相似。但有一个关键区别:Python 版本利用 Numpy 进行高效的并行处理,并在每日粒度上进行了优化,而 Rust 实现中缺少这种优化。得益于一位社区成员的贡献,FSRS-rs 模拟器随后被重构为在卡片级别粒度上运行。在重构过程中,我确保了这一更改与按天处理的方法相比,不会改变模拟结果。这次重构的最终结果是显著的性能提升,模拟速度加快了近 8 倍。
  • Make simulate iterate by card instead of by day. by Luc-Mcgrady · Pull Request #235 · open-spaced-repetition/fsrs-rs

2024 年 10 月 17 日

我对难度更新实施了阻尼(damping),使得难度接近其最大值的速度变慢。这出乎意料地将误差降低了约 1%。

  • 技术细节
  • 一位 FSRS 用户观察到,他们的许多卡片很快就达到了最大难度值 10。这大大降低了难度指标区分卡片的能力,为卡片排序提供的粒度很差。因此,他们建议在难度更新过程中增加阻尼,使得当难度(D)接近 10 时,更新的幅度减小。
  • 我们社区成员进行的基准测试显示,这种方法出人意料地将预测误差降低了约 1%,而且没有引入任何额外的参数。然而,在实施这种方法时,我遇到了一个问题:阻尼效应是双向的。这意味着当 D 接近 10 时,难度的增加和减少都会被阻尼(幅度减小)。这可能导致类似于“Ease Hell”的情况,即难度可能卡在高值。然而,当我实施单向阻尼(只减缓增加而不减缓减少)时,指标的改善消失了。
  • 这让我重新思考:也许“Ease Hell”实际上并不像人们常说的那样是个问题。大多数专门消除它的尝试似乎都对指标产生了负面影响。最终,尽管双向阻尼有潜在的缺点,但鉴于积极的基准测试结果,我决定在 FSRS-5 中实施该版本。
  • Suggestion for Adjusting Difficulty Score to Use an Asymptote at 10 · Issue #697 · open-spaced-repetition/fsrs4anki

2024 年 11 月 5 日 (anki-revlogs-10k)

在 Dae 的帮助下,我们发布了一个新的 Anki 数据集。它包含 10,000 个集合,带有笔记、牌组和预设 ID,用于更详细的分析。

  • 技术细节
  • 这样做的动机来自于我对 20k 数据集的分析,我注意到一些用户的遗忘曲线不是单调的。这看起来像是混合了来自不同学习材料和学习选项的曲线的结果。为了进一步研究这个问题,我需要知道不同的卡片属于哪个牌组,以及这些牌组是否使用了不同的预设配置。最终,我们在新的数据集中添加了笔记 ID、牌组 ID 和预设 ID。这使得分析诸如来自同一笔记的不同卡片之间的相互作用、为不同牌组分别优化参数的效果等成为可能。
  • open-spaced-repetition/anki-revlogs-10k · Datasets at Hugging Face

2024 年 11 月 10 日 (学习步数统计)

由于短期记忆模型开发进展缓慢,我考虑在 FSRS Helper 插件中增加短期复习的统计分析功能。目标是帮助用户量化他们自己的短期记忆,并为他们提供可用于调整学习步数的数据。

Feat/step stats by L-M-Sherlock · Pull Request #487 · open-spaced-repetition/fsrs4anki-helper

New Feature: Quantify Your Short-Term Memory in Detail. : r/Anki

Recommended (re)learning steps powered by FSRS Helper : r/Anki

2024 年 12 月 30 日 (FSRS-5 近期加权)

我向优化器添加了近期加权(recency weighting),对较新的、近期复习的不良预测给予 FSRS 更大的惩罚,而对较旧的复习的不良预测给予较小的惩罚。这将预测误差降低了约 4.5%。

  • 技术细节
  • 在对模型结构进行细化感到疲惫后,我开始重新审视过去的实验。我重新发现了一个实验,其中我尝试为复习样本分配不同的权重:‣ 这促使我重新考虑专注于优化过程本身——可能在不改变模型架构的情况下提高模型性能。SRS Benchmark 中 TimeSeriesSplit 的使用提醒我,用户的记忆模式会随着时间推移而演变(例如,由于学习不同的材料或改变学习习惯)。这引出了一个假设:也许给予更近期的数据更高的权重可以提高模型对未来复习的预测性能?通过与 Claude 的讨论,我了解到这种方法被称为“近期加权”(Recency weighting)。
  • 因此,我在优化器中实现了这种方法并进行了基准测试。结果表明,这种方法将预测误差又降低了 3%:https://discord.com/channels/368267295601983490/1282005522513530952/1318519440647655445
  • 根据社区成员的建议,我随后测试了各种加权函数,最终实现了约 4.5% 的预测误差降低。然后我在 FSRS-rs 中实现了它。
  • Feat/support recency weighting by L-M-Sherlock · Pull Request #260 · open-spaced-repetition/fsrs-rs

FSRS-6 即将到来。未完待续。