Memo

メモ > 技術 > プログラミング言語: Python > scikit-learn(機械学習)でスパムメールを判定

■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.

Advertisement