はじめに
本記事では,迷惑メールの判別に使われるスパムフィルタを自作する方法を解説します.GmailやOutlookなどのスパムフィルタと比べると機能は劣りますが,スパムフィルタが内部でどのような動作をするのかということを理解する目的で実装します.
たとえば,以下のメールは迷惑メール(スパムメール)とみなせるかもしれません.
- 「あなた様限定!今なら最新のスマートフォンの機種代半額セール!」
- 「あなただけの限定プレゼント!詳細は以下のリンクから」
こうしたメールを安易に信用し,添付リンクやファイルを開くと詐欺被害,マルウェア被害に遭う可能性があります.受信したメールから迷惑メールを抽出するのがスパムフィルタの役割です.
本記事の内容は,まずPythonを用いてスパムフィルタを実装します.実装には自然言語処理のライブラリおよびscikit-learnを用います.さらに,付録としてスパムフィルタの数学的理論をざっくりとですが解説します.
Pythonによる実装
必要なライブラリは次の通りです.
- scikit-learn (https://scikit-learn.org/stable/modules/naive_bayes.html)
- MeCab (https://taku910.github.io/mecab/)
MeCabのインストール方法は適当に検索すると出てきます.私はuvでPythonの環境構築をするため,uv add mecabとしてインストールしました.次に,スパムフィルタのコードを示します.
import MeCab
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
tagger = MeCab.Tagger("-Owakati") # prepare MeCab
def tokenize(text):
return tagger.parse(text).strip().split()
# training data (0: Not spam, 1: Spam)
training_data = [
("明日の打ち合わせの件です。開始時間は13時で良いでしょうか?", 0),
("お疲れ様です。プロジェクトの進捗を報告します。", 0),
("来週のランチ、おすすめの場所はありますか?", 0),
("お疲れ様です。表題の件につきまして連絡です。", 0),
("完全無料で100万円が当たるチャンス!今すぐクリック", 1),
("スマホだけで月収50万稼げる秘密の方法を教えます", 1),
("驚きのダイエット効果!期間限定で割引中", 1),
("あなた様限定!70%OFFで最新の電子機器を購入できるチャンス!", 1)
]
texts, labels = zip(*training_data)
# get word vector
vectorizer = CountVectorizer(tokenizer=tokenize, token_pattern=None)
X_train = vectorizer.fit_transform(texts)
# training
model = MultinomialNB()
model.fit(X_train, labels)
# test
test_mails = [
"明日の会議の資料を送付します。ご確認お願いします。", # Not Spam
"無料でプレゼント!当選おめでとうございます!", # Spam
"私は警視庁詐欺対策課の佐藤です。あなたの口座が" # Spam(but not defined text)
]
# predict
X_test = vectorizer.transform(test_mails)
predictions = model.predict(X_test)
# show result
for mail, pred in zip(test_mails, predictions):
if pred == 1:
print(f"{mail} result: Spam")
else:
print(f"{mail} result: Not Spam")
コードの説明をします.まず必要なライブラリをインポートします.今回はscikit-learnにある,単語ベクトルを計算するモジュールのCountVectorizerとナイーブベイズ(スパムフィルタの核心部)を実装するためのMultinomialNBを用います.その後,訓練データをいくつか定義し,非スパムのデータには0,スパムのデータには1を付加します.実用面を考えると,データの量は多くあった方が良いです.そして,訓練データを単語ベクトル化し,ナイーブベイズに入力として与え学習させます.
学習後,テストデータとしていくつかのデータを定義します.今回は非スパム,スパム,スパムであるが学習データに含まれないものという3種類を用意します.最後に学習したモデルで分類をします.
上記コードを実行すると以下のような結果を得ます.
明日の会議の資料を送付します。ご確認お願いします。 result: Not Spam
無料でプレゼント!当選おめでとうございます! result: Spam
私は警視庁詐欺対策課の佐藤です。あなたの口座が result: Not Spam
注目すべき点は,スパムであるが学習データに単語が存在しないものは非スパムに分類されたことです.今回はナイーブベイズというアルゴリズムを用いてスパムフィルタを実装していますが,ナイーブベイズの出力は学習データのみに依存するため,学習データに含まれないスパムデータには対応できません.よってこのような結果となります.さらに精度を向上させ,幅広いスパムメールに対応するフィルタを作るためには学習データの種類(語彙)を増やす必要があります.
今回は,ナイーブベイズを用いた単純なスパムフィルタを実装しました.MeCabのような形態素解析ライブラリがあるため比較的容易に実装できたと思います.こうしたライブラリを作成してくださっている方には本当に感謝です.実際のメールサーバで動いているスパムフィルタは,ナイーブベイズ分類器を基盤としつつも,より高度なアルゴリズムが用いられているようです.
(付録)スパムフィルタの数学的理論
ここでは,スパムフィルタの基盤となっているナイーブベイズ(単純ベイズ)の数学的理論を解説します.あくまでざっくりとした解説なので厳密性に欠ける部分があるかもしれませんがご了承ください.
メールの文を$X=(x_1, x_2,...,x_n)$とし,出現確率が互いに独立であると仮定します(メールの文が$n$個の単語で構成されているということです).この,互いに独立という仮定がナイーブ(単純)なのでナイーブベイズという名前が付いています.また,スパム,非スパムのクラスをそれぞれ$Y_1,Y_2$とします.すると,メールがスパムである確率はベイズの定理により,
P(Y_1 | X) = \frac{P(Y_1)P(X|Y_1)}{P(X)}
となります.一方,スパムでない確率は,
P(Y_2 | X) = \frac{P(Y_2)P(X|Y_2)}{P(X)}
となります.したがって,$P(Y_1 | X)>P(Y_2 | X)$ならスパム,$P(Y_1 | X)<P(Y_2 | X)$なら非スパムというわけです.確率の計算はアンダーフローの懸念があるため,実用上は対数をとって計算します.すなわち,以下のようになります.
\text{arg max}_{y\in (Y_1, Y_2)}P(y)\sum_{i=1}^{n}P(x_i|y)
\therefore\text{arg max}_{y\in (Y_1, Y_2)}\log P(y) + \sum_{i=1}^{n}\log P(x_i|y)
以上がナイーブベイズの基本原理です.今回の実装で用いるscikit-learnのMultinomialNBでは単語の特徴ベクトルの分布に多項式分布を仮定し,計算を行います.
おわりに
今回はナイーブベイズフィルタを自作しました.最近は生成系AIのブームでこうした古典的な自然言語処理は影をひそめている気がしますが,今なお情報システムを支える大切な技術です.私は自然言語処理の素人ですが少しずつ知見を増やしていこうと思います.