コンテンツにスキップ

単一ファイルの理科会話データ

1 管理するのは dataset.json の 1 ファイルだけ

data/dataset.json を開いてください。参考資料、500 件の学習用会話、評価用会話はすべてこの 1 ファイルに入っています。トップレベルには 4 つの並列な配列があります:facts は参考用のファクトカード、train、val、test は完全な会話の 3 つのグループです。実際に学習に参加するのは train だけです。ファクトカードはコンテキストとして使われるだけで、学習ターゲットではありません。

セット 理科のトピック 完全な会話 回答ターゲット 用途
train 50 500 1150 LoRA パラメータを更新する
val 5 50 115 設定を比較し、候補を選ぶ
test 5 50 115 候補を凍結したあとに評価する

学習データの学年帯の分布は、1~2 年生が 167 件、3~4 年生が 167 件、5~6 年生が 166 件です。各トピックには 10 通りの会話パスがあり、それぞれ 2~3 ターンです。同じ知識を異なる学年帯向けに説明したものや、異なるフィードバックによって追加質問の流れが変わるものが含まれています。

data/dataset.json
├── schema_version / description   文件版本与说明
├── facts[60]                      参考事实卡,只存一份,被对话按 id 引用
├── train[500]                     训练对话:唯一参与参数更新的部分
├── val[50]                        验证对话:比较配置、选候选、算 eval loss
└── test[50]                       测试对话:候选冻结后才使用

train / val / test 里的每段对话结构相同:
├── id / group_id                  对话编号 / 科学主题分组
├── grade_band                     1-2 / 3-4 / 5-6
├── fact_ids                       引用 facts 中的 id
└── turns[]                        user / assistant / behavior

(上のツリーの訳:schema_version / description はファイルのバージョンと説明です。facts[60] は参考用のファクトカードで、1 か所だけに保存され、会話から id で参照されます。train[500] は学習用の会話で、パラメータを更新する唯一の部分です。val[50] は検証用の会話で、設定の比較、候補の選定、eval loss の計算に使います。test[50] はテスト用の会話で、候補を凍結したあとにだけ使います。train / val / test の各会話は同じ構造を持ちます:id / group_id は会話 ID / 理科トピックのグループ、grade_band は 1-2 / 3-4 / 5-6、fact_ids は facts 内の id を参照し、turns[] には user / assistant / behavior が入ります。)

会話がどのセットに属するかは、その会話が置かれている配列だけで決まります。会話はもう split フィールドを持たないので、「val に置かれているのに train と書いてある」といった矛盾が起きません。プログラムがファイルを読み込むとき(lab.data.read_dataset、Notebook では tag_splits)に、メモリ上で各会話に split を付け、以降のフィルタリングとセット間のチェックはすべてそれを使います。元のファイルは変更されません。会話を別のセットに移したいときは、会話全体を別の配列に切り取って移してください。同じ group_id を持つほかの会話と、その会話が参照しているファクトカードも一緒に移す必要があります。

JSON は // コメントをサポートしていません。各フィールドの説明はここに書き、Notebook では D1~D7 で実際に使われる箇所を示しています。JSON 全体をプロンプトに詰め込んだり、評価用の会話を trainer に渡したりしないでください。

2 最初のファクトカード

{
  "id": "day-night",
  "category": "space",
  "topic": "昼夜",
  "text": "自转使地球表面各处交替朝向和背向太阳,形成主要的昼夜变化。太阳没有按昼夜关闭发光。地球还绕太阳公转;假定取消自转但保留公转,光照位置仍会随公转缓慢变化。",
  "source_url": "https://spaceplace.nasa.gov/seasons/en/",
  "checked_at": "2026-09-29",
  "source_review": "reference_read_claim_review_pending",
  "scope": "昼夜的入门解释;模型和比喻不能替代真实测量。",
  "kind": "fact",
  "reuse": "科学事实摘要和教学对话为原创中文表达;来源仅供事实核对,不复制来源的教材段落、题目或图像,不把网页正文纳入训练。链接不表示来源机构认可本数据或授予网页内容的训练许可。"
}
フィールド 意味と用途
id 主キー。会話の fact_ids から参照されます
category/topic カテゴリとトピック。検索、統計、レビューに使います
text 回答の根拠にできる理科の資料。コンテキストに入ります
kind/scope 資料の種類と適用範囲。コンテキストに入ります
source_url 人が出典を調べるための入り口。プログラムがオンラインで読みに行くことはありません
checked_at/source_review pending は検証待ちを意味します。日付は参考資料の入り口を読んだことを示すだけで、reference_read_claim_review_pending は個々の主張がまだレビュー待ちであることを示します
reuse 出典の帰属と、独自に書き直したことの注記。出典元の機関がこのデータを承認していることを意味するものではありません

NASA、NOAA、USGS などの公式な科学普及資料と、OpenStax の教科書を参考資料の入り口としています。本文は独自に書き下ろした中国語で、たとえ、子どもの反応、観察の設計は合成したデモンストレーションです。これらがすべて出典に由来すると主張することはできません。URL があっても、すべての文が出典に裏付けられているわけではなく、主張ごとに確認する必要があります。

ファクトカードが必要な理由:よくある 3 つの疑問

ファインチューニングが教えるのは「どう話すか」です(チュートリアル 0.1 節)。ここまで来ると、初心者はたいてい次の 3 つの疑問を続けて抱きます。

Q1:小学校の理科はベースモデルがすでに知っている常識です。なぜファクトカードが必要なのですか?

そのとおり、ベースモデルはこうした常識の大部分を知っています。問題が「知識をどこから得るか」だけなら、ファクトカードは必要ありません。それでもファクトカードを使うのは、知識を補うためではありません。

  1. 学習で「どう話すか」だけを学ばせるため。 loss は参照回答に対して計算され、参照回答には言い回しと事実の内容の両方が含まれています。入力に資料がなければ、モデルは「太陽の方を向く」「光の当たらない側」といった内容を重みに記憶するしかなく、学習の中で「言い回し」と「知識」が混ざってしまいます。資料が入力にそのまま入っていれば、モデルは資料に従うだけで内容を正しく予測できるので、LoRA の限られた容量は主に「短い文で、まず誤解を正し、安全なちょっとした観察を提案する」ことに使われます。仕組みは Q3 を参照してください。
  2. ベースモデルの知識は確認も修正もできませんが、ファクトカードならできます。 4B モデルがどの質問を間違えるか(たとえば季節の原因を太陽との距離だと説明するなど)は事前にはわかりませんし、間違えても重みを開いて直すことはできません。ファクトカードは出典とレビュー状況を持つ目に見えるテキストなので、間違っていればカードを 1 枚直すだけで次のリクエストから反映されます。バックエンドにたとえるなら、ビジネスルールはコンパイル済みのバイナリに散らばらせず、設定やデータベースに置くべきだ、ということです。
  3. カードは、ベースモデルが自分からは守らない境界を運びます。 たとえば day-night の text には「假定取消自转但保留公转,光照位置仍会随公转缓慢变化」(「自転をなくして公転だけを残したとしても、光の当たる位置は公転に伴ってゆっくり変わる」)と書かれており、参照会話の 2 ターン目の追加質問はこれに基づいています。scope には「模型和比喻不能替代真实测量」(「模型やたとえは実際の測定の代わりにはならない」)と書かれています。kind は fact、legend、current を区別するので、伝説を事実として語ってはいけません。
  4. これが実際のプロダクトの姿であり、評価の土台でもあります。 ビジネスの場面では、モデルが説明しなければならないのは、製品仕様、特定の学校の教科書、今日のデータなど、モデルがそもそも知らないことであることが多いです。一般的なやり方は、呼び出し側が資料を検索してプロンプトに入れ、モデルはそれをわかりやすく説明することに専念する、というもので、これがいわゆる RAG(検索拡張生成)です。このプロジェクトでは、ファクトカードを使ってこのパターンを最初から最後まで練習します。ブラインド評価でも、評価者はファクトカードと照らし合わせて各主張が裏付けられているかを判断します(チュートリアル 6.6 節)。

Q2:ファクトカードも学習サンプルに入っているのだから、学習後のモデルはこの知識を覚えているのでは?

