一、合成データの方法論の骨格:「人が作ったデータの安い代用」ではない

1.1 この記事が答えるべき問い

ポストトレーニングに取り組んでいるほぼすべてのチームが、ある月のどこかで同じ場面にぶつかります。オンラインの実データが突然足りなくなった。採取コストが高すぎるのか、ユーザー行動の分布が狭すぎるのか、あるいは新しいプロダクトの形にはそもそも採れる過去データが存在しない。そうするともっとも安直な手は「モデル自身にまとめて作らせる」ことで、生成結果をそのまま SFT に放り込むことです。このステップはふつう効きます。しかしほぼすべてのチームが 3 ヶ月後に同じ症状に直面します。合成データを足するほど、内部評価セットでは強くなるのに、実運用に載せると落ち込み方が大きくなる。2〜3 ラウンド回しても、モデルは同じ種類の新しい誤りを安定して犯すようになるのに、その誤りはモデル自身の出力の中ではまったく読み取れないのです。

これはエンジニアリング上の事故ではなく、方法論の穴です。合成データの本当の問題は「質が悪い」ことには決してなく、モデルが生成した内容を訓練信号として扱うと同時に、世界に対するそのモデルの誤った理解も金標準として固定してしまうことにあります。モデルは「ありそうだと考えるもの」しか生成できず、自分が知らないものを知らないままでは生成できません。この構造的な制約が決めていることがひとつあります。合成データの正当性は、誰が生成したかにはなく、何の信号で判定されたかから来る。

そこで本文は「いい prompt を書いてモデルにデータを多く出させる」という話ではなく、生成・検証・混合・監視という一本の完全な連鎖を扱います。このフレームワークの価値は、合成データを一度限りのテクニックから持続的なエンジニアリングシステムへ変えることにあります。各ステップに明確な失敗モードがあり、明確な計測指標があり、明確な終了条件がある。

1.2 四つの工程、そして順序を逆にできない理由

この連鎖を分解すると工程は四つしかありません。ただし順序は強い制約です。

  1. 生成(Generate):あるモデルが prompt プールを起点に候補サンプルを生産する。鍵となる変数は prompt プールの分布であり、生成モデルの賢さではありません。
  2. 検証(Verify):生成過程から独立した信号で各候補の正誤と品質を判定する。鍵となる変数は検証信号の外部性——本当にジェネレータから独立かどうかです。
  3. 混合(Mix):検証済みの合成データと実データを、ある配分、あるバケット戦略で並べる。鍵となる変数は、独立した三本の軸が混同されていることです。
  4. 監視(Monitor):実データの held-out 集合で継続的に観測し、分布崩壊、能力の忘却、検証器のドリフトを検出する。鍵となる変数は、停止条件が合成データではなく実データで定義されなければならないことです。

順序が強い制約なのは、以降の各工程の計測が前の工程の成果物に依存しているからです。生成層の切り詰め率や重複率は、検証層が選別して初めて意味を持ちます。検証層の誤判定率は、実データの抜き取り検査セットがあって初めて較正できます。混合層のバケット戦略は、バケット指標があって初めて最適化できます。監視層の停止条件は、ファネルの手前四層のデータが揃って初めて完全な説明が得られます。どれか一環を飛ばすと、後段の「改善」はたいていノイズを前に押し出しただけになります。

本文はこの順序に沿って展開します。重点は各ステップの技術的細部ではなく、各ステップがどう壊れるかという形にあります。合成データのパイプラインで起きる「効果が落ちた」事象のほとんどは、不可解なミステリーではなく、いずれかの層の具体的な失敗モードまで辿れます。

二、自己蒸留と rejection sampling のループ

2.1 ループを構成する四つの要素

自己蒸留(self-distillation)の字義通りの意味は「モデル自身が出力したデータでモデル自身を訓練する」ことです。実践の中でそれが裸で走ることは決してなく、いつもループになっています。

  • Prompt ソース:種となるタスク群。この分布がパイプライン全体の天井を決めます。prompt プールに存在しないタスクの種類を、モデルは生成することができません。
  • サンプリング戦略:温度、top-p、1 問あたりのサンプル数、複数回の独立サンプリングを行うかどうか。候補集合がどれだけの確率質量を覆うかを決めます。
  • 検証信号:ルールチェック、ユニットテスト、実行結果、参照解答との照合、プロセスレベルの採点モデル、外部ツールの戻り値。これはループの中で「金標準」の役割を担える唯一のものです。
  • 還流戦略:どのサンプルを訓練集合に入れるか、重複排除の規則、バケットごとの比率での混合、複数回反復した履歴データを残すかどうか。

rejection sampling(rejection sampling / best-of-N)は、このループの中でもっとも素朴でもっとも効果的な形態です。N 個の候補を生成し、検証信号で通過したものを抜き出し、残りを捨てて SFT データにします。その価値は「正解を新しく作り出す」ことにはなく——正解はもともと手元にあることが多くて——訓練分布をモデルの能力境界のすぐそばにある領域へ集中させることにあります。これが次の節で展開する核であり、この一連の方法論のなかで最も実務的に効く経験です。

rejection sampling の本質は重み付けの振り替えです。勾配の予算を、モデルがすでに 100% 把握している簡単なサンプルから引き剥がし、成功率が 10% から 50% の領域へ移す。有限の訓練予算のもとでは、この移動が得られる利得は、同じ分布のデータをさらに多く生成するより格段に大きくなります。

2.2 STaR、ReST、SPIN、RAFT はそれぞれ何を補ったか

自己蒸留の系譜には、何度も引用されてきた変種がいくつかあります。それらは同じことを別の言葉で言い換えたものではなく、それぞれがベースラインの手法の異なる穴を埋めたものです。名前を覚えるより、それらの違いを理解するほうがはるかに実用的です。

STaR(Self-Taught Reasoner)が扱うのは「正解はあるが推論過程が欠けている」という問題です。そのやり方は、まずモデルに答えを直接出させ、正解ならその答えに到達できる推論の連鎖を逆算させて、その連鎖を教師信号として使うことです。隠された仮定は、モデルは正面からその問題を解けなくても、少なくとも正しい解法がどんなものかを見分けられるので、逆方向の生成で近づけるというものです。この仮定は単純な推論タスクではかなり筋が通りますが、複数ステップの探索が必要なタスクでは脆いです。

ReST(Reinforced Self-Training)が扱うのは「データが現在のモデルからのみ来るため、分布が永遠に拡張しない」という問題です。2 つの段階を交互に行います。まず数ラウンドの rejection sampling を行ってから全量再訓練する(期待最大化ステップ)。その後サンプルを取り直す。再サンプルは、前ラウンドの訓練後のモデルでなければ解けない問題を生み出せる。つまり自己増幅(bootstrapping)です。「EM の視点」という比喩はきわめて的確で、期待ステップは良い経験を固定化し、最大化ステップは解ける集合を広げます。

SPIN(Self-Play)が扱うのは「正例由来の教師信号を搾り尽くした」という問題です。現在のポリシーが出した劣化した結果をそのまま相手として扱い、「より良い」疑似ラベルを生成し、自己対局の選好データを作ります。隠されたリスクは直接的で、「より良い」という判断がモデル自身から来るなら、その選好の方向性自体がモデルの偏りを含んでおり、偏りを選好信号に固定化したにすぎません。SPIN は汎用テキストの流暢さでは効果がありますが、客観的な正誤があるタスクでは rejection sampling に比べてはるかに及ばないです。

RAFT(Reward rAnked FineTuning)は rejection sampling のエンジニアリング版です。複数のサンプルを生成し、リワードモデルで採点し、報酬の高い一部を SFT 用に残す。中核的な貢献は「二値での合格」を「連続的な順位づけ」に変えたことであり、検証信号が疎らな(合格か否かは二値で決められないが、上下の優劣は比較できる)状況でその威力が特に効きます。

この四つを並べてみると、法則が見えてきます。自己蒸留が一歩進むたびに埋められてきたのは、「教師信号がモデル自身から来る」という根本的な矛盾です。STaR は逆向きの構成でこれを回り、ReST は反復による拡張で回り、SPIN は相対比較で回り、RAFT はソートで回ります。回り切れなかった部分こそ、次の二節で正面から扱う問題になります。

2.3 学習可能なフロンティア:なぜ成功率の中間が最も貴重なのか

もし本文から実行可能な経験をひとつだけ残せるなら、それがこれです。訓練サンプルの価値は、モデルが正解できる確率と、ある折り返し点まで負の相関をもつ。

prompt プールをモデルの成功率で 0% から 100% まで並べて、勾配の寄与を観察すると考えてみてください。成功率がほぼ 0 のサンプルでは、モデルはどちらへ向かえばよいのかまったく見当がつかず、正例教師信号が実際に与えているのは「たまたま当たった正解の経路」にすぎず、ノイズが非常に大きくなります。成功率がほぼ 100% のサンプルでは、モデルはすでに掌握していて勾配は 0 に近く、訓練しても予算の無駄です。実際に学習ゲインを生むのは、成功率が中間の範囲に落ちるサンプルです。モデルにはある程度の確率で正解できる、したがって検証信号は正例を信頼して選別できる、そしてまだ完全には掌握していない、したがって勾配も依然として有意です。

この直感はおよそ一文に形式化できます。検証を通る確率を p とすると、正例ひとつがもたらす期待情報量は、おおよそ p * (1 - log p) のような量に沿って動き、p が低いときは素早く増え、p が高いときは飽和します。そのため多くのチームは実践の中で、粗いけれども安定した経験的区間にたどり着きます。p を 0.1 から 0.5 のあいだに保つことが、合成データの費用対効果が最も高くなる位置だというものです。この区間が普遍的な定数だとは限りませんが、タスク難度の分布やモデル規模、最適化器の設定とともに漂い、その形は安定しています。

帰結はとても直接的です。prompt プールは「できるだけ難しいタスクの一覧」でも「入門チュートリアルに載っているトピックの一覧」でもあってはならず、現在の checkpoint に対して成功率が中間帯に来るタスク群でなければなりません。つまり prompt プールはモデルの能力に合わせて転がしながら更新し続ける必要があります。v1 のモデルではフロンティアにあった問題は v3 のモデルではもう卒業しており、もっと難しい問題に置き換える必要があります。また、すでに掌握された古い問題を単純に捨てることもできません。サンプリング比率を下げたうえで低頻度でリプレイし続け、忘却に対抗するのです。

これが「カリキュラム設計」が合成データにおいて錦上添花ではなく中枢的な設計要素である理由です。静的な prompt プールを、モデルの能力に追随して自己適応する動的システムへ変えるからです。

2.4 2026 年の変化:検証可能な報酬が合成と強化学習を一本の線にした

この数年間、この 2 つは別々の存在でした。合成データは SFT に使われ、強化学習は reward model か人間の選好で採点されます。2025 年から 2026 年にかけての最も重要な構造的変化は、検証可能な報酬(RLVR, Reinforcement Learning with Verifiable Rewards)パラダイムの普及が、この 2 つを同じパイプラインにまとめたことです。合成データは「訓練したら固定」する一度限りの産物でなくなったかわりに、強化学習の rollout のサンプリングプールになっています。検証信号も単なるフィルターではなく、直接に報酬関数として働きます。

この統合は 3 つの実質的な変化をもたらします。どのチームも自分のパイプライン設計を一度見直す価値があります。

  • 正例だけが唯一のデータ形態ではありません。RL の枠組みでは、失敗サンプルも負の勾配情報を運ぶので価値があります。通ったサンプルだけを残す rejection sampling は、「モデルがどこで系統的に間違えるか」という側面の信号を捨ててしまいますが、その側面こそ、対抗サンプル(adversarial sample)を構成する最も直接的な原料です。
  • プロセスレベルの検証の価値が上がります。結果レベルの検証(答えが正しいか)は多段タスクでは信用分配が極端に悪くなり、モデルはどのステップでズレたのかがわかりません。プロセス報酬モデル(PRM)や、2025 年以降に現れた生成式検証器(あるモデルが採点理由を書いてから点をつける仕組み)はこの穴を埋めようとしますが、コストはルールチェックより明確に高くなります。代わりに数学の導出、マルチホップの質問応答、コードデバッグといった長い連鎖のタスクで、信用分配を明確に改善します。
  • 検証器は「選別器」から「環境」へ昇格します。コード領域では「テストケースをひとつ生成する」ことが実行可能な環境になり、モデルが間違えたら実行して、実行が失敗すれば決定論的な信号が得られます。ツール呼び出しの領域では、合成データはツールの戻り値を含む完全な trace 列にされました。この種の「実行可能な合成データ」は、本質的にデータセットを合成しているのではなく訓練環境を合成しています。この転換はデータ生成の想像空間を一段と大きく広げました。

ただし統合には代償があります。RL フェーズは SFT フェーズよりずっと報酬ハッキングに敏感です。モデルは問題を解くことに努めるのではなく、能動的に検証器の穴を探します。したがって RLVR パイプラインは、純粋な SFT パイプラインより早く、より高頻度に検証器の監査を実施しなければなりません。この点は第九節でまた扱います。

2.5 ループの収束性:自己証明と報酬ハッキング

ここまでのものをつなげてみると、少し落ち着かない結論が見えてきます。自分で生成したデータで自分自身を訓練し、さらに自分かそれに非常に近い関係にあるモデルで検証するループは、収束を保証しないのです。

直観はこうです。各反復において、モデル分布は「検証器が認めたサンプル分布」へと収縮します。収縮自体に問題はありません。それがまさに狙い通りの指向的な能力向上だからです。問題は、ジェネレータと検証器が十分に多くの訓練ソース、アーキテクチャ上の嗜好、言語のスタイルを共有していると、検証器の判定基準がジェネレータと一緒にドリフトすることです。ドリフトの行き着く先は、モデルが「検証器が高点をつけてくれるもの」を出し始め、「本当は正しいもの」を出さなくなる状態です。この時点のループは指標上依然として美しく、通過率は上がり、訓練損失は下がる。しかしすでに本当の目的とは切り離されています。

収束を破る定番の手法は 4 つあり、重ねて使えます。

  • 外部アンカー:各ラウンドで、一度 も合成プロセスに関与していない実データを一定量固定で混ぜます。この部分はあらゆる選別にも参加しません。役割はドリフトしない座標系を提供することです。
  • 検証器のローテーション:新規データのひとまとまりで検証器を周期的に再訓練するか、複数の検証器による投票を用い、少なくとも 1 つの「異源」検証器の同意を必須にします。異源とは、アーキテクチャ・訓練データ・目的体系がそれぞれ異なることを指します。
  • prompt プールのローテーション:人間が書いた高価値タスクを定期的に投入します。とくにモデルが系統的に失敗する領域を優先してください。人間のサンプル量が大量である必要はありませんが、役割は「天井を元に戻しておくこと」です。
  • 収束検出と遮断:第九節の停止条件を参照してください。ポイントは遮断信号を実データの held-out 集合で定義し、その信号が連続して複数ラウンド続けて現れてから発動するようにすることです。訓練ノイズに左右されないようにするためです。

2.6 再利用できるループ実装

以下のコードは、上述したループの核心ロジックを書き起こしたものです。特定のフレームワークには依存せず、あらゆる生成バックエンドと検証バックエンドにそのまま接続できます。いくつかの意図的な設計選択に注意してください。検証結果は失敗サンプル(理由の注釈付き)も保持し、プールに入れる前にセマンティック重複排除を行い、そして明示的な学習可能フロンティアのフィルタを入れています。

import json, hashlib, random
from collections import defaultdict
from dataclasses import dataclass, field
from typing import Callable, Iterable

@dataclass
class Candidate:
    task_id: str
    prompt: str
    output: str
    passed: bool
    verifier_score: float = 0.0
    failure_type: str = ""          # 失败原因分类,仅在 passed=False 时有意义
    difficulty_bucket: str = ""     # 由 seed taxonomy 继承
    domain: str = ""
    fingerprint: str = field(default="")

def semantic_fingerprint(text: str) -> str:
    """归一化后取哈希,作为精确去重的第一道闸门。"""
    norm = "".join(text.lower().split())
    return hashlib.sha1(norm.encode("utf-8")).hexdigest()[:16]

def rejection_sampling_loop(
    prompts: Iterable[str],
    generate: Callable[[str, int, float], list[str]],
    verify: Callable[[str, str], tuple[bool, float, str]],
    n_samples: int = 8,
    temperature: float = 1.0,
    frontier_low: float = 0.10,
    frontier_high: float = 0.55,
) -> list[Candidate]:
    """生成 -> 验证 -> 统计成功率 -> 只保留可学习前沿的题目产出。"""
    kept: list[Candidate] = []
    seen: set[str] = set()

    for prompt in prompts:
        domain = prompt.split("|", 1)[0].strip()
        task_id = semantic_fingerprint(prompt)
        outs = generate(prompt, n_samples, temperature)
        results = []
        for out in outs:
            ok, score, ftype = verify(prompt, out)
            results.append((out, ok, score, ftype))

        pass_rate = sum(1 for r in results if r[1]) / max(1, len(results))
        # 能力边界之外:全对说明已掌握,全错说明无梯度可用
        if pass_rate < frontier_low or pass_rate > frontier_high:
            # 失败的极端仍然有用,标注后进入对抗样本池,而不是直接丢弃
            if pass_rate < frontier_low:
                for out, ok, score, ftype in results:
                    if not ok:
                        kept.append(Candidate(task_id, prompt, out, False,
                                              score, ftype, "very_hard", domain,
                                              semantic_fingerprint(out)))
            continue

        bucket = f"p={round(pass_rate, 1)}"
        for out, ok, score, ftype in results:
            if not ok:
                continue
            fp = semantic_fingerprint(out)
            if fp in seen:            # 精确去重
                continue
            seen.add(fp)
            kept.append(Candidate(task_id, prompt, out, True, score, "",
                                  bucket, domain, fp))

    return kept


def diagnose_pipeline(cands: list[Candidate]) -> dict:
    """用少量诊断指标替代单一通过率,用于早期发现闭环自证。"""
    by_domain = defaultdict(list)
    for c in cands:
        by_domain[c.domain].append(c)

    report = {
        "total": len(cands),
        "pass_rate_by_domain": {},
        "failure_type_hist": defaultdict(int),
        "verifier_score_mean": 0.0,
    }
    if cands:
        report["verifier_score_mean"] = sum(c.verifier_score for c in cands) / len(cands)
    for c in cands:
        if not c.passed and c.failure_type:
            report["failure_type_hist"][c.failure_type] += 1

    for dom, group in by_domain.items():
        ok = sum(1 for c in group if c.passed)
        report["pass_rate_by_domain"][dom] = ok / len(group)

    # 告警:某个领域的失败类型高度集中,通常意味着验证器有系统性缺陷
    for dom, group in by_domain.items():
        if len(group) < 30:
            continue
        hist = defaultdict(int)
        for c in group:
            if not c.passed and c.failure_type:
                hist[c.failure_type] += 1
        if hist:
            top, cnt = max(hist.items(), key=lambda kv: kv[1])
            if cnt / max(1, len(group)) > 0.8:
                report.setdefault("alerts", []).append(
                    f"{dom}: 80% 以上失败集中于 {top},疑似验证器缺陷而非模型缺陷")
    return report

このコードには、レビューで指摘されやすいものの残しておく価値のある詳細が 2 つあります。第一に、frontier_high の上限を超える成功サンプルは、訓練集合に残して「定着させる」のではなく捨てることです。これは直感に反しますが正しい処理で、成功率が高いサンプルの勾配は 0 に近く、残しておくと batch の質が希釈されてしまいます。第二に、極度に難しいタスクの失敗サンプルは捨てずに、失敗タイプの注釈を付けて別のプールに入れます。これらは第七節の対抗サンプルの主要な供給源です。失敗データの大部分が価値を持たないのは、それが誤っているからではなく、誰もそれがどこで間違えたかを記録していないからです。

三、モデルが生成したデータをそのまま金標準にできない理由

3.1 分布崩壊は確率性の問題であって、正しさの問題ではない

合成データのパイプラインで最も誤診されやすい問題が分布崩壊です。通過率 90%、損失曲線はきれい、オフライン評価も伸びる、チームは「データは問題ない」と考えます。しかし崩壊は決して「データが誤った」形で現れるのではなく、「データが狭くなった」形で現れます。個々のサンプルだけ見ればすべて正しいのに、データ全体をまとめると現実分布のごく一部しか覆えていないのです。

この区別は非常に重要です。診断のアプローチがまったく変わってしまうからです。正しさの問題は 1 件ずつ検査すれば見つかります。分布の問題は統計量でなければ見つけられません。代表的な崩壊の統計シグナルは次のとおりです。

  • 合成集合と実集合の、埋め込み空間における最近傍距離の分布が明確に右に偏る。つまり合成サンプルが現実分布のごく少数の領域に「固まる」ということです。
  • 合成集合の token タイプと組み合わせ(TTR)の曲線が異常な速さで低下する。表層の語彙多様性が急速に消耗していることを示します。
  • 新しく生成した合成データと直前のバッチの自己類似度が明確に上昇する一方で、直前の実データとの類似度はむしろ下がる。
  • 長さ分布が収縮する。モデルはあるパターンを学習すると、長さがだんだんと揃った出力を出すようになる傾向があります。

これらのシグナルに共通するのは、個々のサンプルの正確率のスナップショットにはまったく見えないことです。したがって通過率だけを睨んで監視していれば、崩壊は本番稼働してからしか気づけません。

3.2 再帰的な自己消費:なぜ反復が情報の喪失を加速させるのか

再帰的な訓練がモデルを退化させるメカニズムについては、すでに相当確かな研究があります。それらをまとめると、「自分のデータを使うな」という粗い結論よりも、より精密な全体像が得られます。

分布の裾が段階的に削られる。再帰生成は、元の分布に対して繰り返しリサンプリングと選別を行うことに等しい。選別のたびに低確率サンプルが削除されます。実際、それが最も独特で情報量が多いサンプルです。n ラウンド目を経て、分布の裾は n 層分削られ、モデルの能力境界は内側へ縮みます。これが「モデル崩壊(model collapse)」と呼ばれる古典的メカニズムです。再帰訓練は近似的な意味で、モデルを元の分布ではなく収縮した分布へ収束させます。

自己消費のパラドックス(「Go MAD」現象)。より厳しいのは、「モデルのデータを入力として新しいモデルを訓練し、その出力が次のラウンドの入力になる」という交互的な再帰です。実験によれば、この種の訓練は分布の異常を急速に生み、モデルが現実のデータとほとんど無関係な内容を出すようになります。ここでの鍵となる仕組みは、崩壊は緩やかではなく加速するということです。モデルがあるパターンを失うと、その後の自己サンプリングではそれを再構築できません。再構築にはそのパターンから十分な確率質量を持つサンプルを生成しなければならず、それこそが不可能だからです。

