scikit-learnとjanomeを使ってスパムメール判別器を作る

scikit-learnとjanomeを使ってスパムメール判別器を作る

環境情報

MacOS Mojave
Python 3.6.8
pandas 0.24.1
Janome 0.3.9
sklearn 0.0

はじめに

機械学習の勉強中のオリジナルプログラム第1弾として、日々行なっている広告メールとスパムメールのチェックを機械学習で自動でやっちゃおうというものです。

まずは、単純にメールの件名のみを特徴量とする単純なものです。
ですが、これだけでいいんじゃないかというほど、よく判別してくれます。これが機械学習というやつなのですね。

ファイル等の構成情報

.
├── spamcheck_mlearn.py  今回のメインスクリプト
├── out-csv              出力結果格納用ディレクトリ(csv)
└── train                訓練用csv格納ディレクトリ(csv)

csvファイルの構成(入力、出力)

入力用(訓練用)のcsvファイルと、出力用のcsvファイルは同じ構成です。

TimestampADSPAMMessageSubjectRecipientsSendor
2019/07/2910メール件名受信者送信者

広告メールにはADカラムに1と、スパムメールにはSPAMカラムに1と立てます。そうでない場合は両方とも0になります。

簡単な解説

今回は、janomeという形態素解析器を使ってプログラムを作成します。形態素解析ではMeCabなどが有名ですが、janomeの特徴としては、軽量で簡単に導入できるというところがあります。

次のようにして導入できます。

pip install janome

あとは、janomeによって形態素解析をしたあとに、Bag of Wordsという自然言語処理を行います。これは単純で効率が良い方法で、コーパスに現れた単語が対象となるテキストに何回現れたかをカウントし、ベクトル化したデータを特徴量として学習します。

ストップワードなど使って性能を高めたり、他の項目、例えば送信者のメールアドレスなどを特徴量に加えたりしようとも思いましたが、最初から結構いい感じで出来上がってしまったので、しばらくこれで運用してみます。

Pythonソース

#-*- coding:utf-8 -*-
import os
import glob
import pandas as pd
from janome.tokenizer import Tokenizer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
import numpy as np

datestr = "2019-07-29"

#訓練データ用データフレーム作成
os.chdir("/Users/waku/spam")
df = pd.DataFrame()
for f in glob.glob("./train/*.csv"):
    df = pd.concat([df, pd.read_csv(f)])

df = df[~pd.isnull(df.MessageSubject)]

# janomeトークナイザー作成
t = Tokenizer()
def tokenize(word):
    tokens = [token.surface for token in t.tokenize(word)]
    return " ".join(tokens)

# Bag of Wordsによる変換
Messages = df.MessageSubject.apply(tokenize)
vect = CountVectorizer()
vect.fit(Messages)


def learn_and_prediction(sec, secname, pdf):
    print("-" * 50)
    print(" {} Section".format(secname))
    print("-" * 50)

    # 訓練データ
    X_train = vect.transform(Messages)
    y_train = sec

    # 交差検証によるモデル評価
    scores = cross_val_score(LogisticRegression(), X_train, y_train, cv=5)
    print("Cross-validation scores: {}".format(scores))
    print("Mean cross-validation accuracy: {:.2f}".format(np.mean(scores)))

    # ロジスティック回帰モデルによる訓練
    lr = LogisticRegression()
    lr.fit(X_train, y_train)

    # 予測
    predmess = pdf.MessageSubject[~pd.isnull(pdf.MessageSubject)].apply(tokenize)
    X_new = vect.transform(predmess)
    prediction = pd.Series(lr.predict(X_new))

    return prediction


# 予測用ファイル
pdf = pd.read_csv("out-csv/{}mail-out.csv".format(datestr))

# AD Section
pdf.AD = learn_and_prediction(df.AD, "AD", pdf)

# SPAM Section
pdf.SPAM = learn_and_prediction(df.SPAM, "SPAM", pdf)

# NaN対応
pdf.AD[pd.isnull(pdf.AD)] = 0
pdf.SPAM[pd.isnull(pdf.SPAM)] = 0

# 型変換(int8に直す)
pdf = pdf.astype({'AD': 'int8', 'SPAM': 'int8'})
pdf.set_index("Timestamp")
pdf.to_csv("out-csv/{}mail-out.csv".format(datestr))

print("complete...")

プログラム中で、交差検証による制度測定を毎回行っています。
というのは、この出力結果後に、手動にて再チェックをしてADフラグとSPAMフラグを最終調整したcsvファイルをtrainディレクトリへ移動し、訓練用データとして再利用していくからです。これによって、学習精度がどのように育っていくか見ておきたいからです。

また、言い訳的解説としては、NaN対応のところでは、メールの件名が空のメールもよく来るため、このメールについては、判別できないためNaNが入ってしまい、次の型変換でこけてしまうため、無理やり0をセットしています。

まだ、機械学習自体駆け出しのため、これから良いものができたらまた掲載していきたいと思います。