今回はSNSや掲示板及びメールでよくみられるスパム広告というユーザーにとっては非常に迷惑千万な投稿やメールがありますよね?今回はそんなスパム広告を機械学習を通して判定してみましょう。今後自分でメールプラットフォームやサイトを作成する予定のある方がいらっしゃいましたらこの技術を用いてより使いやすいコンテンツの開発を行うことができるかもしれませんね。
前回の記事をご覧になっていない方がいらっしゃいましたらこちらをご覧ください。
SNSや掲示板へのスパム投稿を判定しよう
自然言語処理を利用した機械城学習の例として、掲示板に投稿されたスパムメッセージを判定するプログラムを作ってみましょう。
スパムとは?
スパム (spam) とは、メール、SNS、掲示板などで、受信者運営者の意図とは反する迷惑なメッセージや書き込みのことを言います。会員制出会い系サイトやアダルトサイト、ネズミ講、オンラインカジノ、ダイエット、医薬品類などの宣伝広告、または架空請求やクリック詐欺などが主な内容です。
スパム対策の方法
そのような不快なメッセージを自動的に「スパム」と判定するにはどうしたら良いでしょうか。電子メールであれば、メールの配信元や送信回数などを頼りにして、スパム業者からの送信を判定できます。また、SNS や掲示板への書き込みも同様で、同じ内容のメッセージが大量に書き込まれていればスパムと判定できます。そして、メッセージに含まれる特有の単語を文章から認識してスパムと判定できます。現在、たいていのメールソフトやメールサービスでは、標準で迷惑メールの検出機能を備えています。たとえば、Gmail であれば画面左側にある「迷惑メール」のリンクをクリックすると、スパム判定されたメールを確認できます。
ベイジアンフィルターを作ってみよう
スパムメッセージの判定で定評があるのが『ベイジアンフィルター(Bayesian Filter)』です。ベイジアンフィルターは、単純ベイズ分類器を応用したものです。ベイズ分類器は、統計的な手法を用いてスパムを判定します。本節では、自然言語処理を応用した機械学習により、文章に含まれる単語からスパム判定する方法を紹介します。
スパムテキストのダウンロード
まずは、スパムメールの一覧をダウンロードしましょう。著作権フリーなスパムメールセンテンスがまとめられたデータがネット上にいくつも転がっているのでそちらをまずはローカル環境に落とし、開いてみましょう。
非スパムテキストを用意しよう
次に、スパムではない普通のテキストファイルを用意しましょう。自分の書いたメール、あるいは著作権フリーのテキストデータをダウンロードすると良いでしょう。ここでは、「livedoor ニュースコーパス」を利用しましょう。これは、NHN Japan 株式会社が運営する「livedoor ニュース」のうち、クリエイティブ·コモンズライセンスが適用されるニュース記事を収集し、可能な限りHTML タグを取り除いて作成したものです。
livedoor 二ュースコーパ
[URLIhttps://www.rondhuit.com/download.html#ldoc
上記のWeb サイトより「Idcc-20140209.tar.gz」をダウンロードして利用しましょう。解凍すると、ニュース記事がディレクトリーごとに整理されています。各ディレクトリー以下に、たくさんのテキストファイルが保存されています。今回、これらのテキストファイルを100件ほど適当に抜き出して、機械学習に利用しましょう。
学習用データの準備
それでは、学習に利用するデータを用意しましょう。Jupyter Notebook の実行ディレクトリーに、<spam> と<ok>という2つのディレクトリーを作り、<spam> ディレクトリーには、スパムのテキストをコピーし、<ok> ディレクトリーには、livedoor ニュースコーパスか自身で用意したテキストをコピーしてください。spam と ok のデータが偏ると正しい結果が得られにくいので、だいたい同じ数のファイルになるようにコピーしましょう。それで、以下のようなフォルダー構成にします。
<spam>
– 0001.txt
– 0002.txt
– 00003.txt
<ok>
– it-life-xx.txt
– dokujo-xx.txt
– kaden-xx.txt
テキストデータの学習方法について
ところで、テキストデータを機械学習にかける場合には、テキストデータを数値に変換する必要があります。しかも、ここまで見たような機械学習で使う固定長の配列データに変換しなければなりません。テキストデータは長さも内容もバラバラなので、どのようにして固定長の数値データにしたらいろいろな手法がありますが、今回は単語の並び順を無視して、単語の出現頻度だけを利用します。このように、文章中にどんな単語があるかを数値で表す手法を『BoW(Bag-of-Words)』と呼びます。ここでは、BoW の手法を用いて具体的に次のような手順で機械学習を実践します。
(1) テキストを形態素解析して単語に分ける
(2) ストップワードを取り除く
(3)単語辞書を作り、単語にIDを振る
(4) ファイルごとの単語の出現頻度を調べる
(5) 単語の出現頻度データを元に、ok(0) と spam(1) に分けて学習する
データベース作成の例
ちなみに単語出現頻度のデータベースのイメージを見具体的にするために、以下のような簡単な文章で、データベースを作ってみましょう。
(1) 形態素解析
ネコに小判と言うがネコにはネコの世界があるこれを、形態素解析して単語に分割すると、以下のようになります。
ネコ|に|小判|と|言う|が|ネコ|に|は|ネコ|の|世界|が|ある|
(2) ストップワードを取り除く
ストップワードとは、すべての文章に出現する意味のない語句のことです。ここでは、助詞や助動詞や接続詞や記号をストップワードとして使います。それでは、それらを除去してみましょう。すると、以下のようになります。
ネコ|小判|言う|ネコ|ネコ| 世界|ある
(3) 単語辞書を作り ID を振る
そして、単語にIDを振って元の文章をID で置き換えてみましょう。
辞書に基づいて文章をIDに変換すると、以下のようになります。
0, 1, 2, 0, 0, 3, 4
単語辞書に登録されている語句ごとに、出現頻度を調べましょう。ここでは、単語辞書に5個の単語が登録されていますので、その語句ごとに出現回数を調べ、トータルの単語数で割れば出現頻度が求められます。そこで、出現頻度を求めると以下のようになります。
(4) 出現頻度を求める
これで、出現頻度のデータベースができました。この文章の単語出現頻度データベースは、以下のようになります。
[ 0.43, 0.14, 0.14, 0.14]
ここでは、単語が5個しかありませんが、通常は、何千語·何万語にもなることでしょう。
単語の出現頻度を調べるプログラム
それでは、上記の手順をプログラムに落とし込みましょう。以下のプログラムが学習用のデータベースを作成するプログラムです。
# すべてのテキストを巡回して単語データベースを作成する
import os, glob
import MeCab
import numpy as np
import pickle
# 保存ファイル名
savefile =”./ok-spam.pickle”
# MeCab の準備(* 1)
tagger= MeCab. Tagger()
#変数の準備(*2)
{“-_id”: 0} # 単語辞書
Files = []# 読み込んだ単語データを追加する
# 指定したディレクトリー内のファイル一覧を読む(*3)
def read_files (dir, label):
# テキストファイルの一覧を得る
Files = glob.glob(dir + ‘/*.txt’)
for f in files:
read_file(f, label)
# ファイルを読む (※4)
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):
# 形態素解析(*5)
Word_s =tagger.parse(text)
Words =[]
# 単語を辞書に登録(*6)
for line in word_s.split(“\n”):
if line == ‘EOS’ or line == ‘’ : continue
word= line.split (“\t”) [0]
parms= line.split(“\t”)[4].split(“-“)
hinsi = params [0] # 品詞
hinsi2 = params [1] if len(params) > 1 else ‘” # 品詞の説明
org = line.split(“\t”) [3]#単語の原型
# 助詞。助動詞。記号。数字は捨てる(*7)
if not (hinsi in [‘ 名詞’, ‘動詞 ‘, ‘ 形容詞’ ): continue
if hinsi ==’ 名詞’ and hinsi2 ==’ 数詞 ‘: continue
# 単語をID に変換(*8)
Id = word to_id(org)
words. append (id)
return words
# 単語を ID に変換(*9)
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
(* 10)
# 単語の出現頻度のデータを作る
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
# 回数を出現頻度に直す(*11)
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(savefile, ‘wb’))
print (“単語頻出データ作成完了”)
プログラムを実行すると、ファイルごとに単語の出現頻度を調べ、結果を「ok-spam.pickle」という名前のファイルに保存します。プログラムを確認してみましょう。プログラムの(※1)の部分では MeCab を使う準備をします。ここでは、最新の単語を含む mecab-ipadic-NEologd の辞書を利用します。(※ 2)では、ファイル全体で利用する変数の準備をします。word_dic は単語辞書で、この変数に単語とID番号を記録していきます。word_dic”_id”] に IDを発行した単語の個数を記録し、未知語に新しいIDを付与できるようにします。変数files には、ファイルを読み込んで、IDに変換した単語リストとラベルを追加していきます。プログラムの(※3)では指定したディレクトリー以下のファイル一覧を読み込みます。そして(※4)では、指定されたファイルを読み込んで、文章を単語IDのリストに変換します。単語IDのリストを変換する方法ですが、(※5)の部分で形態素解析を行って、(※6)以降の部分で単語IDに変換します。ただし、MeCab の変換結果の品詞を確認して、(※7)にあるように名詞·動詞形容詞以外であれば、ストップワードと見なします。さらに、名詞であっても、数字であれば、それほど意味がないので無視します。(※8)の部分で、単語を ID に変換して、変数 words に単語Dを追加します。そして、最終的に変数 fles にラベルと単語IDのリストを追加します。(※9)の部分では、単語をIDに変換します。その方法ですが、辞書型の変数 word_dic を調べて、該当する単語があればそのIDを返し、該当する単語がなければ辞書型の変数に最終IDを割り当てます。プログラムの(※10) では、学習用のデータ、つまり、ファイルごとに単語の出現頻度データを作成します。その方法ですが、単語辞書の単語数だけ0で初期化された配列を作成し、出現した単語のIDに相当する要素を1つずつ加算していきます。そして(※11)の部分で、単語の総出現回数がわかったら、単語ごとに出現回数を総出現回数で割ると、単語の出現頻度を求めることができます。
単語頻出データを機械学習するプログラム
それでは、この単語頻出データを利用して、機械学習を実践してみましょう。ここでは、scikit-learnの Gaussian NaiveBayes を利用して、簡単な機械学習を実践してみましょう。
import pickle
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# データファイルの読み込み(*1)
data_file = “./ok-spam.pickle”
save_file =”./ok-spam-model.pickle”
data=pickle.load(open (data_file, “rb”))
y = data[0] # ラベル
x= data[1] # 単語の出現頻度
# 100回、学習とテストを繰り返す(*2)
count = 100
rate = 0
for i in range (count):
# データを学習用とテスト用に分割(*3)
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
# 学習する(*4)
model =GaussianNB()
model.fit(x_train, y_train)
# 評価する (*5)
y-pred=model.predict (x_test)
acc = accuracy_score (y_test, y_pred)
# 評価結果が良ければモデルを保存(*6)
if acc > 0.94: pickle.dump(model, open (save_file, “wb”))
print (acc)
rate += acc
# 平均値を表示(*7)
print(“—-“)
print (“average=”, rate / count)
プログラムを実行すると、データをシャッフルしつつ、学習とテストを100回繰り返します。そして、最終的にテストの平均値0.900..(約 90%)を表示します。プログラムを確認してみましょう。プログラムの(※1) の部分では、データファイルの読み込みを行います。(※2)の部分では、学習とテストを 100回繰り返します。(※3)では、データを学習用とテスト用に分割し、(※4)の部分で、Gaussian NaiveBayes を利用してデータを学習し、(※5)の部分でテストデータを用いて学習結果を評価します。最終的に、(※6)の部分で、評価結果が良ければ作成したモデルを保存します。そして、(※7)でテストの平均値を表示します。
自分で作成したデキストをスパム判定してみよう
それでは、作成したモデルを元に、自分で作成したテキストをスパム判定してみましょう。自分で作成したテキストを判定させるためには、まず、テキストを単語IDのリストに変換し、文章における単語の出現頻度のデータを作成します。
import pickle
import MeCab
import numpy as np
from sklearn.naive_bayes import GaussianNB
# テストするテキスト(*1)
test_text1 = “””
会社から支給されている iPhone の調子が悪いのです。
修理に出すので、しばらくはアプリのテストができません。
“””
test_text2 = “””
億万長者になる方法を教えます。
すぐに以下のアドレスに返信して。
“””
# ファイル名
Data_file=”./ok-spam.pickle”
Model_file=”./ok-spam-model.pickle”
Label_names=[‘OK’, ‘SPAM’]
# 単語辞書を読み出す
pickle.load (open (data_file, data = “rb”))
word_dic = data[2]
# MeCab の準備
Tagger = MeCab. Tagger ()
# 学習済みモデルを読み出す(*3)
Model=pickle.load (open (model_file, “rb”))
# テキストがスパムかどうか判定する(*4)
def check_spam(text):
# テキストを単語 ID のリストに変換し単語の出現頻度を調べる
ZW = np.zeros (word_dic[‘__id’])
count = 0
s= tagger.parse(text)
# 単語ごとの回数を加算(*5)
for line in s.split(“\n”):
if line == “EOS”: break
org=line.split(“\t”)[3]# ##ORY
if org in word_dic:
id =word_dic [org]
zw[id] += 1
count += 1
ZW = zw / count #(*6)
# 予測
pre =model.predict([zw]) [0] #(* 7)
print (“- 結果=”, label_names [pre])#(* 8)
if -name_- == “._main__”:
check_spam(test_text1)
check_spam(test_text2)
実行してみたところ、通常の文章を指定したときには「OK」と表示され、スパムメールらしいテキストを指定すると「SPAM」と正しく判定しました。プログラムを確認してみましょう。プログラムの(※1)の部分で、テストするテキストを定義します。ここでは、2つのテキストを用意しました。次に(※2)の部分では、前に作った単語辞書を読み出します。この単語辞書により、テストするテキストを単語IDのリストに変換できます。そして(※3)の部分で、学習済みのモデルを読み出します。プログラムの(※4)の部分で、テキストがスパムかどうかを判定する、check_spam() 関数を定義します。この関数では、(※5)以下の部分で、MeCab で区切った単語をIDに、単語の出現回数を数えます。単語の出現回数を数え終わったら、(※6)で出現回数を総出現回数で割って出現頻度に変換します。そして(※7)で、predict) メソッドを使って、予測を行います。正解ラベルを得たら print()で結果を出力します。最後の(※8)の部分では、実際にテキストを判定させるよう、check_spam) 関数を実行します。
改良のヒント
今回、自分で用意したテキストを用いて、スパム判定を行うところまで作ることができました。しかし、文章によっては誤判定をしてしまうこともあるでしょう。また、語量は日々増えていくものなので、時代に合わなくなってくることもあります。実際にこのスパム判定ツールをWebサイトに組み込み、掲示板や SNS でスパムと誤判定をしてしまった場合には、ユーザーの操作により、スパム解除の申請を行う画面を用意する必要があります。それに加えて、定期的に誤判定のデータを用いて単語出現頻度のデータベースを更新して、学習をやり直す必要があるでしょう。なお、「文章を単語に分割してみよう(P.198)」の節で紹介したように、MeCab を利用するときにはmecab-ipadic-NEologd の辞書を使って単語分割を行うようにすると、分類精度が向上します。
応用のヒント
今回は、スパムか非スパムかの二値判定でしたが、今回の手法を応用すれば、自動的にメッセージのトピックに合わせてタグ付けできます。たとえば、「料理」「ライフスタイル」「モバイル」など、話題を分類できます。
この節のまとめ
・文章を機械学習にかける場合、単語辞書を用いてテキストをIDに変換して、単語の出現頻度を調べることで実践できる
・文章の分類にはベイズ分類器が威力を発揮する
・スパム判定を応用することで、メッセージに対してトピックに応じた自動的なタグ付けもできる