📚 背景知識(読んでから問題へ)
Day160では、ラベル付き40件・未ラベル300件で、疑似ラベルなし(r=0, test 0.7418)がD・Gのどのラウンドより上だった。本文には「ラベルなしデータが桁違いに多い…といった前提では、r>0が勝つ側に進む可能性が高い」とも書いた。今日はこれを言いっぱなしにせず数字で確かめる。
| 変える前提 | 値 | 確かめたいこと |
|---|---|---|
| n_lab | 40 / 80 / 160 / 400 | 先生モデルが良くなれば、疑似ラベルが得をするか |
| n_unl | 300 / 1200 | 未ラベルを4倍にすると、疑似ラベルが得をするか |
⚖️
ラベルを増やすと3つが同時に動く。 ①先生の精度(疑似ラベル正解率)↑=r>0に有利 ②r=0の性能↑=伸びしろが消えてr>0に不利 ③疑似ラベルの割合↓=効果が薄まる。「件数を増やしたら差が縮んだ」だけでは、どれが効いたか分からない。そこで未ラベル1200件の条件も並べ、割合だけを押し上げた場合も測る。
📄
一次資料(2026-09-24参照)
・Oliver et al. "Realistic Evaluation of Deep Semi-Supervised Learning Algorithms"(NeurIPS 2018, arXiv:1804.09170)§4.5 "Varying Data Amounts" は、ラベルが増えるとSSL手法どうしの性能が収束すると述べる("SSL techniques tends to converge as the number of labels grows")。教師ありとの差の縮小を示したわけではない。§4.6 は小さい検証セットでは比較が信頼できないと論じる
・Bengio & Grandvalet "No Unbiased Estimator of the Variance of K-Fold Cross-Validation"(JMLR 5, 2004): K-fold CVの分散を普遍的に偏りなく推定する方法は無い。今日のfold間SDは「粗さの下限の目安」として扱う
・Oliver et al. "Realistic Evaluation of Deep Semi-Supervised Learning Algorithms"(NeurIPS 2018, arXiv:1804.09170)§4.5 "Varying Data Amounts" は、ラベルが増えるとSSL手法どうしの性能が収束すると述べる("SSL techniques tends to converge as the number of labels grows")。教師ありとの差の縮小を示したわけではない。§4.6 は小さい検証セットでは比較が信頼できないと論じる
・Bengio & Grandvalet "No Unbiased Estimator of the Variance of K-Fold Cross-Validation"(JMLR 5, 2004): K-fold CVの分散を普遍的に偏りなく推定する方法は無い。今日のfold間SDは「粗さの下限の目安」として扱う
🗂️ データ・評価スキーマ
| データ/用語 | 件数・型 | 役割 |
|---|---|---|
| lab | 40 / 80 / 160 / 400件(text, label) | 先頭40件はDay160と同一。後ろに seed+1000 で生成した追加分を足す(入れ子) |
| unl | 300 / 1200件 | 先頭300件はDay160と同一。1200件は seed+2000 の900件を追加 |
| test | 110件 | 全条件で同一(Day160と同じ)。1件 = 0.0091 |
| g1 / g5 | float | acc(r=1)−acc(r=0) / acc(r=5)−acc(r=0)。正なら疑似ラベルが得 |
| g1_se | float | 20seedのg1の標準偏差 ÷ √20(標準誤差) |
| pl_acc_r1 | float | round1の疑似ラベル正解率(未ラベルの真ラベルを使う診断専用) |
| 実験 | 条件 | seed | CV | 目的 |
|---|---|---|---|---|
| 実験1 | n_lab 4水準 × D・G、未ラベル300 | 5(Day160と同じ) | あり | Day160の再現と、CV選択・先生の精度の変化 |
| 実験2 | n_lab 4 × n_unl 2 × D・G = 16条件 | 20 | なし | 「入れ替わったか」を±2SEで判定 |
📏 評価の仕組み — 「入れ替わった」をどう判定するか
指標はtest accuracy。判定は「20seedの平均g1が +2SE を超えたら有意に得、−2SE を下回ったら有意に損、その間は区別できない」。下の図は、Dの未ラベル300件で |g1| と 2SE を並べたもの。40件では |g1| が 2SE の約4倍、400件では 2SE の1/10以下になる。
📐
16条件を同時に見るので、偶然1条件くらい2SEを超えても不思議ではない。それでも正の側で2SEを超えた条件は0個だった、というのが今日の結論の強さ。
🎯 問題
タスクA(予想・設計)
1
ラベル付き件数を40→400件に増やしたとき、g1が「いずれ正になる」か「0に近づくだけ」かを予想し、「一緒に動く3つ」を使って理由を2〜3文で書く
2
「件数を増やしたら差が縮んだ」から「先生が良くなったから」と結論できない理由を1文で述べ、未ラベル300→1200件の条件で何が切り分けられるかを述べる
タスクB(コーディング)
B1
make_split(seed, n_lab, n_unl=300): test・未ラベル先頭300件はDay160と同じ。ラベル付きは40件を先頭に固定して入れ子で追加B2
r1_diag(...): round1の n_added と疑似ラベル正解率(診断専用)B3
実験1: 4件数 × D・G × 5seed、Day160の
ipl_curve・cv_curve をそのまま使うB4
実験2: 4件数 × 未ラベル2水準 × D・G × 20seed、test曲線だけ。g1・g5の平均・SE・勝ち負け数
B5
ラベル付きに感情語20語のうち何語が出るかを件数ごとに数える
B6
assert 6個: (a) 40件でDay160の0.7418/0.6327/0.6800を再現 (b) 未ラベル300件でg1が単調に0へ (c) 16条件どれもg1・g5 < +2SE (d) 40件の4条件はg1 < −2SE (e) 160・400件は|g1| < 2SE (f) 40件で感情語19語以上が出現
タスクC(分析)
C1
「r=0が勝つのは小データ専用か」に1〜2文で答える
C2
Day160のN1を書き直す。件数で分岐させるべきか、別の条件で分岐させるべきかを実測値つきで述べる
💡 ヒント
ヒント1方向性
「差が縮む」と「入れ替わる」は別の現象。r=0が0.97まで上がると、test 110件で残る誤答は約3件。そこで勝つには、疑似ラベルがその3件を直す新しい情報を持っている必要がある。感情語は全部で20語しかない。
ヒント2アプローチ
入れ子は
extra.sample(frac=1.0, random_state=seed) で混ぜてから先頭を取る。5seedでは400件の順序すら安定しないので、CVを省いてtest曲線だけを20seed回し、SEを出す。g1を「110件中何件分か」に直して読む。ヒント3コード骨格
def make_split(seed, n_lab, n_unl=300): lab40, unl, test = split_seed(seed) # Day160 と同じ 40/300/110 lab = lab40 if n_lab > 40: extra = make_text_dataset(n=400, seed=seed + 1000).sample(frac=1.0, random_state=seed) lab = pd.concat([lab40, extra.iloc[: n_lab - 40]], ignore_index=True) if n_unl > 300: unl = pd.concat([unl, make_text_dataset(n=n_unl - 300, seed=seed + 2000)], ignore_index=True) return lab, unl, test
✅ 模範解答(要点。全文はMDファイル)
前提: Day160模範解答の import〜day157_val_es を day160_lib.py として保存しておく。
from day160_lib import * # ---- 実験2: 20seed × 未ラベル300/1200件 (testだけ) ---- for n_lab in [40, 80, 160, 400]: for n_unl in [300, 1200]: for name, fn in METHODS.items(): for s in range(20): lab, unl, test = make_split(s, n_lab, n_unl) full = ipl_curve(lab["text"], lab["label"], unl, fn, s, {"test": test}, n_rounds=5)["test"] rows.append({..., "g1": full[1] - full[0], "g5": full[5] - full[0]}) g = e2.groupby(["n_lab", "n_unl", "method"]) t2 = g[["r0", "g1", "g5"]].mean() t2["g1_se"] = g["g1"].std(ddof=1) / np.sqrt(20) assert (t2["g1"] < 2 * t2["g1_se"]).all() and (t2["g5"] < 2 * t2["g5_se"]).all() # 有意に勝つ条件なし assert (t2.loc[40, "g1"] < -2 * t2.loc[40, "g1_se"]).all() # 40件は有意に負ける big = t2.loc[[160, 400]] assert (big["g1"].abs() < 2 * big["g1_se"]).all() # 160件以上は誤差の範囲
# ===== 実測(2026-09-24 / Python 3.13.12, scikit-learn 1.9.1, pandas 3.0.6, numpy 2.5.3 / 新規uv venv / 2分31秒) =====
[実験2: 20seed, 未ラベル300件] g1 (±SE) 勝ち/負け
D 40件 -0.0923 (0.0119) 0/18 G 40件 -0.0591 (0.0104) 2/18
D 80件 -0.0545 (0.0123) 4/16 G 80件 -0.0300 (0.0087) 4/14
D 160件 -0.0155 (0.0092) 5/11 G 160件 -0.0023 (0.0077) 6/11
D 400件 -0.0005 (0.0032) 9/6 G 400件 -0.0009 (0.0038) 10/7
[未ラベル1200件で最大] G 160件 +0.0064 (0.0055) = +1.2SE, 110件中0.7件分
感情語20語のうちラベル付きに出現: {40: 19.6, 80: 20.0, 160: 20.0, 400: 20.0}
[OK] 6個のassertすべて成立 (146.9s)
🎯
発見1: 入れ替わらなかった。害が0に近づいただけ。 16条件のどれも、g1・g5が+2SEを超えなかった。「r=0が有意に勝つ」のは40〜80件の小データ専用だが、「r>0が勝つ」側には400件・未ラベル1200件まで一度も入れ替わらなかった。
🔍
発見2: 未ラベルを4倍にすると害は減るが、得にはならない。 40件・D: −0.0923 → −0.0709、80件・G: −0.0300 → −0.0059。疑似ラベルの割合を上げても縮んだので、「薄まったから縮んだ」という説明は否定できる。
🧩
発見3: 勝てない理由は件数ではなく「未ラベルに新しい情報が無い」こと(仮説)。 40件の時点で感情語20語中19.6語が既に出ている。疑似ラベルにできるのは既知の語の重み調整だけで、それはラベルを足す方が確実。
⚠️
発見4: 5seedでは方向すら読み違える。 400件Gのg1は5seedで −0.0036(160件の −0.0000 より悪く見える)、20seedでは −0.0009 と −0.0023 で順序が逆。40件のr=0も5seed 0.7418 / 20seed 0.7255。
📊 結果の可視化
2枚目: 先生(疑似ラベル正解率)はDで0.87→0.98まで良くなるが、同時にr=0も0.73→0.97に上がり、test 110件中の残り誤答は30件→3件に減る。先生が良くなるほど、直せる問題が残っていない。
🧭 N1の書き直し(タスクC2の解答例)
📝
タスクC1の答え: 半分だけ小データ専用だった。「有意に損をする」のは40〜80件だけで、160件以上では差が±2SEの中に消えた。しかし「得をする」側には400件・未ラベル1200件まで入れ替わらなかった。N1は件数で分岐させない。
🪜 Step-by-Step 解説
1
件数以外を動かさない(入れ子)。 件数ごとに分割し直すと、件数の効果と分割の運が混ざる。Day160の40件を先頭に固定して後ろに足すと、40件はDay160と完全一致(assert (a))し、80件は160件の一部になる。
| 迷った他案 | 利点 | 採らなかった理由 | 前提が変わったら |
|---|---|---|---|
| 件数ごとに生成・分割し直す | コードが短い | testが変わり、Day160を再現できない | seedを数百本回せるなら許容 |
| 追加分を未ラベル300件から取る | 「アノテーション追加」に近い | 未ラベル件数も同時に動く | 「ラベル予算の使い道」を問う日ならこちら |
| 20seed実験でもCVを回す | CV選択の実力も測れる | 約8倍の時間(6分以上)。今日の問いはtestで答えが出る | CVしきい値を最適化する回では必要 |
2
5seedの差を20seedで確かめる。 400件のg1は0.001〜0.004で、test 1件(0.0091)より小さい。平均だけでなくSEを出して判定する。
3
未ラベル件数を振って原因を1つ消す。 「薄まったから縮んだ」が正しければ、割合を上げると害は増えるはず。実測は逆(40件D: −0.0923 → −0.0709、割合0.69 → 0.90)。ただし先生の精度と伸びしろはまだ分離できていない。
4
「未ラベルに新しい情報があるか」を数える。 40件で感情語19.6/20語が既出。実データ(語彙数万)では40件で語彙が出そろうことはまず無い——ここは測った事実ではなく、次回に測る仮説。
🧮 数学・統計の補足(文系向け)
🎓
「縮む」と「入れ替わる」の違い: 90点の生徒に家庭教師(疑似ラベル)を付けても、残り10点分が家庭教師にも分からない問題なら点は上がらない。下手なら下がる。生徒が97点になると下げられる点も減るので「損」は小さくなる。でも「得」になるには、家庭教師が生徒の知らないことを知っている必要がある。今日の生徒は最初から教科書の単語を全部知っていた。
📏
2SE: 20人の平均点は「たまたま」で少し動く。その目安が標準誤差(SE)。平均がSEの2倍より0から離れていれば「たまたま」とは考えにくい。test 1件 = 1/110 = 0.0091。g1 = +0.0064 は「110問で0.7問分」。
🏆 Kaggleでの実践的な使い方
- 「データが増えたら疑似ラベルを試す」を既定にしない: 件数が多いこと自体は効く理由にならない。今日は400件・未ラベル1200件でもr=0を有意に上回らなかった
- trainとtestの語彙・分布のずれを先に測る: testにしか出ない語・カテゴリ・値域がどれだけあるかを数える。ずれが無ければ疑似ラベルは手間と壊れやすさを足すだけになりやすい(「ずれがあれば効く」側は未検証、Day162で測る)
- 小さい差はseedを増やして確かめる: 差が評価データ1〜2件分なら、分割・初期値を変えて平均とばらつきを出す。5本では順序すら入れ替わる
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| 害が縮んだのを見て「件数が多ければ効く」と考える | 縮みの延長に入れ替わりを想像する | 400件でg1・g5は −0.0041〜+0.0041。縮むことと得をすることは別 |
| 件数ごとに分割をやり直して比べる | コードが楽 | 件数の効果と分割の運が混ざる。入れ子にする |
| 5seedの平均の大小を信じる | 平均は1つの数に見える | 400件Gは5seed −0.0036、20seed −0.0009。1件より小さい差は5seedでは読めない |
| 「先生が良くなったから縮んだ」と言い切る | 疑似ラベル正解率の上昇が目に入る | 伸びしろと疑似ラベルの割合も同時に動く。消せたのは「薄まったから」の1つだけ |
| Oliverら(2018)を「ラベルが増えるとSSLの利点が消える」と引用する | 要旨を記憶で引く | §4.5 はSSL手法どうしの収束。教師ありとの差ではない |
🚧 扱わなかった範囲と理由
- モデルの大きさ・データ拡張: Day160で挙げた「r>0が勝つ前提」3つのうち、動かしたのは件数2種だけ。3つ同時に動かすと原因がまた分からなくなる
- 未ラベル1200件より多い条件: 原因が「新しい情報が無い」ことに見えたので、件数より情報の有無を振る(Day162)方を先にした
- 閾値0.60・クラスタ数8: Day160の結論の再検証なので手順は固定
🚀 次のステップ
- 発展: 20seed実験にCVを足し、件数ごとに「CVでの差」と「testでの差」の散布図を描いて、N1のしきい値を1foldの件数の関数として決める
- 次回予告: Day162では感情語を20→200語に増やし、ラベル付き40件には出ない語が未ラベルには出る状態を作って、r>0がr=0を有意に上回るかを測る(N0.5の「ある」側の検証)
Phase 6 の学習マップ
1-23Phase6主要テーマ・総復習(Day126-149、完了)
発展①〜⑫NLP系コンペへの応用(Day161: 総括フローチャートN1の前提をラベル件数で検証)
📝 自己評価(解いた後に記入)
自分の回答・気づき・メモ: