Day 159 — NLP系コンペへの応用⑩ — (D)のconfirmation biasは「新しい間違い」を生むのか「既存の間違いの確信度」だけ強めるのか

2026-09-18 金 / Phase 6(Grandmaster、総復習後の発展回) コーディング→分析 round1入場モデル vs round5入場モデルの固定サンプル追跡

📚 背景知識(読んでから問題へ)

🔁
Day155〜158のおさらい: 「確信度優先(D)」はround-local疑似ラベル精度が0.867→0.153へ単調に崩壊する。Day157では「round1が事実上の大域最適」だと判明し、Day158では選択に使わない診断指標abs_marginで、これが選択基準ごとに難易度層を偏って消費する一般構造の帰結だと裏付けた。しかし同時に、D_baseのround5は客観的難易度が並程度なのにround_pseudo_accがコイントス以下(0.153)まで崩壊するという、難易度構成の変化だけでは説明しきれない残差も見つかった。
🧭
今日の問い(Day158の次回予告そのもの): round1入場モデル(疑似ラベルを一切含まない、初期40件のみで学習)とround5入場モデル(round1〜4の採用結果を反映)を、同一の固定テストサンプル集合に予測させ、サンプル単位で追跡する。(D)のconfirmation biasは「新しい間違いを生む」(正解→不正解)のか、「既存の間違いを確信度だけ強める」(不正解のまま自信だけ増す)のか、どちらが支配的かを切り分ける。
用語意味
round1入場モデルそのseedの初期40件の真のラベルのみで学習。疑似ラベルは一切含まない、confirmation biasが始まる前の基準点
round5入場モデルround1〜4で採用された疑似ラベル(D_baseで平均300件前後)を反映して再学習したモデル
new_errorround1では正解、round5では不正解になったテストサンプル
stayed_wrong_more_confident両方不正解のままだが、確信度|proba-0.5|がround5でさらに上昇したサンプル

🗂️ データ・分類スキーマ

カテゴリ名条件意味
stayed_correctcorrect1=True, correct5=True両モデルとも正解
new_errorcorrect1=True, correct5=Falseround1では正解→round5で不正解(新しい間違い)
fixedcorrect1=False, correct5=Trueround1では不正解→round5で正解(改善)
stayed_wrong_more_confident両方不正解, conf5>conf1既存の間違いに、より強い自信を持つようになった
stayed_wrong_less_confident両方不正解, conf5≤conf1既存の間違いへの自信が弱まった
🧮
実験設計の自己検証: round1入場モデルはselect_fn(D/G)に依存しないため、同じseedならD_base・G_baseでacc_round1_entryが完全一致するはずである。実測でも5シードすべてで一致した(例: seed0は両方とも0.7636) — これが今日の比較設計が正しく機能している裏付けになる。
SEEDS      = [0,1,2,3,4]
n_rounds   = 5
track_rounds = (1, 5)  # ★このラウンド"入場時点"(選択判断に使われた)モデルを保存
test_df    # 各seedにつき110件、そのseedを通して不変(固定サンプル集合)

🎯 問題

タスクA(理論)

以下2点をそれぞれ2〜3文で説明する。

1
Day158の残差(round5は客観的難易度が並程度なのにround_pseudo_accがコイントス以下)を踏まえ、(D)のconfirmation biasは「新しい間違いを生む」現象と「既存の間違いの確信度だけを強める」現象のどちらが支配的だと予想されるか、理由とともに述べる
2
(G)についても同じ集計を行った場合、(D)と同種の現象が観測されると予想されるか。観測されるとすれば規模はDと比べてどうなると予想されるか

タスクB(コーディング→分析)

B1
run_iterative_track(seed, select_fn, n_rounds=5, track_rounds=(1,5))を実装。Day151-158の反復ロジックを維持しつつ、rがtrack_roundsに含まれる場合、そのラウンドの選択判断に使われたモデル(vectorizer・model)を保存する
B2
predict_on_fixed_setで固定test_dfへの予測確率・正誤・確信度(|proba-0.5|)を計算し、classify_transitionでサンプルごとに5カテゴリへ分類する
B3
D_base・G_baseを5シードで実行し、5シード×110件=550件をプールしてカテゴリ別件数・確信度変化の平均を集計する
B4
(a) Dのround5入場accがround1入場accを下回る (b) stayed_wrong件数≥20 (c) stayed_wrongの確信度変化の平均が正 (d) stayed_wrong_less_confidentが0件 (e) new_error件数>stayed_wrong_more_confident件数 (f) Gのacc低下幅がDより小さい、をassertで確認する
B5
追加診断: stayed_wrong_less_confidentが0件だった完全な非対称性を議論し、D・Gの「新規エラー比率」を比較する
B6
(考察): タスクAの2つの予想が実測とどの程度一致したかを、「新しい間違い」件数と「既存の間違いの確信度強化」件数のどちらが多かったかに触れながら述べる