その考え方は正確ではありません。ファクトカードは学習サンプルの入力の一部であって、学習ターゲットではありません。チュートリアル 2.5 節の 408 トークンのサンプルでは、ファクトカードは mask=0 の 386 トークンの中にあります。モデルはそれを見ることはできますが、「覚えていないこと」でペナルティを受けることはありません。loss が問うのはただ 1 つ、この資料を与えられたとき、回答が参照回答にどれだけ近いか、です。

学習後に身につくか 理由
資料の読み方と、それを使った回答の組み立て方 身につく loss がそれを学ぶように押し進めるため
資料そのものの内容と境界 確実には身につかない 学習中は資料が常に目の前にあるので、記憶する必要がないため

正直に言っておくべき点が 1 つあります。参照回答そのものにも事実の内容が含まれており、それは mask=1 の領域にあるので、モデルはよくある言い回しの一部をついでに覚えます。しかし学習は 1 エポックだけで、各サンプルを 1 回しか見ないため、記憶は浅いものです。カードの内容と境界をすべて覚えることはできず、何を覚えて何を間違えて覚えたのかを知る手段はなおさらありません。したがってデプロイ時には毎回ファクトカードを渡さなければなりません。チュートリアル 5.5 節の呼び出し例が最初に read_dataset でファクトを読み込むのはこのためです。

自分で確かめたい場合は、学習後に同じ質問を 2 回してみてください。1 回はファクトカード付きで、もう 1 回は CURRENT REFERENCE FACTS のセクションを取り除いて行い、回答の正確さと境界を比べます。差がはっきりしているほど、モデルは記憶した知識ではなく、その場で与えられた資料に頼っていることになります。

Q3:知識の記憶に頼らないなら、なぜ学習時にも入れるのですか?デプロイ時に渡すだけではだめなのですか?

だめです。学習の目的はモデルにファクトカードを覚えさせることではなく、使い方を教えることです。使い方を身につけるには、学習中にそれを見る必要があります。新入社員の研修にたとえてみましょう。

方法 A:Q&A だけ 方法 B:製品マニュアルを横に置いた Q&A(このプロジェクト)
研修資料 顧客が X を質問 → 標準回答 Y 顧客が X を質問 + 関連するマニュアルのページ → 標準回答 Y
身につく習慣 質問を聞いたら記憶から答えを暗唱する まずマニュアルを調べてから、標準の口調で説明する
実務でマニュアルを渡されたとき マニュアルを読む練習をしたことがなく、おそらく古い答えを暗唱し続ける まさに練習してきたワークフロー
マニュアルの 1 項目が更新されたとき 古い答えを言い続ける 新しいマニュアルに従う
研修で扱わなかった質問 曖昧な印象から答え、でっち上げやすい マニュアルに基づいて説明し、マニュアルに書かれていないことはでっち上げない

これをこのプロジェクトに当てはめると、次のような同じサンプルになります。ターゲットの回答はまったく同じで、入力だけが異なります。

不带事实卡:
  输入:为什么白天亮、晚上黑?
  目标:地球在不停转,我们住的地方转向太阳就是白天……

带事实卡:
  输入:[事实卡:自转使地球表面交替朝向和背向太阳……] 为什么白天亮、晚上黑?
  目标:地球在不停转,我们住的地方转向太阳就是白天……

(訳:「不带事实卡」= ファクトカードなし、「带事实卡」= ファクトカードあり。「输入」= 入力、「目标」= ターゲット。質問は「昼はどうして明るくて、夜はどうして暗いの?」、ファクトカードは「自転によって地球表面の各地点は太陽の方を向いたり背を向けたりを交互に繰り返す……」、ターゲットの回答は「地球はずっと回っていて、わたしたちの住んでいる場所が太陽の方を向くと昼になる……」です。)

カードがない場合、入力には根拠がまったくないのにターゲットははっきりした科学的結論なので、1150 件のサンプルが「質問を聞いたら、何もないところから自信満々の結論を出す」ことを繰り返し強化します。カードがある場合、ターゲットの内容はすべて入力の中に見つかるので、強化されるのは「与えられた資料から内容を取り出し、子どもにわかる言葉に言い換える」ことです。

