dramaling-vocab-learning/note/智能複習/智能複習系統-演算法規格書.md

17 KiB
Raw Blame History

智能複習系統 - 演算法規格書 (ASD)

目標讀者: 演算法工程師、數據科學家 版本: 1.0 日期: 2025-09-25


🧮 算法概述

核心問題

現有 2^成功次數 算法增長過快,需要設計更科學的間隔重複算法。

設計目標

  • 間隔增長平緩,避免過早達到最大間隔
  • 結合用戶表現動態調整
  • 處理逾期復習的記憶衰減

📐 數學模型

主算法公式

新間隔 = (當前間隔 × 增長係數 × 表現係數) × 逾期懲罰係數
下次復習日期 = 復習行為當日 + 新間隔

時間基準定義 (關鍵)

# 關鍵時間定義
scheduled_date = flashcard.next_review_date     # 預定復習日期
actual_review_date = datetime.now().date()     # 復習行為當日
overdue_days = (actual_review_date - scheduled_date).days

# 下次復習計算基準:以復習行為當日為準
next_review_date = actual_review_date + timedelta(days=new_interval)

設計原則:

  • 以復習行為當日為基準 - 用戶在哪天復習,就從那天開始計算下次復習
  • 避免累積逾期 - 不會因為一次逾期導致後續復習都逾期
  • 用戶友好 - 符合用戶直覺,任何時候復習都是"重新開始"

具體範例:

詞卡應該 2025-09-20 復習,用戶 2025-09-25 才復習 (逾期5天)
- 逾期天數 = 5天 (中度逾期)
- 原間隔 = 14天答對信心等級4
- 新間隔 = 14 × 1.4 × 1.1 × 0.75 = 16天
- 下次復習 = 2025-09-25 + 16天 = 2025-10-11 ✅
- 而非 = 2025-09-20 + 16天 = 2025-10-06 ❌

1. 增長係數函數

def get_growth_factor(current_interval):
    if current_interval <= 7:
        return 1.8      # 短期:快速增長
    elif current_interval <= 30:
        return 1.4      # 中期:中等增長
    elif current_interval <= 90:
        return 1.2      # 長期:緩慢增長
    else:
        return 1.1      # 超長期:極緩增長

設計理念: 分段函數避免指數爆炸,早期快速建立記憶,後期維持長期記憶。

2. 表現係數函數

def get_performance_factor(is_correct, confidence_level=None, question_type="flipcard"):
    """
    根據不同題型計算表現係數
    """
    if question_type == "flipcard":
        # 翻卡題:信心等級映射 (1-5 → 0.5-1.4)
        confidence_mapping = {1: 0.5, 2: 0.7, 3: 0.9, 4: 1.1, 5: 1.4}
        return confidence_mapping.get(confidence_level, 0.9)

    elif question_type in ["multiple_choice", "fill_blank", "sentence_reconstruction"]:
        # 客觀題:正確性導向
        return 1.1 if is_correct else 0.6

    elif question_type in ["vocabulary_listening", "sentence_listening"]:
        # 聽力題:正確性 + 輕微加權 (聽力更困難)
        return 1.2 if is_correct else 0.5

    elif question_type == "sentence_speaking":
        # 口說題:主觀評估 (通常標記為成功)
        return 1.0  # 口說練習重在參與,不重罰

    else:
        # 預設情況
        return 0.9

設計理念: 翻卡題依據主觀信心,客觀題依據正確性,反映不同題型的認知特點。

3. 逾期懲罰函數

def calculate_overdue_penalty(overdue_days):
    if overdue_days <= 0:
        return 1.0      # 準時,無懲罰
    elif overdue_days <= 3:
        return 0.9      # 輕度逾期
    elif overdue_days <= 7:
        return 0.75     # 中度逾期
    elif overdue_days <= 30:
        return 0.5      # 重度逾期
    else:
        return 0.3      # 極度逾期

設計理念: 階梯式懲罰,鼓勵按時復習,但不過度懲罰偶爾延遲。

4. 記憶衰減模型

def calculate_memory_decay(original_mastery, overdue_days):
    # 基於 Ebbinghaus 遺忘曲線的指數衰減
    decay_rate = 0.05  # 每天5%衰減率
    max_decay_days = 30  # 最多考慮30天衰減

    effective_days = min(overdue_days, max_decay_days)
    decay_factor = (1 - decay_rate) ** effective_days

    return max(0, int(original_mastery * decay_factor))

設計理念: 符合認知科學的遺忘曲線,逾期越久記憶衰減越多。

6. 複習方式選擇算法

6.1 學習程度適配算法

def get_review_types_by_difficulty(user_level, word_level):
    """
    根據學習者程度和詞彙難度決定可用的複習方式

    Args:
        user_level: 學習者程度 (1-100)
        word_level: 詞彙難度 (1-100)

    Returns:
        List[str]: 適合的複習題型列表
    """
    difficulty = word_level - user_level

    if user_level <= 20:
        # A1學習者 - 統一基礎題型,建立信心
        return ['flipcard', 'multiple_choice', 'vocabulary_listening']

    elif difficulty < -10:
        # 簡單詞彙 (學習者程度 > 詞彙程度)
        # 重點練習應用和拼寫
        return ['sentence_reconstruction', 'fill_blank']

    elif difficulty >= -10 and difficulty <= 10:
        # 適中詞彙 (學習者程度 ≈ 詞彙程度)
        # 全方位練習,包括口說
        return ['fill_blank', 'sentence_reconstruction', 'sentence_speaking']

    else:
        # 困難詞彙 (學習者程度 < 詞彙程度)
        # 回到基礎,重建記憶
        return ['flipcard', 'multiple_choice']

設計原則:

  • A1優先: 初學者使用最基本的3種題型避免挫折
  • 能力匹配: 難度適中時使用高階題型 (口說、重組)
  • 困難降級: 詞彙太難時回歸基礎題型
  • 逐步進階: 隨著能力提升,逐漸解鎖更多題型

6.2 智能題型推薦算法

def select_review_mode(available_modes, flashcard, review_history=None):
    """
    從可用題型中智能選擇當前最適合的複習方式

    Args:
        available_modes: 可用的複習題型列表
        flashcard: 當前詞卡資訊
        review_history: 最近的復習歷史 (可選)

    Returns:
        str: 推薦的複習題型
    """
    if not review_history:
        review_history = []

    # 1. 避免連續重複 (反單調算法)
    recent_modes = [r.question_type for r in review_history[-3:]]
    if recent_modes:
        last_mode = recent_modes[-1]
        consecutive_count = 0
        for mode in reversed(recent_modes):
            if mode == last_mode:
                consecutive_count += 1
            else:
                break

        # 連續2次以上強制切換
        if consecutive_count >= 2:
            available_modes = [m for m in available_modes if m != last_mode]
            if not available_modes:  # 如果沒有其他選項,保留原選項
                available_modes = [last_mode]

    # 2. A1學習者權重分配
    if flashcard.user_level <= 20:
        weights = {
            'flipcard': 0.4,           # 40% - 主要熟悉方式
            'multiple_choice': 0.4,    # 40% - 概念強化
            'vocabulary_listening': 0.2 # 20% - 發音練習
        }
        return weighted_random_select(available_modes, weights)

    # 3. 根據最近表現調整
    if review_history:
        recent_performance = sum([r.is_correct for r in review_history[-5:]]) / len(review_history[-5:])

        if recent_performance < 0.6:
            # 表現不佳,偏向基礎題型
            priority_order = ['flipcard', 'multiple_choice', 'fill_blank',
                            'sentence_reconstruction', 'vocabulary_listening', 'sentence_speaking']
        else:
            # 表現良好,偏向挑戰題型
            priority_order = ['sentence_speaking', 'sentence_reconstruction', 'fill_blank',
                            'vocabulary_listening', 'multiple_choice', 'flipcard']

        for mode in priority_order:
            if mode in available_modes:
                return mode

    # 4. 預設隨機選擇
    return random.choice(available_modes)

def weighted_random_select(items, weights):
    """權重隨機選擇"""
    total_weight = sum(weights.get(item, 1.0/len(items)) for item in items)
    random_num = random.random() * total_weight

    for item in items:
        weight = weights.get(item, 1.0/len(items))
        random_num -= weight
        if random_num <= 0:
            return item

    return items[0]  # 備用返回

演算法特點:

  • 反單調性: 避免連續使用相同題型超過2次
  • 適應性: 根據最近表現動態調整題型偏好
  • 穩定性: A1學習者有固定的權重分配
  • 魯棒性: 各種邊界情況都有合理的備用方案

5. 熟悉程度計算 (雙重概念)

