📚 背景知識(読んでから問題へ)
Day151〜159の10日間で分かったことを、次に同じ状況に出会ったとき「どの順番で何を決めるか」という設計手順にまとめる日。まず材料を並べる。
| Day | 問い | 実測で分かったこと |
|---|---|---|
| 151 | テキストでもPseudo Labelingは効くか | 閾値3種すべてでbaseline(40件のみ 0.7418)を下回る。疑似ラベル精度89.95%でもtest 0.6236 |
| 152 | クラスタで多様性を持たせれば直るか | post-filter 0.6291。top-N(0.6236)とほぼ同じ |
| 153 | フィルタ前クラスタリングなら直るか | pre-filter 0.6327。同数ランダム選択が0.6800で最良 |
| 154 | クラスタ内の選び方を変えたら | (G)クラスタ内Uncertainty 0.6800。疑似ラベル精度0.5906なのにtestは最良 |
| 155 | 反復でもGの優位は続くか | D 0.6327→0.5455(-13.8%)、G 0.6800→0.6455(-5.1%) |
| 156 | quota配分ルールで結論は変わるか | gapが1ポイント前後動くだけで結論は不変 |
| 157 | 打ち切りはオラクルと検証accのどちらか | val_esはD・Gとも全seedでround1打ち切り。oracle_esはround2を採用し0.5705 |
| 158 | 「round1が最適」は一般構造か | 選択基準ごとに残プールの難易度が逆方向に偏る(D難化、G易化) |
| 159 | Dの劣化は新規エラーか既存エラー強化か | 両方(137 vs 112件)。既存誤答の確信度低下は0件 |
🔎
統合する前の監査で見つかったこと。 Day157の本文は
val_esを「真のラベル不要、実運用でも使える」と説明していたが、コードは次のようにtest_dfの真のラベルでaccを計算して打ち切っていた。Kaggleなら「Private LBを見ながらラウンド数を決める」のと同じで、提出前には原理的にできない。さらにround1を無条件採択していたので、「疑似ラベルを1件も足さない(r=0)」が比較候補に入っていなかった。acc_after, _, _ = train_and_eval(new_texts, new_labels, test_df) # ← 最終報告に使う test_df そのもの if stop_mode == "val" and prev_acc is not None and acc_after < prev_acc: break
🌊
世界の実物で確認する(2026-09-23参照)。
- Oliver ほか "Realistic Evaluation of Deep Semi-Supervised Learning Algorithms"(NeurIPS 2018, arXiv:1804.09170): ラベルなしデータを使わない単純なbaselineが十分に報告されていないと指摘。ラベルが少ない設定で大きな検証セットを使う非現実性も論じる
- Arazo ほか "Pseudo-Labeling and Confirmation Bias in Deep Semi-Supervised Learning"(IJCNN 2020, arXiv:1908.02983): 素朴な疑似ラベリングはconfirmation biasで誤った疑似ラベルに過学習する。Day155・159の現象の一次資料
🗂️ データ・評価スキーマ
| データ/用語 | 件数・型 | 役割 |
|---|---|---|
| labeled_df | 40件(text, label) | 真のラベルがある唯一のデータ。今日はrの選択にもこれだけを使う |
| unlabeled_df | 300件(textのみ使用) | 疑似ラベルを付けて学習に足す候補プール |
| test_df | 110件 | 最終報告だけに使う。選択には使わない(test覗きは比較用の上限としてのみ計算) |
| r | int 0〜5 | 疑似ラベルを足したラウンド数。r=0は疑似ラベルなし |
| test曲線 | float[6] | 40件全部から始め、各rのモデルでtest_dfを測ったacc |
| CV曲線 | float[5, 6] → 平均 float[6] | 40件を5-fold(1fold 8件)。学習側32件から疑似ラベルを作り直し、検証8件で測る |
| rの選び方 | testのラベルを使うか | r=0を候補に含むか |
|---|---|---|
| Day157ルール | 使う(打ち切り判断に) | 含まない |
| test覗き(peek) | 使う(argmax) | 含む — 提出前には使えない上限 |
| 正直なCV | 使わない | 含む(同点なら小さいr) |
📏 評価の仕組み — 40件CVは何件分の差まで見分けられるか
評価指標はaccuracy(正解数÷件数)。ただし検証1foldが8件だと、accは 0/8, 1/8, … 8/8 の0.125刻み、5fold平均でも0.025刻みにしかならない。下の図は、今日の実測差をこの目盛りに重ねたもの。
📐
r=0でのfold間標準偏差は0.141(実測)。CVが誤った2回は、どちらも1〜2目盛り(1〜2件)の差で決まっていた。Dのように4目盛り以上離れていれば5/5で正しく選べた。
🎯 問題
タスクA(戦略・監査)
1
Day151〜159の表からtestのラベルを見ないと出せなかった結論を1つ挙げ、それがフローチャートの分岐としてそのまま使えない理由を2〜3文で述べる
2
40件の5-fold CV(1fold 8件)で「どのrが最良か」を正しく選べるかを予想する。r=0とr=1の差がDとGでどれくらいだったかを手がかりに、DとGで選びやすさが違うかも述べる
タスクB(コーディング)
B1
ipl_curve(...): r=0〜5の各時点のモデルでeval_dfsの各データのaccを返す。プール枯渇・n_added=0なら残りを直前値で埋めるB2
cv_curve(...): StratifiedKFoldで40件を分け、foldごとに学習側だけからipl_curveを回すB3
day157_val_es(test_curve)とargmax_first(同点なら小さいr)を実装するB4
D・G × 5seedで、r=0/1/5・Day157ルール・test覗き・正直なCVのaccを集計する
B5
assert 6個: (a) r=1がDay155/157の0.6327/0.6800を再現 (b) r=0がDay151の0.7418を再現 (c) Day157ルールは全runでr=1 (d) D・Gともr=0 > Day157ルール (e) 正直なCVはDで5/5 r=0 (f) D・Gとも正直なCV > Day157ルール
B6
(戦略) 設計フローチャートを6ノード以内で書く。各ノードに根拠のDay番号と実測値を1つずつ添える
💡 ヒント
ヒント1方向性
「r=0を候補に入れる」だけで比較の意味が変わる。Day152以降はほとんど「疑似ラベルを使う手法どうし」の比較だった。また、40件全部で学習したモデルの疑似ラベルを各foldで使い回すと、検証foldのラベル情報が疑似ラベル経由で漏れる。
ヒント2アプローチ
Day159のループを
range(0, n_rounds+1)にして、ループ先頭のモデルを全評価データで測る。CVは0.025刻みで同点が多いので、同点なら小さいr(手間が少なく壊れにくい方)を選ぶ。test覗きは「上限」として並べ、CVがどれだけ近づけるかを見る。ヒント3コード骨格
def cv_curve(labeled_df, unlabeled_df, select_fn, seed, n_rounds=5, n_splits=5): skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=seed) fold_curves = [] for tr, va in skf.split(labeled_df["text"], labeled_df["label"]): tr_df, va_df = labeled_df.iloc[tr], labeled_df.iloc[va] fold_curves.append(ipl_curve(tr_df["text"], tr_df["label"], unlabeled_df, select_fn, seed, {"val": va_df}, n_rounds)["val"]) return np.vstack(fold_curves)
✅ 模範解答(要点。全文はMDファイル)
# make_text_dataset / split_seed / クラスタ選択(D,G) は Day151〜159 と同じ def ipl_curve(lab_texts, lab_labels, unlabeled_df, select_fn, seed, eval_dfs, n_rounds=5, threshold=0.60, n_clusters=8): cur_t, cur_l = list(lab_texts), list(lab_labels) remaining = unlabeled_df.reset_index(drop=True) curves = {k: [] for k in eval_dfs} for r in range(0, n_rounds + 1): # ★ r=0 (疑似ラベルなし) から測る vec, model = fit(cur_t, cur_l) for k, d in eval_dfs.items(): curves[k].append(acc_on(vec, model, d)) if r == n_rounds: break stop = len(remaining) == 0 if not stop: proba = model.predict_proba(vec.transform(remaining["text"]))[:, 1] n_added = int(((proba >= threshold) | (proba <= 1 - threshold)).sum()) stop = n_added == 0 if stop: for k in curves: curves[k] += [curves[k][-1]] * (n_rounds - r) break mask, pl = select_fn(model, vec, remaining, n_added, n_clusters, seed) cur_t += remaining.loc[mask, "text"].tolist(); cur_l += pl.tolist() remaining = remaining.loc[~mask].reset_index(drop=True) return {k: np.array(v) for k, v in curves.items()} def argmax_first(a): return int(np.flatnonzero(a == a.max())[0]) # 同点なら小さい r def day157_val_es(test_curve): # Day157 の再現: r=1 から開始 r = 1 while r + 1 < len(test_curve) and test_curve[r + 1] >= test_curve[r]: r += 1 return r # D・G × seed 0..4 で full = ipl_curve(40件, {"test": test_df, ...}), cv = cv_curve(...) # r_cv = argmax_first(cv.mean(axis=0)), r_peek = argmax_first(full["test"]), r_157 = day157_val_es(full["test"]) assert (res["r_day157"] == 1).all() assert (m["acc_r0"] > m["acc_day157"]).all() assert (res.loc[res.method == "D", "r_cv"] == 0).all() assert (m["acc_cv"] > m["acc_day157"]).all() print("[OK] 6個のassertすべて成立")
# ===== 実測(2026-09-23 / Python 3.13, scikit-learn 1.9.1, pandas 3.0.6, numpy 2.5.3 / 約21秒) =====
[手法別 5seed平均 test acc]
r=0 r=1 r=5 Day157ルール test覗き(上限) 正直なCV
D 0.7418 0.6327 0.5455 0.6327 0.7418 0.7418
G 0.7418 0.6800 0.6455 0.6800 0.7491 0.7255
選ばれたr: Day157ルール D [1,1,1,1,1] G [1,1,1,1,1]
test覗き D [0,0,0,0,0] G [0,0,0,0,1]
正直なCV D [0,0,0,0,0] G [0,0,2,0,0]
[OK] 6個のassertすべて成立
🎯
発見1: 10日間のどの手法も、疑似ラベルなし(r=0, 0.7418)に勝っていなかった。 10run中、r=0を上回ったのはG seed4のr=1(0.7364 vs 0.7000)の1件だけ。Day152以降は手法どうしの比較に移り、Day157の打ち切りもr=1から始めていたため、r=0が土俵の外にいた。
⚠️
発見2: testを見ない正直なCVの方が、testを見ていたDay157ルールより良かった。 D +0.1091、G +0.0455。覗き見の害より、候補からr=0を落としていた害の方が大きかった。
🔍
発見3: 40件CVは大差は見分けるが小差は見分けない。 Dは5/5正解。Gは2回誤判断(seed2: 2件分の差でr=2を選び −0.0818、seed4: 1件分の差でr=1の +0.0364 を逃す)。
📊 結果の可視化
🧭 設計フローチャート(タスクB6の解答例)
✂️
入れなかった分岐と、この図が当てはまらない前提。 閾値の選び方(Day151で3つとも負け、分岐にする根拠がない)と
abs_margin(Day158、合成データの生成規則を知っているから計算できた診断指標)は入れていない。今日の結論は「ラベル40件・TF-IDF+ロジスティック回帰・未ラベル300件・語彙37語の合成データ」でのもの。ラベルなしデータが桁違いに多い、モデルが大きい、強い拡張を併用する(Arazoらはmixupでconfirmation biasを抑えている)前提ではN1で「r>0」側に進む可能性が高い。一般化してよいのはN0・N1の手順であって、「r=0が勝つ」という結論ではない。🪜 Step-by-Step 解説
1
結論を並べる前に、根拠の出どころを監査する。 説明文(「真のラベル不要」)とコード(
test_dfでacc計算)が食い違っていた。文章ではなくコードを読み、どのラベルを見て判断しているかを確かめるのが監査の中身。2
r=0を曲線の最初の点にする。 ループを
range(0, …)にするだけで「疑似ラベルを使う価値があるか」が他のrと同じ物差しで測れる。実測ではこの1点(0.7418)が曲線全体の最大値だった。3
CVの中で疑似ラベルを作り直す。 40件全部で学習したモデルの疑似ラベルを使い回すと、検証foldの情報が漏れてCVが楽観的になる。計算は5倍(約21秒)だが、この規模なら迷う理由はない。
4
CVが外れた2回を件数に直して読む。 どちらも1〜2件で決まった判断だった。ここからN1の「差0.05(2件)未満ならr=0」を書いた。ただしこのしきい値は今日の10runだけから決めたもので、別データでは確かめていない。
迷った他案と採らなかった理由
| 他案 | 利点 | 採らなかった理由 | 前提が変わったら |
|---|---|---|---|
| 40件を学習30+検証10のホールドアウトに分ける | 計算が1/5 | accが0.1刻みでCVよりさらに粗く、学習側も減る | ラベルが数千件あればホールドアウトで十分 |
| 未ラベルから数十件に人手でラベルを付け検証にする | 判断が安定する | 「ラベル40件しかない」前提を変えてしまう | アノテーション予算があれば最も確実 |
| testを半分に分け、前半で選び後半で報告 | 覗き見の楽観バイアスを測れる | r=0が大差で最良のため両半分とも同じrを選び、バイアスは測れなかった(前半0.7164/後半0.7673の差は分割のばらつき) | r同士の差が小さいデータなら有効 |
🧮 数学・統計の補足(文系向け)
📝
8問の小テストで考える。 8問の小テストを5回やって平均を出すと、点数は1問12.5点刻み、5回平均でも2.5点刻み。2人の平均が2.5点違っても40問中1問の差で、たまたまの可能性が十分ある。Dのように差が10点以上なら見分けられるが、Gのように数点だとよく間違える。
🎟️
testを覗いて選ぶと良く見える理由をくじ引きで考える。 6本のくじ(r=0〜5)を同じ110人に試してもらい一番よかったものを選ぶと、成績には「本当に良い分」と「その110人と相性が良かった分」が混ざる。選ぶのに使った110人の成績をそのまま報告すると、後者の分だけ良く見える。
🏆 Kaggleでの実践的な使い方
| 原則 | 具体的な活用例 |
|---|---|
| Pseudo Labelingを入れる前に、同じCVで「入れないモデル」を並べる | 今日のようにr=0が一番良いことは普通にある。差を見ずに入れると、効いているかどうか分からない |
| 疑似ラベルはfoldごとに作る | 疑似ラベルを作ったモデルが検証foldで学習していると、CVが実際より良く見え、CVとLBがずれる |
| Public LBを見ながらラウンド数を決めない | Day157のval_esはtestを見て止めていた。コンペで同じことをするとPrivate LBでのshake downの原因になる |
| 差が小さいならやらない、を既定値にする | CV差がサンプル1〜2件分しかないなら、手間が増え壊れやすい方(疑似ラベルあり)を選ばない |
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| 総括は過去の結論を並べ直せばよい | 一度確かめた結論は正しいと思い込む | 並べる前に根拠の出どころを監査する。今日はDay157の説明とコードの食い違いが見つかった |
| 手法どうしの比較で勝った方を「効く手法」と呼ぶ | 比較の土俵に何を入れたかを忘れる | r=0を入れないと、勝った手法もbaselineに負けていることがある(G 0.6800 < 0.7418) |
| testを見ないCVは、testを見るより必ず悪い | 情報が少ない方が不利だと考える | 正直なCV(D 0.7418)がtestを見たDay157ルール(0.6327)より良かった。候補の作り方の方が効いた |
| CVで少しでも高いrを選べばよい | 平均値の大小だけを見る | 40件CVは0.025刻み、fold間標準偏差0.141。G seed2は2件差でr=2を選び0.0818失った |
| 「疑似ラベルは使わない方がよい」を一般則として覚える | 総括の結論は一般則だと思いやすい | 小さな合成データ・線形モデル・拡張なしでの結論。一般化してよいのは手順(N0・N1) |
🚀 次のステップ
- 発展: N1のしきい値「差0.05未満ならr=0」を、seed数50で「CVでの差」と「testでの差」の散布図にして測り直す。Repeated Stratified K-FoldでCVの粗さがどれだけ減るかも確かめる
- 次回予告: Day161では、ラベル付き件数を40→400に増やしたとき、r=0とr>0の優劣が入れ替わるか、入れ替わるならどの件数からかを測り、今日の結論が「データが少ないとき専用」なのかを確かめる
Phase 6 の学習マップ
1-23Phase6主要テーマ・総復習(Day126-149、完了)
発展①〜⑪NLP系コンペへの応用(Day160: 根拠を監査し、設計フローチャートに統合)
📝 自己評価(解いた後に記入)
自分の回答・気づき・メモ: