修士論文:AIが書いたフィッシングメールの説明可能な検出
提出済み、結果待ちLiverpool John Moores Universityでの修士論文です。AIが書いたフィッシングメールを検出し、各検出器の説明が、その検出器が実際にしていることに忠実かどうかを測りました。
- 担当
- 著者:実験設計、コード、評価、執筆
- 制作年
- 2026
- 使用技術
- Python · scikit-learn · XGBoost · LightGBM · PyTorch · Transformers · SHAP · LIME · Integrated Gradients
課題
言語モデルが書いたフィッシングメールは、昔のテンプレートとは読み味が違います。また、誰も説明できない検出器は、信頼するのも監査するのも難しいです。そこで2つの問いを立てました。AIが書いたメールに対して、どの種類のモデルが通用するのか。そして、その説明はモデルが実際にしていることに忠実なのか。
どう作られているか
3つのモデルファミリーを同じデータ分割で比較しました。古典的なモデル(ロジスティック回帰、ナイーブベイズ、SVM、決定木、ランダムフォレスト)、勾配ブースティングのアンサンブル(XGBoost、LightGBM)、Transformer(BERT、DistilBERT、RoBERTa)です。従来のメールとAI生成メールのあいだで、コーパスをまたいだテストも行いました。SHAP、LIME、Integrated Gradientsによる説明は、ランダムな寄与度のベースラインと比べて、忠実性、安定性、もっともらしさで評価しました。プロトコルは実験を始める前に登録し、手がかり語の辞書は結果が1つも出ないうちにコミットしました。説明手法は陽性対照のテストスイートで検証し、もっともらしさの指標は人間の評価者とLLMによる判定と照らし合わせて確認しました。すべてローカルのRTX 3090で実行しました。
重要だった判断
実行前に登録
プロトコル、辞書、仮説は、結果が1つも出ないうちにコミットしました。そのため、あとから何かを選び直すことはできませんでした。
説明を測定した
ランダムな寄与度と同程度のスコアしか出ない手法は、そのとおりに報告します。LIMEのスコアがランダムと同程度になったとき、原因は私のLIMEアダプターのバグで、トークナイザーの引数が逆になっていました。そのことを開示し、修正して、もう一度実行しました。修正後のLIMEは忠実でしたが、手法の中で最も安定性が低いものでした。
シンプルなモデルが勝った
AI生成メールでは、チューニングした線形のTF-IDFモデルが、ROC-AUCでRoBERTaを上回りました(0.9993対0.9976)。また、SHAPはこのモデルを忠実に説明でき、修正後のLIMEより64.5倍速く動きました。
リポジトリのコード
def faithfulness(champ, texts, attrs, preds, seed: int = 42) -> dict:
rng = np.random.default_rng(seed)
preds = np.asarray(preds)
p_orig = _p_pred(champ.proba(texts), preds)1
variants: list[str] = []
meta: list[tuple] = [] # (doc_i, kind, k, is_random)
for i, (text, attr, pred) in enumerate(zip(texts, attrs, preds)):
nw = len(textops.word_spans(text))
top = textops.top_k_positions(attr, int(pred), min(20, nw))
randperm = rng.permutation(nw).tolist()2
for k in K_AOPC:
variants.append(textops.remove_words(text, top[:k])); meta.append((i, "comp", k, False))3
variants.append(textops.remove_words(text, randperm[:k])); meta.append((i, "comp", k, True))
for k in K_REPORT:
variants.append(textops.keep_only_words(text, top[:k])); meta.append((i, "suff", k, False))
variants.append(textops.keep_only_words(text, randperm[:k])); meta.append((i, "suff", k, True))
P = _p_pred(champ.proba(variants), preds[[m[0] for m in meta]])4
4行目. 元の文書に対する予測クラスの確率です。すべての比較の基準になります。
11行目. ランダムなベースライン:同じ削除をランダムな単語の順番で行い、まったく同じパイプラインに通します。
13行目. 包括性(comprehensiveness):説明が指し示す上位k個の単語を削除します。確率が大きく下がるほど、説明は忠実だということになります。
19行目. すべての文書のすべてのバリアントを、1つのバッチでGPUに通します。
今の状況
2026年8月に提出し、結果はまだ発表されていません。すべての表と図は、コミット済みのスクリプトと固定したシードから再現できます。今は指導教員と一緒に、学会論文にまとめています。