5.1 基礎熟悉度計算 (存入資料庫)

def calculate_base_mastery_level(times_correct, total_reviews, current_interval):
    """
    計算基礎熟悉度,在復習完成時更新並存入資料庫
    這是熟悉度的「基準值」,不考慮時間衰減
    """
    success_rate = times_correct / total_reviews if total_reviews > 0 else 0

    base_score = min(times_correct * 8, 60)         # 60% 權重
    interval_bonus = min(current_interval / 365 * 25, 25)  # 25% 權重
    accuracy_bonus = success_rate * 15              # 15% 權重

    return min(100, round(base_score + interval_bonus + accuracy_bonus))

5.2 當前熟悉度計算 (實時計算)

def calculate_current_mastery_level(base_mastery, last_review_date):
    """
    計算當前熟悉度,考慮記憶衰減的實時值
    用於前端顯示,不存入資料庫
    """
    days_since_review = (datetime.now().date() - last_review_date).days

    # 如果是當日復習,返回基礎熟悉度
    if days_since_review <= 0:
        return base_mastery

    # 套用記憶衰減
    return calculate_memory_decay(base_mastery, days_since_review)

設計理念:

  • 基礎熟悉度: 學習成果的基準值,反映用戶的學習進度
  • 當前熟悉度: 考慮時間因素的實時值,反映當下的記憶強度

6. 熟悉度計算時機

6.1 基礎熟悉度更新時機

  • 復習完成時: 計算並更新到資料庫
  • 查詢時: 不重新計算,直接讀取資料庫值
  • 定期批次: 不需要排程任務更新

6.2 當前熟悉度計算時機

  • API 查詢時: 每次請求都實時計算
  • 前端顯示時: 根據 API 返回的基礎值和參數計算
  • 列表頁面: 批次計算多個詞卡的當前熟悉度

6.3 計算流程圖

用戶復習 → 更新基礎熟悉度 → 存入資料庫
    ↓
用戶查詢 → 讀取基礎熟悉度 → 計算當前熟悉度 → 返回前端
    ↓
前端展示 → 顯示當前熟悉度 (會隨時間動態變化)

📊 算法特性分析

收斂性分析

  • 間隔上限: 365天確保不會無限增長
  • 收斂速度: 約15-20次復習達到長期記憶階段
  • 穩定性: 表現波動不會導致劇烈間隔變化
  • 題型收斂: A1學習者在基礎題型間穩定切換進階學習者逐步使用高階題型

複習方式算法分析

  • 覆蓋性: 保證每個學習者都有適合的題型可用
  • 多樣性: 避免單一題型平均每次復習切換1-2種題型
  • 適應性: 根據表現調整,表現好→挑戰題型,表現差→基礎題型
  • 公平性: A1學習者有專屬的保護機制不會被推薦困難題型

參數敏感性

參數 影響程度 調優建議
增長係數 謹慎調整,影響整體學習節奏
逾期懲罰 可根據用戶行為數據調優
衰減率 建議基於記憶實驗數據設定
權重分配 相對穩定,微調即可
A1題型權重 影響初學者體驗,需謹慎調整
連續重複限制 2-3次為佳過低影響深入練習

邊界條件處理

# 關鍵邊界條件
def validate_inputs(interval, times_correct, total_reviews):
    assert 0 <= interval <= 365, "間隔必須在 0-365 範圍內"
    assert times_correct >= 0, "成功次數不能為負"
    assert total_reviews >= times_correct, "總次數不能少於成功次數"
    assert total_reviews >= 0, "總次數不能為負"

🔬 算法驗證

理論驗證

  • 單調性: 連續答對時間隔遞增
  • 有界性: 間隔不會超過365天
  • 連續性: 參數小幅變化不會導致間隔劇變
  • 收斂性: 學習軌跡收斂到穩定狀態

數值穩定性

  • 浮點運算精度: 使用 Math.Round() 處理
  • 溢出保護: 所有中間結果都有上下界
  • 零除防護: total_reviews = 0 時返回預設值

性能複雜度

  • 基礎熟悉度: O(1) - 常數時間計算
  • 當前熟悉度: O(1) - 常數時間計算
  • 題型選擇: O(k) - k為可用題型數量 (通常≤7)
  • 智能推薦: O(h) - h為歷史記錄查看數量 (通常≤5)
  • 空間複雜度: O(1) - 無額外存儲需求
  • 預期性能:
    • 單次計算: < 1ms
    • 題型推薦: < 5ms
    • 列表頁批次計算: < 10ms (100個詞卡)
    • A1邏輯檢查: < 1ms

