一つの知識、二つの出典:章と講義文字起こしから重複カードを作らないために

教科書には、外力によるトルクがゼロなら角運動量は保存されると書かれている。後の講義でも、同じ内容が別の言葉で説明される。アップロードのたびに新しいフラッシュカードを作ると、学習者の手元にはほとんど同じ問いが二つ、復習履歴も二つできる。それでも、一つの主張が二つの出典に支えられていることは見えにくい。

新しいカードを今回のアップロードから生成されたカードと比較したくなるが、それでうまくいくのは次の章や文字起こしが届くまでだ。長く使える解決策は、表示されたカードを唯一の基準となるオブジェクトとして扱うのをやめることである。

採用済みの事実は一つに保つ。その事実を裏付けるすべての出典範囲を結び付け、事実の同一性を変えずに学習カードを描画または更新する。

カード、事実、出典はそれぞれ別のもの

Anki はすでに、ノートと、そのテンプレートから生成されるカードを分けている。ただし、通常の重複警告は主に同じノートタイプの中で先頭フィールドを比較する。編集時には便利だが、コレクション全体の意味的な同一性を表すモデルではない。インポーターを設計する前に、Anki マニュアルの基本概念重複チェックを読んでおくとよい。

複数の出典から生成する場合は、三つの層に分ける。

  1. 事実: 採用済みの主張、または一つの質問・回答単位。
  2. 根拠範囲: 教科書の 74 ページや文字起こしの 00:31:20 など、出典内の正確な位置。
  3. 描画済みノート: Anki や別の復習システムへ書き出す、学習者向けのフィールド。

一つの事実に複数の根拠範囲があってもよい。一つの事実を複数のテンプレートで描画してもよい。どちらの場合も、基準となる事実を二つ作る必要はない。

textbook p.74 ─┐
               ├─> accepted fact ─> stable Anki note ─> review history
lecture 31:20 ─┘

この分離は、安全な削除にもつながる。アップロード済みの文字起こしを一つ削除するときは、その根拠との関係だけを切り離すべきである。事実、教科書の引用、学習者のスケジュール履歴まで黙って削除してはいけない。

原文を保ち、照合のためだけに正規化する

重複排除を簡単にするために、表示する表面と裏面を書き換えてはいけない。原文を保存し、その隣に版管理された照合用の表現を作る。

最初の保守的な表現では、次の処理が考えられる。

  • Unicode NFC 正規化を適用する。
  • 言語上問題がない場合は、大文字と小文字を同一視する。
  • 連続する空白を一つにまとめる。
  • 否定、数値、単位、数式、数学記号を保つ。

句読点、ストップワード、アクセント記号を機械的に取り除いてはいけない。「増加する」と「増加しない」は語彙が近くても意味は反対である。同様に、5 mg50 mg を同じキーにしてはならない。Unicode の正規化形式仕様には、NFKC のような互換正規化が、用途によっては残す必要のある区別を消しうる理由が説明されている。

この表現には match_text_v1 のような名前を付ける。後で規則を変更しても、マッチャーの版が判定とともに保存されていれば、過去の判断を再現できる。

厳密な署名を作るには、適用範囲と正規化済みの両フィールドをまとめてハッシュする。

SHA-256(owner_id || language || note_type || normalized_front || normalized_back)

適用範囲は重要である。非公開ノートをアカウント間で比較してはいけない。多くのシステムでは、言語、ノートタイプ、コース、利用者が選んだコレクション境界もキーに含める必要がある。

あいまい検索は候補探しに使い、統合の許可には使わない

厳密な署名なら、再試行や軽微な書式変更による重複を検出できる。しかし、次のような言い換えは検出できない。

  • 「角運動量はどのようなときに保存されるか。」
  • 「角運動量保存の条件を述べよ。」

厳密一致が見つからなかった場合にだけ、第二段階を使う。

new draft
  -> exact signature
       -> hit: attach the new evidence span
       -> miss: retrieve a small candidate set
                  -> score exact resemblance and containment
                  -> duplicate / review / distinct

小規模なローカルコレクションなら、SQLite FTS5 の文書にある trigram tokenizerで候補を絞り込める。PostgreSQL でも `pg_trgm`を使って同様の候補一覧を作れる。規模が大きい場合は、シードを固定した MinHash や局所性鋭敏型ハッシュで探索範囲を縮められる。Broder による文書の類似度と包含度の研究が、その基礎となる集合モデルを示している。

候補一覧は判定結果ではない。各候補について決定的なスコアを再計算し、内訳も保存し、曖昧なものはレビューへ送る。ラベル付きテストセットで安全性が示されるまでは、あいまい一致を根拠に二つの事実を自動統合してはならない。重複の見逃しは不便だが、誤った統合は両方のカードの意味を壊しうる。

