Day 153 — NLP系コンペへの応用④ — 「フィルタ前クラスタリング」は選択バイアスを本当に緩和するのか

2026-09-12 金 / Phase 6(Grandmaster、総復習後の発展回) コーディング→分析 pre-filter clustering / overlap診断 / ランダム選択が最強という逆転

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

🔁
Day152では、「確信度が高い候補プール(threshold=0.55以上)」の内部をKMeansでクラスタリングし、クラスタごとに均等割当して選ぶdiversity-aware選択(post-filter方式)を実装したが、同じ候補プール・同じ件数のプレーンなtop-N選択(acc 0.6236)とほぼ差がなかった(acc 0.6291、差0.0055)。原因として「候補プール自体がすでに確信度でフィルタされた偏った母集団であり、その内部でクラスタリングしても母集団の偏りは解消されない」という考察が示された。

今日はDay152の次回予告どおり、この考察を検証するために「フィルタを適用する前の未ラベルデータ全体」をクラスタリング対象にする方式(pre-filter diversity)を実装する。理屈上は、これによって「そもそも確信度0.55未満で候補プールに入れなかったクラスタ」からも強制的にサンプルが選ばれるようになるはずである。さらに今日は、比較対象に「ランダム選択+モデル自身の疑似ラベル」も追加する。Day151のランダム選択は正解ラベル(オラクル)を使う特殊条件だったため、今日は自己学習の枠組みを揃えたランダム比較を新たに行う。

🧪
検証前の仮説: フィルタ前の未ラベルデータ全体でクラスタリングすれば、post-filter方式(Day152, acc 0.6291)よりも明確にtest精度が改善するだろう。
用語意味
post-filter diversityDay152の方式。確信度でフィルタした後の候補プール内でクラスタリングする
pre-filter diversity今日の方式。確信度でフィルタする前の未ラベルデータ全体でクラスタリングする
クラスタ内確信度優先クラスタのquota分を選ぶ際、そのクラスタの中で確信度が高い順に埋めていくルール
overlap(選択集合の重なり)2つの選択方式(post-filter/pre-filter)がどれだけ同じサンプルを選んでいるかを測る指標

🗂️ データ・分割のスキーマ

変数名型内容
df (n=450)pd.DataFrameDay151・152と同じmake_text_dataset()で生成した合成テキスト全体
test_df (110件)pd.DataFrame最終評価専用のホールドアウト。全設定で共通に使う
labeled_df (40件)pd.DataFrame「ラベル付きデータが少ない」状況を模したベースライン学習セット
unlabeled_df (約300件)pd.DataFrameラベルを隠した疑似ラベル候補プール。今日はこの全体がクラスタリング対象になる
threshold           = 0.60  # n_addedを決める基準(Day151/152と同一)
candidate_threshold = 0.55  # post-filter方式(Day152)が候補プールを作る閾値
n_clusters          = 8     # KMeansのクラスタ数(post-filter/pre-filter共通)

🎯 問題

タスクA(理論)

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

1
フィルタ前の未ラベルデータ全体をクラスタリング対象にすれば、理屈上どのような経路で「確信度0.55未満のクラスタからも選ばれるようになる」と期待できるのかを説明する
2
一方で、「クラスタ内では確信度が高い順に選ぶ」というルールを維持したままだと、この期待した効果がどのような条件で発動しない可能性があるかを予想する(ヒント: クラスタの中身の構成に注目する)

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

B1
make_text_dataset・split_seed・train_and_eval・select_confident_pseudo_labelsはDay151・152と同じ実装を再利用する
B2
select_diverse_postfilter(model, vectorizer, unlabeled_df, candidate_threshold, n_target, n_clusters, seed) — Day152と同じ実装(候補プール内でクラスタリング)を再利用する
B3
select_diverse_prefilter(model, vectorizer, unlabeled_df, n_target, n_clusters, seed) — 確信度による事前フィルタを一切かけず、unlabeled_df全体のTF-IDFベクトルをKMeansでクラスタリングする。各クラスタに均等quotaを割当て(余りは平均確信度が高いクラスタから+1)、クラスタ内は確信度が高い順に選ぶ
B4
順序整合性のルール(Day152で修正した「mask経由でproba・pseudo_labelsを取得する」)を今日も必ず守ること
B5
threshold=0.60・candidate_threshold=0.55・n_clusters=8で、5シードにわたり (A) 固定選択, (C) post-filter, (D) pre-filter, (E) 同数ランダム+自己ラベル、の4パターンを比較する
B6
(D)の平均test精度がbaselineより悪化すること/(C)と(D)の平均test精度差の絶対値が0.02未満であることをassert文で確認する
B7
追加診断: (C)と(D)の選択集合の重なり(overlap)と、選ばれたサンプルの平均確信度を5シードで計算し、なぜ似た結果になった(あるいはならなかった)のかを数値で確認する
B8
(考察): タスクAの2.の予想が実測とどの程度一致したかをoverlap・平均確信度の診断結果と関連づけて述べる。(E)ランダム選択+自己ラベルの結果が(A)(C)(D)と比べてどうだったかにも触れる

💡 ヒント

ヒント1方向性

タスクAの2.は、「クラスタの中身の構成」を考えるとよい。もしあるクラスタが、たまたま確信度0.55以上のサンプルだけで既にquota分を満たせてしまうなら、「クラスタ内で確信度が高い順に選ぶ」というルールのもとでは、そのクラスタから低確信度サンプルが選ばれる出番は一切来ない。つまり「フィルタ前の全体をクラスタリング対象にする」だけでは不十分で、クラスタ内の選び方そのものが確信度優先である限り、多くのクラスタで実質的にpost-filterと同じサンプルが選ばれてしまう可能性がある。

ヒント2アプローチ
  • overlapは(C)と(D)それぞれのmaskを&演算子でAND結合し.sum()を取ればよい: overlap = (mask_div & mask_pre).sum()
  • 平均確信度はconfidence = np.abs(proba - 0.5)を計算し、confidence[mask]の平均を取ればよい
  • 「候補プールに入らなかった低確信度サンプルがpre-filter選択にどれだけ含まれたか」はconfidence[mask_pre] < (candidate_threshold - 0.5)の件数を数えるとよい
  • 5シードのループをrun_comparison(seed, ...)のように関数化し、pd.DataFrame([...])でまとめると集計しやすい(Day151・152と同じパターン)
ヒント3コード骨格
def select_diverse_prefilter(model, vectorizer, unlabeled_df, n_target, n_clusters, seed):
    X_un = vectorizer.transform(unlabeled_df["text"])
    proba = model.predict_proba(X_un)[:, 1]
    n = len(unlabeled_df)
    n_target = min(n_target, n)

    k = min(n_clusters, n)
    km = KMeans(n_clusters=k, random_state=seed, n_init=10)
    cluster_ids = km.fit_predict(X_un)  # ★候補プールでなく全体でクラスタリング

    confidence = np.abs(proba - 0.5)
    order = np.argsort(-confidence)  # 全データを確信度降順に並べる
    quota = n_target // k
    remainder = n_target - quota * k
    cluster_quota = {c: quota for c in range(k)}
    # ... quota配分・クラスタ内選択はDay152と同じロジック ...

    selected_idx = np.sort(np.array(selected, dtype=int))  # 昇順に戻す
    mask = np.zeros(n, dtype=bool)
    mask[selected_idx] = True
    pseudo_labels = (proba[mask] >= 0.5).astype(int)  # mask経由で取得
    return mask, pseudo_labels

✅ 模範解答

# POSITIVE_WORDS / NEGATIVE_WORDS / NEUTRAL_WORDS / make_text_dataset / train_and_eval /
# split_seed / select_confident_pseudo_labels / select_diverse_postfilter は
# Day151・152と同一(省略)

