SM-2 是 1990 年的算法,至今仍在为世界上大多数闪卡排程,包括 Anki 的默认排程和我们的全部排程。它简单到可以完整写出来,所以这里就完整写出来——包括孩子的卡片实际会走过的那些确切数字。
一览
| 概念 | 它是什么 |
|---|---|
| 难易系数 | 按卡片计的乘数,初始 2.5。决定这张卡的间隔增长得有多快 |
| 质量评分 | 这次回忆有多顺利,0–5。我们用 2、4、5 |
| 间隔 | 距下次复习的天数 |
| 重复次数 | 连续成功复习的次数 |
| 失误(lapse) | 一次失败的复习。重置重复次数,但保留累计计数 |
| 起始间隔 | 固定:1 天,然后 6 天 |
| 此后 | 间隔 × 难易系数,四舍五入 |
三个活动部件
SM-2 为每张卡存三个数字,而且真的就只有这三个:
- 难易系数——初始 2.5。这张卡的排程有多宽松。
- 间隔——当前的间隔天数。
- 重复次数——连续成功了多少次。
其余一切都是推导出来的。这里没有记忆模型,没有在拟合任何曲线。它是一个反馈回路:给卡片评级,微调它的系数,相乘。
质量评分
原始算法接受一个 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 用一个合理的常数,而不是去乘一个几乎没有依据的乘数。