LLM を使って学習用データセットを合成する手法は、人手アノテーションのコストを大幅に下げる手段として広く使われています。しかしこの手法には、設計を誤ると静かにデータ品質を破壊する落とし穴がいくつもあります。
落とし穴 1: モデルコラプス
モデルコラプスとは、LLM が自分自身の出力で再学習を繰り返す「世代ループ」によって、出力分布が徐々に縮退していく現象です。最初の世代では人間の書いたテキストに近い多様な出力が得られても、その出力で再学習した次世代モデルは少しずつ尖った分布に収束していきます。これを繰り返すと、元の分布の裾野にあった珍しいパターンが消え、同じような文章ばかりが生成されるようになります。
学習ループを持たない一回きりのデータ生成でも同じ問題は起きます。Chat 形式で連続的にサンプルを生成すると、前の出力が暗黙のコンテキストとして働き、後続の出力を引き寄せる「アンカリング」が生じます。これはモデルコラプスの軽量版です。
落とし穴 2: バイアスの増幅と多様性の欠如
LLM は学習データに含まれる統計的偏りをそのまま内包しています。合成データにはその偏りが色濃く反映されます。さらに問題なのは、世代をまたいで使うほど、この偏りが増幅される点です。マイノリティなパターンは世代ごとに生成確率が下がり、最終的にほぼ消滅します。
多様性の欠如は単なる品質の問題にとどまりません。あるクラスのサンプルが極端に少ないデータセットで学習したモデルは、そのクラスを認識できなくなります。合成データの多様性をモニタリングせずに「量だけ増やす」戦略は、むしろ本番精度を下げるリスクがあります。
落とし穴 3: 検証の困難さとデータ汚染
自然言語の正誤には客観的な基準がないため、LLM 生成データの品質検証は構造的に難しいです。生成した LLM 自身、または別の LLM で検証しようとすると自己参照的評価になり、モデルが間違いを間違いと認識できないケースが生じます。
加えて、評価ベンチマークのデータが合成データセットに混入するデータ汚染のリスクもあります。広く使われるベンチマーク(MMLU や HumanEval など)は LLM の学習データに含まれている可能性があり、その断片が合成データとして再生成されると、テスト/学習のリークが起きます。
本質的な原因: Chat 形式の連続生成
落とし穴の多くは、Chat 形式のステートフルな連続生成に由来します。
典型的な失敗パターンは次のようなものです。
ユーザー: 質問と回答のペアを 100 個作ってください
アシスタント: 1. Q: ... A: ...
2. Q: ... A: ...
...
この方法には構造的な欠陥があります。
- アンカリングバイアス: 1 番目のサンプルのスタイルや語彙が、2 番目以降の生成に引きずられます。100 番目になると最初の出力に強く拘束されています。
- エラーの伝播: 途中で事実誤認や不自然な表現が入ると、それが以降のサンプルへ波及します。
- 多様性の崩壊: 同一コンテキスト内で生成されるため、サンプル間の統計的独立性が失われます。
- 再現性の喪失: 温度パラメータが同じでも、コンテキスト長の変化でトークン確率が変わるため、まったく同じ出力を再現できません。
解決策: マトリクス設計 × ステートレス単一呼び出し
正しい設計原則は「マトリクスでセルごとに生成する」です。
データセットを表として捉えます。
| サンプル ID | input (質問) | output (回答) | difficulty_label |
|---|---|---|---|
| 001 | ... | ... | medium |
| 002 | ... | ... | hard |
各セルをステートレスな単一 API 呼び出しで独立して生成します。同じ行の input と output であっても、別々のプロンプト・別々のリクエストで生成します。
# 悪い例: 1 つのチャット会話でまとめて生成
messages = [{"role": "user", "content": "Q&A ペアを 100 個作って"}]
response = client.chat(messages) # 全サンプルが 1 コンテキスト内
# 良い例: セルごとに独立したリクエスト
def generate_cell(row_id, field, context):
prompt = build_prompt(field, context) # フィールド専用プロンプト
return client.complete(prompt) # ステートレス単一呼び出し
for row_id in range(N):
input_text = generate_cell(row_id, "input", seed=seeds[row_id])
output_text = generate_cell(row_id, "output", context=input_text)
label = generate_cell(row_id, "difficulty_label", context=input_text)
この設計が持つ利点は次のとおりです。
- 統計的独立性: 各サンプルは他のサンプルに引きずられません。アンカリングが原理的に起きません。
- 並列実行: セル間に依存がないため、非同期で全セルを並列生成できます。スループットが大幅に向上します。
- セル単位バリデーション:
difficulty_labelが不正な値であれば、そのセルだけ再生成できます。行全体を作り直す必要がありません。 - 列の差し替え容易性:
outputの生成プロンプトを改善したければ、その列だけ再生成して既存のinput列に結合できます。データセット全体を再生成するコストがかかりません。
同じ行の複数フィールドを別々に生成することは冗長に見えて合理的です。question と answer を同じコンテキストで生成すると、モデルは「自分が書いた質問に都合の良い回答」を生成する傾向があります。独立生成にすることで、回答の汎用性と多様性が増します。
まとめ
- モデルコラプス・バイアス増幅・多様性欠如は、LLM 合成データが持つ構造的な落とし穴です。
- 検証の困難さとデータ汚染も見落とされやすいリスクです。
- 落とし穴の多くは Chat 形式のステートフルな連続生成に起因します。
- 解決原則は「マトリクス設計」と「ステートレス単一 API 呼び出し」の組み合わせです。
- 各セルを独立したリクエストで生成することで、統計的独立性・並列化・セル単位バリデーション・列差し替えのすべてが得られます。
- 同じ行の
input・output・labelも別プロンプト・別リクエストで生成するのが正しい設計です。