def select_diverse_prefilter(model, vectorizer, unlabeled_df, n_target, n_clusters, seed):
    """Day153: クラスタリング対象は「未ラベルデータ全体」(確信度フィルタをかける前)"""
    X_un = vectorizer.transform(unlabeled_df["text"])
    proba = model.predict_proba(X_un)[:, 1]
    n = len(unlabeled_df)
    n_target = min(n_target, n)

    k = min(n_clusters, n)
    km = KMeans(n_clusters=k, random_state=seed, n_init=10)
    cluster_ids = km.fit_predict(X_un)

    confidence = np.abs(proba - 0.5)
    order = np.argsort(-confidence)
    quota = n_target // k
    remainder = n_target - quota * k
    cluster_quota = {c: quota for c in range(k)}
    cluster_conf_rank = pd.Series(confidence, index=cluster_ids).groupby(level=0).mean().sort_values(ascending=False)
    for c in list(cluster_conf_rank.index)[:remainder]:
        cluster_quota[c] += 1

    taken = {c: 0 for c in range(k)}
    selected = []
    for i in order:
        c = cluster_ids[i]
        if taken[c] < cluster_quota[c]:
            selected.append(i)
            taken[c] += 1
        if len(selected) >= n_target:
            break

    selected_idx = np.sort(np.array(selected, dtype=int))
    mask = np.zeros(n, dtype=bool)
    mask[selected_idx] = True
    pseudo_labels = (proba[mask] >= 0.5).astype(int)
    return mask, pseudo_labels


def run_comparison(seed, threshold=0.60, candidate_threshold=0.55, n_clusters=8):
    labeled_df, unlabeled_df, test_df = split_seed(seed)
    acc_base, ll_base, vectorizer, model = train_and_eval(
        labeled_df["text"].tolist(), labeled_df["label"].tolist(), test_df)

    mask_conf, pseudo_conf = select_confident_pseudo_labels(model, vectorizer, unlabeled_df, threshold)
    n_added = int(mask_conf.sum())
    acc_conf, _, _, _ = train_and_eval(
        labeled_df["text"].tolist() + unlabeled_df.loc[mask_conf, "text"].tolist(),
        labeled_df["label"].tolist() + pseudo_conf.tolist(), test_df)

    # (C) Day152: post-filter diversity(候補プール内クラスタリング)
    mask_div, pseudo_div = select_diverse_postfilter(
        model, vectorizer, unlabeled_df, candidate_threshold, n_added, n_clusters, seed)
    acc_div, _, _, _ = train_and_eval(
        labeled_df["text"].tolist() + unlabeled_df.loc[mask_div, "text"].tolist(),
        labeled_df["label"].tolist() + pseudo_div.tolist(), test_df)

    # (D) Day153: pre-filter diversity(未ラベルデータ全体クラスタリング)
    mask_pre, pseudo_pre = select_diverse_prefilter(
        model, vectorizer, unlabeled_df, n_added, n_clusters, seed)
    acc_pre, _, _, _ = train_and_eval(
        labeled_df["text"].tolist() + unlabeled_df.loc[mask_pre, "text"].tolist(),
        labeled_df["label"].tolist() + pseudo_pre.tolist(), test_df)

    # (E) 参考: 同数ランダム選択 + モデル自身の疑似ラベル(自己学習の枠組みを揃える)
    rng = np.random.RandomState(seed)
    rand_idx = np.sort(rng.choice(len(unlabeled_df), size=n_added, replace=False))
    X_un = vectorizer.transform(unlabeled_df["text"])
    proba = model.predict_proba(X_un)[:, 1]
    mask_rand = np.zeros(len(unlabeled_df), dtype=bool)
    mask_rand[rand_idx] = True
    pseudo_rand = (proba[mask_rand] >= 0.5).astype(int)
    acc_rand, _, _, _ = train_and_eval(
        labeled_df["text"].tolist() + unlabeled_df.loc[mask_rand, "text"].tolist(),
        labeled_df["label"].tolist() + pseudo_rand.tolist(), test_df)

    # 追加診断: overlap・平均確信度
    conf = np.abs(proba - 0.5)
    overlap = int((mask_div & mask_pre).sum())

    return {"seed": seed, "acc_base": acc_base, "acc_conf": acc_conf,
            "acc_div": acc_div, "acc_pre": acc_pre, "acc_rand": acc_rand,
            "overlap_frac": overlap / n_added}


SEEDS = [0, 1, 2, 3, 4]
df = pd.DataFrame([run_comparison(s) for s in SEEDS])

mean_acc_base = df["acc_base"].mean()
mean_acc_div = df["acc_div"].mean()
mean_acc_pre = df["acc_pre"].mean()

assert mean_acc_pre < mean_acc_base, "pre-filter diversity選択がbaselineを悪化させていません"
assert abs(mean_acc_pre - mean_acc_div) < 0.02, \
    "pre-filterとpost-filterのtest精度差が0.02以上あります"