厳密一致の衝突をトランザクションで処理する

最小限の SQLite スキーマでも、境界を明示できる。

CREATE TABLE facts (
  fact_id     TEXT PRIMARY KEY,
  owner_id    TEXT NOT NULL,
  language    TEXT NOT NULL,
  note_type   TEXT NOT NULL,
  exact_key   TEXT NOT NULL,
  front       TEXT NOT NULL,
  back        TEXT NOT NULL,
  status      TEXT NOT NULL DEFAULT 'accepted',
  UNIQUE (owner_id, language, note_type, exact_key)
);

CREATE TABLE evidence_spans (
  span_id       TEXT PRIMARY KEY,
  document_id   TEXT NOT NULL,
  locator       TEXT NOT NULL,
  source_hash   TEXT NOT NULL,
  excerpt       TEXT NOT NULL,
  UNIQUE (document_id, locator, source_hash)
);

CREATE TABLE fact_evidence (
  fact_id  TEXT NOT NULL REFERENCES facts(fact_id),
  span_id  TEXT NOT NULL REFERENCES evidence_spans(span_id),
  PRIMARY KEY (fact_id, span_id)
);

一意制約により、同時に走った二つの再試行が基準行を二つ作るのを防げる。一つのトランザクション内で、次の処理を行う。

  1. 提案された事実を挿入する。
  2. 厳密キーがすでに存在する場合は、既存の fact_id を選ぶ。
  3. 新しい根拠範囲を挿入する。
  4. INSERT ... ON CONFLICT DO NOTHING で関連付ける。
  5. 両方の判断をまとめてコミットする。

SQLite は一意制約トランザクションUPSERTを直接文書化している。アプリケーションコードで脆い「検索してから挿入する」処理を書くのではなく、これらの保証を利用する。

学習者の復習履歴を同じ事実に結び付けておく

書き出すたびに新しい Anki ノートが作られるなら、重複排除はまだ完了していない。採用済みの各事実に安定した外部識別子を持たせ、既存ノートのフィールドまたは根拠一覧を更新する。

Anki のテキストインポートに関する文書は、先頭フィールドまたは GUID を使った重複処理を説明し、既存ノートの更新によってスケジュールを維持できることも示している。実際に使うインポート経路でテストすること。見た目の問いが似ているというだけで、再生成したパッケージが同一性を保つと思い込んではならない。

一つの事実に二つ目の出典が追加された場合、学習者向けの表示は次の内容から、

Source: Chapter 4, p.74

次の内容へ変わるかもしれない。

Sources: Chapter 4, p.74 · Lecture 6, 00:31:20

ノートの同一性と復習履歴は変わらない。

類似度スコアが隠してしまう事例をテストする

しきい値を調整する前に、許容できない失敗を表すフィクスチャを作る。

  1. 同じアップロードを並行して再試行しても、一つの事実と一つの根拠関係だけが作られる。
  2. 章と文字起こしの文面が同一なら、一つの事実に二つの位置情報が付く。
  3. 大文字・小文字、空白、正準等価な Unicode の違いは厳密一致する。
  4. 言い換えはレビュー候補となり、自動統合されない。
  5. 否定、異なる数量、単位、母集団、条件は別のものとして残る。
  6. 矛盾する回答はレビュー対象の衝突を作り、どちらも他方を上書きしない。
  7. 教科書の版が変わった場合は、新しい位置情報と出典ハッシュを保持する。
  8. 多言語の文は、レビュー済みの等価関係で結ばれない限り別々に保つ。
  9. 一つの出典を削除しても、もう一方の根拠と復習履歴は残る。
  10. 同じフィクスチャを再実行すると、同じキー、候補順、スコア、判定になる。

候補検索の再現率と、最終統合の適合率は別々に測る。最終判定段階が保守的なら、候補生成器は意図的に広めの候補を返してもよい。

公開プロジェクトですでに役立っているパターン

Video2Bookは、タイムスタンプ付き文字起こしの構造と出典パスを保持する。Susskind archiveは、字幕、文字起こし、生成ノートを通して、コース、実行単位、講義の安定した階層を示している。Local Knowledge Terminalは、カードを採用済みエンティティのビューとして扱い、根拠とともに出典識別子、ハッシュ、位置情報を保持する。

これらのリポジトリはいずれも、アップロードをまたぐフラッシュカード重複排除器として完成しているわけではない。しかし三つを合わせると、有用な境界が見えてくる。出典はそれぞれの同一性を保ち、採用済み知識は独自の同一性を持ち、カードはデータベースそのものではなく交換可能なビューである。

書籍と講義文字起こしが混在するコレクションにこの設計を使えるか判断したい場合は、LKT のサンプルレポートで、着手前に確認する出典、プライバシー、引用、実施可否の項目を見られる。

Leave a Reply