Algorithm 算法对象规范
Algorithm 是 HeurAMS 中用于管理记忆间隔重复算法的对象. 它定义了如何根据用户的复习反馈计算下一次复习时间, 并更新记忆状态.
算法类型概览
HeurAMS 目前支持的算法:
| 算法 | 类名 | 状态 | 描述 |
|---|---|---|---|
| SM-2 | SM2Algorithm | ✅ 已实现 | SuperMemo 2 算法, 经典间隔重复算法 |
| FSRS | FSRSAlgorithm | 🔧 开发中 | 自由间隔重复调度器, 现代算法 |
通用接口
所有算法都继承自 BaseAlgorithm 基类, 实现以下类方法接口:
python
class BaseAlgorithm:
algo_name = "BaseAlgorithm" # 算法标识符
@classmethod
def revisor(cls, algodata: dict, feedback: int = 5,
is_new_activation: bool = False) -> None:
"""根据反馈更新算法数据"""
@classmethod
def is_due(cls, algodata) -> int:
"""检查是否到期需要复习"""
@classmethod
def rate(cls, algodata) -> str:
"""获取评分信息"""
@classmethod
def nextdate(cls, algodata) -> int:
"""获取下一次复习时间戳"""算法数据格式
每个算法使用统一的 algodata 字典结构:
python
algodata = {
"算法名称": {
# 算法特定的数据字段
"field1": value1,
"field2": value2,
...
}
}SM-2 算法
SuperMemo 2 算法, 1987年由Piotr Woźniak开发, 是间隔重复领域的经典算法.
类定义
python
class SM2Algorithm(BaseAlgorithm):
algo_name = "SM-2"
class AlgodataDict(TypedDict):
efactor: float # 易度系数
real_rept: int # 实际复习次数
rept: int # 当前连续成功次数
interval: int # 当前间隔天数
last_date: int # 上次复习日时间戳
next_date: int # 下次复习日时间戳
is_activated: int # 是否已激活
last_modify: float # 最后修改时间戳默认值
python
defaults = {
"efactor": 2.5, # 初始易度系数
"real_rept": 0, # 实际复习次数
"rept": 0, # 连续成功次数
"interval": 0, # 间隔天数
"last_date": 0, # 上次复习日期
"next_date": 0, # 下次复习日期
"is_activated": 0, # 激活状态
"last_modify": 当前时间戳 # 最后修改时间
}算法参数
| 参数 | 范围 | 描述 |
|---|---|---|
efactor | 1.3 - ∞ | 易度系数, 值越小表示越难记忆 |
rept | 0 - ∞ | 连续成功复习次数 |
interval | 0 - ∞ | 当前复习间隔(天数) |
| 反馈质量 | 0-5 | 用户对记忆保留率的评分 |
质量评分标准
| 质量 | 描述 | 典型场景 |
|---|---|---|
| 5 | 完美回忆 | 毫不犹豫, 完全正确 |
| 4 | 正确但犹豫 | 需要思考片刻 |
| 3 | 困难但正确 | 需要提示或较长时间 |
| 2 | 错误但接近 | 答案接近但不对 |
| 1 | 完全错误 | 完全不知道答案 |
| 0 | 完全遗忘 | 完全忘记内容 |
| -1 | 跳过 | 用户选择跳过 |
更新规则(revisor 方法)
1. 易度系数更新
python
efactor_new = efactor_old + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02))
efactor_new = max(1.3, efactor_new) # 下限为1.3质量与易度系数变化关系:
| 质量 | efactor 变化 | 说明 |
|---|---|---|
| 5 | +0.10 | 完美回忆, 稍微增加难度 |
| 4 | +0.02 | 良好回忆, 微调难度 |
| 3 | -0.14 | 困难回忆, 降低难度 |
| 2 | -0.40 | 错误回忆, 显著降低难度 |
| 1 | -0.76 | 完全错误, 大幅降低难度 |
| 0 | -1.22 | 完全遗忘, 最大程度降低难度 |
2. 复习次数更新
python
if quality < 3:
rept = 0 # 重置连续成功次数
else:
rept += 1 # 增加连续成功次数
real_rept += 1 # 总是增加实际复习次数3. 间隔计算
根据连续成功次数 rept 计算新间隔:
| rept | 间隔公式 | 示例 (efactor=2.5) |
|---|---|---|
| 0 | 1天 | 1天 |
| 1 | 6天 | 6天 |
| ≥2 | interval × efactor | 15天 (6 × 2.5) |
4. 新激活处理
python
if is_new_activation:
rept = 0 # 重置连续成功次数
efactor = 2.5 # 重置易度系数5. 日期更新
python
last_date = 当前日时间戳
next_date = last_date + interval
last_modify = 当前时间戳完整更新流程
python
def revisor(algodata, quality=5, is_new_activation=False):
if quality == -1:
return # 跳过更新
# 1. 更新易度系数
algodata["SM-2"]["efactor"] += (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02))
algodata["SM-2"]["efactor"] = max(1.3, algodata["SM-2"]["efactor"])
# 2. 更新复习次数
if quality < 3:
algodata["SM-2"]["rept"] = 0
else:
algodata["SM-2"]["rept"] += 1
algodata["SM-2"]["real_rept"] += 1
# 3. 新激活处理
if is_new_activation:
algodata["SM-2"]["rept"] = 0
algodata["SM-2"]["efactor"] = 2.5
# 4. 计算新间隔
if algodata["SM-2"]["rept"] == 0:
algodata["SM-2"]["interval"] = 1
elif algodata["SM-2"]["rept"] == 1:
algodata["SM-2"]["interval"] = 6
else:
algodata["SM-2"]["interval"] = round(
algodata["SM-2"]["interval"] * algodata["SM-2"]["efactor"]
)
# 5. 更新日期
algodata["SM-2"]["last_date"] = timer.get_daystamp()
algodata["SM-2"]["next_date"] = (
timer.get_daystamp() + algodata["SM-2"]["interval"]
)
algodata["SM-2"]["last_modify"] = timer.get_timestamp()其他方法
is_due 方法
检查是否到期需要复习:
python
def is_due(algodata):
return algodata["SM-2"]["next_date"] <= timer.get_daystamp()rate 方法
获取评分信息(返回易度系数字符串):
python
def rate(algodata):
return str(algodata["SM-2"]["efactor"])nextdate 方法
获取下次复习日时间戳:
python
def nextdate(algodata):
return algodata["SM-2"]["next_date"]FSRS 算法(规划中)
自由间隔重复调度器(Free Spaced Repetition Scheduler), 现代间隔重复算法.
预期数据结构
python
class FSRSAlgorithm(BaseAlgorithm):
algo_name = "FSRS"
class AlgodataDict(TypedDict):
stability: float # 记忆稳定性
difficulty: float # 记忆难度
retrievability: float # 可提取性
last_review: int # 上次复习时间戳
next_review: int # 下次复习时间戳
elapsed_days: int # 已过天数
scheduled_days: int # 计划天数
reps: int # 复习次数
lapses: int # 遗忘次数
is_activated: int # 是否已激活
last_modify: float # 最后修改时间戳预期特性
- 基于记忆模型:使用记忆的三分量模型(稳定性、难度、可提取性)
- 自适应调度:根据记忆状态动态调整间隔
- 优化目标:在遗忘概率约束下最小化复习次数
- 参数学习:可以从复习历史中学习最优参数
时间戳系统
算法使用两种时间戳:
日时间戳 (Daystamp)
- 整数, 表示自纪元(1970-01-01)以来的天数
- 用于
last_date、next_date等字段 - 获取方法:
timer.get_daystamp()
高精度时间戳 (Timestamp)
- 浮点数, 表示自纪元以来的秒数(含小数)
- 用于
last_modify字段 - 获取方法:
timer.get_timestamp()
算法注册与使用
算法注册器
算法通过注册器管理, 可以在配置中选择使用哪个算法.
python
from heurams.kernel.algorithms import algorithms
# 注册的算法
print(algorithms.keys()) # dict_keys(['supermemo2', 'fsrs'])
# 获取算法类
sm2_class = algorithms['supermemo2']
fsrs_class = algorithms['fsrs'] # 未来支持配置选择
在 Electron 创建时指定算法:
python
from heurams.kernel.particles.electron import Electron
# 使用 SM-2 算法
electron = Electron(ident="test", algodata={}, algo_name="supermemo2")
# 未来可以使用 FSRS 算法
# electron = Electron(ident="test", algodata={}, algo_name="fsrs")配置文件
在 config.toml 中可以设置默认算法(未来版本支持):
toml
[algorithm]
default = "supermemo2" # 或 "fsrs"算法选择指南
SM-2 适用场景
- 初学者:算法简单, 参数易于理解
- 稳定性要求:经典算法, 经过长期验证
- 资源受限环境:计算量小, 内存占用低
- 可预测性:间隔增长有明确规则
FSRS 预期优势
- 高效性:在相同遗忘率下减少复习次数
- 个性化:根据用户记忆特点自适应调整
- 现代性:基于最新的记忆科学研究
- 灵活性:支持多种优化目标和约束条件
自定义算法开发
创建新算法
- 继承
BaseAlgorithm基类 - 定义
algo_name和AlgodataDict - 实现
defaults字典 - 实现四个核心类方法
- 注册到算法注册器
python
from heurams.kernel.algorithms.base import BaseAlgorithm
import heurams.services.timer as timer
class CustomAlgorithm(BaseAlgorithm):
algo_name = "CustomAlgo"
class AlgodataDict(TypedDict):
custom_field: float
is_activated: int
last_modify: float
defaults = {
"custom_field": 1.0,
"is_activated": 0,
"last_modify": timer.get_timestamp()
}
@classmethod
def revisor(cls, algodata, feedback=5, is_new_activation=False):
# 实现更新逻辑
pass
@classmethod
def is_due(cls, algodata):
# 实现到期检查
return True
@classmethod
def rate(cls, algodata):
# 实现评分
return "custom"
@classmethod
def nextdate(cls, algodata):
# 实现下次日期
return 0
# 注册算法(需要修改注册器代码)集成到系统
- 在
heurams/kernel/algorithms/__init__.py中导入并注册 - 更新配置支持
- 测试算法正确性
- 更新文档
算法验证与测试
单元测试
每个算法应包含完整的单元测试:
python
def test_sm2_revisor():
# 测试不同质量下的更新
algodata = {"SM-2": SM2Algorithm.defaults.copy()}
# 测试质量5
SM2Algorithm.revisor(algodata, quality=5)
assert algodata["SM-2"]["efactor"] == 2.6 # 2.5 + 0.1
assert algodata["SM-2"]["rept"] == 1
# 测试质量0
algodata = {"SM-2": SM2Algorithm.defaults.copy()}
SM2Algorithm.revisor(algodata, quality=0)
assert algodata["SM-2"]["efactor"] == 1.3 # 下限
assert algodata["SM-2"]["rept"] == 0集成测试
测试算法与 Electron 的集成:
python
def test_electron_with_sm2():
electron = Electron(ident="test", algodata={}, algo_name="supermemo2")
electron.activate()
electron.revisor(quality=4)
assert electron.is_activated() == True
assert electron["rept"] == 1性能考虑
计算复杂度
- SM-2:O(1) 常数时间, 计算量极小
- FSRS:预期 O(1), 但参数更新可能稍复杂
内存占用
- 每个 Electron 存储一份算法数据
- SM-2 数据约 50-100 字节
- FSRS 预期稍大, 约 100-200 字节
存储优化
- 稀疏存储:只存储变化的字段
- 批量更新:多个 Electron 一起更新
- 懒保存:修改后才写入磁盘
版本历史
- v1 (当前版本):SM-2 算法完整实现
- v0.5:基础算法框架
- 未来版本:FSRS 算法支持
相关文档
- Electron 规范 - 算法状态文件格式
- Nucleon 规范 - 记忆内容文件格式
- Puzzle 规范 - 谜题对象定义
- SM-2 原论文 - 算法理论基础
- FSRS 项目 - 现代间隔重复算法