「rejection sampling」への帰結は非常に重要です。rejection sampling はこの問題に対して本質的に免疫しているように見えます。検証を通ったサンプルだけを使うからです。しかしそれが成り立つのはひとつの前提のもとだけです。検証器のカバー範囲が十分に広く、低確率だが正しいサンプルを絶えず拾い上げられるほど広いということです。検証器がモデルになじみの深いタスク形式にしか効かないなら、抽出されるのは分布の中心であって外縁ではなく、崩壊はそのまま起きます。ただ、その速度が遅くなるだけです。したがって検証器のカバー率が合成データ品質の真のボトルネックであり、生成規模ではないのです。

3.3 エラーの増幅:「正しいものを選別しても」学べないことがある理由

この節で扱う問題はもっとも見落とされやすいと同時に、成果ももっとも大きい部分です。チームがしばしば陷入する困惑はこうです。データは 1 件ずつ検証を通っているのに、なぜモデルはなおある誤りを身についてしまったのか。

答えは、検証器の判定基準と訓練の目的は別のものだという点にあります。検証器が判定するのは「この推論の連鎖の結論が正しい」かどうかであり、訓練が最適化するのは「この推論の連鎖のすべての token」です。このあいだには巨大な自由度の差があります。結論が正しい連鎖なら、その途中で無限におそらく誤った推論が許されます。ステップの飛ばし、誤った比喩、誤った帰着、さらに互いに打ち消し合う複数の誤りまで。これらは検証器の判定基準の下ではまったく不可視です。

その結果、直感に反しながら観測可能な現象が起きます。モデルは「誤った道筋で正しい結論に到達する方法」を安定して習得してしまいます。この種のエラーは極めて頑固です。検証器でも、オフライン評価でも、人手の抜き取り検査でも、結論のレベルでは見えないからです。露出する手段は推論の連鎖そのものを読むことしかありません。

さらに厄介なのは第二の類の問題です。系統的なバイアスは選別を生き残る確率が高いのです。ランダムな誤りは検証器に大量に止められます。ランダムな誤りには、结论を誤らせるものが相当な割合で含まれるからです。しかし系統的な誤りは違います。モデルの認識の中の安定した偏りであり、生成したすべての連鎖に一貫して作用します。この種の誤りが招く結論の誤り率は、ランダムな誤りよりむしろ低く、検証を通過しやすいのです。

例を挙げると、モデルがある単位換算に対して安定した 10% の誤差を持つ場合、生成した 10 本の連鎖のうち約 1 本は誤り、9 本は検証を通ります。それでも、その 9 本の「たまたま正解」した連鎖の次元処理は依然としてぐちゃぐちゃで、モデルがそこから抽出する規則もぐちゃぐちゃです。この種の誤りは通過率の曲線にはまったく現れず、通過サンプルの推論連鎖を抜き取り検査して初めて発見できます。

対応するエンジニアリング上の対策は 3 つで、いずれも複雑ではないのにしばしば省略されます。

  • プロセスレベルの検証は結果レベルの検証に優ります。少なくとも重要なタスクでは推論の連鎖を検査し、結論だけを見ないことです。コストを正確さとの引き換えとします。
  • 通過サンプルに対して「誤りの蓄積」に集中した抜き取り検査を行います。ランダムな抜き取り検査ではなく、推論の連鎖の構造が怪しいサンプルを意図的に抜きます。ステップの飛ばしが多すぎる、重要な中間量が明示的に現れない、結論と前提の距離が大きすぎる、というものです。
  • 第 2 の独立した方法で交差検証します。たとえば別の検証器を使う、あるいは逆方向に組み立てた等価な問題で聞き直して、答えが自己整合かどうかを見ることです。この種の「自己整合性チェック」で捉えられない誤りは、率直に言って、これ以上のコストをかけるに値しません。

3.4 検証器の同化:ループが自己証明へ退化する

ここまで述べてきたのはデータ層のリスクです。この節はもっと危険な層の話です。検証器そのものが汚染されると、ループは自己証明の循環へ退化します。

同化の経路は 3 つあり、どれもありふれたものです。

  • 訓練コーパスの共有。検証器とジェネレータが似た事前学習コーパスで訓練されると、言語的な嗜好、事実の死角、そして「正解らしく見えるもの」についての直感を共有します。同源性が高いほど、スコアの相関は高くなります。そしてスコア相関が高いことこそ、まさに崩壊のシグナルです。
  • アノテーションソースの共有。検証器の学習ラベルの一部がジェネレータの出力に由来する場合(同源蒸留)、学び取るのがジェネレータの判断基準であって、客観的な基準ではありません。
  • 同構造が诱发する偏り。同じアーキテクチャ、同じ訓練レシピ、同一のアライメントデータを使っているだけで、モデルは系統的に「自分スタイル」の出力を過大評価してしまいます。これが自己蒸留が流暢性のタスクで効果がはっきり出て、精度が必要なタスクでは効果が薄い理由です。

同化の診断方法は直接的です。検証器のスコアとジェネレータ自身の確信度(logprob または自己評価確率)の相関を計算する。この相関が非常に高い、つまり spearman 係数が 0.8 を超えるなら、検証器がもたらす情報量はほぼゼロで、ループは実質的にモデルが自分で採点していることになります。健全な検証器は、難問は実際に難しいという理由から、ジェネレータの確信度と中程度の相関をもつべきです。ただしほぼ完全に一致していてはいけません。敵対的な検証器は、この不一致を意図的に作ります。モデルが自信があるところでたまに誤判定し、モデルが迷っているところでたまに正解を出すべきです。

実用的な判定基準をひとつ。生成モデルを入れ替えて検証器を保ったとき、スコアの順位がほぼ変わらないなら、その検証器は真に独立しています。生成モデルを入れ替えたあとにスコアが大きく変わるなら、検証器が測っているのは主に「この生成器に似ているかどうか」だということです。

3.5 金標準の正当性:判定信号から来るもので、生成者から来るものではない

3.1 から 3.4 までを一つの原則にまとめると、こうなります。

サンプルが訓練の金標準になりうるかどうかは、どのモデルが生成したかではなく、どの信号で判定されたかで決まります。

この原則には強い実践的な含意があります。同じ「モデル生成の連鎖」でも、ユニットテストを通ればそれは高価値な金標準で、単に「それらしく見える」だけなら有害なノイズです。生成モデルの身元が影響するのは先験的な品質(すなわち通過の難易度分布)であって、金標準の正当性ではありません。この 2 つを混同すると、典型的な資源配分の誤りが 2 つ起こります。「もっと大きなモデルで生成する」ことに過度に投資して検証器の品質を無視するか、「より弱い検証器で絞り込む」ことに妥協してノイズを訓練集合に流し込むかです。

予算の制約があるなら、優先的に投資すべきはほぼ常に検証器であって、ジェネレータではありません。生成能力は通常もっとも調達しやすいものです(モデルを差し替える、推論予算を増やすだけでよい)。真に希少で、成否を左右するのは検証能力のほうです。

四、品質と多様性のバランス:量化でき、調整できる

4.1 品質は 1 つのスコアではなく 3 つの次元

実践でもっともよくある誤りは、単一の品質スコアでデータを絞り込むことです。たとえば検証器のスコアが高い順に top 10% を取る、というものです。この戦略は直感的に妥当に見えますが、実際には系統的に多様性を削ぎ落とします。高品質なスコアは「もっとも典型的で、もっともよくある」種類のサンプルに強く偏っているため、スコア順に並べることは本質的に分布の中心を繰り返しサンプリングしていることになります。

正しいやり方は、問題を少なくとも 3 つの独立した次元に分解し、それぞれを測定し、それぞれに閾値を設定することです。

  • 正確性(correctness):結論が独立した信号で検証を通ったか。ハード制約であり、通らなければ捨てる、妥協の余地はありません。この次元は「正しいかどうか」に対応します。
  • 完全性(completeness):すべての制約を満たしているか。フォーマット、長さの上限下限、必須要素、引用が解決可能か、コードが実行できて未定義変数がないか。「通っているように見える」データの大部分は、この次元のところで落ちています。とくにフォーマット制約の多いシナリオでは。
  • 情報量(informativeness):そのサンプルがモデルが見たことのないものを持っているか。この次元は多様性の門番であり、もっとも見落とされがちな次元です。

三者は並列の関係ではありません。正確性は参加の敷居、完全性は濾過の網、情報量は並び替えと配分の根拠です。三つを重み付きのスコアにまとめると、敷居の強さが失われます。スコアは低くとも許容できるデータが手遅れで殺されますし、スコアを上げるために無意識にサンプリングが分布の中心へ押しやられるのです。

4.2 多様性の 3 つの測度と、それぞれの失敗モード

多様性は測らなければ、「多様性を上げる」は単なるスローガンにすぎません。よく使われる 3 つの測度はそれぞれ意味があり、それぞれ罠もあります。

4.2.1 語彙面の多様性:安価だが、欺きやすい

もっとも基本的な測度は distinct n-gram 比です。n-gram(bigram や trigram など)が語彙中にどれだけの割合で現れるかを数えます。実装コストは極めて低く、パイプラインに載せてリアルタイム監視に向いています。

ただしその失敗モードは知っておく必要があります。必要な重複を罰してしまうのです。コード、数学、フォーマットされた出力、分野用語が密集するシナリオでは、高品質なデータはもともと大量の固定 token 列を繰り返すべきです。関数名、次元の単位、テンプレートの構造などです。このとき distinct n-gram の低下が退行を一切示唆しないことがあります。したがって正しい使い方は、バケットごとに計算してから、バケット間の差を見ることです。全コーパスの数字をひとつ見るのではありません。同じ指標がコードバケットで 5% 下がり、自然言語バケットで 20% 上がっても、全体は横ばいでも、両者の意味まったく違うのです。

4.2.2 意味面の多様性:主役の測度だが較正が必要

サンプルを埋め込み空間に写してクラスタリングし、クラスタ数、クラスタサイズの分布、各クラスタ内の密集度を求めます。現時点でもっとも実用的な多様性の測度です。そのまま「いくつの異なる問題を覆ったか」に対応するからです。

この測度の失敗モードは、埋め込みモデルが表層の言い換えに過度に反応し、実質的な差には鈍感であることです。同じ問題を別の聞き方をすると「新しい内容」と判定されるかもしれませんし、同じ種類の問題を別の領域背景で出しただけで「重複」と判定されるかもしれません。緩和策は 2 つあります。ひとつは領域適応済みの埋め込みモデルを使うことです(汎用埋め込みモデルはコードや法律など領域によって性能差が大きい)。もうひとつは同義改写判定による少規模の人間較正です。クラスタリング結果の隣接クラスタが本当に異なるかどうかを人に判断させ、それを基準に類似度閾値を調整します。閾値は最も較正が必要なハイパーパラメータであり、固定閾値のまま放置するのはもっともよくある手抜きです。

4.2.3 構造の多様性:もっとも見落とされ、一番効果が高い

構造の多様性とは、タスク形態の違いを指します。推論ステップ数の違い、ツール呼び出しの連鎖の長さの違い、制約の数の違い、入力モダリティの組み合わせの違い、出力フォーマットの違いです。指標には、ツール呼び出し回数の分布、制約条項数の分布、複数ターンの澄清が必要か、入力に含まれるノイズ情報の割合があります。

なぜこの次元の効果が最も高いのか。モデルの能力境界を決めるのは、話題ではなく構造の複雑さだからです。あるモデルは「地理の質問応答」には強い一方「3 回のツール呼び出しを伴う情報統合」には弱い、ということがあり、後者は前者の話題とはまったく無関係です。領域だけでバケット分けするサンプリング戦略では、後者は自然分布に占める割合が小さいまま永遠に訓練されません。

経験的なチェック方法:訓練セットを領域ではなくタスク形態で重新バケット分けし、各バケットのサンプル数と pass rate を見ます。あるバケットの pass rate が長いあいだ 0 に近く、サンプル数も 0 に近いなら、それはモデルが覚えたのではなく、サンプリング戦略がその能力を除外しているだけです。

4.3 重み付き和から制約最適化へ:後者のほうが制御しやすい理由

多様性にもっともよくあるエンジニアリング上の実装は、「品質スコアから多様性のペナルティを引く」方式です。Self-BLEU ペナルティや最大マージン関連(MMR)系の再ランキングに似たものです。この道は使えますが、根本的な欠陥があります。ペナルティ項の重みには解釈可能な単位がないのです。重みを 0.3 から 0.5 に調整すると、効果はなめらかに変わるだけで、「多様性はもう基準を満たした」と教えてくれるポイントはどこにもありません。これはハイパーパラメータの調整を当てのない手探りにさせ、しかもデータセットが変わると脆くなります。

より制御しやすいのは、制約最適化問題として書き直すことです。多様性のハード制約を満たすことを前提に、品質を最大化する。制約はたいていこういう形になります。

maximize    sum_i  quality(s_i) * w_i
subject to  cluster_k:  n_samples(k) <= cap_k          # 每簇上限
            ngram:      distinct_3gram >= tau_3          # 表层多样性下限
            structure:  P(struct_bin=j) <= rho_j         # 结构分布上界
            domain:     coverage(domain_d) >= 1 - eps_d  # 领域覆盖下限
            novelty:    nns_dist(s_i) >= delta_i          # 近邻距离下限

この書き方の実用的な利点はこうです。すべての制約が、独立して監視できるアラーム信号に対応します。cluster の上限という制約に対応するアラームは「単一のクラスタの比率が閾値を超えた」。novelty の制約に対応するアラームは「新しいサンプルから既存集合への最近傍距離の中央値が下がり続けている」というものです。アラームは訓練パイプラインに直接吊り下げられるので、総合スコアには一切依存しません。

強調したいのは「各クラスタの上限」という制約がなぜ重要かです。デフォルトのサンプリング挙動は密度の高い領域へ傾きます。そこに品質スコアが高く、検証通過率が高く、生成コストも安いからです。その結果、データ分布は中心へ崩壊します。明示的なクラスタ上限は、この傾きに対抗するための唯一有効な手段です。クラスタ上限は選択の段階に作用するのに対し、品質指向のメカニズムはすべてスコアの段階に作用するからです。スコアの段階は本質的に下流へ偏るため、独立した制約で均衡をとる必要があります。

4.4 リアルタイムに調整できる 6 つのノブ

ここまでの議論を、訓練ループの中で動的に調整できる 6 つのノブに圧縮します。価値は、毎ラウンド人手による介入を必要とせず、リアルタイムの指標に応じて自動でフィードバックする制御系として作れることです。

  1. サンプリング温度:もっとも基本でもっとも過小評価されている。温度を上げると覆う分布範囲が直接広がり、代償は通過率の低下です。正しい使い方は難度のバケットごとに温度を分けることです。すでに中間にあるバケットには低温で通過率を確保し、境界に近いバケットには高温でより珍しい解法を拾い出します。全域一律の温度では、この段階的な調整はできません。
  2. クラスタごとのサンプリング上限:分布崩壊の速度を直接制御します。上記の制約の項です。
  3. 重複排除の閾値:セマンティック重複排除の類似度閾値。緩すぎると同源だが必要な変種を誤って殺し、緩すぎの代償は長い裾のバケットで特に大きくなります。厳しすぎると実質的に効果がありません。バケットごとに設定し、重複排除で殺された割合を別に集計すべきです。あるバケットの 60% が殺されたなら、そのバケットの生成多様性はすでに問題を起こしています。
  4. 検証の閾値:反復のラウンド数とともに上げていきます。これがもっとも有効な崩壊対策のひとつです。モデルが強くなるにつれて、元々通っていたサンプルが,越来越多的サンプルが、より緩い基準で通るようになるからです。閾値を段階的に絞ることで、新しいデータは古いデータより良くなければならないという要求になり、これはループに坂を一つ足すことに等しくなります。
  5. 履歴データの保持比率:毎ラウンドの還流で前のラウンドのデータを残す比率。低すぎると忘却し、高すぎるとノイズが積み上がって最適化が難しくなります。経験的には 30% から 50% で開始し、下のカリキュラム再生の仕組みと組み合わせます。
  6. 異源データの混合比率:毎ラウンド一定量の、選別に加わらない実データを固定で混ぜます。役割は座標系のアンカーであり、比率自体は非常に小さくても構いません(5% から 10% で足りることがよくあります)。ただし固定で混ぜ続けなければなりません。「このラウンドは実データの成績が悪かった」という理由で臨時の引き下げをしてはいけません。もっとも必要とされる場面がまさにそこだからです。

4.5 不均衡を検出する実用ツール

以下のコードはデータを生成せず、診断だけを行います。設計目的は、崩壊が不可逆になる前に信号を発することです。具体的には「品質は上がり、多様性は落ちる」という組み合わせの特徴を識別することです。これが崩壊にもっとも典型的な初期形態だからです。

import math
from collections import Counter, defaultdict
from dataclasses import dataclass

@dataclass
class DiversitySnapshot:
    round_id: int
    pass_rate: float
    ngram_3: float              # distinct 3-gram 比
    n_clusters: int
    top_cluster_share: float    # 最大簇占比
    median_nns: float           # 到最近邻的中位距离
    struct_entropy: float       # 结构桶分布的香农熵

def shannon_entropy(counts) -> float:
    total = sum(counts)
    if total == 0:
        return 0.0
    h = 0.0
    for c in counts:
        if c <= 0:
            continue
        p = c / total
        h -= p * math.log(p)
    return h

def detect_collapse(history: list[DiversitySnapshot],
                    ngram_floor: float = 0.35,
                    cluster_floor: int = 40,
                    entropy_floor: float = 1.10) -> list[str]:
    """在塌缩不可逆之前报警。判据是组合式的,避免单指标误报。"""
    alerts: list[str] = []
    if len(history) < 3:
        return alerts

    cur, prev = history[-1], history[-2]

    # 模式一:质量上升的同时多样性下降 —— 塌缩最典型的形态
    if cur.pass_rate > prev.pass_rate + 0.02:
        if cur.ngram_3 < prev.ngram_3 or cur.median_nns < prev.median_nns:
            alerts.append(
                f"round {cur.round_id}: 通过率上升而近邻距离下降,"
                f"疑似向高密度区过度集中")

    # 模式二:表层多样性触底
    if cur.ngram_3 < ngram_floor:
        alerts.append(f"round {cur.round_id}: 3-gram 多样性 {cur.ngram_3:.3f} 低于下限")

    # 模式三:覆盖范围收缩
    if cur.n_clusters < cluster_floor:
        alerts.append(f"round {cur.round_id}: 语义簇数 {cur.n_clusters} 低于下限")
    if cur.top_cluster_share > 0.45:
        alerts.append(
            f"round {cur.round_id}: 单簇占比 {cur.top_cluster_share:.2f} 过高")

    # 模式四:任务形态分布变窄 —— 最容易被忽略但最严重
    if cur.struct_entropy < entropy_floor:
        alerts.append(
            f"round {cur.round_id}: 结构熵 {cur.struct_entropy:.2f} 过低,"
            f"复杂任务形态正在被采样策略排除")

    # 模式五:多样性已经连续三轮单调下降
    tail = history[-3:]
    if len(tail) == 3 and all(tail[i].n_clusters > tail[i + 1].n_clusters
                             for i in range(2)):
        alerts.append(
            f"round {cur.round_id}: 语义簇数连续三轮下降,处于不可逆塌缩风险区")

    return alerts

def suggested_next_params(snap: DiversitySnapshot) -> dict:
    """根据快照给出下一轮的旋钮建议。这是一个粗粒度的规则控制器。"""
    advice = {"temperature_delta": 0.0, "cluster_cap_delta": 0,
              "verify_threshold_delta": 0.0, "real_data_inject": 0.08}

    if snap.top_cluster_share > 0.40:
        advice["temperature_delta"] += 0.15
        advice["cluster_cap_delta"] -= 0.10
    if snap.struct_entropy < 1.20:
        # 结构熵低说明高难度复杂形态没被采到,需要提高温度并定向补采
        advice["temperature_delta"] += 0.10
        advice["real_data_inject"] += 0.04
    if snap.ngram_3 < 0.40:
        # 表层多样性低但通过率可能仍高,此时应收紧质量阈值逼出更难的题
        advice["verify_threshold_delta"] += 0.02
    if snap.pass_rate > 0.80:
        # 整体太轻松,学习前沿已经上移,需要更激进的边界探测
        advice["temperature_delta"] += 0.10
        advice["verify_threshold_delta"] += 0.03

    advice["temperature_delta"] = min(0.4, max(-0.2, advice["temperature_delta"]))
    advice["cluster_cap_delta"] = min(-0.05, max(-0.30, advice["cluster_cap_delta"]))
    advice["verify_threshold_delta"] = min(0.10, max(-0.05,
                                        advice["verify_threshold_delta"]))
    advice["real_data_inject"] = min(0.20, advice["real_data_inject"])
    return advice

この診断レイヤーは、問題が起きてからとりあえず組むのではなく、パイプラインの常駐コンポーネントとして置くべきです。価値は、「崩壊」という事後説明が必要な曖昧な現象を、数値が常に出て推移が見え、アラームと対で紐付けできるエンジニアリング指標に変えることにあります。上のアラーム規則のうち「構造エントロピー」と「最近傍距離」の 2 つは、優先して実装する価値があります。崩壊に対する感度を通過率や n-gram 比よりはるかに高いからです。それらは「何を覆ったか」を直接測っているのです。「いくつの言い方を覆ったか」ではありません。

五、難易度のカリキュラム設計:データをモデルに適応させる

5.1 固定の難易度セットはなぜ必ず壊れるのか

第 2 節で示した「学習可能なフロンティア」という原理には、直接的な帰結がひとつあります。難易度とはタスクに固有の属性ではなく、タスクが現在のモデルに対して持つ能力属性であるということです。同じ問題でも v1 のモデルではフロンティア上にあったのが、v3 のモデルではもう卒業しています。つまり 固定の難易度を持つ prompt プールであっても、数ラウンド回したうちに效力を失います。プールそのものが劣化したからではなく、モデルが追いついてしまったからです。

もっと隠れた失效モードが「勾配の希釈」です。すでに完全に習得された領域が訓練予算を占め続けると、その領域は有用な勾配を生むだけでなく、同じ batch 内の他のサンプルが取るべき取り分まで奪い去ります。実際に観測されるのは、訓練ロスの低下が遅くなり、忘却が深刻になり、最終的な収束点が本来なら到達しえた水準より下になる、という事態です。これは「自然な難易度分布」でサンプリングするパイプラインでは特に頻出です。自然な分布の中には大量の簡単なサンプルがありつづけ、それが絶えず大量に流れ込むからです。

5.2 難易度の見積もりとそのコスト

タスクに難易度スコアを付けるには計測が要りますが、シーンによって許容できる計測コストの差は大きいです。コストの低い順に並べると、次のようになります。

5.2.1 静的なプロキシ指標:コストはほぼゼロ。ただし粗い選別にしか使えない

モデルを走らせなくても計算できる特徴で一次篩いを選びます。token 長、ネストの階層数、制約条項の数、涉及到るエンティティの数、必要な推論ステップ数の推定(貪欲デコードを 1 回だけ走らせてステップを数える)、ツール呼び出しチェーンの静的解析結果などです。

この手法の価値は精度のほうにはなく、生成の前に prompt を層別化して、高価な検証予算を,看起来価値がありそうな区間に集中させられることにあります。実践的に合理なワークフローはこうです。静的指標で 3〜4 段階に粗く振り分け、それぞれについて小規模なプローブだけを行い、その結果を見てから以降の投入量を決める。単独では足りません。静的指標と実際の難易度の相関は中程度にすぎず——短くてもきわめて難しい問題が存在するからです。

5.2.2 能動的なプローブ:計測コストを情報量の多い場所に集中させる

各問題に対して k 回のサンプリング(k は 4 から 8)を行い、合格率を実測して経験的難易度を得ます。これは費用対効果がもっとも高い方式であり、第 2 節のコードが採用していたのもこの方式です。k が小さすぎると難易度の推定の分散が大きくなりすぎます(k=4 では合格率の信頼区間が非常に広い)。大きすぎるとコストが線形に増えます。折衷案は2 段階プローブです。まず k=2 で明らかに両端にあるもの(全問正解/全問不正)を手早く除外し、残った問題について k=8 まで追加サンプリングします。そうすれば測定精度は難易度の中間帯に集まり、縁のほうは粗い判定で十分になります。そもそも縁とは「すでに労力を割く必要がなくなった」領域だからです。まさにそこです。

5.2.3 交差モデルプローブ:外部の難易度ベンチマークで基準を置く

別のモデル(異なるアーキテクチャか、別の世代)を使って同一の問題群を測り、難易度の並びを突き合わせます。ある問題の難易度が 2 つのモデルで大きく違うなら、その問題はモデル固有の能力パターンを依拠している可能性があります。この種の問題は単一モデルでの訓練価値が高いとは限らないものの、印をつけて別に観察しておく価値はあります。逆に、2 つのモデルとも難しい問題はもともと本当に難しい汎用の難問であり、重点的に投資する価値があります。

この手法の追加的な利点は、「特定のモデルの弱点に過学習した」問題を自然に除去できることです。この種の問題は単一モデルプローブの下では有用に見えます(ちょうど境界上に載っているため)が、実際には訓練しても強化されるのはそのモデルがすでに持つ特定の偏りだけで、汎化への寄与は限定的です。

5.2.4 学習曲線の外挿:精度はもっとも高いが、コストももっとも高い

高価値の問題の一部を少数選んで、複数ラウンドの訓練を通じた合格率の変化を追跡し、学習曲線をフィットします。その外挿から「あと何ラウンドで卒業するか」を判断します。これなら精密な予算配分が出せます——まもなく卒業する問題を早めて重みを下げ、新たに昇格した問題の重みを上げる。代償は、固定した問題群を長期追跡し続ける必要があること、そしてフィット手法そのものにノイズがあることです。パイプラインが安定して走り、精细なチューニングに入る段階で導入するのが適当で、前の段階には向きません。

5.3 カリキュラム設計の三つの原則

難易度の計測と訓練の順序を組み合わせるうえで、明示しておく価値のある原則が三つあります。

原則一:カリキュラムの主な利得は「易しい→難しいへ」だけではなく、予算をフロンティアに集めることにあります。従来のカリキュラム学習は順序を強調します——易しい先に難しいと置くと収束が速くなる。しかし合成データの場面でもっとも重要な利得の源は、予算の絶え間ない再配分です。線形に上昇するカリキュラムは一度きりの順序上の利得しか生みませんが、動的なフロンティア追跡は毎ラウンド正しい予算配分を提供します。実装の実際に言えば、後者のほうがむしろ簡単です。事前に決めた難易度の階段は不要で、毎ラウンドバケットを振り直して、その結果に合わせてサンプリング比率を調整すればよいだけです。

原則二:難易度は層的に扱う必要があり、一軸では足りません。複雑さをいくつかの直交した次元に分解して、それぞれを追跡します。推論の深さ(ステップ数)、制約の密度(同時に満たすべき条件の数)、知識の広さ(何個の異なる領域にまたがるか)、ツールチェーンの長さ(やり取りのラウンド数)、干渉の強さ(入力にいくつの無関係な情報が含まれるか)。モデルは推論の深さは足りていても、ツールチェーンの扱いがまったく下手という場合があります。たった一つの総合難易度スコアでカリキュラムを組むと、後者のケースが平均化されて、针对性的訓練を一度も受け取らないまま放置されます。

原則三:卒業は捨てることではなく、重みを下げて残すことです。すでに習得済みの領域については、サンプル数をかなり低いながら 0 ではない水準(通常は全体の 2% から 5%)まで下げ、その水準を保つべきです。ゼロまで下げると破滅的忘却が起こります。合成データの生成・訓練のループそのものが忘却を加速するのは、データの分布がもともとモデルの能力圏で限られており、忘れた部分には自然に補充される経路がないからです。低い非ゼロの水準に留める役割は、予算を消費せずに継続的に refreshed として扱いつつ、その領域の状態を保つことです。

5.4 カリキュラム実装でよくある三つの落とし穴

  • 難易度ラベルの遅延性。難易度は前のラウンドの checkpoint を基準に測っているため、このラウンドの訓練が終わるとすべてのラベルが低めになります。修正方法は、バケットの閾値を経験的係数だけ引き上げてやること(「習得済み」の合格率閾値を 0.95 から 0.90 まで下げる)、あるいは毎ラウンドの初めに高速な再測定を一度走らせることです。ここを無視すると、カリキュラムの実際のペースが予想よりはるかに速くなり、モデルがまだ準備できていない段階で難易度段が切り替わってしまいます。
  • 難易度層別のリーク。同じタスクの別バリアントが訓練集合と検証集合に分かれた場合、難易度の統計が汚染され、訓練指標が水増しされます。バケット分けはバリアント生成の前に済ませ、サンプル単位ではなくタスク ID 単位で分校します。
  • フロンティアの過剰追求による訓練の不安定化。合格率 10% の領域にほとんどの予算を置くと、勾配ノイズが非常に大きくなり、訓練が発散するか収束しない可能性があります。妥当な構成は、「フロンティアのバケット」が主要的だが圧倒的ではないシェア(たとえば 40% から 50%)を占め、さらに 30% から 40% をすでに習得済みのバケットに残すことです(これらのサンプルの勾配は小さい同时也分散も小さいので、安定剤として働きます)。残りを境界外の探索に回します。安定剤の役割はしばしば過小評価されています——勾配ノイズが多い訓練の後期には、スコアが持続的に落ち込みます。

六、ドメインと難易度によるバケットサンプリング:配分を勘で決めるから計算できる形へ

6.1 バケットの二つの軸:ドメインと難易度は同一視できない

ドメインとはタスクの主題(法律、医療、コード、顧客サポート)で、難易度とはタスクの複雑さです。この二つは直交していますが、実践ではよくまとめて扱われてきました。もっとも一般的なのは「ドメインでバケットを分け、その中は合格率で並べて top を取る」という手法です。このやり方が抱えている問題はこうです。あるドメインが全体として難しすぎる場合、top を取ることは全件を取るのと同じになります。ドメイン全体が簡単すぎる場合は、top の中身の情報量はゼロです。ドメイン内の難易度差が、バケットの境界線に食われてしまいます。

正しいやり方は二次元バケットです。まずドメインで切り、その内側を難易度で切ります。バケットの粒度は十分細かくし、「ドメイン × 難易度」の組み合わせにおけるサンプル数が、統計的な判断を行うのに足りるようにします。おおよその規模の目安は、バケットごとに有効サンプルを最低でも 200 から 500 件確保することです。そうしないと、バケット内の合格率推定の信頼区間が、サンプリング判断の指針として使い物にならない幅まで広がります。

6.2 温度化リサンプリング:なぜ「不自然な」分布を使うのか

もっとも素直な配分戦略は「実ビジネス分布に沿ってサンプリングする」ことです。この戦略は最初のラウンドでは正しい答えです——モデルにまず実分布になじませるからです。しかし致命的な問題がひとつあります。実分布では、高価値領域のサンプル量が学習を支えるには足りないことが多い。実ビジネスの中で、三回以上のツール呼び出しを必要とするチケットは全体の 3% しかありません。モデルはそれを永遠に学びません。一方、60% は一言の単純な問い合わせで、モデルが 100 回学んでもほぼ伸びません。

有効な手法が温度化リサンプリングです。バケット b の実比率を p_b、実際のサンプリング比率を q_b とすると、次のようになります。

q_b  proportional to  p_b ** (1 / tau)        # tau < 1 时更均匀,tau > 1 时更集中

# tau = 1.0 时,采样分布等于真实分布
# tau = 0.5 时,q_b proportional to p_b^2,长尾桶被进一步压缩
# tau = 2.0 时,q_b proportional to sqrt(p_b),长尾桶被显著放大
# tau -> inf 时,趋于均匀分布,所有桶被平均对待

import math

def tempered_sampling_weights(bucket_stats, tau: float) -> dict:
    """bucket_stats: {bucket: {"prior": 真实占比, "value": 训练价值分}}"""
    num = {}
    for b, s in bucket_stats.items():
        prior = max(s["prior"], 1e-9)
        num[b] = prior ** (1.0 / tau)
    total = sum(num.values())
    return {b: v / total for b, v in num.items()}


def value_adjusted_weights(bucket_stats, tau: float,
                           kl_cap: float = 0.35) -> dict:
    """在温度化之后再按训练价值调整,并用 KL 约束限制偏离真实分布的程度。

    纯价值驱动的分配会产出不自然的分布(某些桶被完全排除),
    部署时出现 OOD 崩塌。KL cap 是这个问题的安全阀。
    """
    base = tempered_sampling_weights(bucket_stats, tau)

    # 价值分归一化到 [0.5, 1.5],避免价值项完全压过先验
    vals = [s["value"] for s in bucket_stats.values()]
    lo, hi = min(vals), max(vals)
    span = (hi - lo) or 1.0

    raw = {}
    for b, w in base.items():
        v = bucket_stats[b]["value"]
        v_norm = 0.5 + (v - lo) / span          # [0.5, 1.5]
        raw[b] = w * v_norm
    total = sum(raw.values())
    adjusted = {b: v / total for b, v in raw.items()}

    # 逐步向真实分布回拉,直到 KL 低于上限
    for b in list(adjusted):
        adjusted[b] = 0.5 * adjusted[b] + 0.5 * bucket_stats[b]["prior"]
        t = sum(adjusted.values())
        adjusted = {k: v / t for k, v in adjusted.items()}
        kl = sum(p * math.log(p / q)
                 for p, q in zip(adjusted.values(), base.values()) if p > 0)
        if kl <= kl_cap:
            break

    return adjusted

この KL 制約は、単独に説明しておく価値があります。純粋に価値駆動で決める配分は、一見して妥当に見える一方で、実際には危険な分布を生み出します。低い価値のバケットを、自然にゼロ近くまで押し下げてしまうからです。しかし低い価値のバケットこそ、モデルが実ビジネスの中で必ず遭遇する領域であり、「訓練では見ていないから成績が悪い」のに該当する部分です。これは評価の段階では露見しません(評価セットもまた訓練分布の近似图片来自来ます)。露見するのはデプロイのあとです。KL 上限は、分配結果が実分布から離れすぎないように強制し、このリスクを明示的に管理するための仕組みです。

6.3 階層化バッチ:グローバル配分より効く手法

グローバル配分(データセット全体の構成を決めるもの)は正しい階層ですが、それだけでは不十分です。理由は勾配推定の分散にあります。グローバル比率が、あるバケットに対して 30% であっても、そのバケットのサンプルが mini-batch ごとに現れるのは依然としてランダムです。高分散になるため、そのバケットの学習信号がノイズに埋もれてしまいます。

階層化バッチ(stratified batching)の考え方は、batch の内部で配分を満たすようにすることです。たとえば 256 件の batch なら、「コード × 高難易度」から 40 件、「カスタマーサポート × 中難易度」から 30 件、という具合に固定で確保します。残りの枠は、サンプリングでランダムに引いた分を充てます。

この手法の利点は二重です。勾配の分散を下げるとともに、もうひとつ目立たない問題を消してくれるからです——batch レベルの分布シフトです。ランダムにまとめて batch を作る設定では、モデルはいくつかの batch で偏った分布を見て、それに適応しようとします。それ自体がノイズの源です。階層化バッチはこのノイズを取り除きます。

階層化バッチの代償は、事前に桶の列挙を決めておく必要があり、各 batch を構成する必要があることです。この実装コストは通常過大評価されています——本質的に「定員で切り分けてからシャッフルするだけ」であり、batch の構成コストは現代のデータローダでは無視できる水準です。本当に注意すべきなのは、バケットの数が多すぎないことです。そうしないと、1 つのバケットから batch に含まれるサンプル数が不足し、統計的な意味をなさなくなります。経験的には、稼働するバケットは 10 から 30 個程度に収めます。

6.4 配分の動的調整:静的な表からコントローラへ

静的な配分表は最初のラウンドの終了とともに失効します。モデルの能力の変化を反映していないからです。妥当なループは、毎ラウンド次のシグナルに従ってバケットの重みを調整するはずです。

  • 合格率。合格率が 0 に近づいたバケットは、現在の難易度が想定を超過していることを意味し、重みを下げるとともに再プローブを組み立て直すべきです。合格率が長期間 1 に近いバケットは、リプレイ水準まで重みを落とします。
  • サンプル数と限界収益。同じバケットに連続してサンプルを追加しても、実 held-out のスコアが改善しなくなったなら、そのバケットは飽和しており、予算を移すべきです。この「限界収益」シグナルは、合格率よりも配分の判断に有効です。現在の状態ではなく、投入に対する産出の比を直接測っているからです。
  • 構造カバレッジのギャップ。あるバケットの限界収益がゼロでも、対応するタスク形態が能力プロファイル全体で空白なら、最低限のサンプル数を確保してサンプリングを継続すべきです。能力プロファイルの空白は、オフラインではもっとも多く、テストによって検出できないからです。

この三つを調整ルールとして書き下すとき、強調しておく価値のある原則がひとつあります。重みを下げるのは上げるより簡単ですが、ボトルネックは重みを上げる側にあるというものです。システムは本質的に、すでに成果の出ているバケットへ傾きます(そこで利得が見えるため)。その結果、高価値だが難しいバケットは長らく予算を得られません。対策は「突破を待つバケット」に底の配分を保証することです。現在の成績ではなく過去の成績によって底を決め、そうすることで局所解に陥ることを避けられます。これは本質的に探索項を持つ multi-armed bandit 問題であり、合成データパイプラインは本質的に bandit の決定を行っています。ただし、多くの実装は貪欲法に退行しているだけです。

七、エラー注入と敵対的サンプル:モデルの盲点を意図的に作る

7.1 なぜ錯誤は自分で作らなければならないのか

ここまでの各節に隠されていた前提は「検証器が正しいものを選べる」というものです。この節では逆向きを扱います。モデルの誤りを体系的に収集し、分類し、拡大して、訓練資産に変えます。

受動的に誤りを集める(検証でモデルが失敗するのを待って、その失敗サンプルを見る)には根本的な欠陥があります。集められるのはモデルの現在の能力範囲内の誤り、つまりモデルがすでに自分のできないと知っている種類のものに限られるということです。本当にもっとも危険なのは、モデルが自分のできないことを知らない種類の誤りです——自信を持ち、流れるように、そして誤ったまま答えを出してしまいます。検証器もそれが誤りだと判定できないことがある(誤りそのものが筋が通っているため)、あるいは緩いチェックをうっかり通ってしまうからです。

したがって、敵対的サンプルは能動的に構成しなければなりません。まず「モデルはどこでミスをしうるのか、なぜミスをするのか、そのミスがどんな形か」を整理し、そしてそれに沿ってサンプルを作ります。本質的には、security と robustness の領域で何年分か積み重ねてきた red team の方法論を、汎用能力の訓練に移すということです。

7.2 構成原則:最小編集距離 + 単一の制御変数

敵対的サンプルを構成するとき、もっとも重要なエンジニアリング原則はひとつあります。毎回ひとつだけ制御変数を 바꾸るということです。1 回の編集で複数の変化(言い換え + 数値の変更 + 制約の変更)を導入すると、「モデルが間違えた」サンプルは得られますが、どの変化が原因なのかが分かりません。訓練信号が曖昧になってしまいます。

具体的には、次のようにします。

  • 元の問題はそのままにして、答えだけを変える。自信に満ちた誤った解答を生成し、識別能力を訓練します。
  • 元の問題はそのままにして、邪魔な条件をひとつだけ加える。たとえば「もし X が成り立たなければどうするか」を加える、あるいはすでに与えられた条件を矛盾するものに変更する、ということです。
  • 答えはそのままにして、問題を変更して答えを無効にする。この種のサンプルはとくに価値があります。モデルが入力条件にどれだけ敏感であるかを訓練するからです。
  • 数値かエンティティをひとつだけ変える。「摂動テスト」の考え方です。問題文にある数値をひとつ変更して、モデルの答えがそれに伴って変わるかを見ます。変わらないもの(もともと正しい答えであっても)は、答えが誤った位置にアンカーされていることを意味しており、これは非常に見つけにくい失敗モードです。

コードの領域では、最小編集距離という条件はとくに重要で、かつとくに破られやすいものです。「バグのあるコード」を生成するとき、モデルに自由に書かせると、スタイルも構造もまったく違う誤ったコードを書き戻してきます——この種のサンプルは情報をほとんど提供しません。正しいコードと共有する構造がまったくないからです。正しいやり方は、正しいコードを出発点として、一点だけを変更することです(演算子をひとつ変える、境界検査をひとつ削除する、off-by-one をひとつ入れる)。こうすれば、モデルはその一点を正確に指摘しなければなりません。

7.3 五つの構成手法とその適用場面

7.3.1 変異型:制御された摂動で、安定性をテストする

手法:意味は変えず、表現・語順・冗長な情報・形式を変えます。用途は、表面的な変化に対するモデルのロバスト性を訓練し、表面的な特徴ではなく意味を学ばせることを防ぐことです。

この方向の合成データには、隠れた悪影響がひとつあります。過剰に訓練すると、モデルは入力の変化に対して過度に鈍感になるというものです。これは精密さが要するタスクでは有害です(たとえば「A 類のみ」を「B 類のみ」に変えるとき、モデルはこれらの修飾語を無視することを学んでしまう)。したがって変異型のデータの比率は慎重に制御する必要があり、変異の階層も区別しなければなりません——文の構造の変異は良く、条件の変異は悪い、ということです。

7.3.2 矛盾の注入:衝突の検出力をテストする

手法:それ自体は自己完結している問題を出題し、その問題文に前提と矛盾する条件を仕込む、あるいは互いに矛盾する二つの要件を入れます。用途は、充足不可能な入力を認識し、その理由を説明できるようにモデルを訓練することです。無理に答えを捻り出して返すのではなく。

この種のデータは実ビジネスにおいて非常に価値が高いです。ユーザーの入力は本来就矛盾や自己矛盾した要件に満ちているからです。「この二つの要件は同時に満たせない」と認識できるアシスタントは、いつも片方の条件を無理に適用するアシスタントよりはるかに役立ちます。そしてこの能力は正例データからは学びにくいのです。构成された正例には「矛盾を検出する」という振る舞いパターンがほとんど存在しません。正例サンプルはどれも解けるものだからです。

7.3.3 欠陥の注入:誤りの検出力をテストする

手法:特定の種類の欠陥を注入し、モデルにその位置を特定させ、修正させます。用途は critique 能力と誤りの位置特定能力の訓練です。適用領域:コード(ログ、計算、ロジックの欠陥)、データ(一貫性、外れ値)、ドキュメント(事実誤り、論理の断裂、引用の失效)、設計(手順の欠落、制約への違反)。

この分類は高い検証性を実現できます。コードの欠陥はテストケースで修正できたかを判定でき、データの欠陥は検証ルールで判定でき、ドキュメントの欠陥は事実確認で判定できます。正方向の生成と比べて、欠陥注入の検証コストは通常は低くなります。問題を二値の判定に変換できているからです。これが他の分類よりも工業化しやすい理由です。

7.3.4 分布外の構成:汎化の境界をテストする

手法:既知の領域の問題を領域横断で移行させる(法律の条文をバイオ医療に使う、数学的構造を言語学の問題に用いる)、あるいは新しく現れた概念/用語/形式を用います。用途は、モデルが未知の入力に遭遇したとき、誤った事前知識を持ち込まないように訓練することです。

重要な要件はひとつです。領域横断の移行は、タスクの内在的な構造を保持し、表面的な領域だけを変える必要があります。構造まで変えてしまうと、テストされるのはまったく別の能力になります。この構成の判定基準は「人間の専門家がこれは妥当な問題だと考えるか」です。したがってこの分類は他の分類よりも品質的要求が高く、大規模生成もより困難です。

7.3.5 越境と拒否:境界の識別力をテストする

手法:拒否すべきリクエストを構成します。能力の範囲を超えているもの、有害な内容に関わるもの、存在しない引用の捏造を求めるもの、機密情報の取り扱いを求めるもの。用途は、正しい拒否と紹介(転送)動作を訓練することです。

この種のデータには特殊な要件があります。精度が非常に高くなければなりません。拒否データの誤ラベルは双方向にコストを持ちます。答えるべきものを拒否とラベル付けすれば、モデルは無能で、かつ人に嫌われるようになります。拒否すべきものに応答とラベル付けすれば、それはそのままセキュリティ問題です。しかもこの二種類の誤りは、自動検証ではいずれも発見しにくい。実践上の対処はこうです。この種のデータの生成は規模化しなくても構いませんが(数千から数万件あれば通常は十分です)、人手によるレビューの比率はほぼ 100% にする必要があります。合成データのうち、全自動パイプラインに最も適さない分類です。

7.4 負例サンプルの使い方:ゴミとして捨ててはいけない

負例サンプルの訓練上の用法は正例よりも設計がさらに必要なのです。互いに両立しない複数の使い方があり、混用すると直接的に有害だからです。

  • 負例サンプルを SFT の負のラベルに使わない。誤った答えを SFT に食わせて「これは誤り」とラベル付けすると、多くの実装で勾配が汚染されます。SFT に「負のラベル」という概念はありません。
  • 負例サンプルの正しい使い方は選好ペアを構成することです。同じ問題に対する正負のペアを選好最適化に使い、あるいは critiques を使って修正目標を構築します。
  • 検証器/判別器の訓練に使う。これは深刻に過小評価されている用途です。合成パイプラインの検証器には訓練データが必要ですが、手元には、失敗の種別がラベル付けされた負例サンプルが大量に余っている——これは手元だけが持つ、他者が手に入れないデータの優位性です。これを判別器の訓練に使えば、一般的な選好データで訓練するよりもはるかに有効です。
  • critique 能力の訓練に使う。RLVR フレームワークの下では、負例サンプルは廃棄される廃材ではなく、報酬信号の主要な担い手です。

経験的には、負例サンプルの長期的価値は、同じ由来の少量の正例サンプルを上回ることがよくあります。正例サンプルはモデルに特定の問題を解けることを教えるが、負例サンプルはモデルに「この種類の誤りは自分が犯すかもしれないもの」という認識を与える。後者のほうが汎化への寄与は大きくなります。

7.5 敵対的サンプルの構成とペアリングの実装

以下のコードは「最小編集 + 単一の制御変数」という構成の考え方を実装し、正負のペアリングも行います。変更操作を明示的な追跡可能な記録として意図的に残しているのは、敵対的サンプルの解釈可能性が、それが人手レビューできるかどうかをそのまま決定するためです——そしてこの種のデータは、まさに人手レビューがもっとも必要なデータなのです。

import difflib
import random
from dataclasses import dataclass, field

# 受控编辑操作。每一个操作只改一个语义维度,便于归因。
EDIT_OPS = {
    "negate_condition":   "翻转题目中的一个条件约束",
    "swap_entity":        "把关键实体替换为同类但不同的实体",
    "perturb_number":     "对某个数值做局部扰动",
    "add_contradiction":  "加入与既有前提冲突的条件",
    "weaken_constraint":  "把一个硬约束改成软约束",
    "remove_prerequisite": "删除推理所需的前置条件",
}

@dataclass
class AdversarialPair:
    task_id: str
    domain: str
    good_question: str
    good_answer: str
    bad_question: str
    bad_answer: str
    injected_defect: str = ""     # 注入的缺陷类型
    edit_distance: float = 0.0    # 编辑距离占原文长度的比例
    control_ops: list = field(default_factory=list)  # 对照组操作

def make_adversarial(task_id, domain, question, answer, defect_type,
                     rng: random.Random) -> AdversarialPair:
    """从一条正确样本派生一条对抗样本。

    关键约束:只做一次受控编辑,且编辑后的样本必须仍然
    "看起来像一个合理的问题" —— 否则模型只需识别题面异常,
    训练信号就浪费了。
    """
    if defect_type not in EDIT_OPS:
        raise ValueError(f"unknown defect type: {defect_type}")

    # 这一步需要真实的编辑算子实现;此处保留接口与约束校验
    bad_q, bad_a = _apply_edit(question, answer, defect_type, rng)

    dist = difflib.SequenceMatcher(None, question, bad_q).ratio()
    edit_distance = 1.0 - dist

    pair = AdversarialPair(
        task_id=task_id,
        domain=domain,
        good_question=question,
        good_answer=answer,
        bad_question=bad_q,
        bad_answer=bad_a,
        injected_defect=defect_type,
        edit_distance=edit_distance,
    )

    # 编辑距离过大说明改得太狠,模型只需靠表面异常识别,不提供信息量
    if edit_distance > 0.35:
        pair.injected_defect = "TOO_LARGE_EDIT"
    return pair


def audit_pairs(pairs: list[AdversarialPair],
                human_label_fn=None) -> dict:
    """上线前的质量闸门。人工标注函数存在时用它做二次过滤。"""
    stats = {
        "total": len(pairs),
        "too_large_edit": 0,
        "defect_hist": {},
        "domain_hist": {},
    }
    good_pairs = []

    for p in pairs:
        stats["defect_hist"][p.injected_defect] = \
            stats["defect_hist"].get(p.injected_defect, 0) + 1
        stats["domain_hist"][p.domain] = stats["domain_hist"].get(p.domain, 0) + 1

        if p.injected_defect == "TOO_LARGE_EDIT":
            stats["too_large_edit"] += 1
            continue

        if human_label_fn is not None:
            verdict = human_label_fn(p)
            # verdict: "valid" | "defect_not_real" | "question_broken"
            if verdict != "valid":
                p.injected_defect = f"REJECTED_{verdict}"
                continue

        good_pairs.append(p)

    # 缺陷类型分布检查:任何单一缺陷占比过高说明生成有偏
    if good_pairs:
        hist = {}
        for p in good_pairs:
            hist[p.injected_defect] = hist.get(p.injected_defect, 0) + 1
        n = len(good_pairs)
        top_defect, top_cnt = max(hist.items(), key=lambda kv: kv[1])
        if top_cnt / n > 0.5:
            print(f"[WARN] 单一缺陷类型 {top_defect} 占比 {top_cnt/n:.0%},"
                  f"对抗数据存在构造偏差")

    return stats, good_pairs

このコードでもっとも強調すべきなのは audit_pairs にある二つのゲートです。第一のゲートは編集距離のゲートです。「変更しすぎた」サンプルを遮断します。この種のサンプルの害は、モデルに近道を教えてしまう 것입니다——問題文の異常を識別するのではなく、推論の欠陥を識別することです。モデルはその結果、敵対テストではわずかに良い性能を出しますが、本物の攻撃に対してはまったく防御がありません。第二のゲートは人手ラベルのゲートです。defect_not_real は、注入した欠陥が実際には欠陥になっていないことを意味します(たとえば元の答えが新しい条件下でも成立してしまう)。この種のサンプルが訓練集合に残っていれば、それは明確な誤ったラベルです。

認めるべきですが、この二つのゲートはいずれも高価です。したがって、敵対的サンプルの構成は全自動・大規模生産のルートを取るのに適していません。正しい形態は小ロット、強いレビュー、欠陥タイプごとに的を絞って穴埋めです。実際の失敗事例をひとつ使って「どの種類の欠陥が必要か」を定義し、その種類に集中して生成します。この「必要に応じて構成する方法」の効率は、盲目に大規模生成するよりずっと高いのです。

八、合成データと実データの混合比率

8.1 三つの段階、それぞれ比率がまったく違う

普遍的な最適比率など存在しません。最適な比率の値は訓練の段階ごとに激しく変化します。これを定数として扱うのは、実践でもっともよくある間違いのひとつです。

コールドスタートの段階(SFT による冷起動)。目標は能力の上限ではなく、基本的な挙動の整列です。このとき実データが絶対多数を占めるべきで、50% を下回らないことを推奨し、70% 以上とするのが一般的です。理由はこうです。コールドスタート段階の失敗モードは、あるタスクが「できない」ことではなく、「要求された 방식으로応答できない」ことがほとんどです——フォーマット、口調、構成、拒否の仕方。合成データはこの段階ではほとんど助けになりません。その挙動分布がモデル自身の傾向からサンプリングされている以上、本質的にモデルの現在の好みを固定化してしまうからです。それには本来あるべきでない好みにまで至ります。

能力拡張の段階。これは合成データの価値がもっとも高い窓であり、モデルは能力の境界上にある練習を大量に必要とします。このとき合成データは 60% から 80% まで占められます。ただしこの割合は領域ごとに大きく異なります。検証シグナルが強く、実データが乏しい領域(数学の導出、コードの実行検証、構造化されたタスク)はもっと高くてもかまいません。実世界の知識と長い尾の表現に依存する領域(オープン QA、カスタマーサポートの対話、クリエイティブライティング)は、合成の割合を明確に下げるべきです。この種のタスクには信頼できる検証器がないため、合成データの品質上限は検証器によって固定されてしまうからです。

収束と較正の段階。合成データの割合は 30% 以下まで落とし、残りを実データで分布の較正に使います。この工程はしばしば飛ばされます。代償は、モデルが実シーンでの分布シフトを抱えたまま続くことです——合成データを 1 ラウンド追加するたびに、モデルは合成データの分布にさらに近づき、実分布からは遠ざかる。この漂流は累積的で単調なので、較正をしないと自分では消えません。

何度も検証されている経験として、合成データは「新しい能力を習得する」あるいは「一つの能力を安定したレベルまで練習する」ために向いています。「すでに持っている能力を維持する」ためには向いていません。能力を維持することに対する実データの効率は明確に高い。実データは、モデル自身では生成できない分布の特性を持っているからです。

8.2 混合は単純な連結ではない:必ず分離すべき三つの操作

比率を設定する際、もっともよくある混同は、独立した三つの決定をひとつの数値に押し込むことです。これらは個別に設定する必要があります。

  • サンプリング比率:データローダの中で各ソースが現れる頻度。勾配推定の構成に影響します。
  • サンプルの重み:各サンプルが損失に寄与する倍率。信号がどれだけ増幅されるかに影響します。サンプリング比率と重みは、精确な制御のために同時に使う必要があります:实际贡献 = 采样比例 × 权重。あるバケットの寄与を調整したいが batch の構成を変えたくないというときは、重みだけを調整するのが正しいやり方です。
  • クリーニングと保持率:実データが混合に入る前に保持される割合、そして同様にフィルタされるかどうかです。この項目はしばしば無視されますが、実データの中で「ノイズのように見える」サンプル(口語的な表現、スペルミス、通常とは異なる表現)を残すか削除するかを決めます。これらのサンプルはモデルが実シーンでもっとも処理する必要のあるものなのです。

三項目目に特に注意してください。合成データは通常 prompt プールから来るため、本質的に「クリーン」です。一方、実データには「汚い」しかし完全に有効なサンプルが多数含まれます。統一のために、パイプラインが実データに合成データと同じクリーニング基準(長さの切り詰め、形式の正規化、重複排除の閾値)を課すと、実分布の長尾を体系的に削ってしまいます。結果は「標準化された」入力ではよくでき、実入力では悪い——そして実際の入力こそが本番の条件です。

8.3 比率の検証:実データの held-out 上で必ず行う

比率のチューニングでもっともよくある間違いは、「合成分布上の評価セット」を使って検証することです。評価セットも同じ生成過程と同じ prompt プールから来ているなら、異なる比率の案はその上で示す差分が著しく歪むことになります。合成データの割合を増やすと、訓練と評価の両方が同じ分布への適合を同時に増やしてしまうからです。

正しい検証プロトコルは次の通りです。

  • 評価セットは実分布から取得し、パイプラインを開始する前に固定し、いかなる生成や選別にも決して使わない。
  • 評価セットは層別化し、総点だけを見てはいけません。総点は構造上の弱点を覆い隠します。総合スコアが横ばいでも、ツール呼び出し回数の長い尾のスコアが 10 点落ちていることがあります。合成データの比率調整でもっとも引き起こしやすいのが、この種の局所的な退行です。
  • 純粋に人手で記述され、規模は小さい(たとえば 200 から 500 件)ものの、構造の多様性をカバーした評価セットを一つ残しておきます。その役割は分布の漂流を検出することです——自動化されたフローは、同じ仮定を使っているために、この種の漂流を自分で発見することができません。

最後の項目は、見落とされがちな仕組みに対応します。合成パイプラインの評価セットは、パイプラインと一緒に漂流します。毎ラウンド prompt プールから生成される問題は、パイプラインの固有の好みを強化し続けており、評価セットを prompt プールから抽出するなら、それも徐々にこの好みに整列していきます。この過程は遅く、通常 4 から 6 ラウンドかかって初めて現れます。しかし一度現れると、チームは「指標が着実に上がっていて、すべて正常」と착각してしまいます。本当のシグナルは実データの held-out 上のスコアだけで、それはたいてい数ラウンドすでに下がっています。

九、パイプラインの評価と退化防止:合成データを可視化し、止められるようにする

9.1 三つの工程それぞれに独立した評価セットが必要

合成データパイプラインの評価は、ひとつの指標群では足りません。工程ごとに分けると、少なくとも三種類の評価セットが必要で、それぞれが異なる役割を担います。

  • 生成の評価セット:「生成されたサンプルそのものが良いかどうか」を測るもので、訓練は含みません。prompt プールの質、サンプリングパラメータの妥当性、そして生成モデルを交換する必要があるかどうかを測るものです。指標には、形式の適合率、切り詰め率、prompt との意味的一致度が含まれます。
  • 検証の評価セット:「検証器が正確に判定できるかどうか」を測るものです。この分類はもっとも整備が必要で、もっとも省略されがちです。手法は、検証結果に人手でラベルを付けた一批(1000 から 5000 件規模で十分)を正解として用意し、検証器の正解率、偽陰性率(正しく誤りと判定され、良いデータを無駄にする)、偽陽性率(誤りを正しくと判定され、訓練集合を汚染する)を測定することです。そのうちの偽陽性率が致命的器官です。金標準の信頼性を直接決めるからで、偽陰性は効率の損失にすぎません。
  • 訓練の評価セット:実分布上の held-out で、最終的な効果を測ります。この分類の要件は 8.3 ですでに述べました——層別化し、固定し、人手による小さなセットを含めるべきです。

この三者の関係はこうです。検証の評価セットが生成の評価セットの信頼性を決め、生成の評価セットが訓練の評価セットの帰属の明確さを決めます。二種類目を飛ばすと、較正されていないフィルターの上にパイプライン全体を構築していることになります。問題が起きても帰属できません——生成が悪かったのか、検証が誤判定したのかを区別できないのです。

9.2 退化を防ぐ五つの具体的な仕組み

9.2.1 検証器の独立した監査

検証器の同化(3.4 節)はループ에서도っとも危険な失敗モードです。通常のアラームを一切作らないからです。防御手段は三つの層があります。

  • 検証器と生成器のスコアの相関を継続的に監視する。相関の上昇は同化の初期シグナルです。相関が突然下がるのも注目に値します。通常は検証器にバグが出たことを意味します。
  • 定期的なソースの入れ替え。外部ソースの実ラベル(人手でラベル付けした少量の高品質なデータであっても)を使って検証器を継続的に更新します。重要なのは再訓練ではなく、検証器のスコアに外部アンカーに基づく補正項を加えるだけであり、その効果はしばしば十分です。
  • 「敵対的な検証器」を一つ残す。その訓練目標は、主検証器が見逃してしまう問題を識別することに特化したものです(たとえば滑らかだが論理的につながっていない論理が断裂した出力)。その役割は主検証器を置き換えることではなく、主検証器に継続的に負のフィードバックを与えることです。

9.2.2 データのフィンガープリントと重複検出

合成データに特有のリスクがラウンドをまたぐ隠れた重複です。同じ一批の prompt が複数のイテレーションで繰り返し使われ、モデルは最終的にその問題群上で訓練されます——しかも毎ラウンドのサンプリングにはランダム性があるため、表面上看は「新しいデータ」ですが、実際は同じ問題群のリサンプリングです。これは訓練指標の水増しと実汎化の停滞を引き起こし、しかも既存の監視項のどれにも違反しないため、見つかりにくいのです。

検出方法は、ラウンドをまたぐフィンガープリントのライブラリを維持することです。プールに入る各サンプルについて、厳密なフィンガープリント(正規化したテキストのハッシュ)、意味的フィンガープリント(埋め込みベクトルの粗い量子化)、そして由来フィンガープリント(prompt プール ID + バリアント ID)を記録します。毎ラウンド、入庫の前に三段階で重複チェックを行います。由来フィンガープリントの段階がとくに有用で、「このラウンドのどれだけが前のラウンドの問題のリサンプリングか」を直接教えてくれます——この比率が 50% を超えたら、そのラウンドは基本的に新たな情報を生んでいないと判断できます。

9.2.3 バージョンとリネージの管理

合成データはコードと同じように管理しなければなりません。具体的な要件は次の通りです。

  • 各サンプルについて、完全な生成リネージを記録する:prompt プール版、生成モデル版、サンプリングパラメータ、検証器版、ラウンド ID。
  • データセット自体をバージョン管理し、各バージョンで変更不可なサンプル ID の集合を凍結する。
  • 訓練実行は、それが使用した正確なデータセット版とコミットハッシュを記録する。
  • いかなる指標の異常も、数分以内に「どのデータ、どの検証器、どのパラメータの組合せによるものか」までさかのぼることができる。

この項目は単なるエンジニアの潔癖に見えますが、実際には退化防止の中核となる能力です。合成パイプラインのイテレーション速度は速く、リネージの追跡がなければ、指標の異常はまず帰属できません。チームは最終的に試錯に頼るしかありませんが、複数変数が同時に動くパイプラインで試錯が成功する確率はきわめて低いのです。

9.2.4 人手注入:対抗的な漂流に対する固定アンカー

毎ラウンド、一定量の人手で記述したサンプルを注入しなければなりません。その出典は、実運用での実際の失敗事例、体系的に失敗する領域、そして領域の専門家が指摘した知識の盲点です。このデータの価値は規模にはなく(通常は 5% から 10% で十分)、それらはパイプライン自身の好みに同化され得ない唯一の分布であることにあります。システム全体の免疫系です。

実践上の提案は、人手サンプルを長期的に保守する集合として作り、ラウンドごとに追記するだけで削除せず、そして評価におけるその集合の挙動を別に追跡することです。人手サンプルの集合ですらスコアが下がっているなら、モデルに基礎能力の退化が生じたことを意味し、直ちにサーキットブレーカーを入れなければなりません。

9.2.5 回路遮断のルールと停止条件

パイプラインの停止条件は、開始する前に定義しておかなければなりません。そうしなければ、やめるべき時点のたびに「もう 1 ラウンド」という诱惑が湧きます。三つの層の停止条件は次の通りです。

  • 単一指標の回路遮断:実データの held-out スコアが 2 ラウンド連続で下がり、かつ低下幅が事前に定めた閾値(たとえば 0.5 点)を超えた場合、パイプラインを停止し、原因分析を行います。この閾値は比較的敏感に設定して構いません。誤報のコストが低いためです——分析時間を一度多く使うだけです。
  • 多様性の回路遮断:第 4 節のコードにあるいずれかの崩壊アラームが発火し、次のラウンドでも回復しなかった場合、パイプラインを停止します。この種の遮断は単一指標の遮断よりも早く発火します。崩壊は通常、指標が下がる 2 から 3 ラウンド前から始まるからです。
  • 限界収益の回路遮断:3 ラウンド連続で、同じバケットのサンプル量が倍増しても、実データの held-out スコアの変化がノイズの範囲内にある場合です。これはボトルネックがサンプル量ではなく、検証シグナルの強さか基盤モデルの能力にあることを意味し、合成データを追加し続けても無駄であることを示します。

最後の項目は三つの遮断の中でもっとも価値が高いものです。「続けるべきか」という根本的な問いに答えるからです。多くのチームは、パイプラインがプラトーに入っても「前のラウンドではまだ改善していた」ことを理由に投資を続けます。限界収益の監視はこの曲線を明示的にし、停止を感情的な判断ではなくデータに基づく判断にします。

9.3 回路遮断の意思決定表

観測された現象もっとも考えられる原因優先して調べる項目対処
合格率は上昇、実データの held-out は低下分布の崩壊。モデルが合成データの分布を適合している意味クラスタ数、近傍距離の分布、上位クラスタの比率温度を上げる、クラスタ上限を厳しくする、実データの混合比を一時的に上げる
合格率は上昇、実データの held-out は変化なし検証器の同化。ループが自己証明している検証器のスコアと生成器の確信度の相関異種の検証器または外部の補正項を導入する
合格率は不変、実データの held-out は低下難易度のフロンティア全体が上昇し、サンプリング分布が追従していないバケットごとの合格率の分布、難易度ラベルの遅延性5.4 の方法でバケット閾値を上げて再プローブする
ある種類のタスクのスコアが急落し、他は正常構造カバレッジのギャップ。バケット戦略がその形態を除外した構造エントロピーの分布、その構造のサンプル量とラウンドの傾向その形態に専用の底の配分を設け、価値駆動の配分を迂回させる
人手サンプルの集合のスコアが低下基礎能力の退化であり、分布の問題ではない実データの比率、忘却曲線、訓練ラウンド直ちに回路遮断し、配分とリプレイの仕組みを確認する
検証器の正解率がラウンドとともに低下タスク分布の漂移、検証器のカバー不足検証の評価セットの層別スコア、新しく現れた失敗タイプ層別評価セットを拡充し、検証器を再訓練またはローテーションする
人手レビューの拒否率が高い合成品質の低下、または構成バイアス欠陥タイプごとの比率、編集距離の分布欠陥タイプごとに的を絞って穴埋めを行い、バイアスのあるタイプを停止する

9.4 合成データという道を捨てるべき場合

合成データの方法論では解決できない場合が三つあります。その場合は経路を切り替えるべきです。

  1. 構築可能な検証シグナルがないタスク。あるタスクの正しさが本質的に主観的である、または実世界との相互作用を経て初めて確認できるなら、合成データの品質上限は検証器の上限に固定されます。無理にやっても「それ自体は正しくても系統的なバイアスがある」モデルを生み出すだけです。この種のタスクでの正しいやり方は、実際の注釈に投資し、合成データはdata augmentation として使う(バリアントと近傍サンプルを提供する)ことであって、代替することではありません。
  2. 基盤モデル自体の能力が不足している場合。モデルがあらゆる形態の合成データであっても、ある水準を超えられないなら、合成データへの投資を続けるのは無駄です。判断の根拠は限界収益の遮断曲線の形状です:初期は上昇、中央は横ばい、后期は低下——低下の区間はモデル容量または基盤質がボトルネックであることを示しています。
  3. 分布が長期的に右偏し、かつ制約できない場合。合成データと実データの分布距離が介入を増やしても拡大し続けるなら、この差はサンプリング戦略で埋めるものではなく、生成モデルの能力境界の外から来ているということです。このとき合成データはタスク形態のバリアント拡張にしか使えず、内容そのものには使えません。

十、总括:実行可能な一本道

記事全体を一本の主線に圧縮すると、こうなります。

合成データで自己強化するループを作り、検証シグナルでそのループの毎回の反復が本当に良くなっていることを保証し、多様性の制約とカリキュラム設計でそれが逸脱しないようにし、実データを決して漂流しない座標系として使い、厳格な停止条件で無収益のときには適時手を打たせる。

六つの要点だけを記憶せねばならないなら、重要性順に並べると次の通りです。

  1. 金標準の正当性は生成者からではなく、判定信号から来る。予算が逼迫しているときは、生成器ではなく検証器に優先投資すべきです。
  2. 訓練価値は能力境界に集中する。合格率が 0.1 から 0.5 の区間は合成データの費用対効果がもっとも高い位置であり、prompt プールはモデル能力に合わせてローリング更新しなければなりません。
  3. 正しいことは有用であることではない。検証を通った推論鎖の中間には系統的な誤りが隠れていることがあり、結論だけを見る検証器はそれを検出できません。
  4. 崩壊は分布の問題であり、正当性の問題ではない。意味カバレッジとタスク形態のカバレッジを監視する必要があり、合格率だけを監視していると見落とします。
  5. 比率は段階的なものであり、三つの軸は分けて調整すべき。サンプリング比率、サンプルの重み、クリーニング保持率は三つの別のものです。
  6. 停止条件は実データの held-out で定義し、事前に決定しなければならない。多様性の崩壊は、通常、指標の低下より 2 から 3 ラウンド早く現れます。

最後に実践上の注意をひとつ。合成データパイプラインは状態を持つシステムであり、その退化は漸進的で、沈黙的で、多ラウンドにまたがって進みます。それに対してチームの注意は集中的で、断片的で、最新の指標に錨を下げています。この二つの misalignment が、绝大多数の合成データプロジェクトが最終的に失敗する真の原因です。どのステップを間違えたからではなく、継続的に見ておくべき指標を、誰も継続的に見ていなかったからです。したがって、診断層を常駐コンポーネントとして構築すること——問題が発生したときに初めて組む道具としてではなく——が、このパイプラインでもっとも飛ばされやすいもの、最も飛ばしてはならないことです。