🎛️ 參數調優指南

A/B 測試建議

{
  "interval_algorithm": {
    "conservative": {
      "growth_factors": [1.6, 1.3, 1.1, 1.05],
      "description": "保守增長,更多復習機會"
    },
    "aggressive": {
      "growth_factors": [2.0, 1.5, 1.3, 1.15],
      "description": "積極增長,減少復習負擔"
    },
    "current": {
      "growth_factors": [1.8, 1.4, 1.2, 1.1],
      "description": "當前推薦參數"
    }
  },
  "review_type_strategy": {
    "diversity_focused": {
      "consecutive_limit": 1,
      "a1_weights": {"flipcard": 0.33, "multiple_choice": 0.33, "vocabulary_listening": 0.34},
      "description": "最大多樣性,每次都換題型"
    },
    "stability_focused": {
      "consecutive_limit": 3,
      "a1_weights": {"flipcard": 0.5, "multiple_choice": 0.3, "vocabulary_listening": 0.2},
      "description": "允許深入練習,偏重翻卡題"
    },
    "current": {
      "consecutive_limit": 2,
      "a1_weights": {"flipcard": 0.4, "multiple_choice": 0.4, "vocabulary_listening": 0.2},
      "description": "平衡多樣性和穩定性"
    }
  },
  "a1_protection": {
    "strict": {
      "level_threshold": 25,
      "allowed_types": ["flipcard", "multiple_choice"],
      "description": "更嚴格保護只允許2種題型"
    },
    "moderate": {
      "level_threshold": 20,
      "allowed_types": ["flipcard", "multiple_choice", "vocabulary_listening"],
      "description": "當前標準"
    },
    "relaxed": {
      "level_threshold": 15,
      "allowed_types": ["flipcard", "multiple_choice", "vocabulary_listening", "fill_blank"],
      "description": "較寬鬆,允許填空題"
    }
  }
}

監控指標

  • 學習軌跡分布: 檢查間隔分布是否合理
  • 用戶滿意度: 復習頻率是否符合預期
  • 記憶效果: 長期記憶率是否提升
  • 題型使用分布: 各題型使用率是否平衡
  • A1用戶體驗: 初學者完成率和信心提升
  • 推薦算法準確率: 用戶接受推薦題型的比例
  • 切換頻率: 平均每次復習的題型切換次數
  • 表現關聯性: 不同題型的答對率差異

🔮 未來優化方向

個人化參數

# 未來可考慮的個人化係數
personal_factor = calculate_personal_learning_ability(user_id)
new_interval *= personal_factor

# 個人化題型偏好
def get_personal_type_preference(user_id):
    user_stats = get_user_performance_by_type(user_id)
    # 根據各題型的歷史表現調整權重
    return calculate_preference_weights(user_stats)

遺忘曲線整合

# 更精確的記憶強度模型
memory_strength = math.exp(-time_since_review / forgetting_constant)
review_urgency = 1 - memory_strength

# 題型特定的遺忘曲線
def get_type_specific_decay(question_type):
    # 不同題型可能有不同的記憶保持率
    decay_rates = {
        'flipcard': 0.05,      # 概念記憶
        'fill_blank': 0.07,    # 拼寫記憶衰減較快
        'sentence_speaking': 0.03  # 口說記憶保持較久
    }
    return decay_rates.get(question_type, 0.05)

多維度考量

  • 詞彙難度係數: 基於語料庫統計的客觀難度
  • 學習時間分布: 用戶的學習時間偏好和效率
  • 情境相關性: 詞彙在不同情境下的重要性
  • 認知負荷: 不同題型的認知負荷評估
  • 學習風格適配: 視覺型、聽覺型、動覺型學習者的偏好
  • 進度同步: 多設備間的學習進度同步策略

高級算法方向

  • 深度學習預測: 使用神經網路預測最佳復習時間
  • 強化學習優化: 基於用戶反饋動態優化推薦策略
  • 群體智慧: 利用相似用戶的學習軌跡改進推薦
  • 多目標優化: 同時優化學習效率、用戶滿意度、長期留存

算法設計者: Claude AI 審核狀態: 待算法團隊 Review