print("[OK] pre-filter化しても選択バイアスは明確には緩和されない")
# ============ 実測結果(実際にPython 3で実行して確認済み) ============
#  seed  n_added  acc_base  acc_conf  acc_div  acc_pre  acc_rand  overlap_frac  n_low_conf_in_pre
#     0       94  0.763636  0.581818 0.590909 0.600000  0.709091       0.861702                  0
#     1      125  0.727273  0.590909 0.618182 0.618182  0.600000       0.816000                  5
#     2       81  0.781818  0.645455 0.645455 0.654545  0.700000       0.839506                  0
#     3       75  0.736364  0.672727 0.663636 0.681818  0.690909       0.840000                  0
#     4       78  0.700000  0.627273 0.627273 0.609091  0.700000       0.858974                  0
#
# [まとめ]
#   mean acc_base = 0.7418
#   mean acc_div  (Day152 post-filter) = 0.6291  (pseudo_acc=0.8795)
#   mean acc_pre  (Day153 pre-filter)  = 0.6327  (pseudo_acc=0.8674)
#   mean acc_rand (同数ランダム+自己ラベル) = 0.6800  (pseudo_acc=0.7228)
#   acc_pre - acc_div  = 0.0036(0.02未満)
#   mean overlap_frac(post-filterとpre-filterの選択集合の重なり) = 0.8432
#   mean n_low_conf_in_pre(候補プール外だった低確信度点の混入数)= 1.0 / 平均n_added≈91件
#
# [OK] pre-filter化しても選択バイアスは明確には緩和されない
⚠️
重要な発見1(当初の仮説がまたも支持されなかった): 検証前の仮説は「フィルタ前でクラスタリングすればpost-filter方式より明確に改善するはず」だった。しかし実測ではpre-filter方式(0.6327)はpost-filter方式(0.6291)とほぼ同水準で、差はわずか0.0036。両者ともbaseline(0.7418)を大きく下回ったままで、フィルタを外しても選択バイアスはほとんど緩和されなかった。
🔎
重要な発見2(原因を診断指標で特定できた): post-filterとpre-filterの選択集合は平均84.3%が重なっており、「候補プールに入れなかったはずの低確信度サンプル」がpre-filter選択に混入した件数は5シード平均でわずか1件(1シード平均選択数≈91件)にとどまった。「クラスタ内では確信度優先で選ぶ」というルールを維持したため、ほとんどのクラスタで既に確信度0.55以上のサンプルだけでquotaが満たせてしまい、低確信度サンプルの出番がほとんど来なかったことが原因である。
🎲
重要な発見3(最大の驚き: 単純なランダム選択が全ての洗練された手法に勝った): 今日追加した(E)「同数ランダム選択+自己ラベル」は平均acc 0.6800と、確信度選択(0.6236)・post-filter(0.6291)・pre-filter(0.6327)のどれよりも高かった。しかもランダム選択の疑似ラベル精度(0.7228)は他手法(87〜90%)より明らかに低い——「ラベルの正確さ」より「どのサンプルを選ぶか」が支配的であることを、オラクルという特殊条件なしで直接示した結果である。

📊 結果の可視化

① フィルタを外す

候補プールでなくunlabeled_df全体をKMeansでクラスタリング

② それでも確信度優先で選ぶ

クラスタ内では相変わらず確信度が高い順にquotaを埋める

③ overlapで検証

post-filterとの選択集合の重なりを数値で確認し、効果の有無を判定

4手法の accuracy 比較(5シード平均、いずれもモデル自身の疑似ラベルで自己学習) baseline(40件のみ) 0.7418 (E) ランダム選択+自己ラベル 0.6800(4手法中最良) (D) pre-filter diversity 0.6327 (C) post-filter diversity 0.6291 (A) 確信度固定選択(0.60) 0.6236 (C)と(D)の差はわずか0.0036。overlap_frac=84.3%が示す通り、ほぼ同じサンプルを選んでいる。 一方(E)は疑似ラベル精度で劣る(0.7228 vs 87〜90%)のに、選び方だけで(A)(C)(D)全てを上回った。
設定平均accuracy疑似ラベル精度解釈
baseline(40件のみ)0.7418—追加データなし
(E) ランダム選択+自己ラベル0.68000.7228(最低)ラベルは不正確でも情報量で優る
(D) pre-filter diversity(未ラベル全体クラスタリング)0.63270.8674postとほぼ同じ、明確な改善なし
(C) post-filter diversity(候補プール内クラスタリング)0.62910.8795Day152の結果
(A) 確信度固定選択(threshold=0.60)0.62360.89954手法中最低