学習時にカードを入れず、デプロイ時にだけ追加すると、4 つの問題が起きます。

  1. モデルは資料を読む練習をしたことがありません。 デプロイ時には大きな CURRENT REFERENCE FACTS セクションが追加で現れますが、モデルにとっては見慣れない入力なので無視されるかもしれず、いつもどおり記憶から答えてしまいます。
  2. 資料を変えても効果がありません。 カードの誤りに気づいたり、カードの scope に適用範囲の境界を追加したくなったりしても、方法 A のモデルはそれに合わせて変わりません。学習データを変えて再学習するしかありません。
  3. 新しいトピックではでっち上げが起きやすくなります。 方法 A は「何もないところから結論を出す」習慣を身につけるので、学習データにないトピックでも自信満々に結論を出します。
  4. 入力の形式が一致しません。 学習時のプロンプトにはファクトカードがなく、デプロイ時にはあります。モデルの話し方は学習時の入力の形に依存するので、チュートリアル 5.5 節で「プロンプトは学習時のものと一致させなければならない」と強調しているのもこのためです。

コードで言えば、学習は関数 answer = f(question, facts, grade_band, history) を学びます。学習時に question だけを与えれば、学ぶのは f(question) です。デプロイ時に一度も使ったことのない引数 facts を余分に渡しても、モデルが自動的にそれを使い始めることはありません。

ファクトカードなしでも学習や回答はできます。その代償は上で述べたすべてです:誤りを修正できず、モデルは自信過剰になりがちで、ベースモデルが理解していない分野に移ると一からやり直しになります。「事実」と「言い回し」を分けるのはこのプロジェクトの設計上のトレードオフであって、唯一のやり方ではありません。その効果は最終的にチュートリアル第 6 章の比較で検証する必要があります。

ファクトカードが使われる場所

別々に保存する理由。 60 枚のカードは 600 件の会話から fact_ids で参照されます(1 件の会話が複数のカードを参照することもあります)。これはデータベースの外部キーに似ていて、事実を変更するのは 1 か所だけで済み、それを参照するすべての会話は次に読み込まれたときに新しい内容を使います。

セットごとに分離しなければならない理由。 カードは同じセット(train/val/test のいずれか)の会話からしか参照できません。そうしないと、検証やテストの質問が頼る資料を、モデルが学習中にすでに見ていることになり、評価結果が楽観的になりすぎます。

段階 コードの場所 ファクトカードの使われ方
読み込み時の検証(D1) data.py validate id が一意であること、すべてのフィールドがそろっていること、source_url が https であること、kind は fact/legend/current のいずれかであること、会話の fact_ids の各エントリが存在すること、同じカードが train/val/test をまたがないこと
プロンプトの組み立て(D3) common.py build_messages fact_ids でカードを取り出し、id/text/kind/scope だけを残して、システムプロンプトの CURRENT REFERENCE FACTS: の後ろに JSON として書き込みます。出典やレビューのフィールドはモデルに入りません
Notebook の学習・検証サンプル Notebook ステップ 2、prepare-data セル train の 1150 サンプルと val の 115 サンプルはすべて、それぞれのファクトカードとともに build_messages を通ります
Notebook の観察用質問 Notebook ステップ 4、cases セル 3 つの観察用質問はそれぞれ 1 つの fact_id を指定し、学習前後の回答を同じカードで比較します
CLI スナップショット prepare.py スナップショットの facts.jsonl にそのまま書き込まれます。few-shot の例は自身の fact_ids を記録します
A/B/C 比較の推論 infer.py スナップショットから facts.jsonl を読み込みます。3 つのグループはいずれも現在の質問に同じファクトカードを付けます(6.4 節)
ブラインド評価 evaluate.py blind 採点シートには fact_ids が含まれ、評価者はファクトカードと照らし合わせて fact_claims/supported を記入します(6.6 節)
デプロイ後の呼び出し 5.5 節の呼び出し例 read_dataset でファクトを読み込み、messages_for(fact_id, ...) で学習時と同じシステムプロンプトを組み立てます

3 最初の完全な会話

