解説一覧

修士論文: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倍速く動きました。

リポジトリのコード

src/xai/metrics.pyランダムなベースラインと比べて測る忠実性
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
  1. 4行目. 元の文書に対する予測クラスの確率です。すべての比較の基準になります。

  2. 11行目. ランダムなベースライン:同じ削除をランダムな単語の順番で行い、まったく同じパイプラインに通します。

  3. 13行目. 包括性(comprehensiveness):説明が指し示す上位k個の単語を削除します。確率が大きく下がるほど、説明は忠実だということになります。

  4. 19行目. すべての文書のすべてのバリアントを、1つのバッチでGPUに通します。

今の状況

2026年8月に提出し、結果はまだ発表されていません。すべての表と図は、コミット済みのスクリプトと固定したシードから再現できます。今は指導教員と一緒に、学会論文にまとめています。

次の解説FitEscala