■scikit-learn(機械学習)でスパムメールを判定
以下からスパムメールと通常メールを取得
GitHub - kujirahand/spam-database-ja: Spam database for Japanese
https://github.com/kujirahand/spam-database-ja
not-spam-sjis をもとに、以下の手順で文字コード変換をして not-spam-utf8 を作成する
複数ファイルのエンコードの変換 - EmEditor (テキストエディタ)
https://jp.emeditor.com/text-editor-features/versatility/multiple-file-encoding-conversions/
上記手順で作成した、「spam-utf8」「not-spam-utf8」以下を使用して進める
「spam-utf8」を「spam」として配置し、「not-spam-utf8」を「ok」として配置する
また、以下のプログラムを作成する
makedb_spamcheck.py
import os, glob
import MeCab
import numpy as np
import pickle
# ファイル名を定義
data_file = "./spamcheck-data.pickle"
# 変数の準備
word_dic = {"__id": 0} # 単語辞書
files = [] # 読み込んだ単語データを追加する
# MeCabの準備
tagger = MeCab.Tagger()
# 指定したディレクトリ内のファイル一覧を読み込む
def read_files(dir, label):
# テキストファイルの一覧を得る
files = glob.glob(dir + "/*.txt")
for f in files:
read_file(f, label)
# ファイルを読み込む
def read_file(filename, label):
words = []
# ファイルの内容を読み込む
with open(filename, "rt", encoding="utf-8") as f:
text = f.read()
files.append({
"label": label,
"words": text_to_ids(text)
})
# テキストを単語IDのリストに変換
def text_to_ids(text):
# 形態素解析
word_s = tagger.parse(text)
# 以下のような解析内容を得られるので、これを一行ずつ処理していく
#
# 友達 名詞,一般,*,*,*,*,友達,トモダチ,トモダチ
# が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
# 遊び 名詞,一般,*,*,*,*,遊び,アソビ,アソビ
# に 助詞,格助詞,一般,*,*,*,に,ニ,ニ
# 来る 動詞,自立,*,*,カ変・来ル,基本形,来る,クル,クル
words = []
# 単語を辞書に登録
for line in word_s.split("\n"):
if line == "EOS" or line == "": continue
word = line.split("\t")[0]
params = line.split("\t")[1].split(",")
hinsi = params[0] # 品詞
bunrui = params[1] # 品詞の分類
org = params[6] # 単語の原型
# 助詞・助動詞・記号・数字は処理しない
if not (hinsi in ["名詞", "動詞", "形容詞"]): continue
if hinsi == "名詞" and bunrui == "数詞": continue
# 単語をidに変換
id = word_to_id(org)
words.append(id)
return words
# 単語をidに変換
def word_to_id(word):
# 単語が辞書に登録されているか
if not (word in word_dic):
# 登録されていないので新たにIDを割り振る
id = word_dic["__id"]
word_dic["__id"] += 1
word_dic[word] = id
else:
# 既存の単語IDを返す
id = word_dic[word]
return id
# 単語の頻出頻度のデータを作る
def make_freq_data_allfiles():
y = []
x = []
for f in files:
y.append(f["label"])
x.append(make_freq_data(f["words"]))
return y, x
# 単語の出現頻度を取得
def make_freq_data(words):
# 単語の出現回数を調べる
cnt = 0
dat = np.zeros(word_dic["__id"], "float")
for w in words:
dat[w] += 1
cnt += 1
# 出現回数を出現頻度に直す
dat = dat / cnt
return dat
# ファイルの一覧から学習用の単語頻出データベースを作る
if __name__ == "__main__":
read_files("ok", 0)
read_files("spam", 1)
y, x = make_freq_data_allfiles()
# ファイルにデータを保存
pickle.dump([y, x, word_dic], open(data_file, "wb"))
print("単語頻出データベース作成完了")
train_spamcheck.py
import pickle
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# ファイル名を定義
data_file = "./spamcheck-data.pickle"
model_file = "./spamcheck-model.pickle"
# データファイルの読込
data = pickle.load(open(data_file, "rb"))
y = data[0] # ラベル
x = data[1] # 単語の出現頻度
# 学習とテストを100回繰り返す
count = 100
rate = 0
for i in range(count):
# データを学習用とテスト用に分割
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
# 学習する
model = GaussianNB()
model.fit(x_train, y_train)
# 評価する
y_pred = model.predict(x_test)
acc = accuracy_score(y_test, y_pred)
# 評価結果が良ければモデルを保存
if acc > 0.94: pickle.dump(model, open(model_file, "wb"))
print(acc)
rate += acc
# 平均値を表示
print("----")
print("平均=", rate / count)
test_spamcheck.py
import pickle
import MeCab
import numpy as np
from sklearn.naive_bayes import GaussianNB
# テストするテキスト
test_text1 = """
会社から支給されているiPhoneの調子が悪いのです。
修理に出すので、しばらくはアプリのテストができません。
"""
test_text2 = """
億万長者になる方法を教えます。
すぐに以下のアドレスに返信して。
"""
# ファイル名を定義
data_file = "./spamcheck-data.pickle"
model_file = "./spamcheck-model.pickle"
label_names = ["OK", "SPAM"]
# 単語辞書の読み込む
data = pickle.load(open(data_file, "rb"))
word_dic = data[2]
# 学習済みモデルの読み込む
model = pickle.load(open(model_file, "rb"))
# MeCabの準備
tagger = MeCab.Tagger()
# テキストがスパムかどうか判定する
def check_spam(text):
# テキストを単語IDのリストに変換し単語の頻出頻度を調べる
zw = np.zeros(word_dic["__id"])
count = 0
s = tagger.parse(text)
# 単語毎の回数を加算
for line in s.split("\n"):
if line == "EOS": break
params = line.split("\t")[1].split(",")
org = params[6] # 単語の原型
if org in word_dic:
id = word_dic[org]
zw[id] += 1
count += 1
zw = zw / count
# 予測
pre = model.predict([zw])[0]
print("結果=", label_names[pre])
if __name__ == "__main__":
print("テキスト1")
check_spam(test_text1)
print("テキスト2")
check_spam(test_text2)
以下で単語頻出データ(spamcheck.pickle)を作成する
$ python3.7 makedb_spamcheck.py
単語頻出データ作成完了
以下で学習済みモデル(spamcheck-model.pickle)を作成する
評価結果が良いものをモデルとして保存する
$ python3.7 train_spamcheck.py
1.0
0.9777777777777777
1.0
〜中略〜
1.0
0.9777777777777777
0.9555555555555556
----
平均= 0.9911111111111102
以下でスパムか否かをテスト
$ python3.7 test_spamcheck.py
テキスト1
結果= OK
テキスト2
結果= SPAM
プログラム実行時に以下のようなエラーになる場合、サンプルデータが0件もしくは読み込みに失敗している可能性がある
データファイルが正しく配置されているか確認する
$ python3.7 train_spamcheck.py
Traceback (most recent call last):
File "train_spamcheck.py", line 21, in <module>
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
File "/usr/local/lib64/python3.7/site-packages/sklearn/model_selection/_split.py", line 2176, in train_test_split
default_test_size=0.25)
File "/usr/local/lib64/python3.7/site-packages/sklearn/model_selection/_split.py", line 1861, in _validate_shuffle_split
train_size)
ValueError: With n_samples=0, test_size=0.2 and train_size=None, the resulting train set will be empty. Adjust any of the aforementioned parameters.