SM-2 是 1990 年的算法,至今仍在为世界上大多数闪卡排程,包括 Anki 的默认排程和我们的全部排程。它简单到可以完整写出来,所以这里就完整写出来——包括孩子的卡片实际会走过的那些确切数字。

一览

概念 它是什么
难易系数 按卡片计的乘数,初始 2.5。决定这张卡的间隔增长得有多快
质量评分 这次回忆有多顺利,0–5。我们用 2、4、5
间隔 距下次复习的天数
重复次数 连续成功复习的次数
失误(lapse) 一次失败的复习。重置重复次数,但保留累计计数
起始间隔 固定:1 天,然后 6 天
此后 间隔 × 难易系数,四舍五入

三个活动部件

SM-2 为每张卡存三个数字,而且真的就只有这三个:

  1. 难易系数——初始 2.5。这张卡的排程有多宽松。
  2. 间隔——当前的间隔天数。
  3. 重复次数——连续成功了多少次。

其余一切都是推导出来的。这里没有记忆模型,没有在拟合任何曲线。它是一个反馈回路:给卡片评级,微调它的系数,相乘。

质量评分

原始算法接受一个 0 到 5 的质量分,其中低于 3 都算失败。六个选项对孩子来说太多了,多到他无法诚实使用,所以像大多数实现一样,我们把它们压缩了。我们的映射到三个:

按钮 质量分 含义
有点难 2 失败——低于及格线
记住了 4 费了点劲想起来了
太简单 5 立刻就想起来了

难易系数是怎么移动的

这就是那个公式,与 SM-2 的定义完全一致,其中 q 是质量分:

ease := ease + (0.1 − (5 − q) × (0.08 + (5 − q) × 0.02))

它看上去别扭,但它只是一条曲线:对轻松的回忆略作奖励,对失败则重重惩罚。把我们的三个评级代进去:

评级 q 系数变化
太简单 5 +0.10
记住了 4 0.00——恰好中性
有点难 2 −0.32

有两件事值得注意。

「记住了」在设计上是中性的。 代入 q = 4 得到 0.1 − 1 × (0.08 + 0.02) = 0。一个对所有卡片都按「记住了」的孩子,会永远保持默认排程。这正是预期的行为:「我知道,费了点劲」是那个校准点,不是一份奖励。

一次失败的代价,超过三次成功所能挽回的。 一次「有点难」(−0.32)需要三次「太简单」(+0.30)才能抵消。这个不对称是刻意的——「这张卡很难」的证据,被当作比「这张卡很容易」的证据更有信息量。

还有一个下限。难易系数永远不会低于 1.3,因为低于这个值,间隔几乎不再增长,卡片将永远近乎每天回来一次。一张被钉在下限上的卡片,在告诉你一件排程器修不了的事——这正是我们改为把它标记给导师的原因。

间隔是怎么算的

if q < 3:                      # 一次失败
    repetitions := 0
    interval    := 0           # 重新学习:明天再来
else:
    repetitions := repetitions + 1
    if repetitions == 1: interval := 1
    elif repetitions == 2: interval := 6
    else: interval := round(interval × ease)

前两个间隔是常数,而不是算出来的。这不是偷懒——一张只成功复习过一次的卡片,几乎不携带任何关于自身难度的证据,所以去乘一个几乎没动过的难易系数,是虚假的精确。先一天,再六天,然后才开始信任那个乘数。

有一个细节会绊倒实现 SM-2 的人:那次乘法用的是本次复习调整之前的难易系数值。我们的实现也是如此,这正是下面那些数字之所以如此的原因。

一次完整推演

我们的上限是 90 天,所以这条阶梯会终止,而不是一路跑掉。从零开始,每次都按记住了

第几次复习 复习前的系数 计算 新间隔 落在
1 2.5 固定 1 天 第 1 天
2 2.5 固定 6 天 第 7 天
3 2.5 6 × 2.5 = 15 15 天 第 22 天
4 2.5 15 × 2.5 = 37.5 38 天 第 60 天
5 2.5 38 × 2.5 = 95 → 封顶 90 天 第 150 天
6+ 2.5 封顶 90 天 每学期

现在是同一张卡,每次都按太简单。系数一路上升,所以阶梯更陡:

第几次复习 复习前的系数 计算 新间隔
1 2.5 固定 1 天
2 2.6 固定 6 天
3 2.7 6 × 2.7 = 16.2 16 天
4 2.8 16 × 2.8 = 44.8 45 天
5 2.9 45 × 2.9 = 130 → 封顶 90 天

再看一张新卡上按了一次有点难。系数从 2.5 降到 2.18,重复次数归零,而重建出来的阶梯从此永久地更温和:

1 → 6 → 13 → 28 → 61 → 90     (一次「有点难」之后)
1 → 6 → 15 → 38 → 90          (从未失败)

这张卡从此在余生里都会更频繁地来找孩子。没有人需要作出这个决定;它是从算术里掉出来的。

SM-2 不知道什么

把它的边界说清楚,因为这些边界解释了「为什么会有更好的算法」。

它没有记忆模型。 SM-2 并不估计你明天能回忆起一张卡的概率。它只有一个乘数和一条经验法则。现代排程器——SuperMemo 的 SM-17 及以后,以及 FSRS——把可提取性和稳定性建模为两个独立的量,并直接预测回忆概率。它们确实明显更准。

它无法从其他卡片、或其他人身上学习。 每张卡的难易系数都是从零开始摸索出来的,所以一张卡需要好几次复习,它的排程才能反映现实。而一个训练过的模型,开局就带着先验。

它忽略你花了多久。 立刻答出来,和挣扎了十五秒才答出来,都算「记住了」。

那些常数是任意的。 为什么是 1 天和 6 天?为什么是 ±0.1?因为在 1990 年、在某一套特定的卡片收藏上,它们管用。它们是站得住脚、经过充分检验的默认值,而不是推导出来的真理。

那我们为什么还在用它

三个理由,全都很实际。它是公开且精确的,所以我们可以用一张表把真实的阶梯给你看,而不是描述一个黑箱。更好的模型需要历史——它们要在成千上万次复习上拟合参数,而一个刚用了两个月的孩子一次都没有。而对于一套在每天 20 张上限之下的几百张卡片的收藏,效率差距根本不是横在孩子与习惯之间的那件事;做卡片才是。

我们的复习日志在设计时就考虑了可重新拟合,所以将来可以换上更好的排程器,而不损失任何东西。

常见问题

SM-2 算法是什么?

一个由 Piotr Woźniak 在 1990 年发表的间隔重复排程器。每张卡带着一个难易系数;每次复习都把当前间隔乘以这个系数得到下一个间隔,而系数本身会随着你回忆得好不好而上下移动。

什么是难易系数(ease factor)?

一个按卡片计的乘数,初始值 2.5,决定这张卡的间隔增长得有多快。回忆轻松时它会略微上升,失败时会大幅下降,下限是 1.3。

为什么前两个间隔不使用难易系数?

它们被固定为 1 天和 6 天。一张只成功复习过一次的卡片,几乎不携带任何关于自身难度的信息,所以 SM-2 用一个合理的常数,而不是去乘一个几乎没有依据的乘数。