💡 ヒント

ヒント1方向性

round1入場モデルはselect_fnには一切依存しない。同じseedならD_base・G_baseでacc_round1_entryが完全一致するはずであり、これが崩れていたら実装のどこかにバグがあるという自己検証として機能する。

ヒント2アプローチ
  • run_iterativeループ先頭のtrain_and_eval呼び出しが、そのラウンドの「入場時点」モデルである
  • 途中でbreakしても、breakする直前のそのラウンドのtrain_and_eval自体は既に実行済みであることに注意する
  • 確信度はnp.abs(proba - 0.5)。0に近いほど自信がなく、0.5に近いほど確信度が高い
ヒント3コード骨格
def classify_transition(correct1, correct5, conf1, conf5):
    n = len(correct1)
    cats = np.empty(n, dtype=object)
    for i in range(n):
        if correct1[i] and correct5[i]:
            cats[i] = "stayed_correct"
        elif correct1[i] and not correct5[i]:
            cats[i] = "new_error"          # 正解 -> 不正解
        elif (not correct1[i]) and correct5[i]:
            cats[i] = "fixed"              # 不正解 -> 正解
        else:
            cats[i] = "stayed_wrong_more_confident" if conf5[i] > conf1[i] else "stayed_wrong_less_confident"
    return cats

✅ 模範解答

# make_text_dataset / train_and_eval / split_seed / select_confident_pseudo_labels /
# _cluster_and_quota / select_diverse_prefilter(D) / select_diverse_prefilter_uncertainty(G) は
# Day151-158と同一。run_iterative_track・predict_on_fixed_set・classify_transitionはヒント3・本文参照

def run_iterative_track(seed, select_fn, n_rounds=5, quota_mode="confidence", track_rounds=(1,5)):
    labeled_df, unlabeled_df, test_df = split_seed(seed)
    cur_texts, cur_labels = labeled_df["text"].tolist(), labeled_df["label"].tolist()
    remaining_df = unlabeled_df.copy().reset_index(drop=True)
    tracked = {}
    for r in range(1, n_rounds+1):
        acc, vectorizer, model = train_and_eval(cur_texts, cur_labels, test_df)  # ★入場時点モデル
        if r in track_rounds:
            tracked[r] = {"acc": acc, "vectorizer": vectorizer, "model": model}
        if len(remaining_df) == 0: break
        mask_conf, _ = select_confident_pseudo_labels(model, vectorizer, remaining_df, 0.60)
        n_added = int(mask_conf.sum())
        if n_added == 0: break
        mask_sel, pseudo_sel = select_fn(model, vectorizer, remaining_df, n_added, 8, seed, quota_mode)
        cur_texts = cur_texts + remaining_df.loc[mask_sel, "text"].tolist()
        cur_labels = cur_labels + pseudo_sel.tolist()
        remaining_df = remaining_df.loc[~mask_sel].reset_index(drop=True)
    return tracked, test_df

def predict_on_fixed_set(vectorizer, model, test_df):
    proba = model.predict_proba(vectorizer.transform(test_df["text"]))[:, 1]
    pred = (proba >= 0.5).astype(int)
    correct = (pred == test_df["label"].to_numpy())
    return proba, pred, correct, np.abs(proba - 0.5)