🪜 Step-by-Step 解説

1「フィルタを外す」だけでは不十分だった理由を数値で確認する

mean overlap_frac      = 0.8432  # post-filterとpre-filterの選択集合の84.3%が同じ
mean n_low_conf_in_pre = 1.0     # 候補プール外だったはずの低確信度点はわずか1件混入
📋
なぜこうするか: 「フィルタを外したのに結果が変わらなかった」という現象は、コードのバグか設計上の必然かを切り分ける必要がある。overlapと低確信度混入数という2つの診断指標を計算することで、「クラスタリング対象を全体に広げる」という変更自体は正しく動いている(コードは正常)が、「クラスタ内では確信度優先で選ぶ」というもう1つのルールがその変更の効果を実質的に打ち消していた、という設計上の必然であることが明確になった。「仮説が外れた」で終わらせず、外れた理由を追加の診断指標で特定することが、改善策が効かなかった実験を教材として無駄にしないための鍵になる。

2ランダム選択がpseudo_accで劣るのにtest accで勝った理由

pseudo_acc_rand = 0.7228  # ランダム選択の疑似ラベル正確さ(低い)
pseudo_acc_div  = 0.8795  # post-filter diversityの疑似ラベル正確さ(高い)
acc_rand = 0.6800          # ランダム選択のtest精度(高い)
acc_div  = 0.6291          # post-filter diversityのtest精度(低い)
💡
なぜこうするか: 直感的には「疑似ラベルが正確なほど、追加したときの効果も高いはず」と考えたくなるが、今日の実測はこの直感を裏切った。確信度で選んだサンプル集合は「モデルが既に得意な、決定境界から遠いサンプル」に偏っているため、疑似ラベルが正確でもモデルが既に知っている情報の重複にしかならず、汎化性能の向上に寄与しにくい。一方ランダム選択は、一部の疑似ラベルが誤っていても決定境界付近の「モデルがまだよく分かっていない」領域の情報を含むため、学習データとしての情報量が大きい。「ラベルの正確さ」と「サンプルの情報量」はトレードオフになりうる。

33日間の実験(Day151〜153)が一貫して示した結論

Day151: オラクルでも選択バイアスは緩和されない (acc 0.7291 ≪ ランダムオラクル 0.8818)
Day152: post-filterクラスタリングでも緩和されない (acc 0.6291、top-Nと差0.0055)
Day153: pre-filterクラスタリングでも緩和されない (acc 0.6327、post-filterと差0.0036)
        → ランダム選択+自己ラベルが唯一baselineとの差を縮めた (acc 0.6800)
🧭
なぜこうするか: 3日間にわたって「選択バイアスを緩和する」というアイデアを異なる角度(オラクル比較・post-filterクラスタリング・pre-filterクラスタリング)から検証し、いずれも明確な改善に至らなかった。これは失敗の繰り返しに見えるが、実際には「確信度ベースの選び方そのものが問題の根源であり、選び方をどう工夫しても効果は限定的で、ランダム性を持ち込むことが最も効果的である」という3日間の実験で一貫して支持される結論にたどり着いている。異なる手法で同じ結論に複数回到達することが、結論の信頼性を高める。

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

📝
「クラスタ内で確信度優先」がフィルタ除去の効果を打ち消す様子を「面接の一次選考」として理解する:

「学歴で一次選考した後の候補者だけで多様性を確保しよう」というやり方(post-filter)がうまくいかなかったので、「全応募者を対象に出身地域ごとにグループ分けして各地域枠を作ろう」というやり方(pre-filter)に変えたとする。しかし各地域グループの中でも「結局は学歴が高い人から選ぶ」というルールを変えていなければ、多くの地域グループには学歴の高い応募者が既に十分な人数いるため、結局ほとんど同じ顔ぶれが選ばれてしまう。「対象を広げる」ことと「選ぶ基準を変える」ことは別の話であり、両方を変えない限り根本的な偏りは解消されない、という直感である。
📊
「疑似ラベルの正確さとサンプルの情報量のトレードオフ」を「簡単な問題集と難しい問題集」として理解する:

