LSTMによる迷惑メール判定

2020年08月31日

 

これらのタスクは全て自然言語処理を利用して実行されています。自然言語処理とは、テキストを処理して将来のデータに利用可能な役に立つインサイトに変換することです。テキストデータが文脈に依存するため、自然言語処理は人工知能の分野で最も複雑な研究分野の一つと考えられています。機械が解釈できるように変更しなければならない上、特徴抽出を行うために複数の処理段階が必要なのです。

分類問題は、大まかに言うと二つのカテゴリーに分けられます。二クラス分類(二項分類または二値分類とも言う)問題と多クラス分類問題です。二クラス分類は、患者の症状が癌性かそうでないか、金融取引が不正か不正でないかなど、分類クラスが二つだけの場合です。一方、多クラス分類では、三つ以上のクラスに分類します。例としては、映画の口コミをポジティブ、ネガティブ、ニュートラルの三つの感情に分類する場合が挙げられます。

自然言語処理のタスクには多くの種類がありますが、最も一般的なタスクの一つは、文字列の分類でしょう。映画やニュース記事をジャンルごとに分類したり、迷惑メールかどうかを自動的に判定したりするタスクがこれに含まれます。この記事では、この一番最後の例である、迷惑メール判定について詳しく見ていきましょう。 

 

スパムメール判定の概要

スパムメール判定とは、メールを迷惑メールとそうでないものに分類するプロセスです。本記事では、スパムメール判定について探求し、理解を深めることを目標にします。二クラス分類問題の一つです。

迷惑メール判定を実行する理由は簡単です。不要な迷惑メールを検出することによって、迷惑メールがユーザーの受信トレイに紛れ込むことがなくなり、ユーザー体験を向上させることができるからです。

 

スパムメール判定のデータセット

まず、迷惑メール判定データからスタートしましょう。UCI機械学習リポジトリで公開されているスパムベースデータセット(Spam Base Dataset)を利用します。このデータセットには5569件のメールが含まれ、そのうち745件が迷惑メールです。こちらのデータセットのターゲット変数は「迷惑メール」であり、迷惑メールは1、そうでないものは0にマッピングされます。ターゲット変数とは、これからその値を予測しようとする変数を指すと考えられます。機械学習問題では、この変数の値をモデル化し、他の変数によって予測します。データのスナップショットが図1に示されています。

図1

 

課題: メールを迷惑メールとそうでないものに分類する

ソリューションに到達するためには、以下の四つの処理の概念について理解する必要があります。ここでご説明するそれぞれの概念は、他のテキスト分類問題にも適用できることを覚えておきましょう。

  • テキスト処理
  • テキストシーケンスの処理
  • モデルの選択
  • 実装

 

テキスト処理

データは通常、様々なソースから取得されるので、多くの場合、多種多様な形式が混在しています。そのため、生データの変換が不可欠です。とはいえ、この変換は簡単なプロセスではありません。テキストデータにはしばしば、余分な単語や重複した単語が含まれているからです。そこで、テキスト処理がソリューション構築における最初のステップになります。

テキスト処理に含まれる基本的なステップは次の通りです。

  1. 生データの前処理
  2. 前処理後のデータのトークン化

 

生データの前処理

この段階では、テキストの意味に価値を加えない単語や文字を削除します。標準的な前処理手法の例は次の通りです。

  • 小文字に変換
  • 特殊文字の削除
  • ストップワードの削除
  • ハイパーリンクの削除
  • 数字の削除
  • 空白の削除

 

小文字に変換

テキストを小文字に変換することは、次のような理由から必要不可欠です。

  • 「TEXT」、「Text」、「text」などの語は全て文に同じ価値を加えます。 
  • 全ての単語を小文字に変換することは、語彙サイズを小さくして次元を削減する上で非常に役立ちます。


def to_lower(word):
  result = word.lower()
  return result

 

特殊文字の削除

「おめでとう」と「おめでとう!」などの単語を同じように扱うために役立つテキスト処理の手法の一つです。


def remove_special_characters(word):
  result=word.translate(str.maketrans(dict.fromkeys(string.punctuation)))
  return result

ストップワードの削除

ストップワードは英語の「a」「the」など、その言語において頻出する単語です。大抵の場合、価値ある情報を提供しないため、テキストから削除することが可能です。


def remove_stop_words(words):
  result = [i for i in words if i not in ENGLISH_STOP_WORDS]
  return result

 

URLの削除

次に、データ内のURLを削除します。メールにURLが含まれていることはよくありますが、結果に価値を加えないので、さらに分析を進める際に必要ではありません。


def remove_hyperlink(word):
  return re.sub(r"http\S+", "", word)

 

前処理後のデータのトークン化

トークン化は、トークンと呼ばれる小さな要素にテキストを分割することです。各トークンが特徴量として機械学習アルゴリズムの入力になります。 

keras.preprocessing.text.Tokenizerは、テキストコーパスに最も頻繁に出現する単語のみを保持しながら、テキストをトークン化するユーティリティ関数です。テキストをトークン化すると、膨大な数の単語を含む辞書ができますが、それらの単語が全て重要なわけではありません。「max_features」を設定して、考慮すべき最頻出単語だけを選択することができます。


max_feature = 50000 #number of unique words to consider
from keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer(num_words=max_feature)
tokenizer.fit_on_texts(x_train)
x_train_features = np.array(tokenizer.texts_to_sequences(x_train))
x_test_features = np.array(tokenizer.texts_to_sequences(x_test))

テキストシーケンスの処理

パディング

全てのメールのトークンを同じサイズにすることをパディングと呼びます。

入力するデータポイントはバッチ送信されるので、入力のサイズが揃っていないと情報が失われてしまう可能性があります。そのため、パディングを利用してトークンを同じサイズにします。これによってバッチ更新が容易になります。 

トークン化されたメールのパディング後の長さは、「max_len」を用いて設定できます。

パディングのコード抜粋は次の通りです。

from keras.preprocessing.sequence import pad_sequences
x_train_features = pad_sequences(x_train_features,maxlen=max_len)
x_test_features = pad_sequences(x_test_features,maxlen=max_len)

 

ターゲット変数のラベルエンコーディング

モデルが想定するターゲット変数は、文字列ではなく数値です。sklearnのLabelEncoderを利用すれば、以下のようにターゲット変数を数値に変換できます。

from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
train_y = le.fit_transform(target_train.values)
test_y = le.transform(target_test.values)

 

LSTMネットワーク

映画は一連のシーンで構成されています。我々が特定のシーンを見る時は、それを単独で理解するのではなく、前のシーンと関連づけて理解しようとします。機械学習モデルも人間のニューラルネットワークと同様に、既に学習したテキストを利用して理解する必要があります。

従来の機械学習モデルでは、前のステージの情報を保存することができません。一方、再帰型ニューラルネットワーク(RNN)では、これが可能です。そこで、次に再帰型ニューラルネットワークについて見ていきましょう。

再帰型ニューラルネットワークは、前のステージから入力を受け取り、その出力を次のステージの入力として提供する繰り返しモジュールを持っています。とはいえ、再帰型ニューラルネットワークは直近のステージの情報だけしか保存できません。長期的な依存関係を学習するためには、ネットワークに記憶する仕組みが必要です。ここで役立つのが、Long Short Term Memory (LSTM)ネットワークです。

LSTMとは、再帰型ニューラルネットワークの特別な種類です。再帰型ニューラルネットワークと同じチェーンのような構造を備えていますが、繰り返しモジュールの構造が異なります。

逆方向のLSTMも活用するためには、双方向LSTMを利用します。

 

迷惑メール判定の実装

埋め込み

人間にとってテキストデータを解釈するのは簡単です。しかし、テキストを読み取り、分析するのは、マシンにとって非常に複雑な作業です。このタスクを実行するためには、機械が理解できる形式にテキストを変換しなければなりません。埋め込みはフォーマット化されたテキストデータをマシンが解釈できる数値とベクトルに変換する作業です。


import tensorflow as tf
from keras.layers import Dense, Input, LSTM, Embedding, Dropout, Activation, Conv1D
from keras.layers import Bidirectional, GlobalMaxPool1D
from tensorflow.compat.v1.keras.layers import CuDNNGRU
from keras.models import Model
from keras import initializers, regularizers, constraints, optimizers, layers

#size of the output vector from each layer
embedding_vector_length = 32

#Creating a sequential model
model = tf.keras.Sequential()

#Creating an embedding layer to vectorize
model.add(Embedding(max_feature, embedding_vector_length, input_length=max_len))

#Addding Bi-directional LSTM
model.add(Bidirectional(tf.keras.layers.LSTM(64)))

#Relu allows converging quickly and allows backpropagation
model.add(Dense(16, activation='relu'))

#Deep Learninng models can be overfit easily, to avoid this, we add randomization using drop out
model.add(Dropout(0.1))

#Adding sigmoid activation function to normalize the output
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
print(model.summary())

history = model.fit(x_train_features, train_y, batch_size=512, epochs=20, validation_data=(x_test_features, test_y))
y_predict = [1 if o>0.5 else 0 for o in model.predict(x_test_features)]

以上のように、双方向LSTMモデルをメールデータに適用して、1114件中125件を迷惑メールとして検出しました。

多くの場合、データ内の迷惑メールの割合は小さいので、モデルの精度を正解率だけで測定することはおすすめできません。他のパフォーマンス指標と組み合わせて評価する必要があります。次は、この点について見ていきましょう。

 

パフォーマンス指標

適合率(精度)と再現率は、問題の理解を深めるため、分類問題で最も広範に利用されている二つのパフォーマンス指標です。適合率(精度)とは、取得した全てのインスタンスのうち、実際にマシンが正解したインスタンスの割合です。適合率(精度)は、結果がどの程度有益なのかを理解するために役立ちます。再現率は、全ての正しいインスタンスのうち、マシンが正解したインスタンスの割合です。再現率は、結果がどの程度完璧であるのかを理解するために役立ちます。

F1値は適合率(精度)と再現率の調和平均です。 


from sklearn.metrics import confusion_matrix,f1_score, precision_score,recall_score
cf_matrix =confusion_matrix(test_y,y_predict)
tn,fp,fn,tp = confusion_matrix(test_y,y_predict).ravel()

print("Precision: {:.2f}%".format(100 * precision_score(test_y, y_predict)))
print("Recall: {:.2f}%".format(100 * recall_score(test_y, y_predict)))
print("F1 Score: {:.2f}%".format(100 * f1_score(test_y,y_predict)))

import seaborn as sns
import matplotlib.pyplot as plt
ax= plt.subplot()

#annot=True to annotate cells
sns.heatmap(cf_matrix, annot=True, ax = ax,cmap='Blues',fmt='');

# labels, title and ticks
ax.set_xlabel('Predicted labels');
ax.set_ylabel('True labels');
ax.set_title('Confusion Matrix');
ax.xaxis.set_ticklabels(['Not Spam', 'Spam']); ax.yaxis.set_ticklabels(['Not Spam', 'Spam']);

例えば、マシンが検察クエリの結果を30ページ分表示し、そのうち20ページは適切だったものの、他の関連する結果40ページ分を表示できなかったとします。この場合、適合率(精度)は20/30で、再現率は20/60、そしてF1値は4/9になります。

このように、迷惑メール判定問題でF1値をパフォーマンス指標として利用するのは良い選択肢だと考えられます。

F1が94%であれば、利用可能なモデルであると言えるでしょう。とはいえ、これらの結果は私たちが使用した学習データに基づいていることをお忘れなく。このようなモデルを実際のデータに適用する場合、モデルのパフォーマンスを長期にわたって積極的に監視する必要があります。結果やフィードバックに基づいて、機能を追加したり綴りの誤りを含む単語を削除したりして、モデルの改良を続けることもできます。 

 

さいごに

本記事では、テキストデータをベクトルに変換し、双方向LSTMモデルを作成してモデルをベクトルに対応させ、迷惑メール判定モデルを作成しました。また、さまざまなテキスト処理やテキストシーケンス処理の手法や再帰型ニューラルネットワーク、LSTM、双方向LSTMなどのディープラーニングモデルについて探索しました。

※ 本記事は2020年8月21日、弊社英語ブログに掲載された寄稿記事に基づいたものです

本記事でご紹介したLSTMは、画像キャプション翻訳モデルなど、様々な課題に活用できます。このようなLSTMの応用につきましては、今後の記事でもご紹介しますので、ご興味のある方はぜひメールマガジンに登録してください。

 

Lionbridge AIについて

当社は教師データの作成やアノテーションサービスを提供し、機械学習の研究開発を支援しております。100万人のアノテーターが登録されており、20年に渡るAIプロジェクトの実績がございます。無料トライアルやお見積は、こちらからお問い合わせ下さい。

AI向け教師データの作成やアノテーションサービスを提供し、研究開発をサポートいたします。

メディア掲載結果

    AI・機械学習の最新情報をお届けします!

    Lionbridge AIのブログで紹介している事例記事やトレンドニュースといったビジネスに役立つ情報はもちろん、オープンデータセット集なども合わせてメール配信しております。