# D_base・G_baseを5シードで実行し、round1/round5入場モデルの予測をclassify_transitionで分類・集計
assert d_acc_r5 < d_acc_r1
assert d_stayed_wrong >= 20
assert d_mean_delta_wrong > 0
assert d_less_conf == 0
assert d_new_error > d_more_conf
assert (g_acc_r1 - g_acc_r5) < (d_acc_r1 - d_acc_r5)
print("[OK] 6個のassertすべて成立")
# ============ 実測結果(実際にPython 3で実行して確認済み) ============
#
# [全体集計 カテゴリ別件数(5シード x 110件 = 550件をプール)]
# D_base: stayed_correct=271  new_error=137  stayed_wrong_more_confident=112  fixed=30  stayed_wrong_less_confident=0
#         n_stayed_wrong(total)=112 (more_conf比率=100.0%)  mean_conf_delta(stayed_wrong)=+0.3549
# G_base: stayed_correct=320  stayed_wrong_more_confident=106  new_error=88  fixed=34  stayed_wrong_less_confident=2
#         n_stayed_wrong(total)=108 (more_conf比率=98.1%)  mean_conf_delta(stayed_wrong)=+0.2333
#
# [全体のacc: round1入場モデル vs round5入場モデル、fixed test_dfへの予測、5シード平均]
# D_base: acc_round1_entry=0.7418 -> acc_round5_entry=0.5473  (delta=-0.1945)
# G_base: acc_round1_entry=0.7418 -> acc_round5_entry=0.6436  (delta=-0.0982)
#   ※acc_round1_entryはD/Gで各seedごとに完全一致(設計の妥当性の確認)
#
# [新規エラー比率] new_error / (new_error + stayed_wrong)
# D: 137 / (137+112) = 0.5502
# G:  88 / ( 88+108) = 0.4490
#
# [OK] 6個のassertすべて成立
🎯
重要な発見1(件数では「新しい間違いを生む」がわずかに優勢だが、圧倒的ではない): 550件中、new_errorは137件、stayed_wrong_more_confidentは112件。「どちらが支配的か」という二択の問いへの実測の答えは、「両方が近い規模で同時に起きている」というものだった。
⚠️
重要な発見2(確信度が下がった既存の間違いは、D_baseで0件——完全な非対称性): stayed_wrong_less_confidentはD_baseで0件。一度不正解になったサンプルへの確信度は、上がることはあっても下がることは一度もない。これはconfirmation biasの教科書的な定義を実測で裏付ける最も強い証拠である。
🔍
重要な発見3(Gでも同種の現象は起きるが、規模はDよりはっきり小さい): 新規エラー比率はD 55.0% vs G 44.9%で大差ないが、acc低下幅はD -0.1945 vs G -0.0982とDはGのおよそ2倍。GがDより劣化が小さいのは機構自体の不在ではなく、規模の違いである。

📊 結果の可視化

① Day158の残差

round5はabs_marginが並なのにround_pseudo_acc=0.153

② round1/round5入場モデルを保存

疑似ラベルなし vs round1-4反映後、同一test_dfへ予測

③ サンプル単位で5分類

new_error / fixed / stayed_wrong(confごとに2分割) / stayed_correct

④ 結果

new_error 137 > more_conf 112。ただし下降は0件の完全非対称

D_base: round1→round5でのテストサンプル550件のカテゴリ別件数 stayed_correct 271 new_error 137(正解→不正解) more_confident 112(既存の間違いの確信度↑) fixed 30 less_confident 0(該当なし)
指標D_base(確信度優先)G_base(Uncertainty Sampling)
acc_round1_entry(5シード平均)0.74180.7418(Dと完全一致)
acc_round5_entry(5シード平均)0.54730.6436
acc低下幅-0.1945-0.0982(約半分)
new_error件数13788
stayed_wrong_more_confident件数112106
stayed_wrong_less_confident件数0(完全非対称)2
新規エラー比率D=55.0% / G=44.9%(近いが、acc低下幅は約2倍差)

🪜 Step-by-Step 解説

1「round1入場モデルの精度がD・Gで完全一致する」ことを、実験設計の妥当性の確認に使う

D_base acc_round1_entry: [0.7636, 0.7273, 0.7818, 0.7364, 0.7000]
G_base acc_round1_entry: [0.7636, 0.7273, 0.7818, 0.7364, 0.7000]  # 完全一致
📋
なぜこうするか: round1入場モデルはselect_fnを一切参照しない。同じseedならD・Gで完全一致するはずで、これが崩れていたら実装のどこかにバグがあるという自己検証として機能する。今日は全seedで一致し、以降の比較がD・G間で公平な土俵の上にあることが裏付けられた。

2「どちらが支配的か」という二択の問いを、件数の僅差(137 vs 112)という実測で相対化する

new_error = 137件, stayed_wrong_more_confident = 112件  # 差はわずか25件
💡
なぜこうするか: 実測は、どちらか一方が圧倒するのではなく両方が近い規模で同時に起きていることを示した。二択の問いを立てても、実測の答えが「両方」であることは十分にありうる——この種の発見を切り捨てず、量的な内訳まで踏み込んで報告することが誠実な報告につながる。

3「件数は拮抗」でも「方向性は完全に非対称」という、別の軸の発見を両方残す

stayed_wrong_more_confident = 112件, stayed_wrong_less_confident = 0件
🧭
なぜこうするか: 件数の比較と、確信度変化の方向性の比較は全く別の軸の発見である。もし「件数が拮抗していたから確信度強化は重要ではない」と結論づけていたら、"下降が1件もない"というこの実験で最も強い非対称性を見逃すところだった。複数の切り口で独立に集計結果を眺める姿勢が、今日の発見の核になっている。

🧮 数学・統計の補足(文系向け)

