Skip to content

Algorithm 算法对象规范

Algorithm 是 HeurAMS 中用于管理记忆间隔重复算法的对象. 它定义了如何根据用户的复习反馈计算下一次复习时间, 并更新记忆状态.

算法类型概览

HeurAMS 目前支持的算法:

算法类名状态描述
SM-2SM2Algorithm✅ 已实现SuperMemo 2 算法, 经典间隔重复算法
FSRSFSRSAlgorithm🔧 开发中自由间隔重复调度器, 现代算法

通用接口

所有算法都继承自 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": 当前时间戳   # 最后修改时间
}

算法参数

参数范围描述
efactor1.3 - ∞易度系数, 值越小表示越难记忆
rept0 - ∞连续成功复习次数
interval0 - ∞当前复习间隔(天数)
反馈质量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)
01天1天
16天6天
≥2interval × efactor15天 (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    # 最后修改时间戳

预期特性

  1. 基于记忆模型:使用记忆的三分量模型(稳定性、难度、可提取性)
  2. 自适应调度:根据记忆状态动态调整间隔
  3. 优化目标:在遗忘概率约束下最小化复习次数
  4. 参数学习:可以从复习历史中学习最优参数

时间戳系统

算法使用两种时间戳:

日时间戳 (Daystamp)

  • 整数, 表示自纪元(1970-01-01)以来的天数
  • 用于 last_datenext_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 适用场景

  1. 初学者:算法简单, 参数易于理解
  2. 稳定性要求:经典算法, 经过长期验证
  3. 资源受限环境:计算量小, 内存占用低
  4. 可预测性:间隔增长有明确规则

FSRS 预期优势

  1. 高效性:在相同遗忘率下减少复习次数
  2. 个性化:根据用户记忆特点自适应调整
  3. 现代性:基于最新的记忆科学研究
  4. 灵活性:支持多种优化目标和约束条件

自定义算法开发

创建新算法

  1. 继承 BaseAlgorithm 基类
  2. 定义 algo_nameAlgodataDict
  3. 实现 defaults 字典
  4. 实现四个核心类方法
  5. 注册到算法注册器
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

# 注册算法(需要修改注册器代码)

集成到系统

  1. heurams/kernel/algorithms/__init__.py 中导入并注册
  2. 更新配置支持
  3. 测试算法正确性
  4. 更新文档

算法验证与测试

单元测试

每个算法应包含完整的单元测试:

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 字节

存储优化

  1. 稀疏存储:只存储变化的字段
  2. 批量更新:多个 Electron 一起更新
  3. 懒保存:修改后才写入磁盘

版本历史

  • v1 (当前版本):SM-2 算法完整实现
  • v0.5:基础算法框架
  • 未来版本:FSRS 算法支持

相关文档