{
  "id": "day-night-01",
  "group_id": "day-night",
  "category": "space",
  "topic": "昼夜",
  "grade_band": "1-2",
  "scenario": "repair_misconception",
  "fact_ids": [
    "day-night"
  ],
  "provenance": {
    "type": "synthetic",
    "author": "Codex",
    "date": "2026-09-29",
    "method": "按主题逐项撰写知识、误解、追问、比喻边界和安全观察,再组合为10种教学对话路径;不是儿童真实对话,部分主题内回答复用。"
  },
  "review": {
    "status": "draft",
    "reviewer": "",
    "reviewer_kind": "",
    "notes": "待逐段核查科学事实、来源支持、适读学段、观察可行性、近重复与上下文衔接;成人审阅不代表已验证儿童探索兴趣。"
  },
  "turns": [
    {
      "user": "为什么白天亮、晚上黑?",
      "assistant": "地球在不停转,我们住的地方转向太阳就是白天,转到背光的一面就是晚上。",
      "behavior": "explain_at_grade"
    },
    {
      "user": "太阳晚上关灯睡觉了吧?",
      "assistant": "太阳没有在晚上关灯。是我们所在的地面转到了背向太阳的一侧。还可以接着想:如果地球不转,就没有黑夜了吗?",
      "behavior": "repair_then_offer_specific_question"
    }
  ]
}

子どもはまず昼と夜について質問し、次に「太陽が眠りに行く」という推測を口にします。参照回答はまず現象を説明し、続いてその具体的な誤解を正し、さらに考えを続けるための任意の質問を残しています。「你真棒」(「すごいね」)のような決まり文句だけをモデルに覚えさせないでください。

フィールド モデルに入るか、どこで使われるか
id 追跡用に day-night-01:0 のようなサンプル ID を生成します
group_id 同じ理科トピックをまとめます。すべての学年帯、パス、書き換えは同じ split に入っていなければなりません
(所属する配列) この会話は train 配列にあり、読み込み時に split="train" が付けられます。D2 は train だけを選び出します。プロンプトには書き込まれません
grade_band D3 が「小学学段」(小学校の学年帯)としてシステムプロンプトに書き込みます。目に見える入力です
fact_ids D3 がこのファイルからファクトカードを選び、id/text/kind/scope だけを渡します
turns[].user 子どもの現在の質問。それ以前の質問は履歴に入ります
turns[].assistant 現在の回答が学習ターゲットです。それ以前の回答はコンテキストとしてのみ使われます
turns[].behavior このターンがどんな振る舞いを示しているかを人が見るためのもの。モデルには渡されません
category/topic/scenario 統計とレビュー用のラベル。「子どもが何を間違えたか」を入力にこっそり持ち込むことはありません
provenance 合成方法、作成者、日付を記録します。実際の子どもの会話ではありません
review すべてドラフトです。レビュー後に、実際のレビュアー、レビュアーの種類、変更メモを記入してください

4 読み方、編集のしかた、レビューのしかた

まず topic を見つけ、次に fact_ids、grade_band、そして turns 全体を読みます。言い回しを変えたいときは assistant のターンを編集します。現実的なフィードバックのパスを追加するときは、子どもがモデルの言っていないことを引用してしまわないよう、コンテキストも同時に調整してください。事実を更新したら、それを参照しているすべての会話を見直してください。

python -m lab.data --input data/dataset.json --allow-draft

{'train': 500, 'val': 50, 'test': 50} と表示されるはずです。これは構造チェックであり、科学的なレビューではありません。正式なレビューでは次の点を確認します:出典が事実を裏付けているか、過度な擬人化がないか、子どもが理解できるか、たとえの限界が明確か、観察が安全で実行可能か、子どもが質問をやめたり終わりにしたがったりしたときにモデルがそれを尊重するか。

レビューはこの 1 ファイルの中で直接行えます。履歴はバージョン管理で残ります。本当にレビューされたエントリだけに review.status="approved" を設定し、実際の reviewer/reviewer_kind/notes を記入してください。ステータスを一括で変更して、レビューしたふりをしないでください。ファクトカードにも実際の検証状況を記録してください。そのあと、--allow-draft を付けずに検証を再実行します。

データ内の 10 種類の構造は教え方の振る舞いを意図的に繰り返しており、トピック内では知識に関する回答の一部が再利用されています。それでも、意味的なほぼ重複やテンプレート的な印象がないかは人の手で確認する必要があります。train の 50 トピックは、500 個の独立したトピックではありません。val/test は学習用のトピックから分離されていますが、光、力、生命といった基本的な概念は共有しているので、知識が完全に分離されているわけではありません。

2026-10-04 の改訂(まだドラフト):662 件の回答で文をつなぎ合わせたときに残った半角スペースを取り除き、モデルが「。 下一句」(句点のあとにスペースを挟んで次の文が続く)のような継ぎ目を学ばないようにしました。「多讲一点原因」(「理由をもっと詳しく教えて」、increase_depth_when_requested)に対する 60 件の回答は「仕組み + たとえ + たとえの限界」に変更し、前回よりも本当に深い内容にしました。「观察不一样」(「観察結果が違った」、ask_for_evidence_without_blame)に対する 60 件の回答からは決まり文句「我们刚才讨论的是……」(「さっき話していたのは……」)を取り除き、まず条件を確認するようにしました。「探究を促す」60 件の回答では、追加の質問の前に「还可以想想:」(「こんなことも考えてみよう:」)を加えました。回答は全体としてまだやや短く(平均約 51 文字)、引き続き人によるレビューが必要です。

5 変更が反映されるまで

JSON を保存したら、Notebook に新しい RUN_DIR を指定し、D1 の読み込みと D2~D5 のトークン化を再実行してから学習を始めてください。すでにメモリ上にある行や保存済みの adapter は自動では変わりません。実行ディレクトリ内の dataset.json はその実行の入力スナップショットであり、日常的に管理する元データの 2 つ目のコピーではありません。

2 D1~D7:データが実際に使われる場所

コメント 場所と実際の動作
D1 Notebook のステップ 2 で DATA_FILE を読み込み、train/val/test の会話に split を付け、ファクトカードの参照とセット間の分離を検証します
D2 train だけを選びます。expand_episode は過去の履歴をターンごとに保持します
D3 build_messages が役割、grade_band、選ばれたファクト、履歴、現在の user ターンを組み立てます
D4 render_pair がチャットテンプレートを適用し、現在の assistant の回答と EOS をターゲットとしてマークします
D5 input_ids/completion_mask の 2 列だけを SFTTrainer に渡します
D6 TRL が実際に生成する labels を確認します。コンテキストとパディングは -100 でなければなりません
D7 この実行のデータのコピーとフィンガープリントを保存します。実際にパラメータを更新するのは trainer.train() だけです
单一 dataset.json → train 对话 → 当前问题 + 已发生的历史
                                  ↓
            角色 + 学段 + 对应事实 + history + user
                                  ↓
                       prompt / 当前 assistant + EOS
                                  ↓
                   input_ids + completion_mask
                                  ↓
               labels → loss → 反向传播 → LoRA 参数更新

(訳:単一の dataset.json → train の会話 → 現在の質問 + これまでの履歴 → 役割 + 学年帯 + 対応するファクト + history + user → prompt / 現在の assistant + EOS → input_ids + completion_mask → labels → loss → 誤差逆伝播 → LoRA パラメータの更新。)

3 目に見える 5 つのフィールド

rows には確認用に case_id/prompt/completion/input_ids/completion_mask が残されています。実際の学習が受け取るのは最後の 2 列だけです。Notebook は 1 行目の完全なプロンプト、ターゲット、実際のトークン長を表示します。以前のシナリオの長さの数値を使い回さないでください。

completion_mask=0 はコンテキストを、=1 は現在の回答と EOS を示します。TRL はコンテキストとパディングの labels を -100 に設定するので、それらの位置では直接 loss は計算されませんが、モデルは回答を予測するときにコンテキストを見ることができます。

EOS は <|im_end|> で、モデルが止まることを学べるよう、各ターゲットにちょうど 1 回だけ現れます。PAD は EOS とは別のものです。4096 トークンを超えるとエラーになり、そのサンプルは書き直す必要があります。回答が黙って切り詰められることはありません。

項目ごとの科学的な出典は facts[].source_url にあります。ファクトのレビューと、子どもにとっての読みやすさのレビューはまだ完了していません。