簡単な問題集(確信度の高いサンプル)を完璧に解けても、もう知っていることの復習にしかならず、実力(汎化性能)は伸びにくい。一方、難しい問題集(ランダムに含まれる決定境界付近のサンプル)は間違えることもあるが、間違えた問題こそ「自分がまだ分かっていないこと」を教えてくれるため、多少の誤答があっても総合的な実力向上には効果的なことがある、という直感である。

🏆 Kaggleでの実践的な使い方

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

原則

🔍 「対策が効かなかった理由」まで診断する

overlap_fracのような追加指標

新しい特徴量・対策を追加してスコアが変わらなかったとき、「アイデアが悪かったのか」「別のルールが効果を打ち消していたのか」を切り分ける診断指標を用意しておくと、次の改善につながる。

実例

🎲 確信度ベースの選択に頼りすぎない設計

3日間、確信度ベースの工夫はすべて限定的

閾値調整・post-filterクラスタリング・pre-filterクラスタリングのいずれも選択バイアスの根本解消には至らなかった。ランダム性やUncertainty Samplingを組み合わせる設計の方が有望である可能性が示唆される。

実務

⚖️ 「正確さ」と「情報量」を別軸で評価する

pseudo_acc単独では判断しない

追加データの評価において「正確さ」だけでなく「モデルにとって新しい情報を含むか」も評価軸に入れることで、正確なのに効果が薄いという今日のような逆転現象を見落とさずに済む。

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
「対象範囲を広げる」変更をすれば効果が出るはずだと考える「フィルタを外した」という変更自体に注目し、他のルール(クラスタ内選択基準)によって打ち消される可能性を見落とす複数のルールが組み合わさっている処理では、1つのルールだけを変えても他のルールが効果を吸収してしまうことがある。変更後は必ず「意図した効果が実際に発動しているか」を診断指標で確認する
疑似ラベルの正確さ(pseudo_acc)が高いほど追加データの効果も高いと考える「正しいラベル=良い学習データ」という直感が強く働く正確でも情報量が少ない(モデルが既に知っている領域に偏る)サンプルより、多少不正確でも情報量が多い(決定境界付近の)サンプルの方が汎化性能向上に寄与することがある
同じ結論に複数回到達した実験を「新しい発見がない」と評価する目新しい結果(=改善した、という結果)が出ないと実験に価値がないと感じてしまう異なる手法(オラクル比較・post-filter・pre-filter)で一貫して同じ結論に到達することは、その結論の信頼性を高める重要な証拠であり、失敗の繰り返しではない
クラスタリングを使えば自動的に「多様性」が確保されると考えるKMeansが特徴量空間を分割する手法であることから、分割=多様性確保だと単純化してしまうクラスタリングは「空間をグループに分ける」だけであり、各グループから何を基準に選ぶか(今日の場合は確信度優先)まで変えない限り、多様性は保証されない

🚀 次のステップ

  • 発展: 今日の考察を踏まえると、真に選択バイアスを緩和する設計は「クラスタ内でも確信度優先ではなく、クラスタごとにランダムに選ぶ」、あるいは「クラスタ内で確信度が中央値(0.5)に近いサンプルを優先するUncertainty Sampling」に切り替えることである可能性が高い
  • 次回予告: Day154では、上記の「クラスタ内ランダム選択」または「クラスタ内Uncertainty Sampling」を実装し、3日間(Day151〜153)効果が出なかった選択バイアス対策に、初めて明確な改善をもたらせるかを検証する

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

1-23
Phase6主要テーマ6項目・総復習(Day126-149、完了)
発展①〜④
NLP系コンペへの応用(Day150: TTA逆効果/Day151: 選択バイアス発見/Day152: post-filter対策も限定的/Day153: pre-filter対策も限定的、ランダムが最良)

Day150→153と4日連続で、「表形式・画像で効いた手法をそのままテキストに持ち込むと期待通りには機能しない」というパターンが実測された。今日は洗練された対策(pre-filter clustering)ですら単純なランダム選択に及ばないという、直感に反する結果に行き着いた。

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

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