📝
「確信度が上がることはあっても下がることはない」ことを「一度信じた噂を訂正しにくい人」のたとえで理解する:

ある人が「Aさんは信頼できない」という誤った噂を信じ込むと、その後Aさんの行動を見るたびに「やっぱり証拠だ」と解釈してしまい(確認バイアス)、誤った確信はどんどん強まる一方、噂が誤りだと気づいて確信を弱める方向には動きにくい。今日のstayed_wrong_less_confident=0件という結果は、まさにこの「一方向にしか動かない確信」を、モデルの予測確率という数値で直接観測したものである。
📐
「件数は拮抗、方向性は非対称」という2つの発見を「体重は横ばいでも体脂肪率は一方向に増えている」たとえで理解する:

ある人の体重(全体の件数)が半年間ほぼ横ばいでも、その内訳である体脂肪率(確信度の変化方向)が一貫して増加傾向にあれば見過ごせない変化である。今日のnew_error(137)とstayed_wrong_more_confident(112)の件数は拮抗しているが、後者の内部はconf_deltaが常にプラス(下降0件)という一貫した方向性があり、「合計が近い」ことと「内訳の方向性が偏っている」ことは両立するという教訓である。

🏆 Kaggleでの実践的な使い方

よく使われるコンペカテゴリ: ☑ 自然言語処理(NLP) / ☑ 表形式データ(半教師あり学習全般に応用可)

原則具体的な活用例
疑似ラベリング前後で、固定の検証サンプル単位で予測の変化を追跡する集計されたtest_accだけでは「劣化した」事実しか分からない。サンプル単位で新規エラー生成/既存エラー強化を追跡すると、劣化の性質そのものが見える
「確信度が下がることはない」完全な非対称性をモデル監視のアラート条件に使うある集団で確信度の変化が一貫して片方向(特に誤答方向)にしか動かない場合、それはノイズではなく体系的な歪み(ドリフトやconfirmation bias)のシグナルとして扱える
二値の仮説を立てたときも、実測が「両方」である可能性を排除しない「新しい間違いを生む」か「既存の間違いを強めるか」の二択で予想しても、実測が両方をほぼ拮抗した規模で示すことは十分あり得る。仮説検証の設計段階で「両方」の選択肢を残す

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
round1入場モデルの精度がD・Gで一致するのは当然すぎて確認不要と考える「同じ40件で学習するのだから一致するはず」という直感がある実装バグ(誤ったtest_df参照、seedの扱いミス等)があれば一致は崩れる。実際に一致を確認することが以降の比較全体の妥当性を担保する
「新しい間違いを生む」か「既存の間違いを強めるか」のどちらか一方だけが正しい答えだと決めつける二択の問いを立てると答えも二択のどちらかだと思い込みやすい実測(137件 vs 112件)が示す通り、両方が近い規模で同時に起きることは十分あり得る
確信度変化の平均が正であることだけを見て、分布を確認しない平均値1つで「上がった/下がった」を判断したくなる平均が正であることに加え「下降0件」という分布全体の非対称性まで確認したことで、より強い証拠を得られた
Gでは劣化が小さいから、confirmation biasのような現象自体がGでは起きていないと考える「劣化幅が小さい=原因となる機構が存在しない」と単純化してしまうGでも新規エラー比率(44.9%)・確信度強化(98.1%)がDと近い水準で観測された。劣化幅が小さいのは機構の不在ではなく規模の違い

🚀 次のステップ

  • 発展: round2・round3・round4の境界でも同じ集計を行えば、「新しい間違い」と「既存エラーの確信度強化」のどちらがどのラウンドから顕著になるか、時間的な内訳まで特定できる可能性がある
  • 次回予告: Day160では、Day151〜159で積み上げた知見(quota配分・early stopping・難易度診断・confirmation biasのサンプル単位追跡)を1つの意思決定フローチャートとして統合し、Iterative Pseudo Labelingの設計指針としてまとめ直す回を予定

Phase 6 の学習マップ(NLP系コンペへの応用フェーズ継続中)

1-23
Phase6主要テーマ6項目・総復習(Day126-149、完了)
発展①〜⑩
NLP系コンペへの応用(Day158: 診断指標で一般構造を裏付け/Day159: confirmation biasをサンプル単位で解剖、新規エラーと既存エラー強化が拮抗しつつ後者は完全に非対称)

Day159は、Day158で見つかった「難易度だけでは説明できない残差」を、モデルの予測そのものをサンプル単位で追跡することで直接解剖した回。次はDay151-159の知見を1つの設計指針にまとめる番。

📝 自己評価(解いた後に記入)

自分の回答・気づき・メモ: